PageRenderTime 58ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/perfmon/classes/general/query.php

https://gitlab.com/alexprowars/bitrix
PHP | 678 lines | 570 code | 73 blank | 35 comment | 89 complexity | e93bd48e37682417225255ca838ab1ef MD5 | raw file
  1. <?php
  2. class CPerfQueryJoin
  3. {
  4. var $left_table = "";
  5. var $left_column = "";
  6. var $left_const = "";
  7. var $right_table = "";
  8. var $right_column = "";
  9. var $right_const = "";
  10. function _parse($sql, &$table, &$column, &$const)
  11. {
  12. $match = array();
  13. if (preg_match("/^([`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1})\\.(.+)\$/", $sql, $match))
  14. {
  15. $table = $match[1];
  16. $column = $match[2];
  17. $const = "";
  18. }
  19. else
  20. {
  21. $table = "";
  22. $column = "";
  23. $const = $sql;
  24. }
  25. }
  26. function parse_left($sql)
  27. {
  28. $this->_parse($sql, $this->left_table, $this->left_column, $this->left_const);
  29. }
  30. function parse_right($sql)
  31. {
  32. $this->_parse($sql, $this->right_table, $this->right_column, $this->right_const);
  33. }
  34. }
  35. class CPerfQueryWhere
  36. {
  37. var $table_aliases_regex = "";
  38. var $equation_regex = "";
  39. var $sql = "";
  40. var $simplified_sql = "";
  41. var $joins = array();
  42. function __construct($table_aliases_regex)
  43. {
  44. $this->table_aliases_regex = $table_aliases_regex;
  45. $this->equation_regex = "(?:".$this->table_aliases_regex."\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}|[0-9]+|'[^']*') (?:=|<|>|> =|< =|IS) (?:".$this->table_aliases_regex."\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}|[0-9]+|'[^']*'|NULL)";
  46. }
  47. function parse($sql)
  48. {
  49. //Transform and simplify sql
  50. //
  51. //Remove balanced braces around equals
  52. $sql = $this->_remove_braces(CPerfQuery::removeSpaces($sql));
  53. //Replace "expr1 = <const1> or expr1 = <const2> or expr1 = <const3> ..."
  54. //with "expr1 in (<const1>, ...)"
  55. $new_sql = preg_replace_callback("/\\( (".$this->equation_regex."(?: OR ".$this->equation_regex.")+) \\)/i", array($this, "_or2in"), CPerfQuery::removeSpaces($sql));
  56. if ($new_sql !== null)
  57. $sql = CPerfQuery::removeSpaces($new_sql);
  58. //Replace IN with no more than 5 values to equal
  59. $sql = preg_replace("/ IN[ ]*\\([ ]*([0-9]+|'[^']*')([ ]*,[ ]*([0-9]+|'[^']*')[ ]*){0,5}[ ]*\\)/i", " = \\1 ", $sql);
  60. //Remove complex inner syntax
  61. while (preg_match("/\\([^()]*\\)/", $sql))
  62. $sql = preg_replace("/\\([^()]*\\)/", "", $sql);
  63. $this->simplified_sql = $sql;
  64. foreach (preg_split("/ and /i", $sql) as $str)
  65. {
  66. $match = array();
  67. if (preg_match("/(".$this->table_aliases_regex."\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}) = (".$this->table_aliases_regex."\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1})/", $str, $match))
  68. {
  69. $join = new CPerfQueryJoin;
  70. $join->parse_left($match[1]);
  71. $join->parse_right($match[2]);
  72. $this->joins[] = $join;
  73. }
  74. elseif (preg_match("/(".$this->table_aliases_regex."\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}) = ([0-9]+|'.+')/", $str, $match))
  75. {
  76. $join = new CPerfQueryJoin;
  77. $join->parse_left($match[1]);
  78. $join->parse_right($match[2]);
  79. $this->joins[] = $join;
  80. }
  81. }
  82. return !empty($this->joins);
  83. }
  84. //Remove balanced braces around equals
  85. function _remove_braces($sql)
  86. {
  87. while (true)
  88. {
  89. $new_sql = preg_replace("/\\([ ]*(".$this->equation_regex."(?: AND ".$this->equation_regex.")*)[ ]*\\)/i", "\\1", $sql);
  90. if ($new_sql === null)
  91. break;
  92. if ($new_sql === $sql)
  93. {
  94. $new_sql = preg_replace("/\\( \\( (".$this->equation_regex."(?: OR ".$this->equation_regex.")*) \\) \\)/i", "( \\1 )", trim($sql));
  95. if ($new_sql === null)
  96. break;
  97. if ($new_sql === $sql)
  98. break;
  99. }
  100. $sql = trim($new_sql);
  101. }
  102. return $sql;
  103. }
  104. function _or2in($or_match)
  105. {
  106. $sql = $or_match[0];
  107. $match = array();
  108. if (preg_match_all("/(".$this->table_aliases_regex."\\.[a-zA-Z0-9_]+|[0-9]+|'[^']*') (?:=) ([0-9]+|'[^']*')/", $or_match[1], $match))
  109. {
  110. if (count(array_unique($match[1])) == 1)
  111. $sql = $match[1][0]." IN ( ".implode(", ", $match[2])." )";
  112. }
  113. return $sql;
  114. }
  115. }
  116. class CPerfQueryTable
  117. {
  118. var $sql = "";
  119. var $name = "";
  120. var $alias = "";
  121. var $join = "";
  122. function parse($sql)
  123. {
  124. $sql = CPerfQuery::removeSpaces($sql);
  125. $match = array();
  126. if (preg_match("/^([`\"\\[\\]]{0,1}[a-z0-9_]+[`\"\\[\\]]{0,1}) ([`\"\\[\\]]{0,1}[a-z0-9_]+[`\"\\[\\]]{0,1}) on (.+)\$/i", $sql, $match))
  127. {
  128. $this->name = $match[1];
  129. $this->alias = $match[2];
  130. $this->join = $match[3];
  131. }
  132. if (preg_match("/^([`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}) ([`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1})(\$| )/", $sql, $match))
  133. {
  134. $this->name = $match[1];
  135. $this->alias = $match[2];
  136. }
  137. elseif (preg_match("/^([`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1})\$/", $sql, $match))
  138. {
  139. $this->name = $match[1];
  140. $this->alias = $this->name;
  141. }
  142. else
  143. {
  144. return false;
  145. }
  146. $this->sql = $sql;
  147. return true;
  148. }
  149. }
  150. class CPerfQueryFrom
  151. {
  152. var $sql = "";
  153. /** @var array[]CPerfQueryTable */
  154. var $tables = array();
  155. var $joins = array();
  156. function parse($sql)
  157. {
  158. $sql = CPerfQuery::removeSpaces($sql);
  159. $match = array();
  160. if (preg_match("/^select(.*) from (.*?) (where|group|having|order)/is", $sql, $match))
  161. $this->sql = $match[2];
  162. elseif (preg_match("/^select(.*) from (.*?)\$/is", $sql, $match))
  163. $this->sql = $match[2];
  164. else
  165. $this->sql = "";
  166. if ($this->sql)
  167. {
  168. $arJoinTables = preg_split("/(,|inner\\s+join|left\\s+join)(?=\\s+[`\"\\[\\]]{0,1}[a-z0-9_]+[`\"\\[\\]]{0,1})/is", $this->sql);
  169. foreach ($arJoinTables as $str)
  170. {
  171. $table = new CPerfQueryTable;
  172. if ($table->parse($str))
  173. {
  174. $this->tables[] = $table;
  175. }
  176. }
  177. if (count($this->tables) <= 0)
  178. return false;
  179. $tables_regex = "(?:".implode("|", $this->getTableAliases()).")";
  180. /** @var CPerfQueryTable $table */
  181. foreach ($this->tables as $table)
  182. {
  183. $where = new CPerfQueryWhere($tables_regex);
  184. if ($where->parse($table->join))
  185. {
  186. $this->joins = array_merge($this->joins, $where->joins);
  187. }
  188. }
  189. }
  190. return !empty($this->tables);
  191. }
  192. function getTableAliases()
  193. {
  194. $res = array();
  195. /** @var CPerfQueryTable $table */
  196. foreach ($this->tables as $table)
  197. $res[] = $table->alias;
  198. return $res;
  199. }
  200. }
  201. class CPerfQuery
  202. {
  203. var $sql = "";
  204. var $type = "unknown";
  205. var $subqueries = array();
  206. /** @var CPerfQueryFrom */
  207. var $from = null;
  208. /** @var CPerfQueryWhere */
  209. var $where = null;
  210. public static function transform2select($sql)
  211. {
  212. $match = array();
  213. if (preg_match("#^\\s*insert\\s+into\\s+(.+?)(\\(|)\\s*(\\s*select.*)\\s*\\2\\s*(\$|ON\\s+DUPLICATE\\s+KEY\\s+UPDATE)#is", $sql, $match))
  214. $result = $match[3];
  215. elseif (preg_match("#^\\s*DELETE\\s+#i", $sql))
  216. $result = preg_replace("#^\\s*(DELETE.*?FROM)#is", "select * from", $sql);
  217. elseif (preg_match("#^\\s*SELECT\\s+#i", $sql))
  218. $result = $sql;
  219. else
  220. $result = "";
  221. return $result;
  222. }
  223. public static function removeSpaces($str)
  224. {
  225. return trim(preg_replace("/[ \t\n\r]+/", " ", $str), " \t\n\r");
  226. }
  227. function parse($sql)
  228. {
  229. $this->sql = preg_replace("/([()=])/", " \\1 ", $sql);
  230. $this->sql = CPerfQuery::removeSpaces($this->sql);
  231. $match = array();
  232. if (preg_match("/^(select) /i", $this->sql, $match))
  233. $this->type = mb_strtolower($match[1]);
  234. else
  235. $this->type = "unknown";
  236. if ($this->type == "select")
  237. {
  238. //0 TODO replace literals with placeholders
  239. //1 remove subqueries from sql
  240. if (!$this->parse_subqueries())
  241. return false;
  242. //2 parse from
  243. $this->from = new CPerfQueryFrom;
  244. if (!$this->from->parse($this->sql))
  245. return false;
  246. $tables_regex = "(?:".implode("|", $this->from->getTableAliases()).")";
  247. $this->where = new CPerfQueryWhere($tables_regex);
  248. if (preg_match("/ where (.+?)(\$| group | having | order )/i", $this->sql, $match))
  249. $this->where->parse($match[1]);
  250. return true;
  251. }
  252. else
  253. {
  254. return false;
  255. }
  256. }
  257. function parse_subqueries()
  258. {
  259. $this->subqueries = array();
  260. $ar = preg_split("/(\\(\\s*select|\\(|\\))/is", $this->sql, -1, PREG_SPLIT_DELIM_CAPTURE);
  261. $subq = 0;
  262. $braces = 0;
  263. foreach ($ar as $i => $str)
  264. {
  265. if ($str == ")")
  266. $braces--;
  267. elseif (mb_substr($str, 0, 1) == "(")
  268. $braces++;
  269. if ($subq == 0)
  270. {
  271. if (preg_match("/^\\(\\s*select/is", $str))
  272. {
  273. $this->subqueries[] = mb_substr($str, 1);
  274. $subq++;
  275. unset($ar[$i]);
  276. }
  277. }
  278. elseif ($braces == 0)
  279. {
  280. $subq--;
  281. unset($ar[$i]);
  282. }
  283. else
  284. {
  285. $this->subqueries[count($this->subqueries) - 1] .= $str;
  286. unset($ar[$i]);
  287. }
  288. }
  289. $this->sql = implode('', $ar);
  290. return true;
  291. }
  292. function cmp($table, $alias)
  293. {
  294. if ($table === $alias)
  295. return true;
  296. elseif ($table === "`".$alias."`")
  297. return true;
  298. else
  299. return false;
  300. }
  301. function table_joins($table_alias)
  302. {
  303. //Lookup table by its alias
  304. $suggest_table = null;
  305. /** @var CPerfQueryTable $table */
  306. foreach ($this->from->tables as $table)
  307. {
  308. if ($this->cmp($table->alias, $table_alias))
  309. $suggest_table = $table;
  310. }
  311. if (!isset($suggest_table))
  312. return array();
  313. $arTableJoins = array(
  314. "WHERE" => array()
  315. );
  316. //1 iteration gather inter tables joins
  317. foreach ($this->from->joins as $join)
  318. {
  319. if ($this->cmp($join->left_table, $table_alias) && $join->right_table !== "")
  320. {
  321. if (!isset($arTableJoins[$join->right_table]))
  322. $arTableJoins[$join->right_table] = array();
  323. $arTableJoins[$join->right_table][] = $join->left_column;
  324. }
  325. elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table !== "")
  326. {
  327. if (!isset($arTableJoins[$join->left_table]))
  328. $arTableJoins[$join->left_table] = array();
  329. $arTableJoins[$join->left_table][] = $join->right_column;
  330. }
  331. }
  332. //2 iteration gather inter tables joins from where
  333. foreach ($this->where->joins as $join)
  334. {
  335. if ($this->cmp($join->left_table, $table_alias) && $join->right_table !== "")
  336. {
  337. if (!isset($arTableJoins[$join->right_table]))
  338. $arTableJoins[$join->right_table] = array();
  339. $arTableJoins[$join->right_table][] = $join->left_column;
  340. }
  341. elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table !== "")
  342. {
  343. if (!isset($arTableJoins[$join->left_table]))
  344. $arTableJoins[$join->left_table] = array();
  345. $arTableJoins[$join->left_table][] = $join->right_column;
  346. }
  347. }
  348. //3 iteration add constant filters from joins
  349. foreach ($this->from->joins as $join)
  350. {
  351. if ($this->cmp($join->left_table, $table_alias) && $join->right_table === "")
  352. {
  353. foreach ($arTableJoins as $i => $arColumns)
  354. $arTableJoins[$i][] = $join->left_column;
  355. }
  356. elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table === "")
  357. {
  358. foreach ($arTableJoins as $i => $arColumns)
  359. $arTableJoins[$i][] = $join->right_column;
  360. }
  361. }
  362. //4 iteration add constant filters from where
  363. foreach ($this->where->joins as $join)
  364. {
  365. if ($this->cmp($join->left_table, $table_alias) && $join->right_table === "")
  366. {
  367. foreach ($arTableJoins as $i => $arColumns)
  368. $arTableJoins[$i][] = $join->left_column;
  369. }
  370. elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table === "")
  371. {
  372. foreach ($arTableJoins as $i => $arColumns)
  373. $arTableJoins[$i][] = $join->right_column;
  374. }
  375. }
  376. if (empty($arTableJoins["WHERE"]))
  377. unset($arTableJoins["WHERE"]);
  378. return $arTableJoins;
  379. }
  380. function suggest_index($table_alias)
  381. {
  382. global $DB;
  383. $suggest_table = null;
  384. /** @var CPerfQueryTable $table */
  385. foreach ($this->from->tables as $table)
  386. {
  387. if ($this->cmp($table->alias, $table_alias))
  388. $suggest_table = $table;
  389. }
  390. if (!isset($suggest_table))
  391. return false;
  392. $arTableJoins = $this->table_joins($table_alias);
  393. //Next read indexes already have
  394. $arSuggest = array();
  395. if (!empty($arTableJoins))
  396. {
  397. if (!$DB->TableExists($suggest_table->name))
  398. return false;
  399. $table = new CPerfomanceTable;
  400. $arIndexes = $table->GetIndexes($suggest_table->name);
  401. foreach ($arIndexes as $index_name => $arColumns)
  402. $arIndexes[$index_name] = implode(",", $arColumns);
  403. //Test our suggestion against existing indexes
  404. foreach ($arTableJoins as $i => $arColumns)
  405. {
  406. $index_found = "";
  407. $arColumns = $this->_adjust_columns($arColumns);
  408. //Take all possible combinations of columns
  409. $arCombosToTest = $this->array_power_set($arColumns);
  410. foreach ($arCombosToTest as $arComboColumns)
  411. {
  412. if (!empty($arComboColumns))
  413. {
  414. $index2test = implode(",", $arComboColumns);
  415. //Try to find out if index already exists
  416. foreach ($arIndexes as $index_name => $index_columns)
  417. {
  418. if (mb_substr($index_columns, 0, mb_strlen($index2test)) === $index2test)
  419. {
  420. if (
  421. $index_found === ""
  422. || count(explode(",", $index_found)) < count(explode(",", $index2test))
  423. )
  424. $index_found = $index2test;
  425. }
  426. }
  427. }
  428. }
  429. //
  430. if (!$index_found)
  431. {
  432. sort($arColumns);
  433. $arSuggest[] = $suggest_table->alias.":".$suggest_table->name.":".implode(",", $arColumns);
  434. }
  435. }
  436. }
  437. if (!empty($arSuggest))
  438. {
  439. return $arSuggest;
  440. }
  441. else
  442. {
  443. return false;
  444. }
  445. }
  446. function array_power_set($array)
  447. {
  448. $results = array(array());
  449. foreach ($array as $element)
  450. foreach ($results as $combination)
  451. array_push($results, array_merge(array($element), $combination));
  452. return $results;
  453. }
  454. function _adjust_columns($arColumns)
  455. {
  456. $arColumns = array_unique($arColumns);
  457. while (mb_strlen(implode(",", $arColumns)) > 250)
  458. {
  459. //TODO: add brains here
  460. //1 exclude blobs and clobs
  461. //2 etc.
  462. array_pop($arColumns);
  463. }
  464. return $arColumns;
  465. }
  466. function has_where($table_alias = false)
  467. {
  468. if ($table_alias === false)
  469. return !empty($this->where->joins);
  470. foreach ($this->where->joins as $join)
  471. {
  472. if ($this->cmp($join->left_table, $table_alias))
  473. {
  474. return true;
  475. }
  476. elseif ($this->cmp($join->right_table, $table_alias))
  477. {
  478. return true;
  479. }
  480. }
  481. return false;
  482. }
  483. function find_value($table_name, $column_name)
  484. {
  485. //Lookup table by its name
  486. /** @var CPerfQueryTable $table */
  487. foreach ($this->from->tables as $table)
  488. {
  489. if ($table->name === $table_name)
  490. {
  491. $table_alias = $table->alias;
  492. foreach ($this->where->joins as $join)
  493. {
  494. if (
  495. $join->left_table === $table_alias
  496. && $join->left_column === $column_name
  497. && $join->right_const !== ""
  498. )
  499. {
  500. return $join->right_const;
  501. }
  502. elseif (
  503. $join->right_table === $table_alias
  504. && $join->right_column === $column_name
  505. && $join->left_const !== ""
  506. )
  507. {
  508. return $join->left_const;
  509. }
  510. }
  511. foreach ($this->from->joins as $join)
  512. {
  513. if (
  514. $join->left_table === $table_alias
  515. && $join->left_column === $column_name
  516. && $join->right_const !== ""
  517. )
  518. {
  519. return $join->right_const;
  520. }
  521. elseif (
  522. $join->right_table === $table_alias
  523. && $join->right_column === $column_name
  524. && $join->left_const !== ""
  525. )
  526. {
  527. return $join->left_const;
  528. }
  529. }
  530. }
  531. }
  532. return "";
  533. }
  534. function find_join($table_name, $column_name)
  535. {
  536. //Lookup table by its name
  537. $suggest_table = null;
  538. /** @var CPerfQueryTable $table */
  539. foreach ($this->from->tables as $table)
  540. {
  541. if ($table->name === $table_name)
  542. $suggest_table = $table;
  543. }
  544. if (!isset($suggest_table))
  545. return "";
  546. $table_alias = $suggest_table->alias;
  547. foreach ($this->where->joins as $join)
  548. {
  549. if (
  550. $join->left_table === $table_alias
  551. && $join->left_column === $column_name
  552. && $join->right_table !== ""
  553. )
  554. {
  555. return $join->right_table.".".$join->right_column;
  556. }
  557. elseif (
  558. $join->right_table === $table_alias
  559. && $join->right_column === $column_name
  560. && $join->left_table !== ""
  561. )
  562. {
  563. return $join->left_table.".".$join->left_column;
  564. }
  565. }
  566. foreach ($this->from->joins as $join)
  567. {
  568. if (
  569. $join->left_table === $table_alias
  570. && $join->left_column === $column_name
  571. && $join->right_table !== ""
  572. )
  573. {
  574. return $join->right_table.".".$join->right_column;
  575. }
  576. elseif (
  577. $join->right_table === $table_alias
  578. && $join->right_column === $column_name
  579. && $join->left_table !== ""
  580. )
  581. {
  582. return $join->left_table.".".$join->left_column;
  583. }
  584. }
  585. return "";
  586. }
  587. public static function remove_literals($sql)
  588. {
  589. return preg_replace('/(
  590. "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # match double quoted string
  591. |
  592. \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # match single quoted string
  593. |
  594. (?s:\\/\\*.*?\\*\\/) # multi line comments
  595. |
  596. \\/\\/.*?\\n # single line comments
  597. |
  598. (?<![A-Za-z_])([0-9]+\\.[0-9]+|[0-9]+)(?![A-Za-z_]) # an number
  599. |
  600. (?i:\\sIN\\s*\\(\\s*[0-9.]+(?:\\s*,\\s*[0-9.])*\\s*\\)) # in (1, 2, 3)
  601. |
  602. (?i:\\sIN\\s*\\(\\s*[\'].+?[\'](?:\\s*,\\s*[\'].+?[\'])*\\s*\\)) # in (\'a\', \'b\', \'c\')
  603. )/x', '', $sql);
  604. }
  605. }