Translation update done using Pootle.
[phpmyadmin-themes.git] / libraries / sqlparser.lib.php
bloba9568c4be93231feb6fab6774241160df887895b
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /** SQL Parser Functions for phpMyAdmin
5 * These functions define an SQL parser system, capable of understanding and
6 * extracting data from a MySQL type SQL query.
8 * The basic procedure for using the new SQL parser:
9 * On any page that needs to extract data from a query or to pretty-print a
10 * query, you need code like this up at the top:
12 * ($sql contains the query)
13 * $parsed_sql = PMA_SQP_parse($sql);
15 * If you want to extract data from it then, you just need to run
16 * $sql_info = PMA_SQP_analyze($parsed_sql);
18 * See comments in PMA_SQP_analyze for the returned info
19 * from the analyzer.
21 * If you want a pretty-printed version of the query, do:
22 * $string = PMA_SQP_formatHtml($parsed_sql);
23 * (note that that you need to have syntax.css.php included somehow in your
24 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
25 * href="syntax.css.php" />' at the moment.)
27 * @package phpMyAdmin
29 if (! defined('PHPMYADMIN')) {
30 exit;
33 /**
34 * Minimum inclusion? (i.e. for the stylesheet builder)
36 if (! defined('PMA_MINIMUM_COMMON')) {
37 /**
38 * Include the string library as we use it heavily
40 require_once './libraries/string.lib.php';
42 /**
43 * Include data for the SQL Parser
45 require_once './libraries/sqlparser.data.php';
46 if (!defined('TESTSUITE')) {
47 require_once './libraries/mysql_charsets.lib.php';
49 if (!isset($mysql_charsets)) {
50 $mysql_charsets = array();
51 $mysql_charsets_count = 0;
52 $mysql_collations_flat = array();
53 $mysql_collations_count = 0;
56 if (!defined('DEBUG_TIMING')) {
57 /**
58 * currently we don't need the $pos (token position in query)
59 * for other purposes than LIMIT clause verification,
60 * so many calls to this function do not include the 4th parameter
62 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
64 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
65 $arrsize++;
66 } // end of the "PMA_SQP_arrayAdd()" function
67 } else {
68 /**
69 * This is debug variant of above.
70 * @ignore
72 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
74 global $timer;
76 $t = $timer;
77 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos, 'time' => $t);
78 $timer = microtime();
79 $arrsize++;
80 } // end of the "PMA_SQP_arrayAdd()" function
81 } // end if... else...
84 /**
85 * Reset the error variable for the SQL parser
87 * @access public
89 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
90 function PMA_SQP_resetError()
92 global $SQP_errorString;
93 $SQP_errorString = '';
94 unset($SQP_errorString);
97 /**
98 * Get the contents of the error variable for the SQL parser
100 * @return string Error string from SQL parser
102 * @access public
104 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
105 function PMA_SQP_getErrorString()
107 global $SQP_errorString;
108 return isset($SQP_errorString) ? $SQP_errorString : '';
112 * Check if the SQL parser hit an error
114 * @return boolean error state
116 * @access public
118 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
119 function PMA_SQP_isError()
121 global $SQP_errorString;
122 return isset($SQP_errorString) && !empty($SQP_errorString);
126 * Set an error message for the system
128 * @param string The error message
129 * @param string The failing SQL query
131 * @access private
132 * @scope SQL Parser internal
134 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
135 function PMA_SQP_throwError($message, $sql)
137 global $SQP_errorString;
138 $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"
139 . '<pre>' . "\n"
140 . 'ERROR: ' . $message . "\n"
141 . 'SQL: ' . htmlspecialchars($sql) . "\n"
142 . '</pre>' . "\n";
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
153 * @access public
155 function PMA_SQP_bug($message, $sql)
157 global $SQP_errorString;
158 $debugstr = 'ERROR: ' . $message . "\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: ' . htmlspecialchars($sql);
166 $encodedstr = $debugstr;
167 if (@function_exists('gzcompress')) {
168 $encodedstr = gzcompress($debugstr, 9);
170 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
173 $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:')
174 . '<br />' . "\n"
175 . '----' . __('BEGIN CUT') . '----' . '<br />' . "\n"
176 . $encodedstr . "\n"
177 . '----' . __('END CUT') . '----' . '<br />' . "\n";
179 $SQP_errorString .= '----' . __('BEGIN RAW') . '----<br />' . "\n"
180 . '<pre>' . "\n"
181 . $debugstr
182 . '</pre>' . "\n"
183 . '----' . __('END RAW') . '----<br />' . "\n";
185 } // end of the "PMA_SQP_bug()" function
189 * Parses the SQL queries
191 * @param string The SQL query list
193 * @return mixed Most of times, nothing...
195 * @global array The current PMA configuration
196 * @global array MySQL column attributes
197 * @global array MySQL reserved words
198 * @global array MySQL column types
199 * @global array MySQL function names
200 * @global integer MySQL column attributes count
201 * @global integer MySQL reserved words count
202 * @global integer MySQL column types count
203 * @global integer MySQL function names count
204 * @global array List of available character sets
205 * @global array List of available collations
206 * @global integer Character sets count
207 * @global integer Collations count
209 * @access public
211 function PMA_SQP_parse($sql)
213 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
214 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
215 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
216 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
218 // Convert all line feeds to Unix style
219 $sql = str_replace("\r\n", "\n", $sql);
220 $sql = str_replace("\r", "\n", $sql);
222 $len = PMA_strlen($sql);
223 if ($len == 0) {
224 return array();
227 $sql_array = array();
228 $sql_array['raw'] = $sql;
229 $count1 = 0;
230 $count2 = 0;
231 $punct_queryend = ';';
232 $punct_qualifier = '.';
233 $punct_listsep = ',';
234 $punct_level_plus = '(';
235 $punct_level_minus = ')';
236 $punct_user = '@';
237 $digit_floatdecimal = '.';
238 $digit_hexset = 'x';
239 $bracket_list = '()[]{}';
240 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
241 $allpunct_list_pair = array (
242 0 => '!=',
243 1 => '&&',
244 2 => ':=',
245 3 => '<<',
246 4 => '<=',
247 5 => '<=>',
248 6 => '<>',
249 7 => '>=',
250 8 => '>>',
251 9 => '||'
253 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
254 $quote_list = '\'"`';
255 $arraysize = 0;
257 $previous_was_space = false;
258 $this_was_space = false;
259 $previous_was_bracket = false;
260 $this_was_bracket = false;
261 $previous_was_punct = false;
262 $this_was_punct = false;
263 $previous_was_listsep = false;
264 $this_was_listsep = false;
265 $previous_was_quote = false;
266 $this_was_quote = false;
268 while ($count2 < $len) {
269 $c = $GLOBALS['PMA_substr']($sql, $count2, 1);
270 $count1 = $count2;
272 $previous_was_space = $this_was_space;
273 $this_was_space = false;
274 $previous_was_bracket = $this_was_bracket;
275 $this_was_bracket = false;
276 $previous_was_punct = $this_was_punct;
277 $this_was_punct = false;
278 $previous_was_listsep = $this_was_listsep;
279 $this_was_listsep = false;
280 $previous_was_quote = $this_was_quote;
281 $this_was_quote = false;
283 if (($c == "\n")) {
284 $this_was_space = true;
285 $count2++;
286 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
287 continue;
290 // Checks for white space
291 if ($GLOBALS['PMA_STR_isSpace']($c)) {
292 $this_was_space = true;
293 $count2++;
294 continue;
297 // Checks for comment lines.
298 // MySQL style #
299 // C style /* */
300 // ANSI style --
301 if (($c == '#')
302 || (($count2 + 1 < $len) && ($c == '/') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '*'))
303 || (($count2 + 2 == $len) && ($c == '-') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '-'))
304 || (($count2 + 2 < $len) && ($c == '-') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '-') && (($GLOBALS['PMA_substr']($sql, $count2 + 2, 1) <= ' ')))) {
305 $count2++;
306 $pos = 0;
307 $type = 'bad';
308 switch ($c) {
309 case '#':
310 $type = 'mysql';
311 case '-':
312 $type = 'ansi';
313 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
314 break;
315 case '/':
316 $type = 'c';
317 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
318 $pos += 2;
319 break;
320 default:
321 break;
322 } // end switch
323 $count2 = ($pos < $count2) ? $len : $pos;
324 $str = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
325 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
326 continue;
327 } // end if
329 // Checks for something inside quotation marks
330 if ($GLOBALS['PMA_strpos']($quote_list, $c) !== false) {
331 $startquotepos = $count2;
332 $quotetype = $c;
333 $count2++;
334 $escaped = FALSE;
335 $escaped_escaped = FALSE;
336 $pos = $count2;
337 $oldpos = 0;
338 do {
339 $oldpos = $pos;
340 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
341 // ($pos === FALSE)
342 if ($pos < 0) {
343 if ($c == '`') {
345 * Behave same as MySQL and accept end of query as end of backtick.
346 * I know this is sick, but MySQL behaves like this:
348 * SELECT * FROM `table
350 * is treated like
352 * SELECT * FROM `table`
354 $pos_quote_separator = $GLOBALS['PMA_strpos'](' ' . $sql, $GLOBALS['sql_delimiter'], $oldpos + 1) - 1;
355 if ($pos_quote_separator < 0) {
356 $len += 1;
357 $sql .= '`';
358 $sql_array['raw'] .= '`';
359 $pos = $len;
360 } else {
361 $len += 1;
362 $sql = $GLOBALS['PMA_substr']($sql, 0, $pos_quote_separator) . '`' . $GLOBALS['PMA_substr']($sql, $pos_quote_separator);
363 $sql_array['raw'] = $sql;
364 $pos = $pos_quote_separator;
366 if (class_exists('PMA_Message')) {
367 PMA_Message::warning(__('Automatically appended backtick to the end of query!'))->display();
369 } else {
370 $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
371 . 'STR: ' . htmlspecialchars($quotetype);
372 PMA_SQP_throwError($debugstr, $sql);
373 return $sql_array;
377 // If the quote is the first character, it can't be
378 // escaped, so don't do the rest of the code
379 if ($pos == 0) {
380 break;
383 // Checks for MySQL escaping using a \
384 // And checks for ANSI escaping using the $quotetype character
385 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
386 $pos ++;
387 continue;
388 } elseif (($pos + 1 < $len) && ($GLOBALS['PMA_substr']($sql, $pos, 1) == $quotetype) && ($GLOBALS['PMA_substr']($sql, $pos + 1, 1) == $quotetype)) {
389 $pos = $pos + 2;
390 continue;
391 } else {
392 break;
394 } while ($len > $pos); // end do
396 $count2 = $pos;
397 $count2++;
398 $type = 'quote_';
399 switch ($quotetype) {
400 case '\'':
401 $type .= 'single';
402 $this_was_quote = true;
403 break;
404 case '"':
405 $type .= 'double';
406 $this_was_quote = true;
407 break;
408 case '`':
409 $type .= 'backtick';
410 $this_was_quote = true;
411 break;
412 default:
413 break;
414 } // end switch
415 $data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
416 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
417 continue;
420 // Checks for brackets
421 if ($GLOBALS['PMA_strpos']($bracket_list, $c) !== false) {
422 // All bracket tokens are only one item long
423 $this_was_bracket = true;
424 $count2++;
425 $type_type = '';
426 if ($GLOBALS['PMA_strpos']('([{', $c) !== false) {
427 $type_type = 'open';
428 } else {
429 $type_type = 'close';
432 $type_style = '';
433 if ($GLOBALS['PMA_strpos']('()', $c) !== false) {
434 $type_style = 'round';
435 } elseif ($GLOBALS['PMA_strpos']('[]', $c) !== false) {
436 $type_style = 'square';
437 } else {
438 $type_style = 'curly';
441 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
442 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
443 continue;
446 /* DEBUG
447 echo '<pre>1';
448 var_dump(PMA_STR_isSqlIdentifier($c, false));
449 var_dump($c == '@');
450 var_dump($c == '.');
451 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
452 var_dump($previous_was_space);
453 var_dump($previous_was_bracket);
454 var_dump($previous_was_listsep);
455 echo '</pre>';
458 // Checks for identifier (alpha or numeric)
459 if (PMA_STR_isSqlIdentifier($c, false)
460 || $c == '@'
461 || ($c == '.'
462 && $GLOBALS['PMA_STR_isDigit']($GLOBALS['PMA_substr']($sql, $count2 + 1, 1))
463 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
465 /* DEBUG
466 echo PMA_substr($sql, $count2);
467 echo '<hr />';
470 $count2++;
473 * @todo a @ can also be present in expressions like
474 * FROM 'user'@'%' or TO 'user'@'%'
475 * in this case, the @ is wrongly marked as alpha_variable
477 $is_identifier = $previous_was_punct;
478 $is_sql_variable = $c == '@' && ! $previous_was_quote;
479 $is_user = $c == '@' && $previous_was_quote;
480 $is_digit = !$is_identifier && !$is_sql_variable && $GLOBALS['PMA_STR_isDigit']($c);
481 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && $GLOBALS['PMA_substr']($sql, $count2, 1) == 'x';
482 $is_float_digit = $c == '.';
483 $is_float_digit_exponent = FALSE;
485 /* DEBUG
486 echo '<pre>2';
487 var_dump($is_identifier);
488 var_dump($is_sql_variable);
489 var_dump($is_digit);
490 var_dump($is_float_digit);
491 echo '</pre>';
494 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
495 if ($is_hex_digit) {
496 $count2++;
497 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
498 if ($pos > $count2) {
499 $count2 = $pos;
501 unset($pos);
502 } elseif ($is_digit) {
503 $pos = strspn($sql, '0123456789', $count2);
504 if ($pos > $count2) {
505 $count2 = $pos;
507 unset($pos);
510 while (($count2 < $len) && PMA_STR_isSqlIdentifier($GLOBALS['PMA_substr']($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
511 $c2 = $GLOBALS['PMA_substr']($sql, $count2, 1);
512 if ($is_sql_variable && ($c2 == '.')) {
513 $count2++;
514 continue;
516 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
517 $count2++;
518 if (!$is_float_digit) {
519 $is_float_digit = TRUE;
520 continue;
521 } else {
522 $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
523 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
524 PMA_SQP_throwError($debugstr, $sql);
525 return $sql_array;
528 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
529 if (!$is_float_digit_exponent) {
530 $is_float_digit_exponent = TRUE;
531 $is_float_digit = TRUE;
532 $count2++;
533 continue;
534 } else {
535 $is_digit = FALSE;
536 $is_float_digit = FALSE;
539 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && $GLOBALS['PMA_STR_isDigit']($c2))) {
540 $count2++;
541 continue;
542 } else {
543 $is_digit = FALSE;
544 $is_hex_digit = FALSE;
547 $count2++;
548 } // end while
550 $l = $count2 - $count1;
551 $str = $GLOBALS['PMA_substr']($sql, $count1, $l);
553 $type = '';
554 if ($is_digit || $is_float_digit || $is_hex_digit) {
555 $type = 'digit';
556 if ($is_float_digit) {
557 $type .= '_float';
558 } elseif ($is_hex_digit) {
559 $type .= '_hex';
560 } else {
561 $type .= '_integer';
563 } elseif ($is_user) {
564 $type = 'punct_user';
565 } elseif ($is_sql_variable != FALSE) {
566 $type = 'alpha_variable';
567 } else {
568 $type = 'alpha';
569 } // end if... else....
570 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
572 continue;
575 // Checks for punct
576 if ($GLOBALS['PMA_strpos']($allpunct_list, $c) !== false) {
577 while (($count2 < $len) && $GLOBALS['PMA_strpos']($allpunct_list, $GLOBALS['PMA_substr']($sql, $count2, 1)) !== false) {
578 $count2++;
580 $l = $count2 - $count1;
581 if ($l == 1) {
582 $punct_data = $c;
583 } else {
584 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $l);
587 // Special case, sometimes, althought two characters are
588 // adjectent directly, they ACTUALLY need to be seperate
589 /* DEBUG
590 echo '<pre>';
591 var_dump($l);
592 var_dump($punct_data);
593 echo '</pre>';
596 if ($l == 1) {
597 $t_suffix = '';
598 switch ($punct_data) {
599 case $punct_queryend:
600 $t_suffix = '_queryend';
601 break;
602 case $punct_qualifier:
603 $t_suffix = '_qualifier';
604 $this_was_punct = true;
605 break;
606 case $punct_listsep:
607 $this_was_listsep = true;
608 $t_suffix = '_listsep';
609 break;
610 default:
611 break;
613 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
614 } elseif ($punct_data == $GLOBALS['sql_delimiter'] || PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
615 // Ok, we have one of the valid combined punct expressions
616 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
617 } else {
618 // Bad luck, lets split it up more
619 $first = $punct_data[0];
620 $first2 = $punct_data[0] . $punct_data[1];
621 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
622 $last = $punct_data[$l - 1];
623 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
624 $count2 = $count1 + 1;
625 $punct_data = $first;
626 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || $GLOBALS['PMA_substr']($sql, $count2, 1) <= ' '))) {
627 $count2 -= 2;
628 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
629 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
630 $count2--;
631 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
633 * @todo for negation operator, split in 2 tokens ?
634 * "select x&~1 from t"
635 * becomes "select x & ~ 1 from t" ?
638 } elseif ($last != '~') {
639 $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
640 . 'STR: ' . htmlspecialchars($punct_data);
641 PMA_SQP_throwError($debugstr, $sql);
642 return $sql_array;
644 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
645 continue;
646 } // end if... elseif... else
647 continue;
650 // DEBUG
651 $count2++;
653 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
654 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1) . "\n";
655 PMA_SQP_bug($debugstr, $sql);
656 return $sql_array;
658 } // end while ($count2 < $len)
661 echo '<pre>';
662 print_r($sql_array);
663 echo '</pre>';
666 if ($arraysize > 0) {
667 $t_next = $sql_array[0]['type'];
668 $t_prev = '';
669 $t_bef_prev = '';
670 $t_cur = '';
671 $d_next = $sql_array[0]['data'];
672 $d_prev = '';
673 $d_bef_prev = '';
674 $d_cur = '';
675 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
676 $d_prev_upper = '';
677 $d_bef_prev_upper = '';
678 $d_cur_upper = '';
681 for ($i = 0; $i < $arraysize; $i++) {
682 $t_bef_prev = $t_prev;
683 $t_prev = $t_cur;
684 $t_cur = $t_next;
685 $d_bef_prev = $d_prev;
686 $d_prev = $d_cur;
687 $d_cur = $d_next;
688 $d_bef_prev_upper = $d_prev_upper;
689 $d_prev_upper = $d_cur_upper;
690 $d_cur_upper = $d_next_upper;
691 if (($i + 1) < $arraysize) {
692 $t_next = $sql_array[$i + 1]['type'];
693 $d_next = $sql_array[$i + 1]['data'];
694 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
695 } else {
696 $t_next = '';
697 $d_next = '';
698 $d_next_upper = '';
701 //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
703 if ($t_cur == 'alpha') {
704 $t_suffix = '_identifier';
705 // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
706 if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
707 $t_suffix = '_bitfield_constant_introducer';
708 } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
709 $t_suffix = '_identifier';
710 } elseif (($t_next == 'punct_bracket_open_round')
711 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
713 * @todo 2005-10-16: in the case of a CREATE TABLE containing
714 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
715 * found here and the token is wrongly marked as alpha_functionName.
716 * But we compensate for this when analysing for timestamp_not_null
717 * later in this script.
719 * Same applies to CHAR vs. CHAR() function.
721 $t_suffix = '_functionName';
722 /* There are functions which might be as well column types */
723 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
724 $t_suffix = '_columnType';
727 * Temporary fix for BUG #621357 and #2027720
729 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
731 if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
732 $t_suffix = '_reservedWord';
734 //END OF TEMPORARY FIX
736 // CHARACTER is a synonym for CHAR, but can also be meant as
737 // CHARACTER SET. In this case, we have a reserved word.
738 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
739 $t_suffix = '_reservedWord';
742 // experimental
743 // current is a column type, so previous must not be
744 // a reserved word but an identifier
745 // CREATE TABLE SG_Persons (first varchar(64))
747 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
748 // $sql_array[$i-1]['type'] = 'alpha_identifier';
751 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
752 $t_suffix = '_reservedWord';
753 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
754 $t_suffix = '_columnAttrib';
755 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
756 // it should be regarded as a reserved word.
757 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
758 $t_suffix = '_reservedWord';
761 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
762 $t_suffix = '_reservedWord';
764 // Binary as character set
765 if ($d_cur_upper == 'BINARY' && (
766 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
767 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
768 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
769 || $d_prev_upper == 'CHARSET'
770 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
771 $t_suffix = '_charset';
773 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
774 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
775 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
776 $t_suffix = '_charset';
777 } else {
778 // Do nothing
780 // check if present in the list of forbidden words
781 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
782 $sql_array[$i]['forbidden'] = TRUE;
783 } else {
784 $sql_array[$i]['forbidden'] = FALSE;
786 $sql_array[$i]['type'] .= $t_suffix;
788 } // end for
790 // Stores the size of the array inside the array, as count() is a slow
791 // operation.
792 $sql_array['len'] = $arraysize;
794 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
795 // Sends the data back
796 return $sql_array;
797 } // end of the "PMA_SQP_parse()" function
800 * Checks for token types being what we want...
802 * @param string String of type that we have
803 * @param string String of type that we want
805 * @return boolean result of check
807 * @access private
809 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
811 $typeSeperator = '_';
812 if (strcmp($whatWeWant, $toCheck) == 0) {
813 return TRUE;
814 } else {
815 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
816 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
817 } else {
818 return FALSE;
825 * Analyzes SQL queries
827 * @param array The SQL queries
829 * @return array The analyzed SQL queries
831 * @access public
833 function PMA_SQP_analyze($arr)
835 if ($arr == array() || !isset($arr['len'])) {
836 return array();
838 $result = array();
839 $size = $arr['len'];
840 $subresult = array(
841 'querytype' => '',
842 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
843 'position_of_first_select' => '', // the array index
844 'from_clause'=> '',
845 'group_by_clause'=> '',
846 'order_by_clause'=> '',
847 'having_clause' => '',
848 'limit_clause' => '',
849 'where_clause' => '',
850 'where_clause_identifiers' => array(),
851 'unsorted_query' => '',
852 'queryflags' => array(),
853 'select_expr' => array(),
854 'table_ref' => array(),
855 'foreign_keys' => array(),
856 'create_table_fields' => array()
858 $subresult_empty = $subresult;
859 $seek_queryend = FALSE;
860 $seen_end_of_table_ref = FALSE;
861 $number_of_brackets_in_extract = 0;
862 $number_of_brackets_in_group_concat = 0;
864 $number_of_brackets = 0;
865 $in_subquery = false;
866 $seen_subquery = false;
867 $seen_from = false;
869 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
870 // we must not use CURDATE as a table_ref
871 // so we track whether we are in the EXTRACT()
872 $in_extract = FALSE;
874 // for GROUP_CONCAT(...)
875 $in_group_concat = FALSE;
877 /* Description of analyzer results
879 * db, table, column, alias
880 * ------------------------
882 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
884 * The SELECT syntax (simplified) is
886 * SELECT
887 * select_expression,...
888 * [FROM [table_references]
891 * ['select_expr'] is filled with each expression, the key represents the
892 * expression position in the list (0-based) (so we don't lose track of
893 * multiple occurences of the same column).
895 * ['table_ref'] is filled with each table ref, same thing for the key.
897 * I create all sub-values empty, even if they are
898 * not present (for example no select_expression alias).
900 * There is a debug section at the end of loop #1, if you want to
901 * see the exact contents of select_expr and table_ref
903 * queryflags
904 * ----------
906 * In $subresult, array 'queryflags' is filled, according to what we
907 * find in the query.
909 * Currently, those are generated:
911 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
912 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
913 * ['queryflags']['distinct'] = 1; for a DISTINCT
914 * ['queryflags']['union'] = 1; for a UNION
915 * ['queryflags']['join'] = 1; for a JOIN
916 * ['queryflags']['offset'] = 1; for the presence of OFFSET
917 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
919 * query clauses
920 * -------------
922 * The select is splitted in those clauses:
923 * ['select_expr_clause']
924 * ['from_clause']
925 * ['group_by_clause']
926 * ['order_by_clause']
927 * ['having_clause']
928 * ['limit_clause']
929 * ['where_clause']
931 * The identifiers of the WHERE clause are put into the array
932 * ['where_clause_identifier']
934 * For a SELECT, the whole query without the ORDER BY clause is put into
935 * ['unsorted_query']
937 * foreign keys
938 * ------------
939 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
940 * analyzed and ['foreign_keys'] is an array filled with
941 * the constraint name, the index list,
942 * the REFERENCES table name and REFERENCES index list,
943 * and ON UPDATE | ON DELETE clauses
945 * position_of_first_select
946 * ------------------------
948 * The array index of the first SELECT we find. Will be used to
949 * insert a SQL_CALC_FOUND_ROWS.
951 * create_table_fields
952 * -------------------
954 * Used to detect the DEFAULT CURRENT_TIMESTAMP and
955 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
956 * Also used to store the default value of the field.
957 * An array, each element is the identifier name.
958 * Note that for now, the timestamp_not_null element is created
959 * even for non-TIMESTAMP fields.
961 * Sub-elements: ['type'] which contains the column type
962 * optional (currently they are never false but can be absent):
963 * ['default_current_timestamp'] boolean
964 * ['on_update_current_timestamp'] boolean
965 * ['timestamp_not_null'] boolean
967 * section_before_limit, section_after_limit
968 * -----------------------------------------
970 * Marks the point of the query where we can insert a LIMIT clause;
971 * so the section_before_limit will contain the left part before
972 * a possible LIMIT clause
975 * End of description of analyzer results
978 // must be sorted
979 // TODO: current logic checks for only one word, so I put only the
980 // first word of the reserved expressions that end a table ref;
981 // maybe this is not ok (the first word might mean something else)
982 // $words_ending_table_ref = array(
983 // 'FOR UPDATE',
984 // 'GROUP BY',
985 // 'HAVING',
986 // 'LIMIT',
987 // 'LOCK IN SHARE MODE',
988 // 'ORDER BY',
989 // 'PROCEDURE',
990 // 'UNION',
991 // 'WHERE'
992 // );
993 $words_ending_table_ref = array(
994 'FOR',
995 'GROUP',
996 'HAVING',
997 'LIMIT',
998 'LOCK',
999 'ORDER',
1000 'PROCEDURE',
1001 'UNION',
1002 'WHERE'
1004 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
1006 $words_ending_clauses = array(
1007 'FOR',
1008 'LIMIT',
1009 'LOCK',
1010 'PROCEDURE',
1011 'UNION'
1013 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
1018 // must be sorted
1019 $supported_query_types = array(
1020 'SELECT'
1022 // Support for these additional query types will come later on.
1023 'DELETE',
1024 'INSERT',
1025 'REPLACE',
1026 'TRUNCATE',
1027 'UPDATE'
1028 'EXPLAIN',
1029 'DESCRIBE',
1030 'SHOW',
1031 'CREATE',
1032 'SET',
1033 'ALTER'
1036 $supported_query_types_cnt = count($supported_query_types);
1038 // loop #1 for each token: select_expr, table_ref for SELECT
1040 for ($i = 0; $i < $size; $i++) {
1041 //DEBUG echo "Loop1 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1043 // High speed seek for locating the end of the current query
1044 if ($seek_queryend == TRUE) {
1045 if ($arr[$i]['type'] == 'punct_queryend') {
1046 $seek_queryend = FALSE;
1047 } else {
1048 continue;
1049 } // end if (type == punct_queryend)
1050 } // end if ($seek_queryend)
1053 * Note: do not split if this is a punct_queryend for the first and only query
1054 * @todo when we find a UNION, should we split in another subresult?
1056 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1057 $result[] = $subresult;
1058 $subresult = $subresult_empty;
1059 continue;
1060 } // end if (type == punct_queryend)
1062 // ==============================================================
1063 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1064 $number_of_brackets++;
1065 if ($in_extract) {
1066 $number_of_brackets_in_extract++;
1068 if ($in_group_concat) {
1069 $number_of_brackets_in_group_concat++;
1072 // ==============================================================
1073 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1074 $number_of_brackets--;
1075 if ($number_of_brackets == 0) {
1076 $in_subquery = false;
1078 if ($in_extract) {
1079 $number_of_brackets_in_extract--;
1080 if ($number_of_brackets_in_extract == 0) {
1081 $in_extract = FALSE;
1084 if ($in_group_concat) {
1085 $number_of_brackets_in_group_concat--;
1086 if ($number_of_brackets_in_group_concat == 0) {
1087 $in_group_concat = FALSE;
1092 if ($in_subquery) {
1094 * skip the subquery to avoid setting
1095 * select_expr or table_ref with the contents
1096 * of this subquery; this is to avoid a bug when
1097 * trying to edit the results of
1098 * select * from child where not exists (select id from
1099 * parent where child.parent_id = parent.id);
1101 continue;
1103 // ==============================================================
1104 if ($arr[$i]['type'] == 'alpha_functionName') {
1105 $upper_data = strtoupper($arr[$i]['data']);
1106 if ($upper_data =='EXTRACT') {
1107 $in_extract = TRUE;
1108 $number_of_brackets_in_extract = 0;
1110 if ($upper_data =='GROUP_CONCAT') {
1111 $in_group_concat = TRUE;
1112 $number_of_brackets_in_group_concat = 0;
1116 // ==============================================================
1117 if ($arr[$i]['type'] == 'alpha_reservedWord'
1118 // && $arr[$i]['forbidden'] == FALSE) {
1120 // We don't know what type of query yet, so run this
1121 if ($subresult['querytype'] == '') {
1122 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1123 } // end if (querytype was empty)
1125 // Check if we support this type of query
1126 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1127 // Skip ahead to the next one if we don't
1128 $seek_queryend = TRUE;
1129 continue;
1130 } // end if (query not supported)
1132 // upper once
1133 $upper_data = strtoupper($arr[$i]['data']);
1135 * @todo reset for each query?
1138 if ($upper_data == 'SELECT') {
1139 if ($number_of_brackets > 0) {
1140 $in_subquery = true;
1141 $seen_subquery = true;
1142 // this is a subquery so do not analyze inside it
1143 continue;
1145 $seen_from = FALSE;
1146 $previous_was_identifier = FALSE;
1147 $current_select_expr = -1;
1148 $seen_end_of_table_ref = FALSE;
1149 } // end if (data == SELECT)
1151 if ($upper_data =='FROM' && !$in_extract) {
1152 $current_table_ref = -1;
1153 $seen_from = TRUE;
1154 $previous_was_identifier = FALSE;
1155 $save_table_ref = TRUE;
1156 } // end if (data == FROM)
1158 // here, do not 'continue' the loop, as we have more work for
1159 // reserved words below
1160 } // end if (type == alpha_reservedWord)
1162 // ==============================
1163 if ($arr[$i]['type'] == 'quote_backtick'
1164 || $arr[$i]['type'] == 'quote_double'
1165 || $arr[$i]['type'] == 'quote_single'
1166 || $arr[$i]['type'] == 'alpha_identifier'
1167 || ($arr[$i]['type'] == 'alpha_reservedWord'
1168 && $arr[$i]['forbidden'] == FALSE)) {
1170 switch ($arr[$i]['type']) {
1171 case 'alpha_identifier':
1172 case 'alpha_reservedWord':
1174 * this is not a real reservedWord, because it's not
1175 * present in the list of forbidden words, for example
1176 * "storage" which can be used as an identifier
1178 * @todo avoid the pretty printing in color in this case
1180 $identifier = $arr[$i]['data'];
1181 break;
1183 case 'quote_backtick':
1184 case 'quote_double':
1185 case 'quote_single':
1186 $identifier = PMA_unQuote($arr[$i]['data']);
1187 break;
1188 } // end switch
1190 if ($subresult['querytype'] == 'SELECT'
1191 && ! $in_group_concat
1192 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
1193 if (!$seen_from) {
1194 if ($previous_was_identifier && isset($chain)) {
1195 // found alias for this select_expr, save it
1196 // but only if we got something in $chain
1197 // (for example, SELECT COUNT(*) AS cnt
1198 // puts nothing in $chain, so we avoid
1199 // setting the alias)
1200 $alias_for_select_expr = $identifier;
1201 } else {
1202 $chain[] = $identifier;
1203 $previous_was_identifier = TRUE;
1205 } // end if !$previous_was_identifier
1206 } else {
1207 // ($seen_from)
1208 if ($save_table_ref && !$seen_end_of_table_ref) {
1209 if ($previous_was_identifier) {
1210 // found alias for table ref
1211 // save it for later
1212 $alias_for_table_ref = $identifier;
1213 } else {
1214 $chain[] = $identifier;
1215 $previous_was_identifier = TRUE;
1217 } // end if ($previous_was_identifier)
1218 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1219 } // end if (!$seen_from)
1220 } // end if (querytype SELECT)
1221 } // end if (quote_backtick or double quote or alpha_identifier)
1223 // ===================================
1224 if ($arr[$i]['type'] == 'punct_qualifier') {
1225 // to be able to detect an identifier following another
1226 $previous_was_identifier = FALSE;
1227 continue;
1228 } // end if (punct_qualifier)
1231 * @todo check if 3 identifiers following one another -> error
1234 // s a v e a s e l e c t e x p r
1235 // finding a list separator or FROM
1236 // means that we must save the current chain of identifiers
1237 // into a select expression
1239 // for now, we only save a select expression if it contains
1240 // at least one identifier, as we are interested in checking
1241 // the columns and table names, so in "select * from persons",
1242 // the "*" is not saved
1244 if (isset($chain) && !$seen_end_of_table_ref
1245 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1246 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1247 $size_chain = count($chain);
1248 $current_select_expr++;
1249 $subresult['select_expr'][$current_select_expr] = array(
1250 'expr' => '',
1251 'alias' => '',
1252 'db' => '',
1253 'table_name' => '',
1254 'table_true_name' => '',
1255 'column' => ''
1258 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1259 // we had found an alias for this select expression
1260 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1261 unset($alias_for_select_expr);
1263 // there is at least a column
1264 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1265 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1267 // maybe a table
1268 if ($size_chain > 1) {
1269 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1270 // we assume for now that this is also the true name
1271 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1272 $subresult['select_expr'][$current_select_expr]['expr']
1273 = $subresult['select_expr'][$current_select_expr]['table_name']
1274 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1275 } // end if ($size_chain > 1)
1277 // maybe a db
1278 if ($size_chain > 2) {
1279 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1280 $subresult['select_expr'][$current_select_expr]['expr']
1281 = $subresult['select_expr'][$current_select_expr]['db']
1282 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1283 } // end if ($size_chain > 2)
1284 unset($chain);
1287 * @todo explain this:
1289 if (($arr[$i]['type'] == 'alpha_reservedWord')
1290 && ($upper_data != 'FROM')) {
1291 $previous_was_identifier = TRUE;
1294 } // end if (save a select expr)
1297 //======================================
1298 // s a v e a t a b l e r e f
1299 //======================================
1301 // maybe we just saw the end of table refs
1302 // but the last table ref has to be saved
1303 // or we are at the last token
1304 // or we just got a reserved word
1306 * @todo there could be another query after this one
1309 if (isset($chain) && $seen_from && $save_table_ref
1310 && ($arr[$i]['type'] == 'punct_listsep'
1311 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1312 || $seen_end_of_table_ref
1313 || $i==$size-1)) {
1315 $size_chain = count($chain);
1316 $current_table_ref++;
1317 $subresult['table_ref'][$current_table_ref] = array(
1318 'expr' => '',
1319 'db' => '',
1320 'table_name' => '',
1321 'table_alias' => '',
1322 'table_true_name' => ''
1324 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1325 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1326 unset($alias_for_table_ref);
1328 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1329 // we assume for now that this is also the true name
1330 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1331 $subresult['table_ref'][$current_table_ref]['expr']
1332 = $subresult['table_ref'][$current_table_ref]['table_name'];
1333 // maybe a db
1334 if ($size_chain > 1) {
1335 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1336 $subresult['table_ref'][$current_table_ref]['expr']
1337 = $subresult['table_ref'][$current_table_ref]['db']
1338 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1339 } // end if ($size_chain > 1)
1341 // add the table alias into the whole expression
1342 $subresult['table_ref'][$current_table_ref]['expr']
1343 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1345 unset($chain);
1346 $previous_was_identifier = TRUE;
1347 //continue;
1349 } // end if (save a table ref)
1352 // when we have found all table refs,
1353 // for each table_ref alias, put the true name of the table
1354 // in the corresponding select expressions
1356 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1357 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1358 $alias = $subresult['table_ref'][$tr]['table_alias'];
1359 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1360 for ($se=0; $se <= $current_select_expr; $se++) {
1361 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1362 == $alias) {
1363 $subresult['select_expr'][$se]['table_true_name']
1364 = $truename;
1365 } // end if (found the alias)
1366 } // end for (select expressions)
1368 } // end for (table refs)
1369 } // end if (set the true names)
1372 // e n d i n g l o o p #1
1373 // set the $previous_was_identifier to FALSE if the current
1374 // token is not an identifier
1375 if (($arr[$i]['type'] != 'alpha_identifier')
1376 && ($arr[$i]['type'] != 'quote_double')
1377 && ($arr[$i]['type'] != 'quote_single')
1378 && ($arr[$i]['type'] != 'quote_backtick')) {
1379 $previous_was_identifier = FALSE;
1380 } // end if
1382 // however, if we are on AS, we must keep the $previous_was_identifier
1383 if (($arr[$i]['type'] == 'alpha_reservedWord')
1384 && ($upper_data == 'AS')) {
1385 $previous_was_identifier = TRUE;
1388 if (($arr[$i]['type'] == 'alpha_reservedWord')
1389 && ($upper_data =='ON' || $upper_data =='USING')) {
1390 $save_table_ref = FALSE;
1391 } // end if (data == ON)
1393 if (($arr[$i]['type'] == 'alpha_reservedWord')
1394 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1395 $save_table_ref = TRUE;
1396 } // end if (data == JOIN)
1399 * no need to check the end of table ref if we already did
1401 * @todo maybe add "&& $seen_from"
1403 if (!$seen_end_of_table_ref) {
1404 // if this is the last token, it implies that we have
1405 // seen the end of table references
1406 // Check for the end of table references
1408 // Note: if we are analyzing a GROUP_CONCAT clause,
1409 // we might find a word that seems to indicate that
1410 // we have found the end of table refs (like ORDER)
1411 // but it's a modifier of the GROUP_CONCAT so
1412 // it's not the real end of table refs
1413 if (($i == $size-1)
1414 || ($arr[$i]['type'] == 'alpha_reservedWord'
1415 && !$in_group_concat
1416 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1417 $seen_end_of_table_ref = TRUE;
1418 // to be able to save the last table ref, but do not
1419 // set it true if we found a word like "ON" that has
1420 // already set it to false
1421 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1422 $save_table_ref = TRUE;
1423 } //end if
1425 } // end if (check for end of table ref)
1426 } //end if (!$seen_end_of_table_ref)
1428 if ($seen_end_of_table_ref) {
1429 $save_table_ref = FALSE;
1430 } // end if
1432 } // end for $i (loop #1)
1434 //DEBUG
1436 if (isset($current_select_expr)) {
1437 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1438 echo "<br />";
1439 reset ($subresult['select_expr'][$trace]);
1440 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1441 echo "sel expr $trace $key => $val<br />\n";
1445 if (isset($current_table_ref)) {
1446 echo "current_table_ref = " . $current_table_ref . "<br>";
1447 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1449 echo "<br />";
1450 reset ($subresult['table_ref'][$trace]);
1451 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1452 echo "table ref $trace $key => $val<br />\n";
1456 // -------------------------------------------------------
1459 // loop #2: - queryflags
1460 // - querytype (for queries != 'SELECT')
1461 // - section_before_limit, section_after_limit
1463 // we will also need this queryflag in loop 2
1464 // so set it here
1465 if (isset($current_table_ref) && $current_table_ref > -1) {
1466 $subresult['queryflags']['select_from'] = 1;
1469 $section_before_limit = '';
1470 $section_after_limit = ''; // truly the section after the limit clause
1471 $seen_reserved_word = FALSE;
1472 $seen_group = FALSE;
1473 $seen_order = FALSE;
1474 $seen_order_by = FALSE;
1475 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1476 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1477 $in_having = FALSE; // true when we are inside the HAVING clause
1478 $in_select_expr = FALSE; // true when we are inside the select expr clause
1479 $in_where = FALSE; // true when we are inside the WHERE clause
1480 $seen_limit = FALSE; // true if we have seen a LIMIT clause
1481 $in_limit = FALSE; // true when we are inside the LIMIT clause
1482 $after_limit = FALSE; // true when we are after the LIMIT clause
1483 $in_from = FALSE; // true when we are in the FROM clause
1484 $in_group_concat = FALSE;
1485 $first_reserved_word = '';
1486 $current_identifier = '';
1487 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1488 $number_of_brackets = 0;
1489 $in_subquery = false;
1491 for ($i = 0; $i < $size; $i++) {
1492 //DEBUG echo "Loop2 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1494 // need_confirm
1496 // check for reserved words that will have to generate
1497 // a confirmation request later in sql.php
1498 // the cases are:
1499 // DROP TABLE
1500 // DROP DATABASE
1501 // ALTER TABLE... DROP
1502 // DELETE FROM...
1504 // this code is not used for confirmations coming from functions.js
1506 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1507 $number_of_brackets++;
1510 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1511 $number_of_brackets--;
1512 if ($number_of_brackets == 0) {
1513 $in_subquery = false;
1517 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1518 $upper_data = strtoupper($arr[$i]['data']);
1520 if ($upper_data == 'SELECT' && $number_of_brackets > 0) {
1521 $in_subquery = true;
1524 if (!$seen_reserved_word) {
1525 $first_reserved_word = $upper_data;
1526 $subresult['querytype'] = $upper_data;
1527 $seen_reserved_word = TRUE;
1529 // if the first reserved word is DROP or DELETE,
1530 // we know this is a query that needs to be confirmed
1531 if ($first_reserved_word=='DROP'
1532 || $first_reserved_word == 'DELETE'
1533 || $first_reserved_word == 'TRUNCATE') {
1534 $subresult['queryflags']['need_confirm'] = 1;
1537 if ($first_reserved_word=='SELECT'){
1538 $position_of_first_select = $i;
1541 } else {
1542 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1543 $subresult['queryflags']['need_confirm'] = 1;
1547 if ($upper_data == 'LIMIT' && ! $in_subquery) {
1548 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1549 $in_limit = TRUE;
1550 $seen_limit = TRUE;
1551 $limit_clause = '';
1552 $in_order_by = FALSE; // @todo maybe others to set FALSE
1555 if ($upper_data == 'PROCEDURE') {
1556 $subresult['queryflags']['procedure'] = 1;
1557 $in_limit = FALSE;
1558 $after_limit = TRUE;
1561 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1563 if ($upper_data == 'SELECT') {
1564 $in_select_expr = TRUE;
1565 $select_expr_clause = '';
1567 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1568 $subresult['queryflags']['distinct'] = 1;
1571 if ($upper_data == 'UNION') {
1572 $subresult['queryflags']['union'] = 1;
1575 if ($upper_data == 'JOIN') {
1576 $subresult['queryflags']['join'] = 1;
1579 if ($upper_data == 'OFFSET') {
1580 $subresult['queryflags']['offset'] = 1;
1583 // if this is a real SELECT...FROM
1584 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1585 $in_from = TRUE;
1586 $from_clause = '';
1587 $in_select_expr = FALSE;
1591 // (we could have less resetting of variables to FALSE
1592 // if we trust that the query respects the standard
1593 // MySQL order for clauses)
1595 // we use $seen_group and $seen_order because we are looking
1596 // for the BY
1597 if ($upper_data == 'GROUP') {
1598 $seen_group = TRUE;
1599 $seen_order = FALSE;
1600 $in_having = FALSE;
1601 $in_order_by = FALSE;
1602 $in_where = FALSE;
1603 $in_select_expr = FALSE;
1604 $in_from = FALSE;
1606 if ($upper_data == 'ORDER' && !$in_group_concat) {
1607 $seen_order = TRUE;
1608 $seen_group = FALSE;
1609 $in_having = FALSE;
1610 $in_group_by = FALSE;
1611 $in_where = FALSE;
1612 $in_select_expr = FALSE;
1613 $in_from = FALSE;
1615 if ($upper_data == 'HAVING') {
1616 $in_having = TRUE;
1617 $having_clause = '';
1618 $seen_group = FALSE;
1619 $seen_order = FALSE;
1620 $in_group_by = FALSE;
1621 $in_order_by = FALSE;
1622 $in_where = FALSE;
1623 $in_select_expr = FALSE;
1624 $in_from = FALSE;
1627 if ($upper_data == 'WHERE') {
1628 $in_where = TRUE;
1629 $where_clause = '';
1630 $where_clause_identifiers = array();
1631 $seen_group = FALSE;
1632 $seen_order = FALSE;
1633 $in_group_by = FALSE;
1634 $in_order_by = FALSE;
1635 $in_having = FALSE;
1636 $in_select_expr = FALSE;
1637 $in_from = FALSE;
1640 if ($upper_data == 'BY') {
1641 if ($seen_group) {
1642 $in_group_by = TRUE;
1643 $group_by_clause = '';
1645 if ($seen_order) {
1646 $seen_order_by = TRUE;
1647 // Here we assume that the ORDER BY keywords took
1648 // exactly 8 characters.
1649 // We use PMA_substr() to be charset-safe; otherwise
1650 // if the table name contains accents, the unsorted
1651 // query would be missing some characters.
1652 $unsorted_query = PMA_substr($arr['raw'], 0, $arr[$i]['pos'] - 8);
1653 $in_order_by = TRUE;
1654 $order_by_clause = '';
1658 // if we find one of the words that could end the clause
1659 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1661 $in_group_by = FALSE;
1662 $in_order_by = FALSE;
1663 $in_having = FALSE;
1664 $in_where = FALSE;
1665 $in_select_expr = FALSE;
1666 $in_from = FALSE;
1669 } // endif (reservedWord)
1672 // do not add a space after a function name
1674 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1677 $sep = ' ';
1678 if ($arr[$i]['type'] == 'alpha_functionName') {
1679 $sep='';
1680 $upper_data = strtoupper($arr[$i]['data']);
1681 if ($upper_data =='GROUP_CONCAT') {
1682 $in_group_concat = TRUE;
1683 $number_of_brackets_in_group_concat = 0;
1687 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1688 if ($in_group_concat) {
1689 $number_of_brackets_in_group_concat++;
1692 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1693 if ($in_group_concat) {
1694 $number_of_brackets_in_group_concat--;
1695 if ($number_of_brackets_in_group_concat == 0) {
1696 $in_group_concat = FALSE;
1701 // do not add a space after an identifier if followed by a dot
1702 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1703 $sep = '';
1706 // do not add a space after a dot if followed by an identifier
1707 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1708 $sep = '';
1711 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1712 $select_expr_clause .= $arr[$i]['data'] . $sep;
1714 if ($in_from && $upper_data != 'FROM') {
1715 $from_clause .= $arr[$i]['data'] . $sep;
1717 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1718 $group_by_clause .= $arr[$i]['data'] . $sep;
1720 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1721 // add a space only before ASC or DESC
1722 // not around the dot between dbname and tablename
1723 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1724 $order_by_clause .= $sep;
1726 $order_by_clause .= $arr[$i]['data'];
1728 if ($in_having && $upper_data != 'HAVING') {
1729 $having_clause .= $arr[$i]['data'] . $sep;
1731 if ($in_where && $upper_data != 'WHERE') {
1732 $where_clause .= $arr[$i]['data'] . $sep;
1734 if (($arr[$i]['type'] == 'quote_backtick')
1735 || ($arr[$i]['type'] == 'alpha_identifier')) {
1736 $where_clause_identifiers[] = $arr[$i]['data'];
1740 // to grab the rest of the query after the ORDER BY clause
1741 if (isset($subresult['queryflags']['select_from'])
1742 && $subresult['queryflags']['select_from'] == 1
1743 && ! $in_order_by
1744 && $seen_order_by
1745 && $upper_data != 'BY') {
1746 $unsorted_query .= $arr[$i]['data'];
1747 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1748 && $arr[$i]['type'] != 'punct_bracket_close_round'
1749 && $arr[$i]['type'] != 'punct') {
1750 $unsorted_query .= $sep;
1754 if ($in_limit) {
1755 if ($upper_data == 'OFFSET') {
1756 $limit_clause .= $sep;
1758 $limit_clause .= $arr[$i]['data'];
1759 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1760 $limit_clause .= $sep;
1763 if ($after_limit && $seen_limit) {
1764 $section_after_limit .= $arr[$i]['data'] . $sep;
1767 // clear $upper_data for next iteration
1768 $upper_data='';
1769 } // end for $i (loop #2)
1770 if (empty($section_before_limit)) {
1771 $section_before_limit = $arr['raw'];
1774 // -----------------------------------------------------
1775 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1776 // (for now, check only the first query)
1777 // (for now, identifiers are assumed to be backquoted)
1779 // If we find that we are dealing with a CREATE TABLE query,
1780 // we look for the next punct_bracket_open_round, which
1781 // introduces the fields list. Then, when we find a
1782 // quote_backtick, it must be a field, so we put it into
1783 // the create_table_fields array. Even if this field is
1784 // not a timestamp, it will be useful when logic has been
1785 // added for complete field attributes analysis.
1787 $seen_foreign = FALSE;
1788 $seen_references = FALSE;
1789 $seen_constraint = FALSE;
1790 $foreign_key_number = -1;
1791 $seen_create_table = FALSE;
1792 $seen_create = FALSE;
1793 $seen_alter = FALSE;
1794 $in_create_table_fields = FALSE;
1795 $brackets_level = 0;
1796 $in_timestamp_options = FALSE;
1797 $seen_default = FALSE;
1799 for ($i = 0; $i < $size; $i++) {
1800 // DEBUG echo "Loop 3 <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
1802 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1803 $upper_data = strtoupper($arr[$i]['data']);
1805 if ($upper_data == 'NOT' && $in_timestamp_options) {
1806 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1810 if ($upper_data == 'CREATE') {
1811 $seen_create = TRUE;
1814 if ($upper_data == 'ALTER') {
1815 $seen_alter = TRUE;
1818 if ($upper_data == 'TABLE' && $seen_create) {
1819 $seen_create_table = TRUE;
1820 $create_table_fields = array();
1823 if ($upper_data == 'CURRENT_TIMESTAMP') {
1824 if ($in_timestamp_options) {
1825 if ($seen_default) {
1826 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1831 if ($upper_data == 'CONSTRAINT') {
1832 $foreign_key_number++;
1833 $seen_foreign = FALSE;
1834 $seen_references = FALSE;
1835 $seen_constraint = TRUE;
1837 if ($upper_data == 'FOREIGN') {
1838 $seen_foreign = TRUE;
1839 $seen_references = FALSE;
1840 $seen_constraint = FALSE;
1842 if ($upper_data == 'REFERENCES') {
1843 $seen_foreign = FALSE;
1844 $seen_references = TRUE;
1845 $seen_constraint = FALSE;
1849 // Cases covered:
1851 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1852 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1854 // but we set ['on_delete'] or ['on_cascade'] to
1855 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1857 // ON UPDATE CURRENT_TIMESTAMP
1859 if ($upper_data == 'ON') {
1860 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1861 $second_upper_data = strtoupper($arr[$i+1]['data']);
1862 if ($second_upper_data == 'DELETE') {
1863 $clause = 'on_delete';
1865 if ($second_upper_data == 'UPDATE') {
1866 $clause = 'on_update';
1868 if (isset($clause)
1869 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1871 // ugly workaround because currently, NO is not
1872 // in the list of reserved words in sqlparser.data
1873 // (we got a bug report about not being able to use
1874 // 'no' as an identifier)
1875 || ($arr[$i+2]['type'] == 'alpha_identifier'
1876 && strtoupper($arr[$i+2]['data'])=='NO'))
1878 $third_upper_data = strtoupper($arr[$i+2]['data']);
1879 if ($third_upper_data == 'CASCADE'
1880 || $third_upper_data == 'RESTRICT') {
1881 $value = $third_upper_data;
1882 } elseif ($third_upper_data == 'SET'
1883 || $third_upper_data == 'NO') {
1884 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1885 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1887 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1888 if ($clause == 'on_update'
1889 && $in_timestamp_options) {
1890 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1891 $seen_default = FALSE;
1894 } else {
1895 $value = '';
1897 if (!empty($value)) {
1898 $foreign[$foreign_key_number][$clause] = $value;
1900 unset($clause);
1901 } // endif (isset($clause))
1905 } // end of reserved words analysis
1908 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1909 $brackets_level++;
1910 if ($seen_create_table && $brackets_level == 1) {
1911 $in_create_table_fields = TRUE;
1916 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1917 $brackets_level--;
1918 if ($seen_references) {
1919 $seen_references = FALSE;
1921 if ($seen_create_table && $brackets_level == 0) {
1922 $in_create_table_fields = FALSE;
1926 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1927 $upper_data = strtoupper($arr[$i]['data']);
1928 if ($seen_create_table && $in_create_table_fields) {
1929 if ($upper_data == 'DEFAULT') {
1930 $seen_default = TRUE;
1931 $create_table_fields[$current_identifier]['default_value'] = $arr[$i + 1]['data'];
1937 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1939 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1940 $upper_data = strtoupper($arr[$i]['data']);
1941 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1942 $create_table_fields[$current_identifier]['type'] = $upper_data;
1943 if ($upper_data == 'TIMESTAMP') {
1944 $arr[$i]['type'] = 'alpha_columnType';
1945 $in_timestamp_options = TRUE;
1946 } else {
1947 $in_timestamp_options = FALSE;
1948 if ($upper_data == 'CHAR') {
1949 $arr[$i]['type'] = 'alpha_columnType';
1956 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1958 if ($arr[$i]['type'] == 'quote_backtick') {
1959 // remove backquotes
1960 $identifier = PMA_unQuote($arr[$i]['data']);
1961 } else {
1962 $identifier = $arr[$i]['data'];
1965 if ($seen_create_table && $in_create_table_fields) {
1966 $current_identifier = $identifier;
1967 // warning: we set this one even for non TIMESTAMP type
1968 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1971 if ($seen_constraint) {
1972 $foreign[$foreign_key_number]['constraint'] = $identifier;
1975 if ($seen_foreign && $brackets_level > 0) {
1976 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1979 if ($seen_references) {
1980 if ($seen_alter && $brackets_level > 0) {
1981 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1982 // here, the first bracket level corresponds to the
1983 // bracket of CREATE TABLE
1984 // so if we are on level 2, it must be the index list
1985 // of the foreign key REFERENCES
1986 } elseif ($brackets_level > 1) {
1987 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1988 } elseif ($arr[$i+1]['type'] == 'punct_qualifier') {
1989 // identifier is `db`.`table`
1990 // the first pass will pick the db name
1991 // the next pass will pick the table name
1992 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1993 } else {
1994 // identifier is `table`
1995 $foreign[$foreign_key_number]['ref_table_name'] = $identifier;
1999 } // end for $i (loop #3)
2002 // Fill the $subresult array
2004 if (isset($create_table_fields)) {
2005 $subresult['create_table_fields'] = $create_table_fields;
2008 if (isset($foreign)) {
2009 $subresult['foreign_keys'] = $foreign;
2012 if (isset($select_expr_clause)) {
2013 $subresult['select_expr_clause'] = $select_expr_clause;
2015 if (isset($from_clause)) {
2016 $subresult['from_clause'] = $from_clause;
2018 if (isset($group_by_clause)) {
2019 $subresult['group_by_clause'] = $group_by_clause;
2021 if (isset($order_by_clause)) {
2022 $subresult['order_by_clause'] = $order_by_clause;
2024 if (isset($having_clause)) {
2025 $subresult['having_clause'] = $having_clause;
2027 if (isset($limit_clause)) {
2028 $subresult['limit_clause'] = $limit_clause;
2030 if (isset($where_clause)) {
2031 $subresult['where_clause'] = $where_clause;
2033 if (isset($unsorted_query) && !empty($unsorted_query)) {
2034 $subresult['unsorted_query'] = $unsorted_query;
2036 if (isset($where_clause_identifiers)) {
2037 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
2040 if (isset($position_of_first_select)) {
2041 $subresult['position_of_first_select'] = $position_of_first_select;
2042 $subresult['section_before_limit'] = $section_before_limit;
2043 $subresult['section_after_limit'] = $section_after_limit;
2046 // They are naughty and didn't have a trailing semi-colon,
2047 // then still handle it properly
2048 if ($subresult['querytype'] != '') {
2049 $result[] = $subresult;
2051 return $result;
2052 } // end of the "PMA_SQP_analyze()" function
2056 * Colorizes SQL queries html formatted
2058 * @todo check why adding a "\n" after the </span> would cause extra blanks
2059 * to be displayed: SELECT p . person_name
2060 * @param array The SQL queries html formatted
2062 * @return array The colorized SQL queries
2064 * @access public
2066 function PMA_SQP_formatHtml_colorize($arr)
2068 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
2069 $class = '';
2070 if ($i > 0) {
2071 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2074 $class .= 'syntax_' . $arr['type'];
2076 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2077 } // end of the "PMA_SQP_formatHtml_colorize()" function
2081 * Formats SQL queries to html
2083 * @param array The SQL queries
2084 * @param string mode
2085 * @param integer starting token
2086 * @param integer number of tokens to format, -1 = all
2088 * @return string The formatted SQL queries
2090 * @access public
2092 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2093 $number_of_tokens=-1)
2095 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2096 // then check for an array
2097 if (!is_array($arr)) {
2098 return htmlspecialchars($arr);
2100 // first check for the SQL parser having hit an error
2101 if (PMA_SQP_isError()) {
2102 return htmlspecialchars($arr['raw']);
2104 // else do it properly
2105 switch ($mode) {
2106 case 'color':
2107 $str = '<span class="syntax">';
2108 $html_line_break = '<br />';
2109 $docu = TRUE;
2110 break;
2111 case 'query_only':
2112 $str = '';
2113 $html_line_break = "\n";
2114 $docu = FALSE;
2115 break;
2116 case 'text':
2117 $str = '';
2118 $html_line_break = '<br />';
2119 $docu = TRUE;
2120 break;
2121 } // end switch
2122 // inner_sql is a span that exists for all cases, except query_only
2123 // of $cfg['SQP']['fmtType'] to make possible a replacement
2124 // for inline editing
2125 if ($mode!='query_only') {
2126 $str .= '<span class="inner_sql">';
2128 $close_docu_link = false;
2129 $indent = 0;
2130 $bracketlevel = 0;
2131 $functionlevel = 0;
2132 $infunction = FALSE;
2133 $space_punct_listsep = ' ';
2134 $space_punct_listsep_function_name = ' ';
2135 // $space_alpha_reserved_word = '<br />'."\n";
2136 $space_alpha_reserved_word = ' ';
2138 $keywords_with_brackets_1before = array(
2139 'INDEX',
2140 'KEY',
2141 'ON',
2142 'USING'
2144 $keywords_with_brackets_1before_cnt = 4;
2146 $keywords_with_brackets_2before = array(
2147 'IGNORE',
2148 'INDEX',
2149 'INTO',
2150 'KEY',
2151 'PRIMARY',
2152 'PROCEDURE',
2153 'REFERENCES',
2154 'UNIQUE',
2155 'USE'
2157 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2158 $keywords_with_brackets_2before_cnt = 9;
2160 // These reserved words do NOT get a newline placed near them.
2161 $keywords_no_newline = array(
2162 'AS',
2163 'ASC',
2164 'DESC',
2165 'DISTINCT',
2166 'DUPLICATE',
2167 'HOUR',
2168 'INTERVAL',
2169 'IS',
2170 'LIKE',
2171 'NOT',
2172 'NULL',
2173 'ON',
2174 'REGEXP'
2176 $keywords_no_newline_cnt = 12;
2178 // These reserved words introduce a privilege list
2179 $keywords_priv_list = array(
2180 'GRANT',
2181 'REVOKE'
2183 $keywords_priv_list_cnt = 2;
2185 if ($number_of_tokens == -1) {
2186 $arraysize = $arr['len'];
2187 } else {
2188 $arraysize = $number_of_tokens;
2190 $typearr = array();
2191 if ($arraysize >= 0) {
2192 $typearr[0] = '';
2193 $typearr[1] = '';
2194 $typearr[2] = '';
2195 //$typearr[3] = $arr[0]['type'];
2196 $typearr[3] = $arr[$start_token]['type'];
2199 $in_priv_list = FALSE;
2200 for ($i = $start_token; $i < $arraysize; $i++) {
2201 // DEBUG echo "Loop format <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
2202 $before = '';
2203 $after = '';
2204 $indent = 0;
2205 // array_shift($typearr);
2207 0 prev2
2208 1 prev
2209 2 current
2210 3 next
2212 if (($i + 1) < $arraysize) {
2213 // array_push($typearr, $arr[$i + 1]['type']);
2214 $typearr[4] = $arr[$i + 1]['type'];
2215 } else {
2216 //array_push($typearr, null);
2217 $typearr[4] = '';
2220 for ($j=0; $j<4; $j++) {
2221 $typearr[$j] = $typearr[$j + 1];
2224 switch ($typearr[2]) {
2225 case 'alpha_bitfield_constant_introducer':
2226 $before = ' ';
2227 $after = '';
2228 break;
2229 case 'white_newline':
2230 $before = '';
2231 break;
2232 case 'punct_bracket_open_round':
2233 $bracketlevel++;
2234 $infunction = FALSE;
2235 // Make sure this array is sorted!
2236 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2237 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2238 || (($typearr[0] == 'alpha_reservedWord')
2239 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2240 || (($typearr[1] == 'alpha_reservedWord')
2241 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2243 $functionlevel++;
2244 $infunction = TRUE;
2245 $after .= ' ';
2246 } else {
2247 $indent++;
2248 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2250 break;
2251 case 'alpha_identifier':
2252 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2253 $after = '';
2254 $before = '';
2256 // for example SELECT 1 somealias
2257 if ($typearr[1] == 'digit_integer') {
2258 $before = ' ';
2260 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2261 $after .= ' ';
2263 break;
2264 case 'punct_user':
2265 case 'punct_qualifier':
2266 $before = '';
2267 $after = '';
2268 break;
2269 case 'punct_listsep':
2270 if ($infunction == TRUE) {
2271 $after .= $space_punct_listsep_function_name;
2272 } else {
2273 $after .= $space_punct_listsep;
2275 break;
2276 case 'punct_queryend':
2277 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2278 $after .= $html_line_break;
2279 $after .= $html_line_break;
2281 $space_punct_listsep = ' ';
2282 $space_punct_listsep_function_name = ' ';
2283 $space_alpha_reserved_word = ' ';
2284 $in_priv_list = FALSE;
2285 break;
2286 case 'comment_mysql':
2287 case 'comment_ansi':
2288 $after .= $html_line_break;
2289 break;
2290 case 'punct':
2291 $before .= ' ';
2292 // workaround for
2293 // select * from mytable limit 0,-1
2294 // (a side effect of this workaround is that
2295 // select 20 - 9
2296 // becomes
2297 // select 20 -9
2298 // )
2299 if ($typearr[3] != 'digit_integer') {
2300 $after .= ' ';
2302 break;
2303 case 'punct_bracket_close_round':
2304 $bracketlevel--;
2305 if ($infunction == TRUE) {
2306 $functionlevel--;
2307 $after .= ' ';
2308 $before .= ' ';
2309 } else {
2310 $indent--;
2311 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2313 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2314 break;
2315 case 'alpha_columnType':
2316 if ($docu) {
2317 switch ($arr[$i]['data']) {
2318 case 'tinyint':
2319 case 'smallint':
2320 case 'mediumint':
2321 case 'int':
2322 case 'bigint':
2323 case 'decimal':
2324 case 'float':
2325 case 'double':
2326 case 'real':
2327 case 'bit':
2328 case 'boolean':
2329 case 'serial':
2330 $before .= PMA_showMySQLDocu('data-types', 'numeric-types', false, '', true);
2331 $after = '</a>' . $after;
2332 break;
2333 case 'date':
2334 case 'datetime':
2335 case 'timestamp':
2336 case 'time':
2337 case 'year':
2338 $before .= PMA_showMySQLDocu('data-types', 'date-and-time-types', false, '', true);
2339 $after = '</a>' . $after;
2340 break;
2341 case 'char':
2342 case 'varchar':
2343 case 'tinytext':
2344 case 'text':
2345 case 'mediumtext':
2346 case 'longtext':
2347 case 'binary':
2348 case 'varbinary':
2349 case 'tinyblob':
2350 case 'mediumblob':
2351 case 'blob':
2352 case 'longblob':
2353 case 'enum':
2354 case 'set':
2355 $before .= PMA_showMySQLDocu('data-types', 'string-types', false, '', true);
2356 $after = '</a>' . $after;
2357 break;
2360 if ($typearr[3] == 'alpha_columnAttrib') {
2361 $after .= ' ';
2363 if ($typearr[1] == 'alpha_columnType') {
2364 $before .= ' ';
2366 break;
2367 case 'alpha_columnAttrib':
2369 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2370 // COLLATE LATIN1_GENERAL_CI DEFAULT
2371 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2372 $before .= ' ';
2374 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2375 $after .= ' ';
2377 // workaround for
2378 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2380 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2381 $before .= ' ';
2383 // workaround for
2384 // select * from mysql.user where binary user="root"
2385 // binary is marked as alpha_columnAttrib
2386 // but should be marked as a reserved word
2387 if (strtoupper($arr[$i]['data']) == 'BINARY'
2388 && $typearr[3] == 'alpha_identifier') {
2389 $after .= ' ';
2391 break;
2392 case 'alpha_reservedWord':
2393 // do not uppercase the reserved word if we are calling
2394 // this function in query_only mode, because we need
2395 // the original query (otherwise we get problems with
2396 // semi-reserved words like "storage" which is legal
2397 // as an identifier name)
2399 if ($mode != 'query_only') {
2400 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2403 if ((($typearr[1] != 'alpha_reservedWord')
2404 || (($typearr[1] == 'alpha_reservedWord')
2405 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2406 && ($typearr[1] != 'punct_level_plus')
2407 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2408 // do not put a space before the first token, because
2409 // we use a lot of pattern matching checking for the
2410 // first reserved word at beginning of query
2411 // so do not put a newline before
2413 // also we must not be inside a privilege list
2414 if ($i > 0) {
2415 // the alpha_identifier exception is there to
2416 // catch cases like
2417 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2418 // (else, we get mydb.mytableTO)
2420 // the quote_single exception is there to
2421 // catch cases like
2422 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2424 * @todo fix all cases and find why this happens
2427 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2428 $before .= $space_alpha_reserved_word;
2430 } else {
2431 // on first keyword, check if it introduces a
2432 // privilege list
2433 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2434 $in_priv_list = TRUE;
2437 } else {
2438 $before .= ' ';
2441 switch ($arr[$i]['data']) {
2442 case 'CREATE':
2443 case 'ALTER':
2444 case 'DROP':
2445 case 'RENAME';
2446 case 'TRUNCATE':
2447 case 'ANALYZE':
2448 case 'ANALYSE':
2449 case 'OPTIMIZE':
2450 if ($docu) {
2451 switch ($arr[$i + 1]['data']) {
2452 case 'EVENT':
2453 case 'TABLE':
2454 case 'TABLESPACE':
2455 case 'FUNCTION':
2456 case 'INDEX':
2457 case 'PROCEDURE':
2458 case 'TRIGGER':
2459 case 'SERVER':
2460 case 'DATABASE':
2461 case 'VIEW':
2462 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_' . $arr[$i + 1]['data'], false, '', true);
2463 $close_docu_link = true;
2464 break;
2466 if ($arr[$i + 1]['data'] == 'LOGFILE' && $arr[$i + 2]['data'] == 'GROUP') {
2467 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_LOGFILE_GROUP', false, '', true);
2468 $close_docu_link = true;
2471 if (!$in_priv_list) {
2472 $space_punct_listsep = $html_line_break;
2473 $space_alpha_reserved_word = ' ';
2475 break;
2476 case 'EVENT':
2477 case 'TABLESPACE':
2478 case 'TABLE':
2479 case 'FUNCTION':
2480 case 'INDEX':
2481 case 'PROCEDURE':
2482 case 'SERVER':
2483 case 'TRIGGER':
2484 case 'DATABASE':
2485 case 'VIEW':
2486 case 'GROUP':
2487 if ($close_docu_link) {
2488 $after = '</a>' . $after;
2489 $close_docu_link = false;
2491 break;
2492 case 'SET':
2493 if ($docu && ($i == 0 || $arr[$i - 1]['data'] != 'CHARACTER')) {
2494 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2495 $after = '</a>' . $after;
2497 if (!$in_priv_list) {
2498 $space_punct_listsep = $html_line_break;
2499 $space_alpha_reserved_word = ' ';
2501 break;
2502 case 'EXPLAIN':
2503 case 'DESCRIBE':
2504 case 'DELETE':
2505 case 'SHOW':
2506 case 'UPDATE':
2507 if ($docu) {
2508 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2509 $after = '</a>' . $after;
2511 if (!$in_priv_list) {
2512 $space_punct_listsep = $html_line_break;
2513 $space_alpha_reserved_word = ' ';
2515 break;
2516 case 'INSERT':
2517 case 'REPLACE':
2518 if ($docu) {
2519 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2520 $after = '</a>' . $after;
2522 if (!$in_priv_list) {
2523 $space_punct_listsep = $html_line_break;
2524 $space_alpha_reserved_word = $html_line_break;
2526 break;
2527 case 'VALUES':
2528 $space_punct_listsep = ' ';
2529 $space_alpha_reserved_word = $html_line_break;
2530 break;
2531 case 'SELECT':
2532 if ($docu) {
2533 $before .= PMA_showMySQLDocu('SQL-Syntax', 'SELECT', false, '', true);
2534 $after = '</a>' . $after;
2536 $space_punct_listsep = ' ';
2537 $space_alpha_reserved_word = $html_line_break;
2538 break;
2539 case 'CALL':
2540 case 'DO':
2541 case 'HANDLER':
2542 if ($docu) {
2543 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2544 $after = '</a>' . $after;
2546 break;
2547 default:
2548 break;
2549 } // end switch ($arr[$i]['data'])
2551 $after .= ' ';
2552 break;
2553 case 'digit_integer':
2554 case 'digit_float':
2555 case 'digit_hex':
2557 * @todo could there be other types preceding a digit?
2559 if ($typearr[1] == 'alpha_reservedWord') {
2560 $after .= ' ';
2562 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2563 $after .= ' ';
2565 if ($typearr[1] == 'alpha_columnAttrib') {
2566 $before .= ' ';
2568 break;
2569 case 'alpha_variable':
2570 $after = ' ';
2571 break;
2572 case 'quote_double':
2573 case 'quote_single':
2574 // workaround: for the query
2575 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2576 // the @ is incorrectly marked as alpha_variable
2577 // in the parser, and here, the '%' gets a blank before,
2578 // which is a syntax error
2579 if ($typearr[1] != 'punct_user' && $typearr[1] != 'alpha_bitfield_constant_introducer') {
2580 $before .= ' ';
2582 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2583 $after .= ' ';
2585 break;
2586 case 'quote_backtick':
2587 // here we check for punct_user to handle correctly
2588 // DEFINER = `username`@`%`
2589 // where @ is the punct_user and `%` is the quote_backtick
2590 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2591 $after .= ' ';
2593 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2594 $before .= ' ';
2596 break;
2597 default:
2598 break;
2599 } // end switch ($typearr[2])
2602 if ($typearr[3] != 'punct_qualifier') {
2603 $after .= ' ';
2605 $after .= "\n";
2607 $str .= $before;
2608 if ($mode=='color') {
2609 $str .= PMA_SQP_formatHTML_colorize($arr[$i]);
2610 } elseif ($mode == 'text') {
2611 $str .= htmlspecialchars($arr[$i]['data']);
2612 } else {
2613 $str .= $arr[$i]['data'];
2615 $str .= $after;
2616 } // end for
2617 /* End possibly unclosed documentation link */
2618 if ($close_docu_link) {
2619 $str .= '</a>';
2620 $close_docu_link = false;
2622 if ($mode!='query_only') {
2623 // close inner_sql span
2624 $str .= '</span>';
2626 if ($mode=='color') {
2627 // close syntax span
2628 $str .= '</span>';
2631 return $str;
2632 } // end of the "PMA_SQP_formatHtml()" function
2636 * Builds a CSS rule used for html formatted SQL queries
2638 * @param string The class name
2639 * @param string The property name
2640 * @param string The property value
2642 * @return string The CSS rule
2644 * @access public
2646 * @see PMA_SQP_buildCssData()
2648 function PMA_SQP_buildCssRule($classname, $property, $value)
2650 $str = '.' . $classname . ' {';
2651 if ($value != '') {
2652 $str .= $property . ': ' . $value . ';';
2654 $str .= '}' . "\n";
2656 return $str;
2657 } // end of the "PMA_SQP_buildCssRule()" function
2661 * Builds CSS rules used for html formatted SQL queries
2663 * @return string The CSS rules set
2665 * @access public
2667 * @global array The current PMA configuration
2669 * @see PMA_SQP_buildCssRule()
2671 function PMA_SQP_buildCssData()
2673 global $cfg;
2675 $css_string = '';
2676 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2677 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2680 for ($i = 0; $i < 8; $i++) {
2681 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2684 return $css_string;
2685 } // end of the "PMA_SQP_buildCssData()" function
2687 if (! defined('PMA_MINIMUM_COMMON')) {
2689 * Gets SQL queries with no format
2691 * @param array The SQL queries list
2693 * @return string The SQL queries with no format
2695 * @access public
2697 function PMA_SQP_formatNone($arr)
2699 $formatted_sql = htmlspecialchars($arr['raw']);
2700 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2702 return $formatted_sql;
2703 } // end of the "PMA_SQP_formatNone()" function
2705 } // end if: minimal common.lib needed?