PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/phpMyAdmin/libraries/sqlparser.lib.php

https://bitbucket.org/izubizarreta/https-bitbucket.org-bityvip
PHP | 2759 lines | 1806 code | 241 blank | 712 comment | 805 complexity | 3d5c0861ba74d55b8b762cd32fd376c0 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.0, JSON, GPL-2.0, BSD-3-Clause, LGPL-2.1, MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /** SQL Parser Functions for phpMyAdmin
  4. *
  5. * These functions define an SQL parser system, capable of understanding and
  6. * extracting data from a MySQL type SQL query.
  7. *
  8. * The basic procedure for using the new SQL parser:
  9. * On any page that needs to extract data from a query or to pretty-print a
  10. * query, you need code like this up at the top:
  11. *
  12. * ($sql contains the query)
  13. * $parsed_sql = PMA_SQP_parse($sql);
  14. *
  15. * If you want to extract data from it then, you just need to run
  16. * $sql_info = PMA_SQP_analyze($parsed_sql);
  17. *
  18. * See comments in PMA_SQP_analyze for the returned info
  19. * from the analyzer.
  20. *
  21. * If you want a pretty-printed version of the query, do:
  22. * $string = PMA_SQP_formatHtml($parsed_sql);
  23. * (note that that you need to have syntax.css.php included somehow in your
  24. * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
  25. * href="syntax.css.php" />' at the moment.)
  26. *
  27. * @package PhpMyAdmin
  28. */
  29. if (! defined('PHPMYADMIN')) {
  30. exit;
  31. }
  32. /**
  33. * Minimum inclusion? (i.e. for the stylesheet builder)
  34. */
  35. if (! defined('PMA_MINIMUM_COMMON')) {
  36. /**
  37. * Include the string library as we use it heavily
  38. */
  39. include_once './libraries/string.lib.php';
  40. /**
  41. * Include data for the SQL Parser
  42. */
  43. include_once './libraries/sqlparser.data.php';
  44. if (!defined('TESTSUITE')) {
  45. include_once './libraries/mysql_charsets.lib.php';
  46. }
  47. if (! isset($mysql_charsets)) {
  48. $mysql_charsets = array();
  49. $mysql_collations_flat = array();
  50. }
  51. if (!defined('DEBUG_TIMING')) {
  52. /**
  53. * currently we don't need the $pos (token position in query)
  54. * for other purposes than LIMIT clause verification,
  55. * so many calls to this function do not include the 4th parameter
  56. */
  57. function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
  58. {
  59. $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
  60. $arrsize++;
  61. } // end of the "PMA_SQP_arrayAdd()" function
  62. } else {
  63. /**
  64. * This is debug variant of above.
  65. * @ignore
  66. */
  67. function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
  68. {
  69. global $timer;
  70. $t = $timer;
  71. $arr[] = array(
  72. 'type' => $type,
  73. 'data' => $data,
  74. 'pos' => $pos,
  75. 'time' => $t);
  76. $timer = microtime();
  77. $arrsize++;
  78. } // end of the "PMA_SQP_arrayAdd()" function
  79. } // end if... else...
  80. /**
  81. * Reset the error variable for the SQL parser
  82. *
  83. * @access public
  84. */
  85. function PMA_SQP_resetError()
  86. {
  87. global $SQP_errorString;
  88. $SQP_errorString = '';
  89. unset($SQP_errorString);
  90. }
  91. /**
  92. * Get the contents of the error variable for the SQL parser
  93. *
  94. * @return string Error string from SQL parser
  95. *
  96. * @access public
  97. */
  98. function PMA_SQP_getErrorString()
  99. {
  100. global $SQP_errorString;
  101. return isset($SQP_errorString) ? $SQP_errorString : '';
  102. }
  103. /**
  104. * Check if the SQL parser hit an error
  105. *
  106. * @return boolean error state
  107. *
  108. * @access public
  109. */
  110. function PMA_SQP_isError()
  111. {
  112. global $SQP_errorString;
  113. return isset($SQP_errorString) && !empty($SQP_errorString);
  114. }
  115. /**
  116. * Set an error message for the system
  117. *
  118. * @param string The error message
  119. * @param string The failing SQL query
  120. *
  121. * @access private
  122. * @scope SQL Parser internal
  123. */
  124. function PMA_SQP_throwError($message, $sql)
  125. {
  126. global $SQP_errorString;
  127. $SQP_errorString = '<p>'.__('There seems to be an error in your SQL query. The MySQL server error output below, if there is any, may also help you in diagnosing the problem') . '</p>' . "\n"
  128. . '<pre>' . "\n"
  129. . 'ERROR: ' . $message . "\n"
  130. . 'SQL: ' . htmlspecialchars($sql) . "\n"
  131. . '</pre>' . "\n";
  132. } // end of the "PMA_SQP_throwError()" function
  133. /**
  134. * Do display the bug report
  135. *
  136. * @param string The error message
  137. * @param string The failing SQL query
  138. *
  139. * @access public
  140. */
  141. function PMA_SQP_bug($message, $sql)
  142. {
  143. global $SQP_errorString;
  144. $debugstr = 'ERROR: ' . $message . "\n";
  145. $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
  146. $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ';
  147. $debugstr .= PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
  148. $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
  149. $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
  150. $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
  151. $debugstr .= 'SQL: ' . htmlspecialchars($sql);
  152. $encodedstr = $debugstr;
  153. if (@function_exists('gzcompress')) {
  154. $encodedstr = gzcompress($debugstr, 9);
  155. }
  156. $encodedstr = preg_replace(
  157. "/(\015\012)|(\015)|(\012)/",
  158. '<br />' . "\n",
  159. chunk_split(base64_encode($encodedstr)));
  160. $SQP_errorString .= __('There is a chance that you may have found a bug in the SQL parser. Please examine your query closely, and check that the quotes are correct and not mis-matched. Other possible failure causes may be that you are uploading a file with binary outside of a quoted text area. You can also try your query on the MySQL command line interface. The MySQL server error output below, if there is any, may also help you in diagnosing the problem. If you still have problems or if the parser fails where the command line interface succeeds, please reduce your SQL query input to the single query that causes problems, and submit a bug report with the data chunk in the CUT section below:')
  161. . '<br />' . "\n"
  162. . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
  163. . $encodedstr . "\n"
  164. . '----' . __('END CUT') . '----' . '<br />' . "\n";
  165. $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
  166. . '<pre>' . "\n"
  167. . $debugstr
  168. . '</pre>' . "\n"
  169. . '----' . __('END RAW') . '----<br />' . "\n";
  170. } // end of the "PMA_SQP_bug()" function
  171. /**
  172. * Parses the SQL queries
  173. *
  174. * @param string The SQL query list
  175. *
  176. * @return mixed Most of times, nothing...
  177. *
  178. * @global array The current PMA configuration
  179. * @global array MySQL column attributes
  180. * @global array MySQL reserved words
  181. * @global array MySQL column types
  182. * @global array MySQL function names
  183. * @global array List of available character sets
  184. * @global array List of available collations
  185. *
  186. * @access public
  187. */
  188. function PMA_SQP_parse($sql)
  189. {
  190. static $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word;
  191. static $PMA_SQPdata_column_type;
  192. static $PMA_SQPdata_function_name, $PMA_SQPdata_forbidden_word;
  193. global $mysql_charsets, $mysql_collations_flat;
  194. // Convert all line feeds to Unix style
  195. $sql = str_replace("\r\n", "\n", $sql);
  196. $sql = str_replace("\r", "\n", $sql);
  197. $len = PMA_strlen($sql);
  198. if ($len == 0) {
  199. return array();
  200. }
  201. // Create local hashtables
  202. if (!isset($PMA_SQPdata_column_attrib)) {
  203. $PMA_SQPdata_column_attrib = array_flip(
  204. $GLOBALS['PMA_SQPdata_column_attrib']
  205. );
  206. $PMA_SQPdata_function_name = array_flip(
  207. $GLOBALS['PMA_SQPdata_function_name']
  208. );
  209. $PMA_SQPdata_reserved_word = array_flip(
  210. $GLOBALS['PMA_SQPdata_reserved_word']
  211. );
  212. $PMA_SQPdata_forbidden_word = array_flip(
  213. $GLOBALS['PMA_SQPdata_forbidden_word']
  214. );
  215. $PMA_SQPdata_column_type = array_flip(
  216. $GLOBALS['PMA_SQPdata_column_type']
  217. );
  218. }
  219. $sql_array = array();
  220. $sql_array['raw'] = $sql;
  221. $count1 = 0;
  222. $count2 = 0;
  223. $punct_queryend = ';';
  224. $punct_qualifier = '.';
  225. $punct_listsep = ',';
  226. $punct_level_plus = '(';
  227. $punct_level_minus = ')';
  228. $punct_user = '@';
  229. $digit_floatdecimal = '.';
  230. $digit_hexset = 'x';
  231. $bracket_list = '()[]{}';
  232. $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
  233. $allpunct_list_pair = array(
  234. '!=' => 1,
  235. '&&' => 1,
  236. ':=' => 1,
  237. '<<' => 1,
  238. '<=' => 1,
  239. '<=>' => 1,
  240. '<>' => 1,
  241. '>=' => 1,
  242. '>>' => 1,
  243. '||' => 1,
  244. '==' => 1
  245. );
  246. $quote_list = '\'"`';
  247. $arraysize = 0;
  248. $previous_was_space = false;
  249. $this_was_space = false;
  250. $previous_was_bracket = false;
  251. $this_was_bracket = false;
  252. $previous_was_punct = false;
  253. $this_was_punct = false;
  254. $previous_was_listsep = false;
  255. $this_was_listsep = false;
  256. $previous_was_quote = false;
  257. $this_was_quote = false;
  258. while ($count2 < $len) {
  259. $c = PMA_substr($sql, $count2, 1);
  260. $count1 = $count2;
  261. $previous_was_space = $this_was_space;
  262. $this_was_space = false;
  263. $previous_was_bracket = $this_was_bracket;
  264. $this_was_bracket = false;
  265. $previous_was_punct = $this_was_punct;
  266. $this_was_punct = false;
  267. $previous_was_listsep = $this_was_listsep;
  268. $this_was_listsep = false;
  269. $previous_was_quote = $this_was_quote;
  270. $this_was_quote = false;
  271. if (($c == "\n")) {
  272. $this_was_space = true;
  273. $count2++;
  274. PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
  275. continue;
  276. }
  277. // Checks for white space
  278. if (PMA_STR_isSpace($c)) {
  279. $this_was_space = true;
  280. $count2++;
  281. continue;
  282. }
  283. // Checks for comment lines.
  284. // MySQL style #
  285. // C style /* */
  286. // ANSI style --
  287. $next_c = PMA_substr($sql, $count2 + 1, 1);
  288. if (($c == '#')
  289. || (($count2 + 1 < $len) && ($c == '/') && ($next_c == '*'))
  290. || (($count2 + 2 == $len) && ($c == '-') && ($next_c == '-'))
  291. || (($count2 + 2 < $len) && ($c == '-') && ($next_c == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
  292. $count2++;
  293. $pos = 0;
  294. $type = 'bad';
  295. switch ($c) {
  296. case '#':
  297. $type = 'mysql';
  298. case '-':
  299. $type = 'ansi';
  300. $pos = PMA_strpos($sql, "\n", $count2);
  301. break;
  302. case '/':
  303. $type = 'c';
  304. $pos = PMA_strpos($sql, '*/', $count2);
  305. $pos += 2;
  306. break;
  307. default:
  308. break;
  309. } // end switch
  310. $count2 = ($pos < $count2) ? $len : $pos;
  311. $str = PMA_substr($sql, $count1, $count2 - $count1);
  312. PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
  313. continue;
  314. } // end if
  315. // Checks for something inside quotation marks
  316. if (PMA_strpos($quote_list, $c) !== false) {
  317. $startquotepos = $count2;
  318. $quotetype = $c;
  319. $count2++;
  320. $escaped = false;
  321. $pos = $count2;
  322. $oldpos = 0;
  323. do {
  324. $oldpos = $pos;
  325. $pos = PMA_strpos(' ' . $sql, $quotetype, $oldpos + 1) - 1;
  326. // ($pos === false)
  327. if ($pos < 0) {
  328. if ($c == '`') {
  329. /*
  330. * Behave same as MySQL and accept end of query as end of backtick.
  331. * I know this is sick, but MySQL behaves like this:
  332. *
  333. * SELECT * FROM `table
  334. *
  335. * is treated like
  336. *
  337. * SELECT * FROM `table`
  338. */
  339. $pos_quote_separator = PMA_strpos(' ' . $sql, $GLOBALS['sql_delimiter'], $oldpos + 1) - 1;
  340. if ($pos_quote_separator < 0) {
  341. $len += 1;
  342. $sql .= '`';
  343. $sql_array['raw'] .= '`';
  344. $pos = $len;
  345. } else {
  346. $len += 1;
  347. $sql = PMA_substr($sql, 0, $pos_quote_separator) . '`' . PMA_substr($sql, $pos_quote_separator);
  348. $sql_array['raw'] = $sql;
  349. $pos = $pos_quote_separator;
  350. }
  351. if (class_exists('PMA_Message') && $GLOBALS['is_ajax_request'] != true) {
  352. PMA_Message::notice(__('Automatically appended backtick to the end of query!'))->display();
  353. }
  354. } else {
  355. $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
  356. . 'STR: ' . htmlspecialchars($quotetype);
  357. PMA_SQP_throwError($debugstr, $sql);
  358. return $sql_array;
  359. }
  360. }
  361. // If the quote is the first character, it can't be
  362. // escaped, so don't do the rest of the code
  363. if ($pos == 0) {
  364. break;
  365. }
  366. // Checks for MySQL escaping using a \
  367. // And checks for ANSI escaping using the $quotetype character
  368. if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos) && $c != '`') {
  369. $pos ++;
  370. continue;
  371. } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
  372. $pos = $pos + 2;
  373. continue;
  374. } else {
  375. break;
  376. }
  377. } while ($len > $pos); // end do
  378. $count2 = $pos;
  379. $count2++;
  380. $type = 'quote_';
  381. switch ($quotetype) {
  382. case '\'':
  383. $type .= 'single';
  384. $this_was_quote = true;
  385. break;
  386. case '"':
  387. $type .= 'double';
  388. $this_was_quote = true;
  389. break;
  390. case '`':
  391. $type .= 'backtick';
  392. $this_was_quote = true;
  393. break;
  394. default:
  395. break;
  396. } // end switch
  397. $data = PMA_substr($sql, $count1, $count2 - $count1);
  398. PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
  399. continue;
  400. }
  401. // Checks for brackets
  402. if (PMA_strpos($bracket_list, $c) !== false) {
  403. // All bracket tokens are only one item long
  404. $this_was_bracket = true;
  405. $count2++;
  406. $type_type = '';
  407. if (PMA_strpos('([{', $c) !== false) {
  408. $type_type = 'open';
  409. } else {
  410. $type_type = 'close';
  411. }
  412. $type_style = '';
  413. if (PMA_strpos('()', $c) !== false) {
  414. $type_style = 'round';
  415. } elseif (PMA_strpos('[]', $c) !== false) {
  416. $type_style = 'square';
  417. } else {
  418. $type_style = 'curly';
  419. }
  420. $type = 'punct_bracket_' . $type_type . '_' . $type_style;
  421. PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
  422. continue;
  423. }
  424. /* DEBUG
  425. echo '<pre>1';
  426. var_dump(PMA_STR_isSqlIdentifier($c, false));
  427. var_dump($c == '@');
  428. var_dump($c == '.');
  429. var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
  430. var_dump($previous_was_space);
  431. var_dump($previous_was_bracket);
  432. var_dump($previous_was_listsep);
  433. echo '</pre>';
  434. */
  435. // Checks for identifier (alpha or numeric)
  436. if (PMA_STR_isSqlIdentifier($c, false)
  437. || $c == '@'
  438. || ($c == '.'
  439. && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1))
  440. && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
  441. /* DEBUG
  442. echo PMA_substr($sql, $count2);
  443. echo '<hr />';
  444. */
  445. $count2++;
  446. /**
  447. * @todo a @ can also be present in expressions like
  448. * FROM 'user'@'%' or TO 'user'@'%'
  449. * in this case, the @ is wrongly marked as alpha_variable
  450. */
  451. $is_identifier = $previous_was_punct;
  452. $is_sql_variable = $c == '@' && ! $previous_was_quote;
  453. $is_user = $c == '@' && $previous_was_quote;
  454. $is_digit = !$is_identifier && !$is_sql_variable && PMA_STR_isDigit($c);
  455. $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && PMA_substr($sql, $count2, 1) == 'x';
  456. $is_float_digit = $c == '.';
  457. $is_float_digit_exponent = false;
  458. /* DEBUG
  459. echo '<pre>2';
  460. var_dump($is_identifier);
  461. var_dump($is_sql_variable);
  462. var_dump($is_digit);
  463. var_dump($is_float_digit);
  464. echo '</pre>';
  465. */
  466. // Fast skip is especially needed for huge BLOB data
  467. if ($is_hex_digit) {
  468. $count2++;
  469. $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
  470. if ($pos > $count2) {
  471. $count2 = $pos;
  472. }
  473. unset($pos);
  474. } elseif ($is_digit) {
  475. $pos = strspn($sql, '0123456789', $count2);
  476. if ($pos > $count2) {
  477. $count2 = $pos;
  478. }
  479. unset($pos);
  480. }
  481. while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
  482. $c2 = PMA_substr($sql, $count2, 1);
  483. if ($is_sql_variable && ($c2 == '.')) {
  484. $count2++;
  485. continue;
  486. }
  487. if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
  488. $count2++;
  489. if (!$is_float_digit) {
  490. $is_float_digit = true;
  491. continue;
  492. } else {
  493. $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
  494. . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
  495. PMA_SQP_throwError($debugstr, $sql);
  496. return $sql_array;
  497. }
  498. }
  499. if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
  500. if (!$is_float_digit_exponent) {
  501. $is_float_digit_exponent = true;
  502. $is_float_digit = true;
  503. $count2++;
  504. continue;
  505. } else {
  506. $is_digit = false;
  507. $is_float_digit = false;
  508. }
  509. }
  510. if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
  511. $count2++;
  512. continue;
  513. } else {
  514. $is_digit = false;
  515. $is_hex_digit = false;
  516. }
  517. $count2++;
  518. } // end while
  519. $l = $count2 - $count1;
  520. $str = PMA_substr($sql, $count1, $l);
  521. $type = '';
  522. if ($is_digit || $is_float_digit || $is_hex_digit) {
  523. $type = 'digit';
  524. if ($is_float_digit) {
  525. $type .= '_float';
  526. } elseif ($is_hex_digit) {
  527. $type .= '_hex';
  528. } else {
  529. $type .= '_integer';
  530. }
  531. } elseif ($is_user) {
  532. $type = 'punct_user';
  533. } elseif ($is_sql_variable != false) {
  534. $type = 'alpha_variable';
  535. } else {
  536. $type = 'alpha';
  537. } // end if... else....
  538. PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
  539. continue;
  540. }
  541. // Checks for punct
  542. if (PMA_strpos($allpunct_list, $c) !== false) {
  543. while (($count2 < $len) && PMA_strpos($allpunct_list, PMA_substr($sql, $count2, 1)) !== false) {
  544. $count2++;
  545. }
  546. $l = $count2 - $count1;
  547. if ($l == 1) {
  548. $punct_data = $c;
  549. } else {
  550. $punct_data = PMA_substr($sql, $count1, $l);
  551. }
  552. // Special case, sometimes, althought two characters are
  553. // adjectent directly, they ACTUALLY need to be seperate
  554. /* DEBUG
  555. echo '<pre>';
  556. var_dump($l);
  557. var_dump($punct_data);
  558. echo '</pre>';
  559. */
  560. if ($l == 1) {
  561. $t_suffix = '';
  562. switch ($punct_data) {
  563. case $punct_queryend:
  564. $t_suffix = '_queryend';
  565. break;
  566. case $punct_qualifier:
  567. $t_suffix = '_qualifier';
  568. $this_was_punct = true;
  569. break;
  570. case $punct_listsep:
  571. $this_was_listsep = true;
  572. $t_suffix = '_listsep';
  573. break;
  574. default:
  575. break;
  576. }
  577. PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
  578. } elseif ($punct_data == $GLOBALS['sql_delimiter'] || isset($allpunct_list_pair[$punct_data])) {
  579. // Ok, we have one of the valid combined punct expressions
  580. PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
  581. } else {
  582. // Bad luck, lets split it up more
  583. $first = $punct_data[0];
  584. $first2 = $punct_data[0] . $punct_data[1];
  585. $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
  586. $last = $punct_data[$l - 1];
  587. if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
  588. $count2 = $count1 + 1;
  589. $punct_data = $first;
  590. } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' '))) {
  591. $count2 -= 2;
  592. $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
  593. } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
  594. $count2--;
  595. $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
  596. } elseif ($last != '~') {
  597. /**
  598. * @todo for negation operator, split in 2 tokens ?
  599. * "select x&~1 from t"
  600. * becomes "select x & ~ 1 from t" ?
  601. */
  602. $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
  603. . 'STR: ' . htmlspecialchars($punct_data);
  604. PMA_SQP_throwError($debugstr, $sql);
  605. return $sql_array;
  606. }
  607. PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
  608. continue;
  609. } // end if... elseif... else
  610. continue;
  611. }
  612. // DEBUG
  613. $count2++;
  614. $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
  615. . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
  616. PMA_SQP_bug($debugstr, $sql);
  617. return $sql_array;
  618. } // end while ($count2 < $len)
  619. /*
  620. echo '<pre>';
  621. print_r($sql_array);
  622. echo '</pre>';
  623. */
  624. if ($arraysize > 0) {
  625. $t_next = $sql_array[0]['type'];
  626. $t_prev = '';
  627. $t_bef_prev = '';
  628. $t_cur = '';
  629. $d_next = $sql_array[0]['data'];
  630. $d_prev = '';
  631. $d_bef_prev = '';
  632. $d_cur = '';
  633. $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
  634. $d_prev_upper = '';
  635. $d_bef_prev_upper = '';
  636. $d_cur_upper = '';
  637. }
  638. for ($i = 0; $i < $arraysize; $i++) {
  639. $t_bef_prev = $t_prev;
  640. $t_prev = $t_cur;
  641. $t_cur = $t_next;
  642. $d_bef_prev = $d_prev;
  643. $d_prev = $d_cur;
  644. $d_cur = $d_next;
  645. $d_bef_prev_upper = $d_prev_upper;
  646. $d_prev_upper = $d_cur_upper;
  647. $d_cur_upper = $d_next_upper;
  648. if (($i + 1) < $arraysize) {
  649. $t_next = $sql_array[$i + 1]['type'];
  650. $d_next = $sql_array[$i + 1]['data'];
  651. $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
  652. } else {
  653. $t_next = '';
  654. $d_next = '';
  655. $d_next_upper = '';
  656. }
  657. //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
  658. if ($t_cur == 'alpha') {
  659. $t_suffix = '_identifier';
  660. // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
  661. if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
  662. $t_suffix = '_bitfield_constant_introducer';
  663. } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
  664. $t_suffix = '_identifier';
  665. } elseif (($t_next == 'punct_bracket_open_round')
  666. && isset($PMA_SQPdata_function_name[$d_cur_upper])) {
  667. /**
  668. * @todo 2005-10-16: in the case of a CREATE TABLE containing
  669. * a TIMESTAMP, since TIMESTAMP() is also a function, it's
  670. * found here and the token is wrongly marked as alpha_functionName.
  671. * But we compensate for this when analysing for timestamp_not_null
  672. * later in this script.
  673. *
  674. * Same applies to CHAR vs. CHAR() function.
  675. */
  676. $t_suffix = '_functionName';
  677. /* There are functions which might be as well column types */
  678. } elseif (isset($PMA_SQPdata_column_type[$d_cur_upper])) {
  679. $t_suffix = '_columnType';
  680. /**
  681. * Temporary fix for BUG #621357 and #2027720
  682. *
  683. * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
  684. */
  685. if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
  686. $t_suffix = '_reservedWord';
  687. }
  688. //END OF TEMPORARY FIX
  689. // CHARACTER is a synonym for CHAR, but can also be meant as
  690. // CHARACTER SET. In this case, we have a reserved word.
  691. if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
  692. $t_suffix = '_reservedWord';
  693. }
  694. // experimental
  695. // current is a column type, so previous must not be
  696. // a reserved word but an identifier
  697. // CREATE TABLE SG_Persons (first varchar(64))
  698. //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
  699. // $sql_array[$i-1]['type'] = 'alpha_identifier';
  700. //}
  701. } elseif (isset($PMA_SQPdata_reserved_word[$d_cur_upper])) {
  702. $t_suffix = '_reservedWord';
  703. } elseif (isset($PMA_SQPdata_column_attrib[$d_cur_upper])) {
  704. $t_suffix = '_columnAttrib';
  705. // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
  706. // it should be regarded as a reserved word.
  707. if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
  708. $t_suffix = '_reservedWord';
  709. }
  710. if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
  711. $t_suffix = '_reservedWord';
  712. }
  713. // Binary as character set
  714. if ($d_cur_upper == 'BINARY' && (
  715. ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
  716. || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
  717. || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
  718. || $d_prev_upper == 'CHARSET'
  719. ) && in_array($d_cur, $mysql_charsets)) {
  720. $t_suffix = '_charset';
  721. }
  722. } elseif (in_array($d_cur, $mysql_charsets)
  723. || in_array($d_cur, $mysql_collations_flat)
  724. || ($d_cur{0} == '_' && in_array(substr($d_cur, 1), $mysql_charsets))) {
  725. $t_suffix = '_charset';
  726. } else {
  727. // Do nothing
  728. }
  729. // check if present in the list of forbidden words
  730. if ($t_suffix == '_reservedWord' && isset($PMA_SQPdata_forbidden_word[$d_cur_upper])) {
  731. $sql_array[$i]['forbidden'] = true;
  732. } else {
  733. $sql_array[$i]['forbidden'] = false;
  734. }
  735. $sql_array[$i]['type'] .= $t_suffix;
  736. }
  737. } // end for
  738. // Stores the size of the array inside the array, as count() is a slow
  739. // operation.
  740. $sql_array['len'] = $arraysize;
  741. // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
  742. // Sends the data back
  743. return $sql_array;
  744. } // end of the "PMA_SQP_parse()" function
  745. /**
  746. * Checks for token types being what we want...
  747. *
  748. * @param string String of type that we have
  749. * @param string String of type that we want
  750. *
  751. * @return boolean result of check
  752. *
  753. * @access private
  754. */
  755. function PMA_SQP_typeCheck($toCheck, $whatWeWant)
  756. {
  757. $typeSeperator = '_';
  758. if (strcmp($whatWeWant, $toCheck) == 0) {
  759. return true;
  760. } else {
  761. if (strpos($whatWeWant, $typeSeperator) === false) {
  762. return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
  763. } else {
  764. return false;
  765. }
  766. }
  767. }
  768. /**
  769. * Analyzes SQL queries
  770. *
  771. * @param array The SQL queries
  772. *
  773. * @return array The analyzed SQL queries
  774. *
  775. * @access public
  776. */
  777. function PMA_SQP_analyze($arr)
  778. {
  779. if ($arr == array() || ! isset($arr['len'])) {
  780. return array();
  781. }
  782. $result = array();
  783. $size = $arr['len'];
  784. $subresult = array(
  785. 'querytype' => '',
  786. 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
  787. 'position_of_first_select' => '', // the array index
  788. 'from_clause'=> '',
  789. 'group_by_clause'=> '',
  790. 'order_by_clause'=> '',
  791. 'having_clause' => '',
  792. 'limit_clause' => '',
  793. 'where_clause' => '',
  794. 'where_clause_identifiers' => array(),
  795. 'unsorted_query' => '',
  796. 'queryflags' => array(),
  797. 'select_expr' => array(),
  798. 'table_ref' => array(),
  799. 'foreign_keys' => array(),
  800. 'create_table_fields' => array()
  801. );
  802. $subresult_empty = $subresult;
  803. $seek_queryend = false;
  804. $seen_end_of_table_ref = false;
  805. $number_of_brackets_in_extract = 0;
  806. $number_of_brackets_in_group_concat = 0;
  807. $number_of_brackets = 0;
  808. $in_subquery = false;
  809. $seen_subquery = false;
  810. $seen_from = false;
  811. // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
  812. // we must not use CURDATE as a table_ref
  813. // so we track whether we are in the EXTRACT()
  814. $in_extract = false;
  815. // for GROUP_CONCAT(...)
  816. $in_group_concat = false;
  817. /* Description of analyzer results
  818. *
  819. * db, table, column, alias
  820. * ------------------------
  821. *
  822. * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
  823. *
  824. * The SELECT syntax (simplified) is
  825. *
  826. * SELECT
  827. * select_expression,...
  828. * [FROM [table_references]
  829. *
  830. *
  831. * ['select_expr'] is filled with each expression, the key represents the
  832. * expression position in the list (0-based) (so we don't lose track of
  833. * multiple occurences of the same column).
  834. *
  835. * ['table_ref'] is filled with each table ref, same thing for the key.
  836. *
  837. * I create all sub-values empty, even if they are
  838. * not present (for example no select_expression alias).
  839. *
  840. * There is a debug section at the end of loop #1, if you want to
  841. * see the exact contents of select_expr and table_ref
  842. *
  843. * queryflags
  844. * ----------
  845. *
  846. * In $subresult, array 'queryflags' is filled, according to what we
  847. * find in the query.
  848. *
  849. * Currently, those are generated:
  850. *
  851. * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
  852. * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
  853. * ['queryflags']['distinct'] = 1; for a DISTINCT
  854. * ['queryflags']['union'] = 1; for a UNION
  855. * ['queryflags']['join'] = 1; for a JOIN
  856. * ['queryflags']['offset'] = 1; for the presence of OFFSET
  857. * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
  858. *
  859. * query clauses
  860. * -------------
  861. *
  862. * The select is splitted in those clauses:
  863. * ['select_expr_clause']
  864. * ['from_clause']
  865. * ['group_by_clause']
  866. * ['order_by_clause']
  867. * ['having_clause']
  868. * ['limit_clause']
  869. * ['where_clause']
  870. *
  871. * The identifiers of the WHERE clause are put into the array
  872. * ['where_clause_identifier']
  873. *
  874. * For a SELECT, the whole query without the ORDER BY clause is put into
  875. * ['unsorted_query']
  876. *
  877. * foreign keys
  878. * ------------
  879. * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
  880. * analyzed and ['foreign_keys'] is an array filled with
  881. * the constraint name, the index list,
  882. * the REFERENCES table name and REFERENCES index list,
  883. * and ON UPDATE | ON DELETE clauses
  884. *
  885. * position_of_first_select
  886. * ------------------------
  887. *
  888. * The array index of the first SELECT we find. Will be used to
  889. * insert a SQL_CALC_FOUND_ROWS.
  890. *
  891. * create_table_fields
  892. * -------------------
  893. *
  894. * Used to detect the DEFAULT CURRENT_TIMESTAMP and
  895. * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
  896. * Also used to store the default value of the field.
  897. * An array, each element is the identifier name.
  898. * Note that for now, the timestamp_not_null element is created
  899. * even for non-TIMESTAMP fields.
  900. *
  901. * Sub-elements: ['type'] which contains the column type
  902. * optional (currently they are never false but can be absent):
  903. * ['default_current_timestamp'] boolean
  904. * ['on_update_current_timestamp'] boolean
  905. * ['timestamp_not_null'] boolean
  906. *
  907. * section_before_limit, section_after_limit
  908. * -----------------------------------------
  909. *
  910. * Marks the point of the query where we can insert a LIMIT clause;
  911. * so the section_before_limit will contain the left part before
  912. * a possible LIMIT clause
  913. *
  914. *
  915. * End of description of analyzer results
  916. */
  917. // must be sorted
  918. // TODO: current logic checks for only one word, so I put only the
  919. // first word of the reserved expressions that end a table ref;
  920. // maybe this is not ok (the first word might mean something else)
  921. // $words_ending_table_ref = array(
  922. // 'FOR UPDATE',
  923. // 'GROUP BY',
  924. // 'HAVING',
  925. // 'LIMIT',
  926. // 'LOCK IN SHARE MODE',
  927. // 'ORDER BY',
  928. // 'PROCEDURE',
  929. // 'UNION',
  930. // 'WHERE'
  931. // );
  932. $words_ending_table_ref = array(
  933. 'FOR' => 1,
  934. 'GROUP' => 1,
  935. 'HAVING' => 1,
  936. 'LIMIT' => 1,
  937. 'LOCK' => 1,
  938. 'ORDER' => 1,
  939. 'PROCEDURE' => 1,
  940. 'UNION' => 1,
  941. 'WHERE' => 1
  942. );
  943. $words_ending_clauses = array(
  944. 'FOR' => 1,
  945. 'LIMIT' => 1,
  946. 'LOCK' => 1,
  947. 'PROCEDURE' => 1,
  948. 'UNION' => 1
  949. );
  950. $supported_query_types = array(
  951. 'SELECT' => 1,
  952. /*
  953. // Support for these additional query types will come later on.
  954. 'DELETE' => 1,
  955. 'INSERT' => 1,
  956. 'REPLACE' => 1,
  957. 'TRUNCATE' => 1,
  958. 'UPDATE' => 1,
  959. 'EXPLAIN' => 1,
  960. 'DESCRIBE' => 1,
  961. 'SHOW' => 1,
  962. 'CREATE' => 1,
  963. 'SET' => 1,
  964. 'ALTER' => 1
  965. */
  966. );
  967. // loop #1 for each token: select_expr, table_ref for SELECT
  968. for ($i = 0; $i < $size; $i++) {
  969. //DEBUG echo "Loop1 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
  970. // High speed seek for locating the end of the current query
  971. if ($seek_queryend == true) {
  972. if ($arr[$i]['type'] == 'punct_queryend') {
  973. $seek_queryend = false;
  974. } else {
  975. continue;
  976. } // end if (type == punct_queryend)
  977. } // end if ($seek_queryend)
  978. /**
  979. * Note: do not split if this is a punct_queryend for the first and only query
  980. * @todo when we find a UNION, should we split in another subresult?
  981. */
  982. if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
  983. $result[] = $subresult;
  984. $subresult = $subresult_empty;
  985. continue;
  986. } // end if (type == punct_queryend)
  987. // ==============================================================
  988. if ($arr[$i]['type'] == 'punct_bracket_open_round') {
  989. $number_of_brackets++;
  990. if ($in_extract) {
  991. $number_of_brackets_in_extract++;
  992. }
  993. if ($in_group_concat) {
  994. $number_of_brackets_in_group_concat++;
  995. }
  996. }
  997. // ==============================================================
  998. if ($arr[$i]['type'] == 'punct_bracket_close_round') {
  999. $number_of_brackets--;
  1000. if ($number_of_brackets == 0) {
  1001. $in_subquery = false;
  1002. }
  1003. if ($in_extract) {
  1004. $number_of_brackets_in_extract--;
  1005. if ($number_of_brackets_in_extract == 0) {
  1006. $in_extract = false;
  1007. }
  1008. }
  1009. if ($in_group_concat) {
  1010. $number_of_brackets_in_group_concat--;
  1011. if ($number_of_brackets_in_group_concat == 0) {
  1012. $in_group_concat = false;
  1013. }
  1014. }
  1015. }
  1016. if ($in_subquery) {
  1017. /**
  1018. * skip the subquery to avoid setting
  1019. * select_expr or table_ref with the contents
  1020. * of this subquery; this is to avoid a bug when
  1021. * trying to edit the results of
  1022. * select * from child where not exists (select id from
  1023. * parent where child.parent_id = parent.id);
  1024. */
  1025. continue;
  1026. }
  1027. // ==============================================================
  1028. if ($arr[$i]['type'] == 'alpha_functionName') {
  1029. $upper_data = strtoupper($arr[$i]['data']);
  1030. if ($upper_data =='EXTRACT') {
  1031. $in_extract = true;
  1032. $number_of_brackets_in_extract = 0;
  1033. }
  1034. if ($upper_data =='GROUP_CONCAT') {
  1035. $in_group_concat = true;
  1036. $number_of_brackets_in_group_concat = 0;
  1037. }
  1038. }
  1039. // ==============================================================
  1040. if ($arr[$i]['type'] == 'alpha_reservedWord'
  1041. //&& $arr[$i]['forbidden'] == false) {
  1042. ) {
  1043. // We don't know what type of query yet, so run this
  1044. if ($subresult['querytype'] == '') {
  1045. $subresult['querytype'] = strtoupper($arr[$i]['data']);
  1046. } // end if (querytype was empty)
  1047. // Check if we support this type of query
  1048. if (!isset($supported_query_types[$subresult['querytype']])) {
  1049. // Skip ahead to the next one if we don't
  1050. $seek_queryend = true;
  1051. continue;
  1052. } // end if (query not supported)
  1053. // upper once
  1054. $upper_data = strtoupper($arr[$i]['data']);
  1055. /**
  1056. * @todo reset for each query?
  1057. */
  1058. if ($upper_data == 'SELECT') {
  1059. if ($number_of_brackets > 0) {
  1060. $in_subquery = true;
  1061. $seen_subquery = true;
  1062. // this is a subquery so do not analyze inside it
  1063. continue;
  1064. }
  1065. $seen_from = false;
  1066. $previous_was_identifier = false;
  1067. $current_select_expr = -1;
  1068. $seen_end_of_table_ref = false;
  1069. } // end if (data == SELECT)
  1070. if ($upper_data =='FROM' && !$in_extract) {
  1071. $current_table_ref = -1;
  1072. $seen_from = true;
  1073. $previous_was_identifier = false;
  1074. $save_table_ref = true;
  1075. } // end if (data == FROM)
  1076. // here, do not 'continue' the loop, as we have more work for
  1077. // reserved words below
  1078. } // end if (type == alpha_reservedWord)
  1079. // ==============================
  1080. if ($arr[$i]['type'] == 'quote_backtick'
  1081. || $arr[$i]['type'] == 'quote_double'
  1082. || $arr[$i]['type'] == 'quote_single'
  1083. || $arr[$i]['type'] == 'alpha_identifier'
  1084. || ($arr[$i]['type'] == 'alpha_reservedWord'
  1085. && $arr[$i]['forbidden'] == false)) {
  1086. switch ($arr[$i]['type']) {
  1087. case 'alpha_identifier':
  1088. case 'alpha_reservedWord':
  1089. /**
  1090. * this is not a real reservedWord, because it's not
  1091. * present in the list of forbidden words, for example
  1092. * "storage" which can be used as an identifier
  1093. *
  1094. * @todo avoid the pretty printing in color in this case
  1095. */
  1096. $identifier = $arr[$i]['data'];
  1097. break;
  1098. case 'quote_backtick':
  1099. case 'quote_double':
  1100. case 'quote_single':
  1101. $identifier = PMA_unQuote($arr[$i]['data']);
  1102. break;
  1103. } // end switch
  1104. if ($subresult['querytype'] == 'SELECT'
  1105. && ! $in_group_concat
  1106. && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
  1107. if (!$seen_from) {
  1108. if ($previous_was_identifier && isset($chain)) {
  1109. // found alias for this select_expr, save it
  1110. // but only if we got something in $chain
  1111. // (for example, SELECT COUNT(*) AS cnt
  1112. // puts nothing in $chain, so we avoid
  1113. // setting the alias)
  1114. $alias_for_select_expr = $identifier;
  1115. } else {
  1116. $chain[] = $identifier;
  1117. $previous_was_identifier = true;
  1118. } // end if !$previous_was_identifier
  1119. } else {
  1120. // ($seen_from)
  1121. if ($save_table_ref && !$seen_end_of_table_ref) {
  1122. if ($previous_was_identifier) {
  1123. // found alias for table ref
  1124. // save it for later
  1125. $alias_for_table_ref = $identifier;
  1126. } else {
  1127. $chain[] = $identifier;
  1128. $previous_was_identifier = true;
  1129. } // end if ($previous_was_identifier)
  1130. } // end if ($save_table_ref &&!$seen_end_of_table_ref)
  1131. } // end if (!$seen_from)
  1132. } // end if (querytype SELECT)
  1133. } // end if (quote_backtick or double quote or alpha_identifier)
  1134. // ===================================
  1135. if ($arr[$i]['type'] == 'punct_qualifier') {
  1136. // to be able to detect an identifier following another
  1137. $previous_was_identifier = false;
  1138. continue;
  1139. } // end if (punct_qualifier)
  1140. /**
  1141. * @todo check if 3 identifiers following one another -> error
  1142. */
  1143. // s a v e a s e l e c t e x p r
  1144. // finding a list separator or FROM
  1145. // means that we must save the current chain of identifiers
  1146. // into a select expression
  1147. // for now, we only save a select expression if it contains
  1148. // at least one identifier, as we are interested in checking
  1149. // the columns and table names, so in "select * from persons",
  1150. // the "*" is not saved

Large files files are truncated, but you can click here to view the full file