3 // vim: expandtab sw=4 ts=4 sts=4:
5 /** SQL Parser Functions for phpMyAdmin
7 * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
8 * http://www.orbis-terrarum.net/?l=people.robbat2
10 * These functions define an SQL parser system, capable of understanding and
11 * extracting data from a MySQL type SQL query.
13 * The basic procedure for using the new SQL parser:
14 * On any page that needs to extract data from a query or to pretty-print a
15 * query, you need code like this up at the top:
17 * ($sql contains the query)
18 * $parsed_sql = PMA_SQP_parse($sql);
20 * If you want to extract data from it then, you just need to run
21 * $sql_info = PMA_SQP_analyze($parsed_sql);
23 * lem9: See comments in PMA_SQP_analyze for the returned info
26 * If you want a pretty-printed version of the query, do:
27 * $string = PMA_SQP_formatHtml($parsed_sql);
28 * (note that that you need to have syntax.css.php3 included somehow in your
29 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
30 * href="syntax.css.php3" />' at the moment.)
34 if (!defined('PMA_SQP_LIB_INCLUDED')) {
35 define('PMA_SQP_LIB_INCLUDED', 1);
38 * Minimum inclusion? (i.e. for the stylesheet builder)
41 if (!isset($is_minimum_common)) {
42 $is_minimum_common = FALSE;
45 if ($is_minimum_common == FALSE) {
47 * Include the string library as we use it heavily
49 if (!defined('PMA_STR_LIB_INCLUDED')) {
50 include('./libraries/string.lib.php3');
54 * Include data for the SQL Parser
56 if (!defined('PMA_SQP_DATA_INCLUDED')) {
57 include('./libraries/sqlparser.data.php3');
60 if (!defined('DEBUG_TIMING')) {
61 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
63 $arr[] = array('type' => $type, 'data' => $data);
65 } // end of the "PMA_SQP_arrayAdd()" function
67 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
72 $arr[] = array('type' => $type, 'data' => $data , 'time' => $t);
75 } // end of the "PMA_SQP_arrayAdd()" function
76 } // end if... else...
80 * Reset the error variable for the SQL parser
84 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
85 function PMA_SQP_resetError() {
86 global $SQP_errorString;
87 $SQP_errorString = '';
88 unset($SQP_errorString);
92 * Get the contents of the error variable for the SQL parser
94 * @return string Error string from SQL parser
98 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
99 function PMA_SQP_getErrorString() {
100 global $SQP_errorString;
101 return isset($SQP_errorString) ?
$SQP_errorString : '';
105 * Check if the SQL parser hit an error
107 * @return boolean error state
111 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
112 function PMA_SQP_isError() {
113 global $SQP_errorString;
114 return isset($SQP_errorString) && !empty($SQP_errorString);
118 * Set an error message for the system
120 * @param string The error message
121 * @param string The failing SQL query
124 * @scope SQL Parser internal
126 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
127 function PMA_SQP_throwError($message, $sql)
130 global $SQP_errorString;
131 $SQP_errorString = '<p>'.$GLOBALS['strSQLParserUserError'] . '</p>' . "\n"
133 . 'ERROR: ' . $message . "\n"
134 . 'SQL: ' . $sql . "\n"
138 // Removed to solve bug #641765 - Robbat2 - 12 January 2003, 9:46PM
140 if (PMA_PHP_INT_VERSION >= 40200 && @function_exists('ob_flush')) {
144 } // end of the "PMA_SQP_throwError()" function
148 * Do display the bug report
150 * @param string The error message
151 * @param string The failing SQL query
155 function PMA_SQP_bug($message, $sql)
157 $debugstr = 'ERROR: ' . $message . "\n";
158 $debugstr .= 'CVS: $Id$' . "\n";
159 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION
. "\n";
160 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS
. ' ' . PMA_USR_BROWSER_AGENT
. ' ' . PMA_USR_BROWSER_VER
. "\n";
161 $debugstr .= 'PMA: ' . PMA_VERSION
. "\n";
162 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION
. ' ' . PHP_OS
. "\n";
163 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
164 $debugstr .= 'SQL: ' . $sql;
166 $encodedstr = $debugstr;
167 if (PMA_PHP_INT_VERSION
>= 40001 && @function_exists
('gzcompress')) {
168 $encodedstr = gzcompress($debugstr, 9);
170 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
172 echo $GLOBALS['strSQLParserBugMessage'] . '<br />' . "\n"
173 . '----' . $GLOBALS['strBeginCut'] . '----' . '<br />' . "\n"
175 . '----' . $GLOBALS['strEndCut'] . '----' . '<br />' . "\n";
178 if (PMA_PHP_INT_VERSION
>= 40200 && @function_exists
('ob_flush')) {
182 echo '----' . $GLOBALS['strBeginRaw'] . '----<br />' . "\n"
186 . '----' . $GLOBALS['strEndRaw'] . '----<br />' . "\n";
189 if (PMA_PHP_INT_VERSION
>= 40200 && @function_exists
('ob_flush')) {
192 } // end of the "PMA_SQP_bug()" function
196 * Parses the SQL queries
198 * @param string The SQL query list
200 * @return mixed Most of times, nothing...
202 * @global array The current PMA configuration
203 * @global array MySQL column attributes
204 * @global array MySQL reserved words
205 * @global array MySQL column types
206 * @global array MySQL function names
207 * @global integer MySQL column attributes count
208 * @global integer MySQL reserved words count
209 * @global integer MySQL column types count
210 * @global integer MySQL function names count
214 function PMA_SQP_parse($sql)
217 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
218 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
220 // rabus: Convert all line feeds to Unix style
221 $sql = str_replace("\r\n", "\n", $sql);
222 $sql = str_replace("\r", "\n", $sql);
224 $len = $GLOBALS['PMA_strlen']($sql);
229 $sql_array = array();
230 $sql_array['raw'] = $sql;
233 $punct_queryend = ';';
234 $punct_qualifier = '.';
235 $punct_listsep = ',';
236 $punct_level_plus = '(';
237 $punct_level_minus = ')';
238 $digit_floatdecimal = '.';
240 $bracket_list = '()[]{}';
241 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
242 $allpunct_list_pair = array (
254 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
255 $quote_list = '\'"`';
258 while ($count2 < $len) {
264 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
268 // Checks for white space
269 if (PMA_STR_isSpace($c)) {
274 // Checks for comment lines.
279 ||
(($count2 +
1 < $len) && ($c == '/') && ($sql[$count2 +
1] == '*'))
280 ||
(($count2 +
2 < $len) && ($c == '-') && ($sql[$count2 +
1] == '-') && (($sql[$count2 +
2] == ' ') ||
($sql[$count2 +
2] == "\n")))) {
289 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
293 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
299 $count2 = ($pos < $count2) ?
$len : $pos;
300 $str = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
301 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
305 // Checks for something inside quotation marks
306 if (PMA_STR_strInStr($c, $quote_list)) {
307 $startquotepos = $count2;
311 $escaped_escaped = FALSE;
316 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos +
1) - 1;
319 $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
320 . 'STR: ' . $quotetype;
321 PMA_SQP_throwError($debugstr, $sql);
325 // If the quote is the first character, it can't be
326 // escaped, so don't do the rest of the code
331 // Checks for MySQL escaping using a \
332 // And checks for ANSI escaping using the $quotetype character
333 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
336 } else if (($pos +
1 < $len) && ($sql[$pos] == $quotetype) && ($sql[$pos +
1] == $quotetype)) {
342 } while ($len > $pos); // end do
347 switch ($quotetype) {
360 $data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
361 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
365 // Checks for brackets
366 if (PMA_STR_strInStr($c, $bracket_list)) {
367 // All bracket tokens are only one item long
370 if (PMA_STR_strInStr($c, '([{')) {
373 $type_type = 'close';
377 if (PMA_STR_strInStr($c, '()')) {
378 $type_style = 'round';
379 } elseif (PMA_STR_strInStr($c, '[]')) {
380 $type_style = 'square';
382 $type_style = 'curly';
385 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
386 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
391 if (PMA_STR_strInStr($c, $allpunct_list)) {
392 while (($count2 < $len) && PMA_STR_strInStr($sql[$count2], $allpunct_list)) {
395 $l = $count2 - $count1;
399 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $l);
402 // Special case, sometimes, althought two characters are
403 // adjectent directly, they ACTUALLY need to be seperate
406 switch ($punct_data) {
407 case $punct_queryend:
408 $t_suffix = '_queryend';
410 case $punct_qualifier:
411 $t_suffix = '_qualifier';
414 $t_suffix = '_listsep';
419 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
421 else if (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
422 // Ok, we have one of the valid combined punct expressions
423 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
426 // Bad luck, lets split it up more
427 $first = $punct_data[0];
428 $first2 = $punct_data[0] . $punct_data[1];
429 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
430 $last = $punct_data[$l - 1];
431 if (($first == ',') ||
($first == ';') ||
($first == '.') ||
($first = '*')) {
432 $count2 = $count1 +
1;
433 $punct_data = $first;
434 } else if (($last2 == '/*') ||
($last2 == '--')) {
436 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
437 } else if (($last == '-') ||
($last == '+') ||
($last == '!')) {
439 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
441 $debugstr = $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+
1) . "\n"
442 . 'STR: ' . $punct_data;
443 PMA_SQP_throwError($debugstr, $sql);
446 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
448 } // end if... else if... else
453 if (PMA_STR_isSqlIdentifier($c, FALSE) ||
($c == '@')) {
456 //TODO: a @ can also be present in expressions like
458 // in this case, the @ is wrongly marked as alpha_variable
460 $is_sql_variable = ($c == '@');
461 $is_digit = (!$is_sql_variable) && PMA_STR_isDigit($c);
462 $is_hex_digit = ($is_digit) && ($c == '0') && ($count2 < $len) && ($sql[$count2] == 'x');
463 $is_float_digit = FALSE;
464 $is_float_digit_exponent = FALSE;
470 while (($count2 < $len) && PMA_STR_isSqlIdentifier($sql[$count2], ($is_sql_variable ||
$is_digit))) {
472 if ($is_sql_variable && ($c2 == '.')) {
476 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
478 if (!$is_float_digit) {
479 $is_float_digit = TRUE;
482 $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+
1) . "\n"
483 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
484 PMA_SQP_throwError($debugstr, $sql);
488 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') ||
($c2 == 'E'))) {
489 if (!$is_float_digit_exponent) {
490 $is_float_digit_exponent = TRUE;
491 $is_float_digit = TRUE;
496 $is_float_digit = FALSE;
499 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) ||
($is_digit && PMA_STR_isDigit($c2))) {
504 $is_hex_digit = FALSE;
510 $l = $count2 - $count1;
511 $str = $GLOBALS['PMA_substr']($sql, $count1, $l);
516 if ($is_float_digit) {
518 } else if ($is_hex_digit) {
525 if ($is_sql_variable != FALSE) {
526 $type = 'alpha_variable';
530 } // end if... else....
531 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize);
539 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
540 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1) . "\n";
541 PMA_SQP_bug($debugstr, $sql);
544 } // end while ($count2 < $len)
547 if ($arraysize > 0) {
548 $t_next = $sql_array[0]['type'];
553 for ($i = 0; $i < $arraysize; $i++
) {
556 if (($i +
1) < $arraysize) {
557 $t_next = $sql_array[$i +
1]['type'];
561 if ($t_cur == 'alpha') {
562 $t_suffix = '_identifier';
563 $d_cur_upper = strtoupper($sql_array[$i]['data']);
564 if (($t_next == 'punct_qualifier') ||
($t_prev == 'punct_qualifier')) {
565 $t_suffix = '_identifier';
566 } else if (($t_next == 'punct_bracket_open_round')
567 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
568 $t_suffix = '_functionName';
569 } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
570 $t_suffix = '_columnType';
571 // Temporary fix for BUG #621357
572 //TODO FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
573 if($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
574 $t_suffix = '_reservedWord';
576 //END OF TEMPORARY FIX
577 } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
578 $t_suffix = '_reservedWord';
579 } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
580 $t_suffix = '_columnAttrib';
584 $sql_array[$i]['type'] .= $t_suffix;
588 // Stores the size of the array inside the array, as count() is a slow
590 $sql_array['len'] = $arraysize;
592 // Sends the data back
594 } // end of the "PMA_SQP_parse()" function
597 * Checks for token types being what we want...
599 * @param string String of type that we have
600 * @param string String of type that we want
602 * @return boolean result of check
606 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
608 $typeSeperator = '_';
609 if(strcmp($whatWeWant, $toCheck) == 0) {
612 //if(strpos($whatWeWant, $typeSeperator) === FALSE) {
613 // PHP3 compatible (works unless there is a real ff character)
614 if(!strpos("\xff" . $whatWeWant, $typeSeperator)) {
615 return strncmp($whatWeWant, $toCheck , strpos($toCheck, $typeSeperator)) == 0;
624 * Analyzes SQL queries
626 * @param array The SQL queries
628 * @return array The analyzed SQL queries
632 function PMA_SQP_analyze($arr)
638 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
640 'group_by_clause'=> '',
641 'order_by_clause'=> '',
642 'having_clause' => '',
643 'where_clause' => '',
644 'where_clause_identifiers' => array(),
645 'queryflags' => array(),
646 'select_expr' => array(),
647 'table_ref' => array()
649 $subresult_empty = $subresult;
650 $seek_queryend = FALSE;
651 $seen_end_of_table_ref = FALSE;
653 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
654 // we must not use CURDATE as a table_ref
655 // so we track wether we are in the EXTRACT()
658 /* Description of analyzer results
660 * lem9: db, table, column, alias
661 * ------------------------
663 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
665 * The SELECT syntax (simplified) is
668 * select_expression,...
669 * [FROM [table_references]
672 * ['select_expr'] is filled with each expression, the key represents the
673 * expression position in the list (0-based) (so we don't lose track of
674 * multiple occurences of the same column).
676 * ['table_ref'] is filled with each table ref, same thing for the key.
678 * I create all sub-values empty, even if they are
679 * not present (for example no select_expression alias).
681 * There is a debug section at the end of the main loop, if you want to
682 * see the exact contents of select_expr and table_ref
687 * In $subresult, array 'queryflags' is filled, according to what we
690 * Currently, those are generated:
692 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
693 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
695 * lem9: query clauses
698 * The select is splitted in those clauses:
699 * ['select_expr_clause']
701 * ['group_by_clause']
702 * ['order_by_clause']
706 * and the identifiers of the where clause are put into the array
707 * ['where_clause_identifier']
711 // TODO: current logic checks for only one word, so I put only the
712 // first word of the reserved expressions that end a table ref;
713 // maybe this is not ok (the first word might mean something else)
714 // $words_ending_table_ref = array(
719 // 'LOCK IN SHARE MODE',
725 $words_ending_table_ref = array(
736 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
738 $words_ending_clauses = array(
745 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
751 $supported_query_types = array(
754 // Support for these additional query types will come later on.
768 $supported_query_types_cnt = count($supported_query_types);
770 // loop #1 for each token: select_expr, table_ref for SELECT
772 for ($i = 0; $i < $size; $i++
) {
773 //echo "trace <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br>";
775 // High speed seek for locating the end of the current query
776 if ($seek_queryend == TRUE) {
777 if ($arr[$i]['type'] == 'punct_queryend') {
778 $seek_queryend = FALSE;
781 } // end if (type == punct_queryend)
782 } // end if ($seek_queryend)
784 // TODO: when we find a UNION, should we split
785 // in another subresult?
786 if ($arr[$i]['type'] == 'punct_queryend') {
787 $result[] = $subresult;
788 $subresult = $subresult_empty;
790 } // end if (type == punct_queryend)
792 // ==============================================================
793 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
795 $number_of_brackets_in_extract++
;
798 // ==============================================================
799 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
801 $number_of_brackets_in_extract--;
802 if ($number_of_brackets_in_extract == 0) {
807 // ==============================================================
808 if ($arr[$i]['type'] == 'alpha_functionName') {
809 $upper_data = strtoupper($arr[$i]['data']);
810 if ($upper_data =='EXTRACT') {
812 $number_of_brackets_in_extract = 0;
816 // ==============================================================
817 if ($arr[$i]['type'] == 'alpha_reservedWord') {
818 // We don't know what type of query yet, so run this
819 if ($subresult['querytype'] == '') {
820 $subresult['querytype'] = strtoupper($arr[$i]['data']);
821 } // end if (querytype was empty)
823 // Check if we support this type of query
824 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
825 // Skip ahead to the next one if we don't
826 $seek_queryend = TRUE;
828 } // end if (query not supported)
831 $upper_data = strtoupper($arr[$i]['data']);
832 //TODO: reset for each query?
834 if ($upper_data == 'SELECT') {
836 $previous_was_identifier = FALSE;
837 $current_select_expr = -1;
838 $seen_end_of_table_ref = FALSE;
839 } // end if ( data == SELECT)
841 if ($upper_data =='FROM' && !$in_extract) {
842 $current_table_ref = -1;
844 $previous_was_identifier = FALSE;
845 $save_table_ref = TRUE;
846 } // end if (data == FROM)
848 // here, do not 'continue' the loop, as we have more work for
849 // reserved words below
850 } // end if (type == alpha_reservedWord)
852 // ==============================
853 if (($arr[$i]['type'] == 'quote_backtick')
854 ||
($arr[$i]['type'] == 'quote_double')
855 ||
($arr[$i]['type'] == 'quote_single')
856 ||
($arr[$i]['type'] == 'alpha_identifier')) {
858 switch ($arr[$i]['type']) {
859 case 'alpha_identifier':
860 $identifier = $arr[$i]['data'];
863 //TODO: check embedded double quotes or backticks?
864 // and/or remove just the first and last character?
865 case 'quote_backtick':
866 $identifier = str_replace('`','',$arr[$i]['data']);
869 $identifier = str_replace('"','',$arr[$i]['data']);
872 $identifier = str_replace("'","",$arr[$i]['data']);
876 if ($subresult['querytype'] == 'SELECT') {
878 if ($previous_was_identifier) {
879 // found alias for this select_expr, save it
880 $alias_for_select_expr = $identifier;
882 $chain[] = $identifier;
883 $previous_was_identifier = TRUE;
885 } // end if !$previous_was_identifier
888 if ($save_table_ref && !$seen_end_of_table_ref) {
889 if ($previous_was_identifier) {
890 // found alias for table ref
892 $alias_for_table_ref = $identifier;
894 $chain[] = $identifier;
895 $previous_was_identifier = TRUE;
897 } // end if ($previous_was_identifier)
898 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
899 } // end if (!$seen_from)
900 } // end if (querytype SELECT)
901 } // end if ( quote_backtick or double quote or alpha_identifier)
903 // ===================================
904 if ($arr[$i]['type'] == 'punct_qualifier') {
905 // to be able to detect an identifier following another
906 $previous_was_identifier = FALSE;
908 } // end if (punct_qualifier)
910 // TODO: check if 3 identifiers following one another -> error
912 // s a v e a s e l e c t e x p r
913 // finding a list separator or FROM
914 // means that we must save the current chain of identifiers
915 // into a select expression
917 // for now, we only save a select expression if it contains
918 // at least one identifier, as we are interested in checking
919 // the columns and table names, so in "select * from persons",
920 // the "*" is not saved
922 if (isset($chain) && !$seen_end_of_table_ref
924 && $arr[$i]['type'] == 'punct_listsep')
925 ||
($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM')) ) {
926 $size_chain = count($chain);
927 $current_select_expr++
;
928 $subresult['select_expr'][$current_select_expr] = array(
933 'table_true_name' => '',
937 if (!empty($alias_for_select_expr)) {
938 // we had found an alias for this select expression
939 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
940 unset($alias_for_select_expr);
942 // there is at least a column
943 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
944 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
947 if ($size_chain > 1) {
948 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
949 // we assume for now that this is also the true name
950 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
951 $subresult['select_expr'][$current_select_expr]['expr']
952 = $subresult['select_expr'][$current_select_expr]['table_name']
953 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
954 } // end if ($size_chain > 1)
957 if ($size_chain > 2) {
958 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
959 $subresult['select_expr'][$current_select_expr]['expr']
960 = $subresult['select_expr'][$current_select_expr]['db']
961 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
962 } // end if ($size_chain > 2)
965 // TODO: explain this:
966 if (($arr[$i]['type'] == 'alpha_reservedWord')
967 && ($upper_data != 'FROM')) {
968 $previous_was_identifier = TRUE;
971 } // end if (save a select expr)
974 //======================================
975 // s a v e a t a b l e r e f
976 //======================================
978 // maybe we just saw the end of table refs
979 // but the last table ref has to be saved
980 // or we are at the last token (TODO: there could be another
981 // query after this one)
982 // or we just got a reserved word
984 if (isset($chain) && $seen_from && $save_table_ref
985 && ($arr[$i]['type'] == 'punct_listsep'
986 ||
($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
987 ||
$seen_end_of_table_ref
990 $size_chain = count($chain);
991 $current_table_ref++
;
992 $subresult['table_ref'][$current_table_ref] = array(
997 'table_true_name' => ''
999 if (!empty($alias_for_table_ref)) {
1000 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1001 unset($alias_for_table_ref);
1003 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1004 // we assume for now that this is also the true name
1005 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1006 $subresult['table_ref'][$current_table_ref]['expr']
1007 = $subresult['table_ref'][$current_table_ref]['table_name'];
1009 if ($size_chain > 1) {
1010 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1011 $subresult['table_ref'][$current_table_ref]['expr']
1012 = $subresult['table_ref'][$current_table_ref]['db']
1013 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1014 } // end if ($size_chain > 1)
1016 // add the table alias into the whole expression
1017 $subresult['table_ref'][$current_table_ref]['expr']
1018 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1021 $previous_was_identifier = TRUE;
1024 } // end if (save a table ref)
1027 // when we have found all table refs,
1028 // for each table_ref alias, put the true name of the table
1029 // in the corresponding select expressions
1031 if (isset($current_table_ref) && ($seen_end_of_table_ref ||
$i == $size-1)) {
1032 for ($tr=0; $tr <= $current_table_ref; $tr++
) {
1033 $alias = $subresult['table_ref'][$tr]['table_alias'];
1034 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1035 for ($se=0; $se <= $current_select_expr; $se++
) {
1036 if (!empty($alias) && $subresult['select_expr'][$se]['table_true_name']
1038 $subresult['select_expr'][$se]['table_true_name']
1040 } // end if (found the alias)
1041 } // end for (select expressions)
1043 } // end for (table refs)
1044 } // end if (set the true names)
1047 // e n d i n g l o o p #1
1048 // set the $previous_was_identifier to FALSE if the current
1049 // token is not an identifier
1050 if (($arr[$i]['type'] != 'alpha_identifier')
1051 && ($arr[$i]['type'] != 'quote_double')
1052 && ($arr[$i]['type'] != 'quote_single')
1053 && ($arr[$i]['type'] != 'quote_backtick')) {
1054 $previous_was_identifier = FALSE;
1057 // however, if we are on AS, we must keep the $previous_was_identifier
1058 if (($arr[$i]['type'] == 'alpha_reservedWord')
1059 && ($upper_data == 'AS')) {
1060 $previous_was_identifier = TRUE;
1063 if (($arr[$i]['type'] == 'alpha_reservedWord')
1064 && ($upper_data =='ON' ||
$upper_data =='USING')) {
1065 $save_table_ref = FALSE;
1066 } // end if (data == ON)
1068 if (($arr[$i]['type'] == 'alpha_reservedWord')
1069 && ($upper_data =='JOIN' ||
$upper_data =='FROM')) {
1070 $save_table_ref = TRUE;
1071 } // end if (data == JOIN)
1073 // no need to check the end of table ref if we already did
1074 // TODO: maybe add "&& $seen_from"
1075 if (!$seen_end_of_table_ref) {
1076 // if this is the last token, it implies that we have
1077 // seen the end of table references
1078 // Check for the end of table references
1080 ||
($arr[$i]['type'] == 'alpha_reservedWord'
1081 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1082 $seen_end_of_table_ref = TRUE;
1084 // to be able to save the last table ref, but do not
1085 // set it true if we found a word like "ON" that has
1086 // already set it to false
1087 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1088 $save_table_ref = TRUE;
1091 } // end if (check for end of table ref)
1092 } //end if (!$seen_end_of_table_ref)
1094 if ($seen_end_of_table_ref) {
1095 $save_table_ref = FALSE;
1098 } // end for $i (loop #1)
1100 // -------------------------------------------------------
1101 // This is a big hunk of debugging code by Marc for this.
1102 // -------------------------------------------------------
1104 if (isset($current_select_expr)) {
1105 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1108 reset ($subresult['select_expr'][$trace]);
1109 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1110 echo "sel expr $trace $key => $val<br />\n";
1114 if (isset($current_table_ref)) {
1115 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1118 reset ($subresult['table_ref'][$trace]);
1119 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1120 echo "table ref $trace $key => $val<br />\n";
1124 // -------------------------------------------------------
1127 // loop #2: for queryflags
1128 // ,querytype (for queries != 'SELECT')
1130 // This is not in the loop 1 to keep logic simple
1132 // we will also need this queryflag in loop 2
1134 if (isset($current_table_ref) && $current_table_ref > -1) {
1135 $subresult['queryflags']['select_from'] = 1;
1138 $seen_reserved_word = FALSE;
1139 $seen_group = FALSE;
1140 $seen_order = FALSE;
1141 $in_group_by = FALSE; // true when we are into the GROUP BY clause
1142 $in_order_by = FALSE; // true when we are into the ORDER BY clause
1143 $in_having = FALSE; // true when we are into the HAVING clause
1144 $in_select_expr = FALSE; // true when we are into the select expr clause
1145 $in_where = FALSE; // true when we are into the WHERE clause
1148 for ($i = 0; $i < $size; $i++
) {
1149 //echo "trace loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br>";
1153 // check for reserved words that will have to generate
1154 // a confirmation request later in sql.php3
1158 // ALTER TABLE... DROP
1161 // this code is not used for confirmations coming from functions.js
1163 // TODO: check for punct_queryend
1165 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1166 $upper_data = strtoupper($arr[$i]['data']);
1167 if (!$seen_reserved_word) {
1168 $first_reserved_word = $upper_data;
1169 $subresult['querytype'] = $upper_data;
1170 $seen_reserved_word = TRUE;
1172 // if the first reserved word is DROP or DELETE,
1173 // we know this is a query that needs to be confirmed
1174 if ($first_reserved_word=='DROP'
1175 ||
$first_reserved_word == 'DELETE') {
1176 $subresult['queryflags']['need_confirm'] = 1;
1179 if ($upper_data=='DROP' && $first_reserved_word=='ALTER') {
1180 $subresult['queryflags']['need_confirm'] = 1;
1184 if ($upper_data == 'SELECT') {
1185 $in_select_expr = TRUE;
1186 $select_expr_clause = '';
1189 // if this is a real SELECT...FROM
1190 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1193 $in_select_expr = FALSE;
1197 // (we could have less resetting of variables to FALSE
1198 // if we trust that the query respects the standard
1199 // MySQL order for clauses)
1201 // we use $seen_group and $seen_order because we are looking
1203 if ($upper_data == 'GROUP') {
1205 $seen_order = FALSE;
1207 $in_order_by = FALSE;
1209 $in_select_expr = FALSE;
1212 if ($upper_data == 'ORDER') {
1214 $seen_group = FALSE;
1216 $in_group_by = FALSE;
1218 $in_select_expr = FALSE;
1221 if ($upper_data == 'HAVING') {
1223 $having_clause = '';
1224 $seen_group = FALSE;
1225 $seen_order = FALSE;
1226 $in_group_by = FALSE;
1227 $in_order_by = FALSE;
1229 $in_select_expr = FALSE;
1233 if ($upper_data == 'WHERE') {
1236 $where_clause_identifiers = array();
1237 $seen_group = FALSE;
1238 $seen_order = FALSE;
1239 $in_group_by = FALSE;
1240 $in_order_by = FALSE;
1242 $in_select_expr = FALSE;
1246 if ($upper_data == 'BY') {
1248 $in_group_by = TRUE;
1249 $group_by_clause = '';
1252 $in_order_by = TRUE;
1253 $order_by_clause = '';
1257 // if we find one of the words that could end the clause
1258 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1260 $in_group_by = FALSE;
1261 $in_order_by = FALSE;
1264 $in_select_expr = FALSE;
1268 } // endif (reservedWord)
1271 // do not add a blank after a function name
1274 if ($arr[$i]['type'] == 'alpha_functionName') {
1278 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1279 $select_expr_clause .= $arr[$i]['data'] . $sep;
1281 if ($in_from && $upper_data != 'FROM') {
1282 $from_clause .= $arr[$i]['data'] . $sep;
1284 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1285 $group_by_clause .= $arr[$i]['data'] . $sep;
1287 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1288 $order_by_clause .= $arr[$i]['data'] . $sep;
1290 if ($in_having && $upper_data != 'HAVING') {
1291 $having_clause .= $arr[$i]['data'] . $sep;
1293 if ($in_where && $upper_data != 'WHERE') {
1294 $where_clause .= $arr[$i]['data'] . $sep;
1296 if (($arr[$i]['type'] == 'quote_backtick')
1297 ||
($arr[$i]['type'] == 'alpha_identifier')) {
1298 $where_clause_identifiers[] = $arr[$i]['data'];
1302 // clear $upper_data for next iteration
1305 } // end for $i (loop #2)
1307 if (isset($select_expr_clause)) {
1308 $subresult['select_expr_clause'] = $select_expr_clause;
1310 if (isset($from_clause)) {
1311 $subresult['from_clause'] = $from_clause;
1313 if (isset($group_by_clause)) {
1314 $subresult['group_by_clause'] = $group_by_clause;
1316 if (isset($order_by_clause)) {
1317 $subresult['order_by_clause'] = $order_by_clause;
1319 if (isset($having_clause)) {
1320 $subresult['having_clause'] = $having_clause;
1322 if (isset($where_clause)) {
1323 $subresult['where_clause'] = $where_clause;
1325 if (isset($where_clause_identifiers)) {
1326 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1330 // They are naughty and didn't have a trailing semi-colon,
1331 // then still handle it properly
1332 if ($subresult['querytype'] != '') {
1333 $result[] = $subresult;
1336 } // end of the "PMA_SQP_analyze()" function
1340 * Colorizes SQL queries html formatted
1342 * @param array The SQL queries html formatted
1344 * @return array The colorized SQL queries
1348 function PMA_SQP_formatHtml_colorize($arr)
1350 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
1353 $class = 'syntax_' . $GLOBALS['PMA_substr']($arr['type'], 0, $i) . ' ';
1356 $class .= 'syntax_' . $arr['type'];
1358 //TODO: check why adding a "\n" after the </span> would cause extra
1359 // blanks to be displayed:
1360 // SELECT p . person_name
1362 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
1363 } // end of the "PMA_SQP_formatHtml_colorize()" function
1367 * Formats SQL queries to html
1369 * @param array The SQL queries
1371 * @return string The formatted SQL queries
1375 function PMA_SQP_formatHtml($arr, $mode='color')
1377 // first check for the SQL parser having hit an error
1378 if (PMA_SQP_isError()) {
1381 // then check for an array
1382 if (!is_array($arr)) {
1385 // else do it properly
1388 $str = '<span class="syntax">';
1389 $html_line_break = '<br />';
1393 $html_line_break = "\n";
1397 $html_line_break = '<br />';
1403 $infunction = FALSE;
1404 $space_punct_listsep = ' ';
1405 $space_punct_listsep_function_name = ' ';
1406 // $space_alpha_reserved_word = '<br />'."\n";
1407 $space_alpha_reserved_word = ' ';
1409 $keywords_with_brackets_1before = array(
1415 $keywords_with_brackets_1before_cnt = 4;
1417 $keywords_with_brackets_2before = array(
1428 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
1429 $keywords_with_brackets_2before_cnt = 9;
1431 // These reserved words do NOT get a newline placed near them.
1432 $keywords_no_newline = array(
1443 $keywords_no_newline_cnt = 9;
1445 $arraysize = $arr['len'];
1447 if ($arraysize >= 0) {
1451 $typearr[3] = $arr[0]['type'];
1454 for ($i = 0; $i < $arraysize; $i++
) {
1455 // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1459 // array_shift($typearr);
1466 if (($i +
1) < $arraysize) {
1467 // array_push($typearr, $arr[$i + 1]['type']);
1468 $typearr[4] = $arr[$i +
1]['type'];
1470 //array_push($typearr, NULL);
1474 for ($j=0; $j<4; $j++
) {
1475 $typearr[$j] = $typearr[$j +
1];
1478 switch ($typearr[2]) {
1479 case 'white_newline':
1480 // $after = '<br />';
1483 case 'punct_bracket_open_round':
1485 $infunction = FALSE;
1486 // Make sure this array is sorted!
1487 if (($typearr[1] == 'alpha_functionName') ||
($typearr[1] == 'alpha_columnType') ||
($typearr[1] == 'punct')
1488 ||
($typearr[3] == 'digit_integer') ||
($typearr[3] == 'digit_hex') ||
($typearr[3] == 'digit_float')
1489 ||
(($typearr[0] == 'alpha_reservedWord')
1490 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
1491 ||
(($typearr[1] == 'alpha_reservedWord')
1492 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
1499 $after .= ($mode != 'query_only' ?
'<div class="syntax_indent' . $indent . '">' : ' ');
1502 case 'alpha_identifier':
1503 if (($typearr[1] == 'punct_qualifier') ||
($typearr[3] == 'punct_qualifier')) {
1507 if (($typearr[3] == 'alpha_columnType') ||
($typearr[3] == 'alpha_identifier')) {
1511 case 'punct_qualifier':
1515 case 'punct_listsep':
1516 if ($infunction == TRUE) {
1517 $after .= $space_punct_listsep_function_name;
1519 $after .= $space_punct_listsep;
1522 case 'punct_queryend':
1523 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi')) {
1524 $after .= $html_line_break;
1525 $after .= $html_line_break;
1527 $space_punct_listsep = ' ';
1528 $space_punct_listsep_function_name = ' ';
1529 $space_alpha_reserved_word = ' ';
1531 case 'comment_mysql':
1532 case 'comment_ansi':
1533 $after .= $html_line_break;
1539 case 'punct_bracket_close_round':
1541 if ($infunction == TRUE) {
1547 $before .= ($mode != 'query_only' ?
'</div>' : ' ');
1549 $infunction = ($functionlevel > 0) ?
TRUE : FALSE;
1551 case 'alpha_columnType':
1552 if ($typearr[3] == 'alpha_columnAttrib') {
1555 if ($typearr[1] == 'alpha_columnType') {
1559 case 'alpha_columnAttrib':
1561 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
1562 if ($typearr[1] == 'alpha_identifier') {
1565 if (($typearr[3] == 'alpha_columnAttrib') ||
($typearr[3] == 'quote_single')) {
1569 case 'alpha_reservedWord':
1570 //$upper = $arr[$i]['data'];
1571 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
1572 if ((($typearr[1] != 'alpha_reservedWord')
1573 ||
(($typearr[1] == 'alpha_reservedWord')
1574 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
1575 && ($typearr[1] != 'punct_level_plus')
1576 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
1577 // do not put a space before the first token, because
1578 // we use a lot of eregi() checking for the first
1579 // reserved word at beginning of query
1581 $before .= $space_alpha_reserved_word;
1587 switch ($arr[$i]['data']) {
1589 $space_punct_listsep = $html_line_break;
1590 $space_alpha_reserved_word = ' ';
1603 $space_punct_listsep = $html_line_break;
1604 $space_alpha_reserved_word = ' ';
1608 $space_punct_listsep = $html_line_break;
1609 $space_alpha_reserved_word = $html_line_break;
1612 $space_punct_listsep = ' ';
1613 $space_alpha_reserved_word = $html_line_break;
1616 $space_punct_listsep = ' ';
1617 $space_alpha_reserved_word = $html_line_break;
1621 } // end switch ($arr[$i]['data'])
1625 case 'digit_integer':
1628 //TODO: could there be other types preceding a digit?
1629 if ($typearr[1] == 'alpha_reservedWord') {
1632 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
1636 case 'quote_double':
1637 case 'quote_single':
1638 // workaround: for the query
1639 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
1640 // the @ is incorrectly marked as alpha_variable
1641 // in the parser, and here, the '%' gets a blank before,
1642 // which is a syntax error
1643 if ($typearr[1]!='alpha_variable') {
1646 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
1650 case 'quote_backtick':
1651 if ($typearr[3] != 'punct_qualifier') {
1654 if ($typearr[1] != 'punct_qualifier') {
1660 } // end switch ($typearr[2])
1663 if ($typearr[3] != 'punct_qualifier') {
1668 $str .= $before . ($mode=='color' ?
PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
1670 if ($mode=='color') {
1675 } // end of the "PMA_SQP_formatHtml()" function
1679 * Builds a CSS rule used for html formatted SQL queries
1681 * @param string The class name
1682 * @param string The property name
1683 * @param string The property value
1685 * @return string The CSS rule
1689 * @see PMA_SQP_buildCssData()
1691 function PMA_SQP_buildCssRule($classname, $property, $value)
1693 $str = '.' . $classname . ' {';
1695 $str .= $property . ': ' . $value . ';';
1700 } // end of the "PMA_SQP_buildCssRule()" function
1704 * Builds CSS rules used for html formatted SQL queries
1706 * @return string The CSS rules set
1710 * @global array The current PMA configuration
1712 * @see PMA_SQP_buildCssRule()
1714 function PMA_SQP_buildCssData()
1719 while (list($key, $col) = each($cfg['SQP']['fmtColor'])) {
1720 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
1722 for ($i = 0; $i < 8; $i++
) {
1723 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
1727 } // end of the "PMA_SQP_buildCssData()" function
1729 if ($is_minimum_common == FALSE) {
1731 * Gets SQL queries with no format
1733 * @param array The SQL queries list
1735 * @return string The SQL queries with no format
1739 function PMA_SQP_formatNone($arr)
1741 $formatted_sql = htmlspecialchars($arr['raw']);
1742 $formatted_sql = ereg_replace("((\015\012)|(\015)|(\012)){3,}", "\n\n", $formatted_sql);
1744 return $formatted_sql;
1745 } // end of the "PMA_SQP_formatNone()" function
1749 * Gets SQL queries in text format
1751 * @param array The SQL queries list
1753 * @return string The SQL queries in text format
1757 function PMA_SQP_formatText($arr)
1762 return PMA_SQP_formatNone($arr);
1763 } // end of the "PMA_SQP_formatText()" function
1764 } // end if: minimal common.lib needed?
1765 } // $__PMA_SQP_LIB__