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 // if the SQL parser is disabled just return the original query string
221 if ($cfg['SQP']['enable'] == FALSE) {
222 // Debug : echo 'FALSE';
226 // rabus: Convert all line feeds to Unix style
227 $sql = str_replace("\r\n", "\n", $sql);
228 $sql = str_replace("\r", "\n", $sql);
230 $len = $GLOBALS['PMA_strlen']($sql);
235 $sql_array = array();
236 $sql_array['raw'] = $sql;
239 $punct_queryend = ';';
240 $punct_qualifier = '.';
241 $punct_listsep = ',';
242 $punct_level_plus = '(';
243 $punct_level_minus = ')';
244 $digit_floatdecimal = '.';
246 $bracket_list = '()[]{}';
247 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
248 $allpunct_list_pair = array (
260 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
261 $quote_list = '\'"`';
264 while ($count2 < $len) {
270 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
274 // Checks for white space
275 if (PMA_STR_isSpace($c)) {
280 // Checks for comment lines.
285 ||
(($count2 +
1 < $len) && ($c == '/') && ($sql[$count2 +
1] == '*'))
286 ||
(($count2 +
2 < $len) && ($c == '-') && ($sql[$count2 +
1] == '-') && (($sql[$count2 +
2] == ' ') ||
($sql[$count2 +
2] == "\n")))) {
295 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
299 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
305 $count2 = ($pos < $count2) ?
$len : $pos;
306 $str = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
307 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
311 // Checks for something inside quotation marks
312 if (PMA_STR_strInStr($c, $quote_list)) {
313 $startquotepos = $count2;
317 $escaped_escaped = FALSE;
322 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos +
1) - 1;
325 $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
326 . 'STR: ' . $quotetype;
327 PMA_SQP_throwError($debugstr, $sql);
331 // If the quote is the first character, it can't be
332 // escaped, so don't do the rest of the code
337 // Checks for MySQL escaping using a \
338 // And checks for ANSI escaping using the $quotetype character
339 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
342 } else if (($pos +
1 < $len) && ($sql[$pos] == $quotetype) && ($sql[$pos +
1] == $quotetype)) {
348 } while ($len > $pos); // end do
353 switch ($quotetype) {
366 $data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
367 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
371 // Checks for brackets
372 if (PMA_STR_strInStr($c, $bracket_list)) {
373 // All bracket tokens are only one item long
376 if (PMA_STR_strInStr($c, '([{')) {
379 $type_type = 'close';
383 if (PMA_STR_strInStr($c, '()')) {
384 $type_style = 'round';
385 } elseif (PMA_STR_strInStr($c, '[]')) {
386 $type_style = 'square';
388 $type_style = 'curly';
391 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
392 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
397 if (PMA_STR_strInStr($c, $allpunct_list)) {
398 while (($count2 < $len) && PMA_STR_strInStr($sql[$count2], $allpunct_list)) {
401 $l = $count2 - $count1;
405 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $l);
408 // Special case, sometimes, althought two characters are
409 // adjectent directly, they ACTUALLY need to be seperate
412 switch ($punct_data) {
413 case $punct_queryend:
414 $t_suffix = '_queryend';
416 case $punct_qualifier:
417 $t_suffix = '_qualifier';
420 $t_suffix = '_listsep';
425 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
427 else if (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
428 // Ok, we have one of the valid combined punct expressions
429 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
432 // Bad luck, lets split it up more
433 $first = $punct_data[0];
434 $first2 = $punct_data[0] . $punct_data[1];
435 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
436 $last = $punct_data[$l - 1];
437 if (($first == ',') ||
($first == ';') ||
($first == '.') ||
($first = '*')) {
438 $count2 = $count1 +
1;
439 $punct_data = $first;
440 } else if (($last2 == '/*') ||
($last2 == '--')) {
442 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
443 } else if (($last == '-') ||
($last == '+') ||
($last == '!')) {
445 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
447 $debugstr = $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+
1) . "\n"
448 . 'STR: ' . $punct_data;
449 PMA_SQP_throwError($debugstr, $sql);
452 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
454 } // end if... else if... else
459 if (PMA_STR_isSqlIdentifier($c, FALSE) ||
($c == '@')) {
462 //TODO: a @ can also be present in expressions like
464 // in this case, the @ is wrongly marked as alpha_variable
466 $is_sql_variable = ($c == '@');
467 $is_digit = (!$is_sql_variable) && PMA_STR_isDigit($c);
468 $is_hex_digit = ($is_digit) && ($c == '0') && ($count2 < $len) && ($sql[$count2] == 'x');
469 $is_float_digit = FALSE;
470 $is_float_digit_exponent = FALSE;
476 while (($count2 < $len) && PMA_STR_isSqlIdentifier($sql[$count2], ($is_sql_variable ||
$is_digit))) {
478 if ($is_sql_variable && ($c2 == '.')) {
482 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
484 if (!$is_float_digit) {
485 $is_float_digit = TRUE;
488 $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+
1) . "\n"
489 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
490 PMA_SQP_throwError($debugstr, $sql);
494 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') ||
($c2 == 'E'))) {
495 if (!$is_float_digit_exponent) {
496 $is_float_digit_exponent = TRUE;
497 $is_float_digit = TRUE;
502 $is_float_digit = FALSE;
505 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) ||
($is_digit && PMA_STR_isDigit($c2))) {
510 $is_hex_digit = FALSE;
516 $l = $count2 - $count1;
517 $str = $GLOBALS['PMA_substr']($sql, $count1, $l);
522 if ($is_float_digit) {
524 } else if ($is_hex_digit) {
531 if ($is_sql_variable != FALSE) {
532 $type = 'alpha_variable';
536 } // end if... else....
537 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize);
545 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
546 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1) . "\n";
547 PMA_SQP_bug($debugstr, $sql);
550 } // end while ($count2 < $len)
553 if ($arraysize > 0) {
554 $t_next = $sql_array[0]['type'];
559 for ($i = 0; $i < $arraysize; $i++
) {
562 if (($i +
1) < $arraysize) {
563 $t_next = $sql_array[$i +
1]['type'];
567 if ($t_cur == 'alpha') {
568 $t_suffix = '_identifier';
569 $d_cur_upper = strtoupper($sql_array[$i]['data']);
570 if (($t_next == 'punct_qualifier') ||
($t_prev == 'punct_qualifier')) {
571 $t_suffix = '_identifier';
572 } else if (($t_next == 'punct_bracket_open_round')
573 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
574 $t_suffix = '_functionName';
575 } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
576 $t_suffix = '_columnType';
577 // Temporary fix for BUG #621357
578 //TODO FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
579 if($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
580 $t_suffix = '_reservedWord';
582 //END OF TEMPORARY FIX
583 } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
584 $t_suffix = '_reservedWord';
585 } else if (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
586 $t_suffix = '_columnAttrib';
590 $sql_array[$i]['type'] .= $t_suffix;
594 // Stores the size of the array inside the array, as count() is a slow
596 $sql_array['len'] = $arraysize;
598 // Sends the data back
600 } // end of the "PMA_SQP_parse()" function
603 * Checks for token types being what we want...
605 * @param string String of type that we have
606 * @param string String of type that we want
608 * @return boolean result of check
612 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
614 $typeSeperator = '_';
615 if(strcmp($whatWeWant, $toCheck) == 0) {
618 //if(strpos($whatWeWant, $typeSeperator) === FALSE) {
619 // PHP3 compatible (works unless there is a real ff character)
620 if(!strpos("\xff" . $whatWeWant, $typeSeperator)) {
621 return strncmp($whatWeWant, $toCheck , strpos($toCheck, $typeSeperator)) == 0;
630 * Analyzes SQL queries
632 * @param array The SQL queries
634 * @return array The analyzed SQL queries
638 function PMA_SQP_analyze($arr)
644 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
646 'group_by_clause'=> '',
647 'order_by_clause'=> '',
648 'having_clause' => '',
649 'where_clause' => '',
650 'where_clause_identifiers' => array(),
651 'queryflags' => array(),
652 'select_expr' => array(),
653 'table_ref' => array()
655 $subresult_empty = $subresult;
656 $seek_queryend = FALSE;
657 $seen_end_of_table_ref = FALSE;
659 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
660 // we must not use CURDATE as a table_ref
661 // so we track wether we are in the EXTRACT()
664 /* Description of analyzer results
666 * lem9: db, table, column, alias
667 * ------------------------
669 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
671 * The SELECT syntax (simplified) is
674 * select_expression,...
675 * [FROM [table_references]
678 * ['select_expr'] is filled with each expression, the key represents the
679 * expression position in the list (0-based) (so we don't lose track of
680 * multiple occurences of the same column).
682 * ['table_ref'] is filled with each table ref, same thing for the key.
684 * I create all sub-values empty, even if they are
685 * not present (for example no select_expression alias).
687 * There is a debug section at the end of the main loop, if you want to
688 * see the exact contents of select_expr and table_ref
693 * In $subresult, array 'queryflags' is filled, according to what we
696 * Currently, those are generated:
698 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
699 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
701 * lem9: query clauses
704 * The select is splitted in those clauses:
705 * ['select_expr_clause']
707 * ['group_by_clause']
708 * ['order_by_clause']
712 * and the identifiers of the where clause are put into the array
713 * ['where_clause_identifier']
717 // TODO: current logic checks for only one word, so I put only the
718 // first word of the reserved expressions that end a table ref;
719 // maybe this is not ok (the first word might mean something else)
720 // $words_ending_table_ref = array(
725 // 'LOCK IN SHARE MODE',
731 $words_ending_table_ref = array(
742 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
744 $words_ending_clauses = array(
751 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
757 $supported_query_types = array(
760 // Support for these additional query types will come later on.
774 $supported_query_types_cnt = count($supported_query_types);
776 // loop #1 for each token: select_expr, table_ref for SELECT
778 for ($i = 0; $i < $size; $i++
) {
779 //echo "trace <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br>";
781 // High speed seek for locating the end of the current query
782 if ($seek_queryend == TRUE) {
783 if ($arr[$i]['type'] == 'punct_queryend') {
784 $seek_queryend = FALSE;
787 } // end if (type == punct_queryend)
788 } // end if ($seek_queryend)
790 // TODO: when we find a UNION, should we split
791 // in another subresult?
792 if ($arr[$i]['type'] == 'punct_queryend') {
793 $result[] = $subresult;
794 $subresult = $subresult_empty;
796 } // end if (type == punct_queryend)
798 // ==============================================================
799 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
801 $number_of_brackets_in_extract++
;
804 // ==============================================================
805 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
807 $number_of_brackets_in_extract--;
808 if ($number_of_brackets_in_extract == 0) {
813 // ==============================================================
814 if ($arr[$i]['type'] == 'alpha_functionName') {
815 $upper_data = strtoupper($arr[$i]['data']);
816 if ($upper_data =='EXTRACT') {
818 $number_of_brackets_in_extract = 0;
822 // ==============================================================
823 if ($arr[$i]['type'] == 'alpha_reservedWord') {
824 // We don't know what type of query yet, so run this
825 if ($subresult['querytype'] == '') {
826 $subresult['querytype'] = strtoupper($arr[$i]['data']);
827 } // end if (querytype was empty)
829 // Check if we support this type of query
830 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
831 // Skip ahead to the next one if we don't
832 $seek_queryend = TRUE;
834 } // end if (query not supported)
837 $upper_data = strtoupper($arr[$i]['data']);
838 //TODO: reset for each query?
840 if ($upper_data == 'SELECT') {
842 $previous_was_identifier = FALSE;
843 $current_select_expr = -1;
844 $seen_end_of_table_ref = FALSE;
845 } // end if ( data == SELECT)
847 if ($upper_data =='FROM' && !$in_extract) {
848 $current_table_ref = -1;
850 $previous_was_identifier = FALSE;
851 $save_table_ref = TRUE;
852 } // end if (data == FROM)
854 // here, do not 'continue' the loop, as we have more work for
855 // reserved words below
856 } // end if (type == alpha_reservedWord)
858 // ==============================
859 if (($arr[$i]['type'] == 'quote_backtick')
860 ||
($arr[$i]['type'] == 'quote_double')
861 ||
($arr[$i]['type'] == 'quote_single')
862 ||
($arr[$i]['type'] == 'alpha_identifier')) {
864 switch ($arr[$i]['type']) {
865 case 'alpha_identifier':
866 $identifier = $arr[$i]['data'];
869 //TODO: check embedded double quotes or backticks?
870 // and/or remove just the first and last character?
871 case 'quote_backtick':
872 $identifier = str_replace('`','',$arr[$i]['data']);
875 $identifier = str_replace('"','',$arr[$i]['data']);
878 $identifier = str_replace("'","",$arr[$i]['data']);
882 if ($subresult['querytype'] == 'SELECT') {
884 if ($previous_was_identifier) {
885 // found alias for this select_expr, save it
886 $alias_for_select_expr = $identifier;
888 $chain[] = $identifier;
889 $previous_was_identifier = TRUE;
891 } // end if !$previous_was_identifier
894 if ($save_table_ref && !$seen_end_of_table_ref) {
895 if ($previous_was_identifier) {
896 // found alias for table ref
898 $alias_for_table_ref = $identifier;
900 $chain[] = $identifier;
901 $previous_was_identifier = TRUE;
903 } // end if ($previous_was_identifier)
904 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
905 } // end if (!$seen_from)
906 } // end if (querytype SELECT)
907 } // end if ( quote_backtick or double quote or alpha_identifier)
909 // ===================================
910 if ($arr[$i]['type'] == 'punct_qualifier') {
911 // to be able to detect an identifier following another
912 $previous_was_identifier = FALSE;
914 } // end if (punct_qualifier)
916 // TODO: check if 3 identifiers following one another -> error
918 // s a v e a s e l e c t e x p r
919 // finding a list separator or FROM
920 // means that we must save the current chain of identifiers
921 // into a select expression
923 // for now, we only save a select expression if it contains
924 // at least one identifier, as we are interested in checking
925 // the columns and table names, so in "select * from persons",
926 // the "*" is not saved
928 if (isset($chain) && !$seen_end_of_table_ref
930 && $arr[$i]['type'] == 'punct_listsep')
931 ||
($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM')) ) {
932 $size_chain = count($chain);
933 $current_select_expr++
;
934 $subresult['select_expr'][$current_select_expr] = array(
939 'table_true_name' => '',
943 if (!empty($alias_for_select_expr)) {
944 // we had found an alias for this select expression
945 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
946 unset($alias_for_select_expr);
948 // there is at least a column
949 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
950 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
953 if ($size_chain > 1) {
954 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
955 // we assume for now that this is also the true name
956 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
957 $subresult['select_expr'][$current_select_expr]['expr']
958 = $subresult['select_expr'][$current_select_expr]['table_name']
959 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
960 } // end if ($size_chain > 1)
963 if ($size_chain > 2) {
964 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
965 $subresult['select_expr'][$current_select_expr]['expr']
966 = $subresult['select_expr'][$current_select_expr]['db']
967 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
968 } // end if ($size_chain > 2)
971 // TODO: explain this:
972 if (($arr[$i]['type'] == 'alpha_reservedWord')
973 && ($upper_data != 'FROM')) {
974 $previous_was_identifier = TRUE;
977 } // end if (save a select expr)
980 //======================================
981 // s a v e a t a b l e r e f
982 //======================================
984 // maybe we just saw the end of table refs
985 // but the last table ref has to be saved
986 // or we are at the last token (TODO: there could be another
987 // query after this one)
988 // or we just got a reserved word
990 if (isset($chain) && $seen_from && $save_table_ref
991 && ($arr[$i]['type'] == 'punct_listsep'
992 ||
($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
993 ||
$seen_end_of_table_ref
996 $size_chain = count($chain);
997 $current_table_ref++
;
998 $subresult['table_ref'][$current_table_ref] = array(
1002 'table_alias' => '',
1003 'table_true_name' => ''
1005 if (!empty($alias_for_table_ref)) {
1006 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1007 unset($alias_for_table_ref);
1009 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1010 // we assume for now that this is also the true name
1011 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1012 $subresult['table_ref'][$current_table_ref]['expr']
1013 = $subresult['table_ref'][$current_table_ref]['table_name'];
1015 if ($size_chain > 1) {
1016 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1017 $subresult['table_ref'][$current_table_ref]['expr']
1018 = $subresult['table_ref'][$current_table_ref]['db']
1019 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1020 } // end if ($size_chain > 1)
1022 // add the table alias into the whole expression
1023 $subresult['table_ref'][$current_table_ref]['expr']
1024 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1027 $previous_was_identifier = TRUE;
1030 } // end if (save a table ref)
1033 // when we have found all table refs,
1034 // for each table_ref alias, put the true name of the table
1035 // in the corresponding select expressions
1037 if (isset($current_table_ref) && ($seen_end_of_table_ref ||
$i == $size-1)) {
1038 for ($tr=0; $tr <= $current_table_ref; $tr++
) {
1039 $alias = $subresult['table_ref'][$tr]['table_alias'];
1040 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1041 for ($se=0; $se <= $current_select_expr; $se++
) {
1042 if (!empty($alias) && $subresult['select_expr'][$se]['table_true_name']
1044 $subresult['select_expr'][$se]['table_true_name']
1046 } // end if (found the alias)
1047 } // end for (select expressions)
1049 } // end for (table refs)
1050 } // end if (set the true names)
1053 // e n d i n g l o o p #1
1054 // set the $previous_was_identifier to FALSE if the current
1055 // token is not an identifier
1056 if (($arr[$i]['type'] != 'alpha_identifier')
1057 && ($arr[$i]['type'] != 'quote_double')
1058 && ($arr[$i]['type'] != 'quote_single')
1059 && ($arr[$i]['type'] != 'quote_backtick')) {
1060 $previous_was_identifier = FALSE;
1063 // however, if we are on AS, we must keep the $previous_was_identifier
1064 if (($arr[$i]['type'] == 'alpha_reservedWord')
1065 && ($upper_data == 'AS')) {
1066 $previous_was_identifier = TRUE;
1069 if (($arr[$i]['type'] == 'alpha_reservedWord')
1070 && ($upper_data =='ON' ||
$upper_data =='USING')) {
1071 $save_table_ref = FALSE;
1072 } // end if (data == ON)
1074 if (($arr[$i]['type'] == 'alpha_reservedWord')
1075 && ($upper_data =='JOIN' ||
$upper_data =='FROM')) {
1076 $save_table_ref = TRUE;
1077 } // end if (data == JOIN)
1079 // no need to check the end of table ref if we already did
1080 // TODO: maybe add "&& $seen_from"
1081 if (!$seen_end_of_table_ref) {
1082 // if this is the last token, it implies that we have
1083 // seen the end of table references
1084 // Check for the end of table references
1086 ||
($arr[$i]['type'] == 'alpha_reservedWord'
1087 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1088 $seen_end_of_table_ref = TRUE;
1090 // to be able to save the last table ref, but do not
1091 // set it true if we found a word like "ON" that has
1092 // already set it to false
1093 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1094 $save_table_ref = TRUE;
1097 } // end if (check for end of table ref)
1098 } //end if (!$seen_end_of_table_ref)
1100 if ($seen_end_of_table_ref) {
1101 $save_table_ref = FALSE;
1104 } // end for $i (loop #1)
1106 // -------------------------------------------------------
1107 // This is a big hunk of debugging code by Marc for this.
1108 // -------------------------------------------------------
1110 if (isset($current_select_expr)) {
1111 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1114 reset ($subresult['select_expr'][$trace]);
1115 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1116 echo "sel expr $trace $key => $val<br />\n";
1120 if (isset($current_table_ref)) {
1121 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1124 reset ($subresult['table_ref'][$trace]);
1125 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1126 echo "table ref $trace $key => $val<br />\n";
1130 // -------------------------------------------------------
1133 // loop #2: for queryflags
1134 // ,querytype (for queries != 'SELECT')
1136 // This is not in the loop 1 to keep logic simple
1138 // we will also need this queryflag in loop 2
1140 if (isset($current_table_ref) && $current_table_ref > -1) {
1141 $subresult['queryflags']['select_from'] = 1;
1144 $seen_reserved_word = FALSE;
1145 $seen_group = FALSE;
1146 $seen_order = FALSE;
1147 $in_group_by = FALSE; // true when we are into the GROUP BY clause
1148 $in_order_by = FALSE; // true when we are into the ORDER BY clause
1149 $in_having = FALSE; // true when we are into the HAVING clause
1150 $in_select_expr = FALSE; // true when we are into the select expr clause
1151 $in_where = FALSE; // true when we are into the WHERE clause
1154 for ($i = 0; $i < $size; $i++
) {
1155 //echo "trace loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br>";
1159 // check for reserved words that will have to generate
1160 // a confirmation request later in sql.php3
1164 // ALTER TABLE... DROP
1167 // this code is not used for confirmations coming from functions.js
1169 // TODO: check for punct_queryend
1171 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1172 $upper_data = strtoupper($arr[$i]['data']);
1173 if (!$seen_reserved_word) {
1174 $first_reserved_word = $upper_data;
1175 $subresult['querytype'] = $upper_data;
1176 $seen_reserved_word = TRUE;
1178 // if the first reserved word is DROP or DELETE,
1179 // we know this is a query that needs to be confirmed
1180 if ($first_reserved_word=='DROP'
1181 ||
$first_reserved_word == 'DELETE') {
1182 $subresult['queryflags']['need_confirm'] = 1;
1185 if ($upper_data=='DROP' && $first_reserved_word=='ALTER') {
1186 $subresult['queryflags']['need_confirm'] = 1;
1190 if ($upper_data == 'SELECT') {
1191 $in_select_expr = TRUE;
1192 $select_expr_clause = '';
1195 // if this is a real SELECT...FROM
1196 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1199 $in_select_expr = FALSE;
1203 // (we could have less resetting of variables to FALSE
1204 // if we trust that the query respects the standard
1205 // MySQL order for clauses)
1207 // we use $seen_group and $seen_order because we are looking
1209 if ($upper_data == 'GROUP') {
1211 $seen_order = FALSE;
1213 $in_order_by = FALSE;
1215 $in_select_expr = FALSE;
1218 if ($upper_data == 'ORDER') {
1220 $seen_group = FALSE;
1222 $in_group_by = FALSE;
1224 $in_select_expr = FALSE;
1227 if ($upper_data == 'HAVING') {
1229 $having_clause = '';
1230 $seen_group = FALSE;
1231 $seen_order = FALSE;
1232 $in_group_by = FALSE;
1233 $in_order_by = FALSE;
1235 $in_select_expr = FALSE;
1239 if ($upper_data == 'WHERE') {
1242 $where_clause_identifiers = array();
1243 $seen_group = FALSE;
1244 $seen_order = FALSE;
1245 $in_group_by = FALSE;
1246 $in_order_by = FALSE;
1248 $in_select_expr = FALSE;
1252 if ($upper_data == 'BY') {
1254 $in_group_by = TRUE;
1255 $group_by_clause = '';
1258 $in_order_by = TRUE;
1259 $order_by_clause = '';
1263 // if we find one of the words that could end the clause
1264 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1266 $in_group_by = FALSE;
1267 $in_order_by = FALSE;
1270 $in_select_expr = FALSE;
1274 } // endif (reservedWord)
1277 // do not add a blank after a function name
1280 if ($arr[$i]['type'] == 'alpha_functionName') {
1284 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1285 $select_expr_clause .= $arr[$i]['data'] . $sep;
1287 if ($in_from && $upper_data != 'FROM') {
1288 $from_clause .= $arr[$i]['data'] . $sep;
1290 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1291 $group_by_clause .= $arr[$i]['data'] . $sep;
1293 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1294 $order_by_clause .= $arr[$i]['data'] . $sep;
1296 if ($in_having && $upper_data != 'HAVING') {
1297 $having_clause .= $arr[$i]['data'] . $sep;
1299 if ($in_where && $upper_data != 'WHERE') {
1300 $where_clause .= $arr[$i]['data'] . $sep;
1302 if (($arr[$i]['type'] == 'quote_backtick')
1303 ||
($arr[$i]['type'] == 'alpha_identifier')) {
1304 $where_clause_identifiers[] = $arr[$i]['data'];
1308 // clear $upper_data for next iteration
1311 } // end for $i (loop #2)
1313 if (isset($select_expr_clause)) {
1314 $subresult['select_expr_clause'] = $select_expr_clause;
1316 if (isset($from_clause)) {
1317 $subresult['from_clause'] = $from_clause;
1319 if (isset($group_by_clause)) {
1320 $subresult['group_by_clause'] = $group_by_clause;
1322 if (isset($order_by_clause)) {
1323 $subresult['order_by_clause'] = $order_by_clause;
1325 if (isset($having_clause)) {
1326 $subresult['having_clause'] = $having_clause;
1328 if (isset($where_clause)) {
1329 $subresult['where_clause'] = $where_clause;
1331 if (isset($where_clause_identifiers)) {
1332 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1336 // They are naughty and didn't have a trailing semi-colon,
1337 // then still handle it properly
1338 if ($subresult['querytype'] != '') {
1339 $result[] = $subresult;
1342 } // end of the "PMA_SQP_analyze()" function
1346 * Colorizes SQL queries html formatted
1348 * @param array The SQL queries html formatted
1350 * @return array The colorized SQL queries
1354 function PMA_SQP_formatHtml_colorize($arr)
1356 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
1359 $class = 'syntax_' . $GLOBALS['PMA_substr']($arr['type'], 0, $i) . ' ';
1362 $class .= 'syntax_' . $arr['type'];
1364 //TODO: check why adding a "\n" after the </span> would cause extra
1365 // blanks to be displayed:
1366 // SELECT p . person_name
1368 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
1369 } // end of the "PMA_SQP_formatHtml_colorize()" function
1373 * Formats SQL queries to html
1375 * @param array The SQL queries
1377 * @return string The formatted SQL queries
1381 function PMA_SQP_formatHtml($arr, $mode='color')
1383 // first check for the SQL parser having hit an error
1384 if (PMA_SQP_isError()) {
1387 // then check for an array
1388 if (!is_array($arr)) {
1391 // else do it properly
1394 $str = '<span class="syntax">';
1395 $html_line_break = '<br />';
1399 $html_line_break = ' ';
1403 $html_line_break = '<br />';
1409 $infunction = FALSE;
1410 $space_punct_listsep = ' ';
1411 $space_punct_listsep_function_name = ' ';
1412 // $space_alpha_reserved_word = '<br />'."\n";
1413 $space_alpha_reserved_word = ' ';
1415 $keywords_with_brackets_1before = array(
1421 $keywords_with_brackets_1before_cnt = 4;
1423 $keywords_with_brackets_2before = array(
1434 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
1435 $keywords_with_brackets_2before_cnt = 9;
1437 // These reserved words do NOT get a newline placed near them.
1438 $keywords_no_newline = array(
1449 $keywords_no_newline_cnt = 9;
1451 $arraysize = $arr['len'];
1453 if ($arraysize >= 0) {
1457 $typearr[3] = $arr[0]['type'];
1460 for ($i = 0; $i < $arraysize; $i++
) {
1461 // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1465 // array_shift($typearr);
1472 if (($i +
1) < $arraysize) {
1473 // array_push($typearr, $arr[$i + 1]['type']);
1474 $typearr[4] = $arr[$i +
1]['type'];
1476 //array_push($typearr, NULL);
1480 for ($j=0; $j<4; $j++
) {
1481 $typearr[$j] = $typearr[$j +
1];
1484 switch ($typearr[2]) {
1485 case 'white_newline':
1486 // $after = '<br />';
1489 case 'punct_bracket_open_round':
1491 $infunction = FALSE;
1492 // Make sure this array is sorted!
1493 if (($typearr[1] == 'alpha_functionName') ||
($typearr[1] == 'alpha_columnType') ||
($typearr[1] == 'punct')
1494 ||
($typearr[3] == 'digit_integer') ||
($typearr[3] == 'digit_hex') ||
($typearr[3] == 'digit_float')
1495 ||
(($typearr[0] == 'alpha_reservedWord')
1496 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
1497 ||
(($typearr[1] == 'alpha_reservedWord')
1498 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
1505 $after .= ($mode != 'query_only' ?
'<div class="syntax_indent' . $indent . '">' : ' ');
1508 case 'alpha_identifier':
1509 if (($typearr[1] == 'punct_qualifier') ||
($typearr[3] == 'punct_qualifier')) {
1513 if (($typearr[3] == 'alpha_columnType') ||
($typearr[3] == 'alpha_identifier')) {
1517 case 'punct_qualifier':
1521 case 'punct_listsep':
1522 if ($infunction == TRUE) {
1523 $after .= $space_punct_listsep_function_name;
1525 $after .= $space_punct_listsep;
1528 case 'punct_queryend':
1529 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi')) {
1530 $after .= $html_line_break;
1531 $after .= $html_line_break;
1533 $space_punct_listsep = ' ';
1534 $space_punct_listsep_function_name = ' ';
1535 $space_alpha_reserved_word = ' ';
1537 case 'comment_mysql':
1538 case 'comment_ansi':
1539 $after .= $html_line_break;
1545 case 'punct_bracket_close_round':
1547 if ($infunction == TRUE) {
1553 $before .= ($mode != 'query_only' ?
'</div>' : ' ');
1555 $infunction = ($functionlevel > 0) ?
TRUE : FALSE;
1557 case 'alpha_columnType':
1558 if ($typearr[3] == 'alpha_columnAttrib') {
1561 if ($typearr[1] == 'alpha_columnType') {
1565 case 'alpha_columnAttrib':
1567 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
1568 if ($typearr[1] == 'alpha_identifier') {
1571 if (($typearr[3] == 'alpha_columnAttrib') ||
($typearr[3] == 'quote_single')) {
1575 case 'alpha_reservedWord':
1576 //$upper = $arr[$i]['data'];
1577 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
1578 if ((($typearr[1] != 'alpha_reservedWord')
1579 ||
(($typearr[1] == 'alpha_reservedWord')
1580 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
1581 && ($typearr[1] != 'punct_level_plus')
1582 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
1583 // do not put a space before the first token, because
1584 // we use a lot of eregi() checking for the first
1585 // reserved word at beginning of query
1587 $before .= $space_alpha_reserved_word;
1593 switch ($arr[$i]['data']) {
1595 $space_punct_listsep = $html_line_break;
1596 $space_alpha_reserved_word = ' ';
1609 $space_punct_listsep = $html_line_break;
1610 $space_alpha_reserved_word = ' ';
1614 $space_punct_listsep = $html_line_break;
1615 $space_alpha_reserved_word = $html_line_break;
1618 $space_punct_listsep = ' ';
1619 $space_alpha_reserved_word = $html_line_break;
1622 $space_punct_listsep = ' ';
1623 $space_alpha_reserved_word = $html_line_break;
1627 } // end switch ($arr[$i]['data'])
1631 case 'digit_integer':
1634 //TODO: could there be other types preceding a digit?
1635 if ($typearr[1] == 'alpha_reservedWord') {
1638 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
1642 case 'quote_double':
1643 case 'quote_single':
1644 // workaround: for the query
1645 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
1646 // the @ is incorrectly marked as alpha_variable
1647 // in the parser, and here, the '%' gets a blank before,
1648 // which is a syntax error
1649 if ($typearr[1]!='alpha_variable') {
1652 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
1656 case 'quote_backtick':
1657 if ($typearr[3] != 'punct_qualifier') {
1660 if ($typearr[1] != 'punct_qualifier') {
1666 } // end switch ($typearr[2])
1669 if ($typearr[3] != 'punct_qualifier') {
1674 $str .= $before . ($mode=='color' ?
PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
1676 if ($mode=='color') {
1681 } // end of the "PMA_SQP_formatHtml()" function
1685 * Builds a CSS rule used for html formatted SQL queries
1687 * @param string The class name
1688 * @param string The property name
1689 * @param string The property value
1691 * @return string The CSS rule
1695 * @see PMA_SQP_buildCssData()
1697 function PMA_SQP_buildCssRule($classname, $property, $value)
1699 $str = '.' . $classname . ' {';
1701 $str .= $property . ': ' . $value . ';';
1706 } // end of the "PMA_SQP_buildCssRule()" function
1710 * Builds CSS rules used for html formatted SQL queries
1712 * @return string The CSS rules set
1716 * @global array The current PMA configuration
1718 * @see PMA_SQP_buildCssRule()
1720 function PMA_SQP_buildCssData()
1725 while (list($key, $col) = each($cfg['SQP']['fmtColor'])) {
1726 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
1728 for ($i = 0; $i < 8; $i++
) {
1729 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
1733 } // end of the "PMA_SQP_buildCssData()" function
1735 if ($is_minimum_common == FALSE) {
1737 * Gets SQL queries with no format
1739 * @param array The SQL queries list
1741 * @return string The SQL queries with no format
1745 function PMA_SQP_formatNone($arr)
1747 $formatted_sql = htmlspecialchars($arr['raw']);
1748 $formatted_sql = ereg_replace("((\015\012)|(\015)|(\012)){3,}", "\n\n", $formatted_sql);
1750 return $formatted_sql;
1751 } // end of the "PMA_SQP_formatNone()" function
1755 * Gets SQL queries in text format
1757 * @param array The SQL queries list
1759 * @return string The SQL queries in text format
1763 function PMA_SQP_formatText($arr)
1768 return PMA_SQP_formatNone($arr);
1769 } // end of the "PMA_SQP_formatText()" function
1770 } // end if: minimal common.lib needed?
1771 } // $__PMA_SQP_LIB__