/phpMyAdmin/libraries/sqlparser.lib.php
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
- <?php
- /* vim: set expandtab sw=4 ts=4 sts=4: */
- /** SQL Parser Functions for phpMyAdmin
- *
- * These functions define an SQL parser system, capable of understanding and
- * extracting data from a MySQL type SQL query.
- *
- * The basic procedure for using the new SQL parser:
- * On any page that needs to extract data from a query or to pretty-print a
- * query, you need code like this up at the top:
- *
- * ($sql contains the query)
- * $parsed_sql = PMA_SQP_parse($sql);
- *
- * If you want to extract data from it then, you just need to run
- * $sql_info = PMA_SQP_analyze($parsed_sql);
- *
- * See comments in PMA_SQP_analyze for the returned info
- * from the analyzer.
- *
- * If you want a pretty-printed version of the query, do:
- * $string = PMA_SQP_formatHtml($parsed_sql);
- * (note that that you need to have syntax.css.php included somehow in your
- * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
- * href="syntax.css.php" />' at the moment.)
- *
- * @package PhpMyAdmin
- */
- if (! defined('PHPMYADMIN')) {
- exit;
- }
- /**
- * Minimum inclusion? (i.e. for the stylesheet builder)
- */
- if (! defined('PMA_MINIMUM_COMMON')) {
- /**
- * Include the string library as we use it heavily
- */
- include_once './libraries/string.lib.php';
- /**
- * Include data for the SQL Parser
- */
- include_once './libraries/sqlparser.data.php';
- if (!defined('TESTSUITE')) {
- include_once './libraries/mysql_charsets.lib.php';
- }
- if (! isset($mysql_charsets)) {
- $mysql_charsets = array();
- $mysql_collations_flat = array();
- }
- if (!defined('DEBUG_TIMING')) {
- /**
- * currently we don't need the $pos (token position in query)
- * for other purposes than LIMIT clause verification,
- * so many calls to this function do not include the 4th parameter
- */
- function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
- {
- $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
- $arrsize++;
- } // end of the "PMA_SQP_arrayAdd()" function
- } else {
- /**
- * This is debug variant of above.
- * @ignore
- */
- function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
- {
- global $timer;
- $t = $timer;
- $arr[] = array(
- 'type' => $type,
- 'data' => $data,
- 'pos' => $pos,
- 'time' => $t);
- $timer = microtime();
- $arrsize++;
- } // end of the "PMA_SQP_arrayAdd()" function
- } // end if... else...
- /**
- * Reset the error variable for the SQL parser
- *
- * @access public
- */
- function PMA_SQP_resetError()
- {
- global $SQP_errorString;
- $SQP_errorString = '';
- unset($SQP_errorString);
- }
- /**
- * Get the contents of the error variable for the SQL parser
- *
- * @return string Error string from SQL parser
- *
- * @access public
- */
- function PMA_SQP_getErrorString()
- {
- global $SQP_errorString;
- return isset($SQP_errorString) ? $SQP_errorString : '';
- }
- /**
- * Check if the SQL parser hit an error
- *
- * @return boolean error state
- *
- * @access public
- */
- function PMA_SQP_isError()
- {
- global $SQP_errorString;
- return isset($SQP_errorString) && !empty($SQP_errorString);
- }
- /**
- * Set an error message for the system
- *
- * @param string The error message
- * @param string The failing SQL query
- *
- * @access private
- * @scope SQL Parser internal
- */
- function PMA_SQP_throwError($message, $sql)
- {
- global $SQP_errorString;
- $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"
- . '<pre>' . "\n"
- . 'ERROR: ' . $message . "\n"
- . 'SQL: ' . htmlspecialchars($sql) . "\n"
- . '</pre>' . "\n";
- } // end of the "PMA_SQP_throwError()" function
- /**
- * Do display the bug report
- *
- * @param string The error message
- * @param string The failing SQL query
- *
- * @access public
- */
- function PMA_SQP_bug($message, $sql)
- {
- global $SQP_errorString;
- $debugstr = 'ERROR: ' . $message . "\n";
- $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
- $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ';
- $debugstr .= PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
- $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
- $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
- $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
- $debugstr .= 'SQL: ' . htmlspecialchars($sql);
- $encodedstr = $debugstr;
- if (@function_exists('gzcompress')) {
- $encodedstr = gzcompress($debugstr, 9);
- }
- $encodedstr = preg_replace(
- "/(\015\012)|(\015)|(\012)/",
- '<br />' . "\n",
- chunk_split(base64_encode($encodedstr)));
- $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:')
- . '<br />' . "\n"
- . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
- . $encodedstr . "\n"
- . '----' . __('END CUT') . '----' . '<br />' . "\n";
- $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
- . '<pre>' . "\n"
- . $debugstr
- . '</pre>' . "\n"
- . '----' . __('END RAW') . '----<br />' . "\n";
- } // end of the "PMA_SQP_bug()" function
- /**
- * Parses the SQL queries
- *
- * @param string The SQL query list
- *
- * @return mixed Most of times, nothing...
- *
- * @global array The current PMA configuration
- * @global array MySQL column attributes
- * @global array MySQL reserved words
- * @global array MySQL column types
- * @global array MySQL function names
- * @global array List of available character sets
- * @global array List of available collations
- *
- * @access public
- */
- function PMA_SQP_parse($sql)
- {
- static $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word;
- static $PMA_SQPdata_column_type;
- static $PMA_SQPdata_function_name, $PMA_SQPdata_forbidden_word;
- global $mysql_charsets, $mysql_collations_flat;
- // Convert all line feeds to Unix style
- $sql = str_replace("\r\n", "\n", $sql);
- $sql = str_replace("\r", "\n", $sql);
- $len = PMA_strlen($sql);
- if ($len == 0) {
- return array();
- }
- // Create local hashtables
- if (!isset($PMA_SQPdata_column_attrib)) {
- $PMA_SQPdata_column_attrib = array_flip(
- $GLOBALS['PMA_SQPdata_column_attrib']
- );
- $PMA_SQPdata_function_name = array_flip(
- $GLOBALS['PMA_SQPdata_function_name']
- );
- $PMA_SQPdata_reserved_word = array_flip(
- $GLOBALS['PMA_SQPdata_reserved_word']
- );
- $PMA_SQPdata_forbidden_word = array_flip(
- $GLOBALS['PMA_SQPdata_forbidden_word']
- );
- $PMA_SQPdata_column_type = array_flip(
- $GLOBALS['PMA_SQPdata_column_type']
- );
- }
- $sql_array = array();
- $sql_array['raw'] = $sql;
- $count1 = 0;
- $count2 = 0;
- $punct_queryend = ';';
- $punct_qualifier = '.';
- $punct_listsep = ',';
- $punct_level_plus = '(';
- $punct_level_minus = ')';
- $punct_user = '@';
- $digit_floatdecimal = '.';
- $digit_hexset = 'x';
- $bracket_list = '()[]{}';
- $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
- $allpunct_list_pair = array(
- '!=' => 1,
- '&&' => 1,
- ':=' => 1,
- '<<' => 1,
- '<=' => 1,
- '<=>' => 1,
- '<>' => 1,
- '>=' => 1,
- '>>' => 1,
- '||' => 1,
- '==' => 1
- );
- $quote_list = '\'"`';
- $arraysize = 0;
- $previous_was_space = false;
- $this_was_space = false;
- $previous_was_bracket = false;
- $this_was_bracket = false;
- $previous_was_punct = false;
- $this_was_punct = false;
- $previous_was_listsep = false;
- $this_was_listsep = false;
- $previous_was_quote = false;
- $this_was_quote = false;
- while ($count2 < $len) {
- $c = PMA_substr($sql, $count2, 1);
- $count1 = $count2;
- $previous_was_space = $this_was_space;
- $this_was_space = false;
- $previous_was_bracket = $this_was_bracket;
- $this_was_bracket = false;
- $previous_was_punct = $this_was_punct;
- $this_was_punct = false;
- $previous_was_listsep = $this_was_listsep;
- $this_was_listsep = false;
- $previous_was_quote = $this_was_quote;
- $this_was_quote = false;
- if (($c == "\n")) {
- $this_was_space = true;
- $count2++;
- PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
- continue;
- }
- // Checks for white space
- if (PMA_STR_isSpace($c)) {
- $this_was_space = true;
- $count2++;
- continue;
- }
- // Checks for comment lines.
- // MySQL style #
- // C style /* */
- // ANSI style --
- $next_c = PMA_substr($sql, $count2 + 1, 1);
- if (($c == '#')
- || (($count2 + 1 < $len) && ($c == '/') && ($next_c == '*'))
- || (($count2 + 2 == $len) && ($c == '-') && ($next_c == '-'))
- || (($count2 + 2 < $len) && ($c == '-') && ($next_c == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
- $count2++;
- $pos = 0;
- $type = 'bad';
- switch ($c) {
- case '#':
- $type = 'mysql';
- case '-':
- $type = 'ansi';
- $pos = PMA_strpos($sql, "\n", $count2);
- break;
- case '/':
- $type = 'c';
- $pos = PMA_strpos($sql, '*/', $count2);
- $pos += 2;
- break;
- default:
- break;
- } // end switch
- $count2 = ($pos < $count2) ? $len : $pos;
- $str = PMA_substr($sql, $count1, $count2 - $count1);
- PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
- continue;
- } // end if
- // Checks for something inside quotation marks
- if (PMA_strpos($quote_list, $c) !== false) {
- $startquotepos = $count2;
- $quotetype = $c;
- $count2++;
- $escaped = false;
- $pos = $count2;
- $oldpos = 0;
- do {
- $oldpos = $pos;
- $pos = PMA_strpos(' ' . $sql, $quotetype, $oldpos + 1) - 1;
- // ($pos === false)
- if ($pos < 0) {
- if ($c == '`') {
- /*
- * Behave same as MySQL and accept end of query as end of backtick.
- * I know this is sick, but MySQL behaves like this:
- *
- * SELECT * FROM `table
- *
- * is treated like
- *
- * SELECT * FROM `table`
- */
- $pos_quote_separator = PMA_strpos(' ' . $sql, $GLOBALS['sql_delimiter'], $oldpos + 1) - 1;
- if ($pos_quote_separator < 0) {
- $len += 1;
- $sql .= '`';
- $sql_array['raw'] .= '`';
- $pos = $len;
- } else {
- $len += 1;
- $sql = PMA_substr($sql, 0, $pos_quote_separator) . '`' . PMA_substr($sql, $pos_quote_separator);
- $sql_array['raw'] = $sql;
- $pos = $pos_quote_separator;
- }
- if (class_exists('PMA_Message') && $GLOBALS['is_ajax_request'] != true) {
- PMA_Message::notice(__('Automatically appended backtick to the end of query!'))->display();
- }
- } else {
- $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
- . 'STR: ' . htmlspecialchars($quotetype);
- PMA_SQP_throwError($debugstr, $sql);
- return $sql_array;
- }
- }
- // If the quote is the first character, it can't be
- // escaped, so don't do the rest of the code
- if ($pos == 0) {
- break;
- }
- // Checks for MySQL escaping using a \
- // And checks for ANSI escaping using the $quotetype character
- if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos) && $c != '`') {
- $pos ++;
- continue;
- } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
- $pos = $pos + 2;
- continue;
- } else {
- break;
- }
- } while ($len > $pos); // end do
- $count2 = $pos;
- $count2++;
- $type = 'quote_';
- switch ($quotetype) {
- case '\'':
- $type .= 'single';
- $this_was_quote = true;
- break;
- case '"':
- $type .= 'double';
- $this_was_quote = true;
- break;
- case '`':
- $type .= 'backtick';
- $this_was_quote = true;
- break;
- default:
- break;
- } // end switch
- $data = PMA_substr($sql, $count1, $count2 - $count1);
- PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
- continue;
- }
- // Checks for brackets
- if (PMA_strpos($bracket_list, $c) !== false) {
- // All bracket tokens are only one item long
- $this_was_bracket = true;
- $count2++;
- $type_type = '';
- if (PMA_strpos('([{', $c) !== false) {
- $type_type = 'open';
- } else {
- $type_type = 'close';
- }
- $type_style = '';
- if (PMA_strpos('()', $c) !== false) {
- $type_style = 'round';
- } elseif (PMA_strpos('[]', $c) !== false) {
- $type_style = 'square';
- } else {
- $type_style = 'curly';
- }
- $type = 'punct_bracket_' . $type_type . '_' . $type_style;
- PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
- continue;
- }
- /* DEBUG
- echo '<pre>1';
- var_dump(PMA_STR_isSqlIdentifier($c, false));
- var_dump($c == '@');
- var_dump($c == '.');
- var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
- var_dump($previous_was_space);
- var_dump($previous_was_bracket);
- var_dump($previous_was_listsep);
- echo '</pre>';
- */
- // Checks for identifier (alpha or numeric)
- if (PMA_STR_isSqlIdentifier($c, false)
- || $c == '@'
- || ($c == '.'
- && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1))
- && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
- /* DEBUG
- echo PMA_substr($sql, $count2);
- echo '<hr />';
- */
- $count2++;
- /**
- * @todo a @ can also be present in expressions like
- * FROM 'user'@'%' or TO 'user'@'%'
- * in this case, the @ is wrongly marked as alpha_variable
- */
- $is_identifier = $previous_was_punct;
- $is_sql_variable = $c == '@' && ! $previous_was_quote;
- $is_user = $c == '@' && $previous_was_quote;
- $is_digit = !$is_identifier && !$is_sql_variable && PMA_STR_isDigit($c);
- $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && PMA_substr($sql, $count2, 1) == 'x';
- $is_float_digit = $c == '.';
- $is_float_digit_exponent = false;
- /* DEBUG
- echo '<pre>2';
- var_dump($is_identifier);
- var_dump($is_sql_variable);
- var_dump($is_digit);
- var_dump($is_float_digit);
- echo '</pre>';
- */
- // Fast skip is especially needed for huge BLOB data
- if ($is_hex_digit) {
- $count2++;
- $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
- if ($pos > $count2) {
- $count2 = $pos;
- }
- unset($pos);
- } elseif ($is_digit) {
- $pos = strspn($sql, '0123456789', $count2);
- if ($pos > $count2) {
- $count2 = $pos;
- }
- unset($pos);
- }
- while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
- $c2 = PMA_substr($sql, $count2, 1);
- if ($is_sql_variable && ($c2 == '.')) {
- $count2++;
- continue;
- }
- if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
- $count2++;
- if (!$is_float_digit) {
- $is_float_digit = true;
- continue;
- } else {
- $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
- . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
- PMA_SQP_throwError($debugstr, $sql);
- return $sql_array;
- }
- }
- if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
- if (!$is_float_digit_exponent) {
- $is_float_digit_exponent = true;
- $is_float_digit = true;
- $count2++;
- continue;
- } else {
- $is_digit = false;
- $is_float_digit = false;
- }
- }
- if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
- $count2++;
- continue;
- } else {
- $is_digit = false;
- $is_hex_digit = false;
- }
- $count2++;
- } // end while
- $l = $count2 - $count1;
- $str = PMA_substr($sql, $count1, $l);
- $type = '';
- if ($is_digit || $is_float_digit || $is_hex_digit) {
- $type = 'digit';
- if ($is_float_digit) {
- $type .= '_float';
- } elseif ($is_hex_digit) {
- $type .= '_hex';
- } else {
- $type .= '_integer';
- }
- } elseif ($is_user) {
- $type = 'punct_user';
- } elseif ($is_sql_variable != false) {
- $type = 'alpha_variable';
- } else {
- $type = 'alpha';
- } // end if... else....
- PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
- continue;
- }
- // Checks for punct
- if (PMA_strpos($allpunct_list, $c) !== false) {
- while (($count2 < $len) && PMA_strpos($allpunct_list, PMA_substr($sql, $count2, 1)) !== false) {
- $count2++;
- }
- $l = $count2 - $count1;
- if ($l == 1) {
- $punct_data = $c;
- } else {
- $punct_data = PMA_substr($sql, $count1, $l);
- }
- // Special case, sometimes, althought two characters are
- // adjectent directly, they ACTUALLY need to be seperate
- /* DEBUG
- echo '<pre>';
- var_dump($l);
- var_dump($punct_data);
- echo '</pre>';
- */
- if ($l == 1) {
- $t_suffix = '';
- switch ($punct_data) {
- case $punct_queryend:
- $t_suffix = '_queryend';
- break;
- case $punct_qualifier:
- $t_suffix = '_qualifier';
- $this_was_punct = true;
- break;
- case $punct_listsep:
- $this_was_listsep = true;
- $t_suffix = '_listsep';
- break;
- default:
- break;
- }
- PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
- } elseif ($punct_data == $GLOBALS['sql_delimiter'] || isset($allpunct_list_pair[$punct_data])) {
- // Ok, we have one of the valid combined punct expressions
- PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
- } else {
- // Bad luck, lets split it up more
- $first = $punct_data[0];
- $first2 = $punct_data[0] . $punct_data[1];
- $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
- $last = $punct_data[$l - 1];
- if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
- $count2 = $count1 + 1;
- $punct_data = $first;
- } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' '))) {
- $count2 -= 2;
- $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
- } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
- $count2--;
- $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
- } elseif ($last != '~') {
- /**
- * @todo for negation operator, split in 2 tokens ?
- * "select x&~1 from t"
- * becomes "select x & ~ 1 from t" ?
- */
- $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
- . 'STR: ' . htmlspecialchars($punct_data);
- PMA_SQP_throwError($debugstr, $sql);
- return $sql_array;
- }
- PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
- continue;
- } // end if... elseif... else
- continue;
- }
- // DEBUG
- $count2++;
- $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
- . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
- PMA_SQP_bug($debugstr, $sql);
- return $sql_array;
- } // end while ($count2 < $len)
- /*
- echo '<pre>';
- print_r($sql_array);
- echo '</pre>';
- */
- if ($arraysize > 0) {
- $t_next = $sql_array[0]['type'];
- $t_prev = '';
- $t_bef_prev = '';
- $t_cur = '';
- $d_next = $sql_array[0]['data'];
- $d_prev = '';
- $d_bef_prev = '';
- $d_cur = '';
- $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
- $d_prev_upper = '';
- $d_bef_prev_upper = '';
- $d_cur_upper = '';
- }
- for ($i = 0; $i < $arraysize; $i++) {
- $t_bef_prev = $t_prev;
- $t_prev = $t_cur;
- $t_cur = $t_next;
- $d_bef_prev = $d_prev;
- $d_prev = $d_cur;
- $d_cur = $d_next;
- $d_bef_prev_upper = $d_prev_upper;
- $d_prev_upper = $d_cur_upper;
- $d_cur_upper = $d_next_upper;
- if (($i + 1) < $arraysize) {
- $t_next = $sql_array[$i + 1]['type'];
- $d_next = $sql_array[$i + 1]['data'];
- $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
- } else {
- $t_next = '';
- $d_next = '';
- $d_next_upper = '';
- }
- //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
- if ($t_cur == 'alpha') {
- $t_suffix = '_identifier';
- // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
- if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
- $t_suffix = '_bitfield_constant_introducer';
- } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
- $t_suffix = '_identifier';
- } elseif (($t_next == 'punct_bracket_open_round')
- && isset($PMA_SQPdata_function_name[$d_cur_upper])) {
- /**
- * @todo 2005-10-16: in the case of a CREATE TABLE containing
- * a TIMESTAMP, since TIMESTAMP() is also a function, it's
- * found here and the token is wrongly marked as alpha_functionName.
- * But we compensate for this when analysing for timestamp_not_null
- * later in this script.
- *
- * Same applies to CHAR vs. CHAR() function.
- */
- $t_suffix = '_functionName';
- /* There are functions which might be as well column types */
- } elseif (isset($PMA_SQPdata_column_type[$d_cur_upper])) {
- $t_suffix = '_columnType';
- /**
- * Temporary fix for BUG #621357 and #2027720
- *
- * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
- */
- if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
- $t_suffix = '_reservedWord';
- }
- //END OF TEMPORARY FIX
- // CHARACTER is a synonym for CHAR, but can also be meant as
- // CHARACTER SET. In this case, we have a reserved word.
- if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
- $t_suffix = '_reservedWord';
- }
- // experimental
- // current is a column type, so previous must not be
- // a reserved word but an identifier
- // CREATE TABLE SG_Persons (first varchar(64))
- //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
- // $sql_array[$i-1]['type'] = 'alpha_identifier';
- //}
- } elseif (isset($PMA_SQPdata_reserved_word[$d_cur_upper])) {
- $t_suffix = '_reservedWord';
- } elseif (isset($PMA_SQPdata_column_attrib[$d_cur_upper])) {
- $t_suffix = '_columnAttrib';
- // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
- // it should be regarded as a reserved word.
- if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
- $t_suffix = '_reservedWord';
- }
- if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
- $t_suffix = '_reservedWord';
- }
- // Binary as character set
- if ($d_cur_upper == 'BINARY' && (
- ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
- || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
- || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
- || $d_prev_upper == 'CHARSET'
- ) && in_array($d_cur, $mysql_charsets)) {
- $t_suffix = '_charset';
- }
- } elseif (in_array($d_cur, $mysql_charsets)
- || in_array($d_cur, $mysql_collations_flat)
- || ($d_cur{0} == '_' && in_array(substr($d_cur, 1), $mysql_charsets))) {
- $t_suffix = '_charset';
- } else {
- // Do nothing
- }
- // check if present in the list of forbidden words
- if ($t_suffix == '_reservedWord' && isset($PMA_SQPdata_forbidden_word[$d_cur_upper])) {
- $sql_array[$i]['forbidden'] = true;
- } else {
- $sql_array[$i]['forbidden'] = false;
- }
- $sql_array[$i]['type'] .= $t_suffix;
- }
- } // end for
- // Stores the size of the array inside the array, as count() is a slow
- // operation.
- $sql_array['len'] = $arraysize;
- // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
- // Sends the data back
- return $sql_array;
- } // end of the "PMA_SQP_parse()" function
- /**
- * Checks for token types being what we want...
- *
- * @param string String of type that we have
- * @param string String of type that we want
- *
- * @return boolean result of check
- *
- * @access private
- */
- function PMA_SQP_typeCheck($toCheck, $whatWeWant)
- {
- $typeSeperator = '_';
- if (strcmp($whatWeWant, $toCheck) == 0) {
- return true;
- } else {
- if (strpos($whatWeWant, $typeSeperator) === false) {
- return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
- } else {
- return false;
- }
- }
- }
- /**
- * Analyzes SQL queries
- *
- * @param array The SQL queries
- *
- * @return array The analyzed SQL queries
- *
- * @access public
- */
- function PMA_SQP_analyze($arr)
- {
- if ($arr == array() || ! isset($arr['len'])) {
- return array();
- }
- $result = array();
- $size = $arr['len'];
- $subresult = array(
- 'querytype' => '',
- 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
- 'position_of_first_select' => '', // the array index
- 'from_clause'=> '',
- 'group_by_clause'=> '',
- 'order_by_clause'=> '',
- 'having_clause' => '',
- 'limit_clause' => '',
- 'where_clause' => '',
- 'where_clause_identifiers' => array(),
- 'unsorted_query' => '',
- 'queryflags' => array(),
- 'select_expr' => array(),
- 'table_ref' => array(),
- 'foreign_keys' => array(),
- 'create_table_fields' => array()
- );
- $subresult_empty = $subresult;
- $seek_queryend = false;
- $seen_end_of_table_ref = false;
- $number_of_brackets_in_extract = 0;
- $number_of_brackets_in_group_concat = 0;
- $number_of_brackets = 0;
- $in_subquery = false;
- $seen_subquery = false;
- $seen_from = false;
- // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
- // we must not use CURDATE as a table_ref
- // so we track whether we are in the EXTRACT()
- $in_extract = false;
- // for GROUP_CONCAT(...)
- $in_group_concat = false;
- /* Description of analyzer results
- *
- * db, table, column, alias
- * ------------------------
- *
- * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
- *
- * The SELECT syntax (simplified) is
- *
- * SELECT
- * select_expression,...
- * [FROM [table_references]
- *
- *
- * ['select_expr'] is filled with each expression, the key represents the
- * expression position in the list (0-based) (so we don't lose track of
- * multiple occurences of the same column).
- *
- * ['table_ref'] is filled with each table ref, same thing for the key.
- *
- * I create all sub-values empty, even if they are
- * not present (for example no select_expression alias).
- *
- * There is a debug section at the end of loop #1, if you want to
- * see the exact contents of select_expr and table_ref
- *
- * queryflags
- * ----------
- *
- * In $subresult, array 'queryflags' is filled, according to what we
- * find in the query.
- *
- * Currently, those are generated:
- *
- * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
- * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
- * ['queryflags']['distinct'] = 1; for a DISTINCT
- * ['queryflags']['union'] = 1; for a UNION
- * ['queryflags']['join'] = 1; for a JOIN
- * ['queryflags']['offset'] = 1; for the presence of OFFSET
- * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
- *
- * query clauses
- * -------------
- *
- * The select is splitted in those clauses:
- * ['select_expr_clause']
- * ['from_clause']
- * ['group_by_clause']
- * ['order_by_clause']
- * ['having_clause']
- * ['limit_clause']
- * ['where_clause']
- *
- * The identifiers of the WHERE clause are put into the array
- * ['where_clause_identifier']
- *
- * For a SELECT, the whole query without the ORDER BY clause is put into
- * ['unsorted_query']
- *
- * foreign keys
- * ------------
- * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
- * analyzed and ['foreign_keys'] is an array filled with
- * the constraint name, the index list,
- * the REFERENCES table name and REFERENCES index list,
- * and ON UPDATE | ON DELETE clauses
- *
- * position_of_first_select
- * ------------------------
- *
- * The array index of the first SELECT we find. Will be used to
- * insert a SQL_CALC_FOUND_ROWS.
- *
- * create_table_fields
- * -------------------
- *
- * Used to detect the DEFAULT CURRENT_TIMESTAMP and
- * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
- * Also used to store the default value of the field.
- * An array, each element is the identifier name.
- * Note that for now, the timestamp_not_null element is created
- * even for non-TIMESTAMP fields.
- *
- * Sub-elements: ['type'] which contains the column type
- * optional (currently they are never false but can be absent):
- * ['default_current_timestamp'] boolean
- * ['on_update_current_timestamp'] boolean
- * ['timestamp_not_null'] boolean
- *
- * section_before_limit, section_after_limit
- * -----------------------------------------
- *
- * Marks the point of the query where we can insert a LIMIT clause;
- * so the section_before_limit will contain the left part before
- * a possible LIMIT clause
- *
- *
- * End of description of analyzer results
- */
- // must be sorted
- // TODO: current logic checks for only one word, so I put only the
- // first word of the reserved expressions that end a table ref;
- // maybe this is not ok (the first word might mean something else)
- // $words_ending_table_ref = array(
- // 'FOR UPDATE',
- // 'GROUP BY',
- // 'HAVING',
- // 'LIMIT',
- // 'LOCK IN SHARE MODE',
- // 'ORDER BY',
- // 'PROCEDURE',
- // 'UNION',
- // 'WHERE'
- // );
- $words_ending_table_ref = array(
- 'FOR' => 1,
- 'GROUP' => 1,
- 'HAVING' => 1,
- 'LIMIT' => 1,
- 'LOCK' => 1,
- 'ORDER' => 1,
- 'PROCEDURE' => 1,
- 'UNION' => 1,
- 'WHERE' => 1
- );
- $words_ending_clauses = array(
- 'FOR' => 1,
- 'LIMIT' => 1,
- 'LOCK' => 1,
- 'PROCEDURE' => 1,
- 'UNION' => 1
- );
- $supported_query_types = array(
- 'SELECT' => 1,
- /*
- // Support for these additional query types will come later on.
- 'DELETE' => 1,
- 'INSERT' => 1,
- 'REPLACE' => 1,
- 'TRUNCATE' => 1,
- 'UPDATE' => 1,
- 'EXPLAIN' => 1,
- 'DESCRIBE' => 1,
- 'SHOW' => 1,
- 'CREATE' => 1,
- 'SET' => 1,
- 'ALTER' => 1
- */
- );
- // loop #1 for each token: select_expr, table_ref for SELECT
- for ($i = 0; $i < $size; $i++) {
- //DEBUG echo "Loop1 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
- // High speed seek for locating the end of the current query
- if ($seek_queryend == true) {
- if ($arr[$i]['type'] == 'punct_queryend') {
- $seek_queryend = false;
- } else {
- continue;
- } // end if (type == punct_queryend)
- } // end if ($seek_queryend)
- /**
- * Note: do not split if this is a punct_queryend for the first and only query
- * @todo when we find a UNION, should we split in another subresult?
- */
- if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
- $result[] = $subresult;
- $subresult = $subresult_empty;
- continue;
- } // end if (type == punct_queryend)
- // ==============================================================
- if ($arr[$i]['type'] == 'punct_bracket_open_round') {
- $number_of_brackets++;
- if ($in_extract) {
- $number_of_brackets_in_extract++;
- }
- if ($in_group_concat) {
- $number_of_brackets_in_group_concat++;
- }
- }
- // ==============================================================
- if ($arr[$i]['type'] == 'punct_bracket_close_round') {
- $number_of_brackets--;
- if ($number_of_brackets == 0) {
- $in_subquery = false;
- }
- if ($in_extract) {
- $number_of_brackets_in_extract--;
- if ($number_of_brackets_in_extract == 0) {
- $in_extract = false;
- }
- }
- if ($in_group_concat) {
- $number_of_brackets_in_group_concat--;
- if ($number_of_brackets_in_group_concat == 0) {
- $in_group_concat = false;
- }
- }
- }
- if ($in_subquery) {
- /**
- * skip the subquery to avoid setting
- * select_expr or table_ref with the contents
- * of this subquery; this is to avoid a bug when
- * trying to edit the results of
- * select * from child where not exists (select id from
- * parent where child.parent_id = parent.id);
- */
- continue;
- }
- // ==============================================================
- if ($arr[$i]['type'] == 'alpha_functionName') {
- $upper_data = strtoupper($arr[$i]['data']);
- if ($upper_data =='EXTRACT') {
- $in_extract = true;
- $number_of_brackets_in_extract = 0;
- }
- if ($upper_data =='GROUP_CONCAT') {
- $in_group_concat = true;
- $number_of_brackets_in_group_concat = 0;
- }
- }
- // ==============================================================
- if ($arr[$i]['type'] == 'alpha_reservedWord'
- //&& $arr[$i]['forbidden'] == false) {
- ) {
- // We don't know what type of query yet, so run this
- if ($subresult['querytype'] == '') {
- $subresult['querytype'] = strtoupper($arr[$i]['data']);
- } // end if (querytype was empty)
- // Check if we support this type of query
- if (!isset($supported_query_types[$subresult['querytype']])) {
- // Skip ahead to the next one if we don't
- $seek_queryend = true;
- continue;
- } // end if (query not supported)
- // upper once
- $upper_data = strtoupper($arr[$i]['data']);
- /**
- * @todo reset for each query?
- */
- if ($upper_data == 'SELECT') {
- if ($number_of_brackets > 0) {
- $in_subquery = true;
- $seen_subquery = true;
- // this is a subquery so do not analyze inside it
- continue;
- }
- $seen_from = false;
- $previous_was_identifier = false;
- $current_select_expr = -1;
- $seen_end_of_table_ref = false;
- } // end if (data == SELECT)
- if ($upper_data =='FROM' && !$in_extract) {
- $current_table_ref = -1;
- $seen_from = true;
- $previous_was_identifier = false;
- $save_table_ref = true;
- } // end if (data == FROM)
- // here, do not 'continue' the loop, as we have more work for
- // reserved words below
- } // end if (type == alpha_reservedWord)
- // ==============================
- if ($arr[$i]['type'] == 'quote_backtick'
- || $arr[$i]['type'] == 'quote_double'
- || $arr[$i]['type'] == 'quote_single'
- || $arr[$i]['type'] == 'alpha_identifier'
- || ($arr[$i]['type'] == 'alpha_reservedWord'
- && $arr[$i]['forbidden'] == false)) {
- switch ($arr[$i]['type']) {
- case 'alpha_identifier':
- case 'alpha_reservedWord':
- /**
- * this is not a real reservedWord, because it's not
- * present in the list of forbidden words, for example
- * "storage" which can be used as an identifier
- *
- * @todo avoid the pretty printing in color in this case
- */
- $identifier = $arr[$i]['data'];
- break;
- case 'quote_backtick':
- case 'quote_double':
- case 'quote_single':
- $identifier = PMA_unQuote($arr[$i]['data']);
- break;
- } // end switch
- if ($subresult['querytype'] == 'SELECT'
- && ! $in_group_concat
- && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
- if (!$seen_from) {
- if ($previous_was_identifier && isset($chain)) {
- // found alias for this select_expr, save it
- // but only if we got something in $chain
- // (for example, SELECT COUNT(*) AS cnt
- // puts nothing in $chain, so we avoid
- // setting the alias)
- $alias_for_select_expr = $identifier;
- } else {
- $chain[] = $identifier;
- $previous_was_identifier = true;
- } // end if !$previous_was_identifier
- } else {
- // ($seen_from)
- if ($save_table_ref && !$seen_end_of_table_ref) {
- if ($previous_was_identifier) {
- // found alias for table ref
- // save it for later
- $alias_for_table_ref = $identifier;
- } else {
- $chain[] = $identifier;
- $previous_was_identifier = true;
- } // end if ($previous_was_identifier)
- } // end if ($save_table_ref &&!$seen_end_of_table_ref)
- } // end if (!$seen_from)
- } // end if (querytype SELECT)
- } // end if (quote_backtick or double quote or alpha_identifier)
- // ===================================
- if ($arr[$i]['type'] == 'punct_qualifier') {
- // to be able to detect an identifier following another
- $previous_was_identifier = false;
- continue;
- } // end if (punct_qualifier)
- /**
- * @todo check if 3 identifiers following one another -> error
- */
- // s a v e a s e l e c t e x p r
- // finding a list separator or FROM
- // means that we must save the current chain of identifiers
- // into a select expression
- // for now, we only save a select expression if it contains
- // at least one identifier, as we are interested in checking
- // the columns and table names, so in "select * from persons",
- // the "*" is not saved
- …
Large files files are truncated, but you can click here to view the full file