User_Schema class : removed tabs + commented code + organized structure, Delete listi...
[phpmyadmin-themes.git] / libraries / sqlparser.lib.php
blobbb103aa5084eceb99b4c15972c76939b10d0ae10
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 * @version $Id$
28 * @package phpMyAdmin
30 if (! defined('PHPMYADMIN')) {
31 exit;
34 /**
35 * Minimum inclusion? (i.e. for the stylesheet builder)
37 if (! defined('PMA_MINIMUM_COMMON')) {
38 /**
39 * Include the string library as we use it heavily
41 require_once './libraries/string.lib.php';
43 /**
44 * Include data for the SQL Parser
46 require_once './libraries/sqlparser.data.php';
47 require_once './libraries/mysql_charsets.lib.php';
48 if (!isset($mysql_charsets)) {
49 $mysql_charsets = array();
50 $mysql_charsets_count = 0;
51 $mysql_collations_flat = array();
52 $mysql_collations_count = 0;
55 if (!defined('DEBUG_TIMING')) {
56 /**
57 * currently we don't need the $pos (token position in query)
58 * for other purposes than LIMIT clause verification,
59 * so many calls to this function do not include the 4th parameter
61 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
63 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos);
64 $arrsize++;
65 } // end of the "PMA_SQP_arrayAdd()" function
66 } else {
67 /**
68 * This is debug variant of above.
69 * @ignore
71 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize, $pos = 0)
73 global $timer;
75 $t = $timer;
76 $arr[] = array('type' => $type, 'data' => $data, 'pos' => $pos, 'time' => $t);
77 $timer = microtime();
78 $arrsize++;
79 } // end of the "PMA_SQP_arrayAdd()" function
80 } // end if... else...
83 /**
84 * Reset the error variable for the SQL parser
86 * @access public
88 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
89 function PMA_SQP_resetError()
91 global $SQP_errorString;
92 $SQP_errorString = '';
93 unset($SQP_errorString);
96 /**
97 * Get the contents of the error variable for the SQL parser
99 * @return string Error string from SQL parser
101 * @access public
103 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
104 function PMA_SQP_getErrorString()
106 global $SQP_errorString;
107 return isset($SQP_errorString) ? $SQP_errorString : '';
111 * Check if the SQL parser hit an error
113 * @return boolean error state
115 * @access public
117 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
118 function PMA_SQP_isError()
120 global $SQP_errorString;
121 return isset($SQP_errorString) && !empty($SQP_errorString);
125 * Set an error message for the system
127 * @param string The error message
128 * @param string The failing SQL query
130 * @access private
131 * @scope SQL Parser internal
133 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
134 function PMA_SQP_throwError($message, $sql)
136 global $SQP_errorString;
137 $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"
138 . '<pre>' . "\n"
139 . 'ERROR: ' . $message . "\n"
140 . 'SQL: ' . htmlspecialchars($sql) . "\n"
141 . '</pre>' . "\n";
143 } // end of the "PMA_SQP_throwError()" function
147 * Do display the bug report
149 * @param string The error message
150 * @param string The failing SQL query
152 * @access public
154 function PMA_SQP_bug($message, $sql)
156 global $SQP_errorString;
157 $debugstr = 'ERROR: ' . $message . "\n";
158 $debugstr .= 'SVN: $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: ' . 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 $cfg;
214 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
215 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
216 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
217 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
219 // Convert all line feeds to Unix style
220 $sql = str_replace("\r\n", "\n", $sql);
221 $sql = str_replace("\r", "\n", $sql);
223 $len = PMA_strlen($sql);
224 if ($len == 0) {
225 return array();
228 $sql_array = array();
229 $sql_array['raw'] = $sql;
230 $count1 = 0;
231 $count2 = 0;
232 $punct_queryend = ';';
233 $punct_qualifier = '.';
234 $punct_listsep = ',';
235 $punct_level_plus = '(';
236 $punct_level_minus = ')';
237 $punct_user = '@';
238 $digit_floatdecimal = '.';
239 $digit_hexset = 'x';
240 $bracket_list = '()[]{}';
241 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
242 $allpunct_list_pair = array (
243 0 => '!=',
244 1 => '&&',
245 2 => ':=',
246 3 => '<<',
247 4 => '<=',
248 5 => '<=>',
249 6 => '<>',
250 7 => '>=',
251 8 => '>>',
252 9 => '||'
254 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
255 $quote_list = '\'"`';
256 $arraysize = 0;
258 $previous_was_space = false;
259 $this_was_space = false;
260 $previous_was_bracket = false;
261 $this_was_bracket = false;
262 $previous_was_punct = false;
263 $this_was_punct = false;
264 $previous_was_listsep = false;
265 $this_was_listsep = false;
266 $previous_was_quote = false;
267 $this_was_quote = false;
269 while ($count2 < $len) {
270 $c = $GLOBALS['PMA_substr']($sql, $count2, 1);
271 $count1 = $count2;
273 $previous_was_space = $this_was_space;
274 $this_was_space = false;
275 $previous_was_bracket = $this_was_bracket;
276 $this_was_bracket = false;
277 $previous_was_punct = $this_was_punct;
278 $this_was_punct = false;
279 $previous_was_listsep = $this_was_listsep;
280 $this_was_listsep = false;
281 $previous_was_quote = $this_was_quote;
282 $this_was_quote = false;
284 if (($c == "\n")) {
285 $this_was_space = true;
286 $count2++;
287 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
288 continue;
291 // Checks for white space
292 if ($GLOBALS['PMA_STR_isSpace']($c)) {
293 $this_was_space = true;
294 $count2++;
295 continue;
298 // Checks for comment lines.
299 // MySQL style #
300 // C style /* */
301 // ANSI style --
302 if (($c == '#')
303 || (($count2 + 1 < $len) && ($c == '/') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '*'))
304 || (($count2 + 2 == $len) && ($c == '-') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '-'))
305 || (($count2 + 2 < $len) && ($c == '-') && ($GLOBALS['PMA_substr']($sql, $count2 + 1, 1) == '-') && (($GLOBALS['PMA_substr']($sql, $count2 + 2, 1) <= ' ')))) {
306 $count2++;
307 $pos = 0;
308 $type = 'bad';
309 switch ($c) {
310 case '#':
311 $type = 'mysql';
312 case '-':
313 $type = 'ansi';
314 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
315 break;
316 case '/':
317 $type = 'c';
318 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
319 $pos += 2;
320 break;
321 default:
322 break;
323 } // end switch
324 $count2 = ($pos < $count2) ? $len : $pos;
325 $str = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
326 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
327 continue;
328 } // end if
330 // Checks for something inside quotation marks
331 if ($GLOBALS['PMA_strpos']($quote_list, $c) !== false) {
332 $startquotepos = $count2;
333 $quotetype = $c;
334 $count2++;
335 $escaped = FALSE;
336 $escaped_escaped = FALSE;
337 $pos = $count2;
338 $oldpos = 0;
339 do {
340 $oldpos = $pos;
341 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
342 // ($pos === FALSE)
343 if ($pos < 0) {
344 $debugstr = __('Unclosed quote') . ' @ ' . $startquotepos. "\n"
345 . 'STR: ' . htmlspecialchars($quotetype);
346 PMA_SQP_throwError($debugstr, $sql);
347 return $sql_array;
350 // If the quote is the first character, it can't be
351 // escaped, so don't do the rest of the code
352 if ($pos == 0) {
353 break;
356 // Checks for MySQL escaping using a \
357 // And checks for ANSI escaping using the $quotetype character
358 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
359 $pos ++;
360 continue;
361 } elseif (($pos + 1 < $len) && ($GLOBALS['PMA_substr']($sql, $pos, 1) == $quotetype) && ($GLOBALS['PMA_substr']($sql, $pos + 1, 1) == $quotetype)) {
362 $pos = $pos + 2;
363 continue;
364 } else {
365 break;
367 } while ($len > $pos); // end do
369 $count2 = $pos;
370 $count2++;
371 $type = 'quote_';
372 switch ($quotetype) {
373 case '\'':
374 $type .= 'single';
375 $this_was_quote = true;
376 break;
377 case '"':
378 $type .= 'double';
379 $this_was_quote = true;
380 break;
381 case '`':
382 $type .= 'backtick';
383 $this_was_quote = true;
384 break;
385 default:
386 break;
387 } // end switch
388 $data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
389 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
390 continue;
393 // Checks for brackets
394 if ($GLOBALS['PMA_strpos']($bracket_list, $c) !== false) {
395 // All bracket tokens are only one item long
396 $this_was_bracket = true;
397 $count2++;
398 $type_type = '';
399 if ($GLOBALS['PMA_strpos']('([{', $c) !== false) {
400 $type_type = 'open';
401 } else {
402 $type_type = 'close';
405 $type_style = '';
406 if ($GLOBALS['PMA_strpos']('()', $c) !== false) {
407 $type_style = 'round';
408 } elseif ($GLOBALS['PMA_strpos']('[]', $c) !== false) {
409 $type_style = 'square';
410 } else {
411 $type_style = 'curly';
414 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
415 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
416 continue;
419 /* DEBUG
420 echo '<pre>1';
421 var_dump(PMA_STR_isSqlIdentifier($c, false));
422 var_dump($c == '@');
423 var_dump($c == '.');
424 var_dump(PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)));
425 var_dump($previous_was_space);
426 var_dump($previous_was_bracket);
427 var_dump($previous_was_listsep);
428 echo '</pre>';
431 // Checks for identifier (alpha or numeric)
432 if (PMA_STR_isSqlIdentifier($c, false)
433 || $c == '@'
434 || ($c == '.'
435 && $GLOBALS['PMA_STR_isDigit']($GLOBALS['PMA_substr']($sql, $count2 + 1, 1))
436 && ($previous_was_space || $previous_was_bracket || $previous_was_listsep))) {
438 /* DEBUG
439 echo PMA_substr($sql, $count2);
440 echo '<hr />';
443 $count2++;
446 * @todo a @ can also be present in expressions like
447 * FROM 'user'@'%' or TO 'user'@'%'
448 * in this case, the @ is wrongly marked as alpha_variable
450 $is_identifier = $previous_was_punct;
451 $is_sql_variable = $c == '@' && ! $previous_was_quote;
452 $is_user = $c == '@' && $previous_was_quote;
453 $is_digit = !$is_identifier && !$is_sql_variable && $GLOBALS['PMA_STR_isDigit']($c);
454 $is_hex_digit = $is_digit && $c == '0' && $count2 < $len && $GLOBALS['PMA_substr']($sql, $count2, 1) == 'x';
455 $is_float_digit = $c == '.';
456 $is_float_digit_exponent = FALSE;
458 /* DEBUG
459 echo '<pre>2';
460 var_dump($is_identifier);
461 var_dump($is_sql_variable);
462 var_dump($is_digit);
463 var_dump($is_float_digit);
464 echo '</pre>';
467 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
468 if ($is_hex_digit) {
469 $count2++;
470 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
471 if ($pos > $count2) {
472 $count2 = $pos;
474 unset($pos);
475 } elseif ($is_digit) {
476 $pos = strspn($sql, '0123456789', $count2);
477 if ($pos > $count2) {
478 $count2 = $pos;
480 unset($pos);
483 while (($count2 < $len) && PMA_STR_isSqlIdentifier($GLOBALS['PMA_substr']($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
484 $c2 = $GLOBALS['PMA_substr']($sql, $count2, 1);
485 if ($is_sql_variable && ($c2 == '.')) {
486 $count2++;
487 continue;
489 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
490 $count2++;
491 if (!$is_float_digit) {
492 $is_float_digit = TRUE;
493 continue;
494 } else {
495 $debugstr = __('Invalid Identifer') . ' @ ' . ($count1+1) . "\n"
496 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
497 PMA_SQP_throwError($debugstr, $sql);
498 return $sql_array;
501 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
502 if (!$is_float_digit_exponent) {
503 $is_float_digit_exponent = TRUE;
504 $is_float_digit = TRUE;
505 $count2++;
506 continue;
507 } else {
508 $is_digit = FALSE;
509 $is_float_digit = FALSE;
512 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && $GLOBALS['PMA_STR_isDigit']($c2))) {
513 $count2++;
514 continue;
515 } else {
516 $is_digit = FALSE;
517 $is_hex_digit = FALSE;
520 $count2++;
521 } // end while
523 $l = $count2 - $count1;
524 $str = $GLOBALS['PMA_substr']($sql, $count1, $l);
526 $type = '';
527 if ($is_digit || $is_float_digit || $is_hex_digit) {
528 $type = 'digit';
529 if ($is_float_digit) {
530 $type .= '_float';
531 } elseif ($is_hex_digit) {
532 $type .= '_hex';
533 } else {
534 $type .= '_integer';
536 } elseif ($is_user) {
537 $type = 'punct_user';
538 } elseif ($is_sql_variable != FALSE) {
539 $type = 'alpha_variable';
540 } else {
541 $type = 'alpha';
542 } // end if... else....
543 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize, $count2);
545 continue;
548 // Checks for punct
549 if ($GLOBALS['PMA_strpos']($allpunct_list, $c) !== false) {
550 while (($count2 < $len) && $GLOBALS['PMA_strpos']($allpunct_list, $GLOBALS['PMA_substr']($sql, $count2, 1)) !== false) {
551 $count2++;
553 $l = $count2 - $count1;
554 if ($l == 1) {
555 $punct_data = $c;
556 } else {
557 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $l);
560 // Special case, sometimes, althought two characters are
561 // adjectent directly, they ACTUALLY need to be seperate
562 /* DEBUG
563 echo '<pre>';
564 var_dump($l);
565 var_dump($punct_data);
566 echo '</pre>';
569 if ($l == 1) {
570 $t_suffix = '';
571 switch ($punct_data) {
572 case $punct_queryend:
573 $t_suffix = '_queryend';
574 break;
575 case $punct_qualifier:
576 $t_suffix = '_qualifier';
577 $this_was_punct = true;
578 break;
579 case $punct_listsep:
580 $this_was_listsep = true;
581 $t_suffix = '_listsep';
582 break;
583 default:
584 break;
586 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
587 } elseif ($punct_data == $GLOBALS['sql_delimiter'] || PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
588 // Ok, we have one of the valid combined punct expressions
589 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
590 } else {
591 // Bad luck, lets split it up more
592 $first = $punct_data[0];
593 $first2 = $punct_data[0] . $punct_data[1];
594 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
595 $last = $punct_data[$l - 1];
596 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
597 $count2 = $count1 + 1;
598 $punct_data = $first;
599 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || $GLOBALS['PMA_substr']($sql, $count2, 1) <= ' '))) {
600 $count2 -= 2;
601 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
602 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
603 $count2--;
604 $punct_data = $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1);
606 * @todo for negation operator, split in 2 tokens ?
607 * "select x&~1 from t"
608 * becomes "select x & ~ 1 from t" ?
611 } elseif ($last != '~') {
612 $debugstr = __('Unknown Punctuation String') . ' @ ' . ($count1+1) . "\n"
613 . 'STR: ' . htmlspecialchars($punct_data);
614 PMA_SQP_throwError($debugstr, $sql);
615 return $sql_array;
617 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
618 continue;
619 } // end if... elseif... else
620 continue;
623 // DEBUG
624 $count2++;
626 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
627 . 'STR: ' . $GLOBALS['PMA_substr']($sql, $count1, $count2 - $count1) . "\n";
628 PMA_SQP_bug($debugstr, $sql);
629 return $sql_array;
631 } // end while ($count2 < $len)
634 echo '<pre>';
635 print_r($sql_array);
636 echo '</pre>';
639 if ($arraysize > 0) {
640 $t_next = $sql_array[0]['type'];
641 $t_prev = '';
642 $t_bef_prev = '';
643 $t_cur = '';
644 $d_next = $sql_array[0]['data'];
645 $d_prev = '';
646 $d_bef_prev = '';
647 $d_cur = '';
648 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
649 $d_prev_upper = '';
650 $d_bef_prev_upper = '';
651 $d_cur_upper = '';
654 for ($i = 0; $i < $arraysize; $i++) {
655 $t_bef_prev = $t_prev;
656 $t_prev = $t_cur;
657 $t_cur = $t_next;
658 $d_bef_prev = $d_prev;
659 $d_prev = $d_cur;
660 $d_cur = $d_next;
661 $d_bef_prev_upper = $d_prev_upper;
662 $d_prev_upper = $d_cur_upper;
663 $d_cur_upper = $d_next_upper;
664 if (($i + 1) < $arraysize) {
665 $t_next = $sql_array[$i + 1]['type'];
666 $d_next = $sql_array[$i + 1]['data'];
667 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
668 } else {
669 $t_next = '';
670 $d_next = '';
671 $d_next_upper = '';
674 //DEBUG echo "[prev: <strong>".$d_prev."</strong> ".$t_prev."][cur: <strong>".$d_cur."</strong> ".$t_cur."][next: <strong>".$d_next."</strong> ".$t_next."]<br />";
676 if ($t_cur == 'alpha') {
677 $t_suffix = '_identifier';
678 // for example: `thebit` bit(8) NOT NULL DEFAULT b'0'
679 if ($t_prev == 'alpha' && $d_prev == 'DEFAULT' && $d_cur == 'b' && $t_next == 'quote_single') {
680 $t_suffix = '_bitfield_constant_introducer';
681 } elseif (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
682 $t_suffix = '_identifier';
683 } elseif (($t_next == 'punct_bracket_open_round')
684 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
686 * @todo 2005-10-16: in the case of a CREATE TABLE containing
687 * a TIMESTAMP, since TIMESTAMP() is also a function, it's
688 * found here and the token is wrongly marked as alpha_functionName.
689 * But we compensate for this when analysing for timestamp_not_null
690 * later in this script.
692 * Same applies to CHAR vs. CHAR() function.
694 $t_suffix = '_functionName';
695 /* There are functions which might be as well column types */
696 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
697 $t_suffix = '_columnType';
700 * Temporary fix for BUG #621357 and #2027720
702 * @todo FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
704 if (($d_cur_upper == 'SET' || $d_cur_upper == 'BINARY') && $t_next != 'punct_bracket_open_round') {
705 $t_suffix = '_reservedWord';
707 //END OF TEMPORARY FIX
709 // CHARACTER is a synonym for CHAR, but can also be meant as
710 // CHARACTER SET. In this case, we have a reserved word.
711 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
712 $t_suffix = '_reservedWord';
715 // experimental
716 // current is a column type, so previous must not be
717 // a reserved word but an identifier
718 // CREATE TABLE SG_Persons (first varchar(64))
720 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
721 // $sql_array[$i-1]['type'] = 'alpha_identifier';
724 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
725 $t_suffix = '_reservedWord';
726 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
727 $t_suffix = '_columnAttrib';
728 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
729 // it should be regarded as a reserved word.
730 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
731 $t_suffix = '_reservedWord';
734 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
735 $t_suffix = '_reservedWord';
737 // Binary as character set
738 if ($d_cur_upper == 'BINARY' && (
739 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
740 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
741 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
742 || $d_prev_upper == 'CHARSET'
743 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
744 $t_suffix = '_charset';
746 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
747 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
748 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
749 $t_suffix = '_charset';
750 } else {
751 // Do nothing
753 // check if present in the list of forbidden words
754 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
755 $sql_array[$i]['forbidden'] = TRUE;
756 } else {
757 $sql_array[$i]['forbidden'] = FALSE;
759 $sql_array[$i]['type'] .= $t_suffix;
761 } // end for
763 // Stores the size of the array inside the array, as count() is a slow
764 // operation.
765 $sql_array['len'] = $arraysize;
767 // DEBUG echo 'After parsing<pre>'; print_r($sql_array); echo '</pre>';
768 // Sends the data back
769 return $sql_array;
770 } // end of the "PMA_SQP_parse()" function
773 * Checks for token types being what we want...
775 * @param string String of type that we have
776 * @param string String of type that we want
778 * @return boolean result of check
780 * @access private
782 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
784 $typeSeperator = '_';
785 if (strcmp($whatWeWant, $toCheck) == 0) {
786 return TRUE;
787 } else {
788 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
789 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
790 } else {
791 return FALSE;
798 * Analyzes SQL queries
800 * @param array The SQL queries
802 * @return array The analyzed SQL queries
804 * @access public
806 function PMA_SQP_analyze($arr)
808 if ($arr == array()) {
809 return array();
811 $result = array();
812 $size = $arr['len'];
813 $subresult = array(
814 'querytype' => '',
815 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
816 'position_of_first_select' => '', // the array index
817 'from_clause'=> '',
818 'group_by_clause'=> '',
819 'order_by_clause'=> '',
820 'having_clause' => '',
821 'limit_clause' => '',
822 'where_clause' => '',
823 'where_clause_identifiers' => array(),
824 'unsorted_query' => '',
825 'queryflags' => array(),
826 'select_expr' => array(),
827 'table_ref' => array(),
828 'foreign_keys' => array(),
829 'create_table_fields' => array()
831 $subresult_empty = $subresult;
832 $seek_queryend = FALSE;
833 $seen_end_of_table_ref = FALSE;
834 $number_of_brackets_in_extract = 0;
835 $number_of_brackets_in_group_concat = 0;
837 $number_of_brackets = 0;
838 $in_subquery = false;
839 $seen_subquery = false;
840 $seen_from = false;
842 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
843 // we must not use CURDATE as a table_ref
844 // so we track whether we are in the EXTRACT()
845 $in_extract = FALSE;
847 // for GROUP_CONCAT(...)
848 $in_group_concat = FALSE;
850 /* Description of analyzer results
852 * db, table, column, alias
853 * ------------------------
855 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
857 * The SELECT syntax (simplified) is
859 * SELECT
860 * select_expression,...
861 * [FROM [table_references]
864 * ['select_expr'] is filled with each expression, the key represents the
865 * expression position in the list (0-based) (so we don't lose track of
866 * multiple occurences of the same column).
868 * ['table_ref'] is filled with each table ref, same thing for the key.
870 * I create all sub-values empty, even if they are
871 * not present (for example no select_expression alias).
873 * There is a debug section at the end of loop #1, if you want to
874 * see the exact contents of select_expr and table_ref
876 * queryflags
877 * ----------
879 * In $subresult, array 'queryflags' is filled, according to what we
880 * find in the query.
882 * Currently, those are generated:
884 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
885 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
886 * ['queryflags']['distinct'] = 1; for a DISTINCT
887 * ['queryflags']['union'] = 1; for a UNION
888 * ['queryflags']['join'] = 1; for a JOIN
889 * ['queryflags']['offset'] = 1; for the presence of OFFSET
890 * ['queryflags']['procedure'] = 1; for the presence of PROCEDURE
892 * query clauses
893 * -------------
895 * The select is splitted in those clauses:
896 * ['select_expr_clause']
897 * ['from_clause']
898 * ['group_by_clause']
899 * ['order_by_clause']
900 * ['having_clause']
901 * ['limit_clause']
902 * ['where_clause']
904 * The identifiers of the WHERE clause are put into the array
905 * ['where_clause_identifier']
907 * For a SELECT, the whole query without the ORDER BY clause is put into
908 * ['unsorted_query']
910 * foreign keys
911 * ------------
912 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
913 * analyzed and ['foreign_keys'] is an array filled with
914 * the constraint name, the index list,
915 * the REFERENCES table name and REFERENCES index list,
916 * and ON UPDATE | ON DELETE clauses
918 * position_of_first_select
919 * ------------------------
921 * The array index of the first SELECT we find. Will be used to
922 * insert a SQL_CALC_FOUND_ROWS.
924 * create_table_fields
925 * -------------------
927 * Used to detect the DEFAULT CURRENT_TIMESTAMP and
928 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
929 * Also used to store the default value of the field.
930 * An array, each element is the identifier name.
931 * Note that for now, the timestamp_not_null element is created
932 * even for non-TIMESTAMP fields.
934 * Sub-elements: ['type'] which contains the column type
935 * optional (currently they are never false but can be absent):
936 * ['default_current_timestamp'] boolean
937 * ['on_update_current_timestamp'] boolean
938 * ['timestamp_not_null'] boolean
940 * section_before_limit, section_after_limit
941 * -----------------------------------------
943 * Marks the point of the query where we can insert a LIMIT clause;
944 * so the section_before_limit will contain the left part before
945 * a possible LIMIT clause
948 * End of description of analyzer results
951 // must be sorted
952 // TODO: current logic checks for only one word, so I put only the
953 // first word of the reserved expressions that end a table ref;
954 // maybe this is not ok (the first word might mean something else)
955 // $words_ending_table_ref = array(
956 // 'FOR UPDATE',
957 // 'GROUP BY',
958 // 'HAVING',
959 // 'LIMIT',
960 // 'LOCK IN SHARE MODE',
961 // 'ORDER BY',
962 // 'PROCEDURE',
963 // 'UNION',
964 // 'WHERE'
965 // );
966 $words_ending_table_ref = array(
967 'FOR',
968 'GROUP',
969 'HAVING',
970 'LIMIT',
971 'LOCK',
972 'ORDER',
973 'PROCEDURE',
974 'UNION',
975 'WHERE'
977 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
979 $words_ending_clauses = array(
980 'FOR',
981 'LIMIT',
982 'LOCK',
983 'PROCEDURE',
984 'UNION'
986 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
991 // must be sorted
992 $supported_query_types = array(
993 'SELECT'
995 // Support for these additional query types will come later on.
996 'DELETE',
997 'INSERT',
998 'REPLACE',
999 'TRUNCATE',
1000 'UPDATE'
1001 'EXPLAIN',
1002 'DESCRIBE',
1003 'SHOW',
1004 'CREATE',
1005 'SET',
1006 'ALTER'
1009 $supported_query_types_cnt = count($supported_query_types);
1011 // loop #1 for each token: select_expr, table_ref for SELECT
1013 for ($i = 0; $i < $size; $i++) {
1014 //DEBUG echo "Loop1 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1016 // High speed seek for locating the end of the current query
1017 if ($seek_queryend == TRUE) {
1018 if ($arr[$i]['type'] == 'punct_queryend') {
1019 $seek_queryend = FALSE;
1020 } else {
1021 continue;
1022 } // end if (type == punct_queryend)
1023 } // end if ($seek_queryend)
1026 * Note: do not split if this is a punct_queryend for the first and only query
1027 * @todo when we find a UNION, should we split in another subresult?
1029 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
1030 $result[] = $subresult;
1031 $subresult = $subresult_empty;
1032 continue;
1033 } // end if (type == punct_queryend)
1035 // ==============================================================
1036 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1037 $number_of_brackets++;
1038 if ($in_extract) {
1039 $number_of_brackets_in_extract++;
1041 if ($in_group_concat) {
1042 $number_of_brackets_in_group_concat++;
1045 // ==============================================================
1046 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1047 $number_of_brackets--;
1048 if ($number_of_brackets == 0) {
1049 $in_subquery = false;
1051 if ($in_extract) {
1052 $number_of_brackets_in_extract--;
1053 if ($number_of_brackets_in_extract == 0) {
1054 $in_extract = FALSE;
1057 if ($in_group_concat) {
1058 $number_of_brackets_in_group_concat--;
1059 if ($number_of_brackets_in_group_concat == 0) {
1060 $in_group_concat = FALSE;
1065 if ($in_subquery) {
1067 * skip the subquery to avoid setting
1068 * select_expr or table_ref with the contents
1069 * of this subquery; this is to avoid a bug when
1070 * trying to edit the results of
1071 * select * from child where not exists (select id from
1072 * parent where child.parent_id = parent.id);
1074 continue;
1076 // ==============================================================
1077 if ($arr[$i]['type'] == 'alpha_functionName') {
1078 $upper_data = strtoupper($arr[$i]['data']);
1079 if ($upper_data =='EXTRACT') {
1080 $in_extract = TRUE;
1081 $number_of_brackets_in_extract = 0;
1083 if ($upper_data =='GROUP_CONCAT') {
1084 $in_group_concat = TRUE;
1085 $number_of_brackets_in_group_concat = 0;
1089 // ==============================================================
1090 if ($arr[$i]['type'] == 'alpha_reservedWord'
1091 // && $arr[$i]['forbidden'] == FALSE) {
1093 // We don't know what type of query yet, so run this
1094 if ($subresult['querytype'] == '') {
1095 $subresult['querytype'] = strtoupper($arr[$i]['data']);
1096 } // end if (querytype was empty)
1098 // Check if we support this type of query
1099 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
1100 // Skip ahead to the next one if we don't
1101 $seek_queryend = TRUE;
1102 continue;
1103 } // end if (query not supported)
1105 // upper once
1106 $upper_data = strtoupper($arr[$i]['data']);
1108 * @todo reset for each query?
1111 if ($upper_data == 'SELECT') {
1112 if ($number_of_brackets > 0) {
1113 $in_subquery = true;
1114 $seen_subquery = true;
1115 // this is a subquery so do not analyze inside it
1116 continue;
1118 $seen_from = FALSE;
1119 $previous_was_identifier = FALSE;
1120 $current_select_expr = -1;
1121 $seen_end_of_table_ref = FALSE;
1122 } // end if (data == SELECT)
1124 if ($upper_data =='FROM' && !$in_extract) {
1125 $current_table_ref = -1;
1126 $seen_from = TRUE;
1127 $previous_was_identifier = FALSE;
1128 $save_table_ref = TRUE;
1129 } // end if (data == FROM)
1131 // here, do not 'continue' the loop, as we have more work for
1132 // reserved words below
1133 } // end if (type == alpha_reservedWord)
1135 // ==============================
1136 if ($arr[$i]['type'] == 'quote_backtick'
1137 || $arr[$i]['type'] == 'quote_double'
1138 || $arr[$i]['type'] == 'quote_single'
1139 || $arr[$i]['type'] == 'alpha_identifier'
1140 || ($arr[$i]['type'] == 'alpha_reservedWord'
1141 && $arr[$i]['forbidden'] == FALSE)) {
1143 switch ($arr[$i]['type']) {
1144 case 'alpha_identifier':
1145 case 'alpha_reservedWord':
1147 * this is not a real reservedWord, because it's not
1148 * present in the list of forbidden words, for example
1149 * "storage" which can be used as an identifier
1151 * @todo avoid the pretty printing in color in this case
1153 $identifier = $arr[$i]['data'];
1154 break;
1156 case 'quote_backtick':
1157 case 'quote_double':
1158 case 'quote_single':
1159 $identifier = PMA_unQuote($arr[$i]['data']);
1160 break;
1161 } // end switch
1163 if ($subresult['querytype'] == 'SELECT'
1164 && ! $in_group_concat
1165 && ! ($seen_subquery && $arr[$i - 1]['type'] == 'punct_bracket_close_round')) {
1166 if (!$seen_from) {
1167 if ($previous_was_identifier && isset($chain)) {
1168 // found alias for this select_expr, save it
1169 // but only if we got something in $chain
1170 // (for example, SELECT COUNT(*) AS cnt
1171 // puts nothing in $chain, so we avoid
1172 // setting the alias)
1173 $alias_for_select_expr = $identifier;
1174 } else {
1175 $chain[] = $identifier;
1176 $previous_was_identifier = TRUE;
1178 } // end if !$previous_was_identifier
1179 } else {
1180 // ($seen_from)
1181 if ($save_table_ref && !$seen_end_of_table_ref) {
1182 if ($previous_was_identifier) {
1183 // found alias for table ref
1184 // save it for later
1185 $alias_for_table_ref = $identifier;
1186 } else {
1187 $chain[] = $identifier;
1188 $previous_was_identifier = TRUE;
1190 } // end if ($previous_was_identifier)
1191 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1192 } // end if (!$seen_from)
1193 } // end if (querytype SELECT)
1194 } // end if (quote_backtick or double quote or alpha_identifier)
1196 // ===================================
1197 if ($arr[$i]['type'] == 'punct_qualifier') {
1198 // to be able to detect an identifier following another
1199 $previous_was_identifier = FALSE;
1200 continue;
1201 } // end if (punct_qualifier)
1204 * @todo check if 3 identifiers following one another -> error
1207 // s a v e a s e l e c t e x p r
1208 // finding a list separator or FROM
1209 // means that we must save the current chain of identifiers
1210 // into a select expression
1212 // for now, we only save a select expression if it contains
1213 // at least one identifier, as we are interested in checking
1214 // the columns and table names, so in "select * from persons",
1215 // the "*" is not saved
1217 if (isset($chain) && !$seen_end_of_table_ref
1218 && ((!$seen_from && $arr[$i]['type'] == 'punct_listsep')
1219 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM'))) {
1220 $size_chain = count($chain);
1221 $current_select_expr++;
1222 $subresult['select_expr'][$current_select_expr] = array(
1223 'expr' => '',
1224 'alias' => '',
1225 'db' => '',
1226 'table_name' => '',
1227 'table_true_name' => '',
1228 'column' => ''
1231 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1232 // we had found an alias for this select expression
1233 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1234 unset($alias_for_select_expr);
1236 // there is at least a column
1237 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1238 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1240 // maybe a table
1241 if ($size_chain > 1) {
1242 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1243 // we assume for now that this is also the true name
1244 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1245 $subresult['select_expr'][$current_select_expr]['expr']
1246 = $subresult['select_expr'][$current_select_expr]['table_name']
1247 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1248 } // end if ($size_chain > 1)
1250 // maybe a db
1251 if ($size_chain > 2) {
1252 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1253 $subresult['select_expr'][$current_select_expr]['expr']
1254 = $subresult['select_expr'][$current_select_expr]['db']
1255 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1256 } // end if ($size_chain > 2)
1257 unset($chain);
1260 * @todo explain this:
1262 if (($arr[$i]['type'] == 'alpha_reservedWord')
1263 && ($upper_data != 'FROM')) {
1264 $previous_was_identifier = TRUE;
1267 } // end if (save a select expr)
1270 //======================================
1271 // s a v e a t a b l e r e f
1272 //======================================
1274 // maybe we just saw the end of table refs
1275 // but the last table ref has to be saved
1276 // or we are at the last token
1277 // or we just got a reserved word
1279 * @todo there could be another query after this one
1282 if (isset($chain) && $seen_from && $save_table_ref
1283 && ($arr[$i]['type'] == 'punct_listsep'
1284 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1285 || $seen_end_of_table_ref
1286 || $i==$size-1)) {
1288 $size_chain = count($chain);
1289 $current_table_ref++;
1290 $subresult['table_ref'][$current_table_ref] = array(
1291 'expr' => '',
1292 'db' => '',
1293 'table_name' => '',
1294 'table_alias' => '',
1295 'table_true_name' => ''
1297 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1298 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1299 unset($alias_for_table_ref);
1301 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1302 // we assume for now that this is also the true name
1303 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1304 $subresult['table_ref'][$current_table_ref]['expr']
1305 = $subresult['table_ref'][$current_table_ref]['table_name'];
1306 // maybe a db
1307 if ($size_chain > 1) {
1308 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1309 $subresult['table_ref'][$current_table_ref]['expr']
1310 = $subresult['table_ref'][$current_table_ref]['db']
1311 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1312 } // end if ($size_chain > 1)
1314 // add the table alias into the whole expression
1315 $subresult['table_ref'][$current_table_ref]['expr']
1316 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1318 unset($chain);
1319 $previous_was_identifier = TRUE;
1320 //continue;
1322 } // end if (save a table ref)
1325 // when we have found all table refs,
1326 // for each table_ref alias, put the true name of the table
1327 // in the corresponding select expressions
1329 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1330 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1331 $alias = $subresult['table_ref'][$tr]['table_alias'];
1332 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1333 for ($se=0; $se <= $current_select_expr; $se++) {
1334 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1335 == $alias) {
1336 $subresult['select_expr'][$se]['table_true_name']
1337 = $truename;
1338 } // end if (found the alias)
1339 } // end for (select expressions)
1341 } // end for (table refs)
1342 } // end if (set the true names)
1345 // e n d i n g l o o p #1
1346 // set the $previous_was_identifier to FALSE if the current
1347 // token is not an identifier
1348 if (($arr[$i]['type'] != 'alpha_identifier')
1349 && ($arr[$i]['type'] != 'quote_double')
1350 && ($arr[$i]['type'] != 'quote_single')
1351 && ($arr[$i]['type'] != 'quote_backtick')) {
1352 $previous_was_identifier = FALSE;
1353 } // end if
1355 // however, if we are on AS, we must keep the $previous_was_identifier
1356 if (($arr[$i]['type'] == 'alpha_reservedWord')
1357 && ($upper_data == 'AS')) {
1358 $previous_was_identifier = TRUE;
1361 if (($arr[$i]['type'] == 'alpha_reservedWord')
1362 && ($upper_data =='ON' || $upper_data =='USING')) {
1363 $save_table_ref = FALSE;
1364 } // end if (data == ON)
1366 if (($arr[$i]['type'] == 'alpha_reservedWord')
1367 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1368 $save_table_ref = TRUE;
1369 } // end if (data == JOIN)
1372 * no need to check the end of table ref if we already did
1374 * @todo maybe add "&& $seen_from"
1376 if (!$seen_end_of_table_ref) {
1377 // if this is the last token, it implies that we have
1378 // seen the end of table references
1379 // Check for the end of table references
1381 // Note: if we are analyzing a GROUP_CONCAT clause,
1382 // we might find a word that seems to indicate that
1383 // we have found the end of table refs (like ORDER)
1384 // but it's a modifier of the GROUP_CONCAT so
1385 // it's not the real end of table refs
1386 if (($i == $size-1)
1387 || ($arr[$i]['type'] == 'alpha_reservedWord'
1388 && !$in_group_concat
1389 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1390 $seen_end_of_table_ref = TRUE;
1391 // to be able to save the last table ref, but do not
1392 // set it true if we found a word like "ON" that has
1393 // already set it to false
1394 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1395 $save_table_ref = TRUE;
1396 } //end if
1398 } // end if (check for end of table ref)
1399 } //end if (!$seen_end_of_table_ref)
1401 if ($seen_end_of_table_ref) {
1402 $save_table_ref = FALSE;
1403 } // end if
1405 } // end for $i (loop #1)
1407 //DEBUG
1409 if (isset($current_select_expr)) {
1410 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1411 echo "<br />";
1412 reset ($subresult['select_expr'][$trace]);
1413 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1414 echo "sel expr $trace $key => $val<br />\n";
1418 if (isset($current_table_ref)) {
1419 echo "current_table_ref = " . $current_table_ref . "<br>";
1420 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1422 echo "<br />";
1423 reset ($subresult['table_ref'][$trace]);
1424 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1425 echo "table ref $trace $key => $val<br />\n";
1429 // -------------------------------------------------------
1432 // loop #2: - queryflags
1433 // - querytype (for queries != 'SELECT')
1434 // - section_before_limit, section_after_limit
1436 // we will also need this queryflag in loop 2
1437 // so set it here
1438 if (isset($current_table_ref) && $current_table_ref > -1) {
1439 $subresult['queryflags']['select_from'] = 1;
1442 $section_before_limit = '';
1443 $section_after_limit = ''; // truly the section after the limit clause
1444 $seen_reserved_word = FALSE;
1445 $seen_group = FALSE;
1446 $seen_order = FALSE;
1447 $seen_order_by = FALSE;
1448 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1449 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1450 $in_having = FALSE; // true when we are inside the HAVING clause
1451 $in_select_expr = FALSE; // true when we are inside the select expr clause
1452 $in_where = FALSE; // true when we are inside the WHERE clause
1453 $seen_limit = FALSE; // true if we have seen a LIMIT clause
1454 $in_limit = FALSE; // true when we are inside the LIMIT clause
1455 $after_limit = FALSE; // true when we are after the LIMIT clause
1456 $in_from = FALSE; // true when we are in the FROM clause
1457 $in_group_concat = FALSE;
1458 $first_reserved_word = '';
1459 $current_identifier = '';
1460 $unsorted_query = $arr['raw']; // in case there is no ORDER BY
1461 $number_of_brackets = 0;
1462 $in_subquery = false;
1464 for ($i = 0; $i < $size; $i++) {
1465 //DEBUG echo "Loop2 <strong>" . $arr[$i]['data'] . "</strong> (" . $arr[$i]['type'] . ")<br />";
1467 // need_confirm
1469 // check for reserved words that will have to generate
1470 // a confirmation request later in sql.php
1471 // the cases are:
1472 // DROP TABLE
1473 // DROP DATABASE
1474 // ALTER TABLE... DROP
1475 // DELETE FROM...
1477 // this code is not used for confirmations coming from functions.js
1479 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1480 $number_of_brackets++;
1483 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1484 $number_of_brackets--;
1485 if ($number_of_brackets == 0) {
1486 $in_subquery = false;
1490 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1491 $upper_data = strtoupper($arr[$i]['data']);
1493 if ($upper_data == 'SELECT' && $number_of_brackets > 0) {
1494 $in_subquery = true;
1497 if (!$seen_reserved_word) {
1498 $first_reserved_word = $upper_data;
1499 $subresult['querytype'] = $upper_data;
1500 $seen_reserved_word = TRUE;
1502 // if the first reserved word is DROP or DELETE,
1503 // we know this is a query that needs to be confirmed
1504 if ($first_reserved_word=='DROP'
1505 || $first_reserved_word == 'DELETE'
1506 || $first_reserved_word == 'TRUNCATE') {
1507 $subresult['queryflags']['need_confirm'] = 1;
1510 if ($first_reserved_word=='SELECT'){
1511 $position_of_first_select = $i;
1514 } else {
1515 if ($upper_data == 'DROP' && $first_reserved_word == 'ALTER') {
1516 $subresult['queryflags']['need_confirm'] = 1;
1520 if ($upper_data == 'LIMIT' && ! $in_subquery) {
1521 $section_before_limit = substr($arr['raw'], 0, $arr[$i]['pos'] - 5);
1522 $in_limit = TRUE;
1523 $seen_limit = TRUE;
1524 $limit_clause = '';
1525 $in_order_by = FALSE; // @todo maybe others to set FALSE
1528 if ($upper_data == 'PROCEDURE') {
1529 $subresult['queryflags']['procedure'] = 1;
1530 $in_limit = FALSE;
1531 $after_limit = TRUE;
1534 * @todo set also to FALSE if we find FOR UPDATE or LOCK IN SHARE MODE
1536 if ($upper_data == 'SELECT') {
1537 $in_select_expr = TRUE;
1538 $select_expr_clause = '';
1540 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1541 $subresult['queryflags']['distinct'] = 1;
1544 if ($upper_data == 'UNION') {
1545 $subresult['queryflags']['union'] = 1;
1548 if ($upper_data == 'JOIN') {
1549 $subresult['queryflags']['join'] = 1;
1552 if ($upper_data == 'OFFSET') {
1553 $subresult['queryflags']['offset'] = 1;
1556 // if this is a real SELECT...FROM
1557 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1558 $in_from = TRUE;
1559 $from_clause = '';
1560 $in_select_expr = FALSE;
1564 // (we could have less resetting of variables to FALSE
1565 // if we trust that the query respects the standard
1566 // MySQL order for clauses)
1568 // we use $seen_group and $seen_order because we are looking
1569 // for the BY
1570 if ($upper_data == 'GROUP') {
1571 $seen_group = TRUE;
1572 $seen_order = FALSE;
1573 $in_having = FALSE;
1574 $in_order_by = FALSE;
1575 $in_where = FALSE;
1576 $in_select_expr = FALSE;
1577 $in_from = FALSE;
1579 if ($upper_data == 'ORDER' && !$in_group_concat) {
1580 $seen_order = TRUE;
1581 $seen_group = FALSE;
1582 $in_having = FALSE;
1583 $in_group_by = FALSE;
1584 $in_where = FALSE;
1585 $in_select_expr = FALSE;
1586 $in_from = FALSE;
1588 if ($upper_data == 'HAVING') {
1589 $in_having = TRUE;
1590 $having_clause = '';
1591 $seen_group = FALSE;
1592 $seen_order = FALSE;
1593 $in_group_by = FALSE;
1594 $in_order_by = FALSE;
1595 $in_where = FALSE;
1596 $in_select_expr = FALSE;
1597 $in_from = FALSE;
1600 if ($upper_data == 'WHERE') {
1601 $in_where = TRUE;
1602 $where_clause = '';
1603 $where_clause_identifiers = array();
1604 $seen_group = FALSE;
1605 $seen_order = FALSE;
1606 $in_group_by = FALSE;
1607 $in_order_by = FALSE;
1608 $in_having = FALSE;
1609 $in_select_expr = FALSE;
1610 $in_from = FALSE;
1613 if ($upper_data == 'BY') {
1614 if ($seen_group) {
1615 $in_group_by = TRUE;
1616 $group_by_clause = '';
1618 if ($seen_order) {
1619 $seen_order_by = TRUE;
1620 // Here we assume that the ORDER BY keywords took
1621 // exactly 8 characters.
1622 // We use PMA_substr() to be charset-safe; otherwise
1623 // if the table name contains accents, the unsorted
1624 // query would be missing some characters.
1625 $unsorted_query = PMA_substr($arr['raw'], 0, $arr[$i]['pos'] - 8);
1626 $in_order_by = TRUE;
1627 $order_by_clause = '';
1631 // if we find one of the words that could end the clause
1632 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1634 $in_group_by = FALSE;
1635 $in_order_by = FALSE;
1636 $in_having = FALSE;
1637 $in_where = FALSE;
1638 $in_select_expr = FALSE;
1639 $in_from = FALSE;
1642 } // endif (reservedWord)
1645 // do not add a space after a function name
1647 * @todo can we combine loop 2 and loop 1? some code is repeated here...
1650 $sep = ' ';
1651 if ($arr[$i]['type'] == 'alpha_functionName') {
1652 $sep='';
1653 $upper_data = strtoupper($arr[$i]['data']);
1654 if ($upper_data =='GROUP_CONCAT') {
1655 $in_group_concat = TRUE;
1656 $number_of_brackets_in_group_concat = 0;
1660 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1661 if ($in_group_concat) {
1662 $number_of_brackets_in_group_concat++;
1665 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1666 if ($in_group_concat) {
1667 $number_of_brackets_in_group_concat--;
1668 if ($number_of_brackets_in_group_concat == 0) {
1669 $in_group_concat = FALSE;
1674 // do not add a space after an identifier if followed by a dot
1675 if ($arr[$i]['type'] == 'alpha_identifier' && $i < $size - 1 && $arr[$i + 1]['data'] == '.') {
1676 $sep = '';
1679 // do not add a space after a dot if followed by an identifier
1680 if ($arr[$i]['data'] == '.' && $i < $size - 1 && $arr[$i + 1]['type'] == 'alpha_identifier') {
1681 $sep = '';
1684 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1685 $select_expr_clause .= $arr[$i]['data'] . $sep;
1687 if ($in_from && $upper_data != 'FROM') {
1688 $from_clause .= $arr[$i]['data'] . $sep;
1690 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1691 $group_by_clause .= $arr[$i]['data'] . $sep;
1693 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1694 // add a space only before ASC or DESC
1695 // not around the dot between dbname and tablename
1696 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1697 $order_by_clause .= $sep;
1699 $order_by_clause .= $arr[$i]['data'];
1701 if ($in_having && $upper_data != 'HAVING') {
1702 $having_clause .= $arr[$i]['data'] . $sep;
1704 if ($in_where && $upper_data != 'WHERE') {
1705 $where_clause .= $arr[$i]['data'] . $sep;
1707 if (($arr[$i]['type'] == 'quote_backtick')
1708 || ($arr[$i]['type'] == 'alpha_identifier')) {
1709 $where_clause_identifiers[] = $arr[$i]['data'];
1713 // to grab the rest of the query after the ORDER BY clause
1714 if (isset($subresult['queryflags']['select_from'])
1715 && $subresult['queryflags']['select_from'] == 1
1716 && ! $in_order_by
1717 && $seen_order_by
1718 && $upper_data != 'BY') {
1719 $unsorted_query .= $arr[$i]['data'];
1720 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1721 && $arr[$i]['type'] != 'punct_bracket_close_round'
1722 && $arr[$i]['type'] != 'punct') {
1723 $unsorted_query .= $sep;
1727 if ($in_limit) {
1728 if ($upper_data == 'OFFSET') {
1729 $limit_clause .= $sep;
1731 $limit_clause .= $arr[$i]['data'];
1732 if ($upper_data == 'LIMIT' || $upper_data == 'OFFSET') {
1733 $limit_clause .= $sep;
1736 if ($after_limit && $seen_limit) {
1737 $section_after_limit .= $arr[$i]['data'] . $sep;
1740 // clear $upper_data for next iteration
1741 $upper_data='';
1742 } // end for $i (loop #2)
1743 if (empty($section_before_limit)) {
1744 $section_before_limit = $arr['raw'];
1747 // -----------------------------------------------------
1748 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1749 // (for now, check only the first query)
1750 // (for now, identifiers are assumed to be backquoted)
1752 // If we find that we are dealing with a CREATE TABLE query,
1753 // we look for the next punct_bracket_open_round, which
1754 // introduces the fields list. Then, when we find a
1755 // quote_backtick, it must be a field, so we put it into
1756 // the create_table_fields array. Even if this field is
1757 // not a timestamp, it will be useful when logic has been
1758 // added for complete field attributes analysis.
1760 $seen_foreign = FALSE;
1761 $seen_references = FALSE;
1762 $seen_constraint = FALSE;
1763 $foreign_key_number = -1;
1764 $seen_create_table = FALSE;
1765 $seen_create = FALSE;
1766 $seen_alter = FALSE;
1767 $in_create_table_fields = FALSE;
1768 $brackets_level = 0;
1769 $in_timestamp_options = FALSE;
1770 $seen_default = FALSE;
1772 for ($i = 0; $i < $size; $i++) {
1773 // DEBUG echo "Loop 3 <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
1775 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1776 $upper_data = strtoupper($arr[$i]['data']);
1778 if ($upper_data == 'NOT' && $in_timestamp_options) {
1779 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1783 if ($upper_data == 'CREATE') {
1784 $seen_create = TRUE;
1787 if ($upper_data == 'ALTER') {
1788 $seen_alter = TRUE;
1791 if ($upper_data == 'TABLE' && $seen_create) {
1792 $seen_create_table = TRUE;
1793 $create_table_fields = array();
1796 if ($upper_data == 'CURRENT_TIMESTAMP') {
1797 if ($in_timestamp_options) {
1798 if ($seen_default) {
1799 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1804 if ($upper_data == 'CONSTRAINT') {
1805 $foreign_key_number++;
1806 $seen_foreign = FALSE;
1807 $seen_references = FALSE;
1808 $seen_constraint = TRUE;
1810 if ($upper_data == 'FOREIGN') {
1811 $seen_foreign = TRUE;
1812 $seen_references = FALSE;
1813 $seen_constraint = FALSE;
1815 if ($upper_data == 'REFERENCES') {
1816 $seen_foreign = FALSE;
1817 $seen_references = TRUE;
1818 $seen_constraint = FALSE;
1822 // Cases covered:
1824 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1825 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1827 // but we set ['on_delete'] or ['on_cascade'] to
1828 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1830 // ON UPDATE CURRENT_TIMESTAMP
1832 if ($upper_data == 'ON') {
1833 if (isset($arr[$i+1]) && $arr[$i+1]['type'] == 'alpha_reservedWord') {
1834 $second_upper_data = strtoupper($arr[$i+1]['data']);
1835 if ($second_upper_data == 'DELETE') {
1836 $clause = 'on_delete';
1838 if ($second_upper_data == 'UPDATE') {
1839 $clause = 'on_update';
1841 if (isset($clause)
1842 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1844 // ugly workaround because currently, NO is not
1845 // in the list of reserved words in sqlparser.data
1846 // (we got a bug report about not being able to use
1847 // 'no' as an identifier)
1848 || ($arr[$i+2]['type'] == 'alpha_identifier'
1849 && strtoupper($arr[$i+2]['data'])=='NO'))
1851 $third_upper_data = strtoupper($arr[$i+2]['data']);
1852 if ($third_upper_data == 'CASCADE'
1853 || $third_upper_data == 'RESTRICT') {
1854 $value = $third_upper_data;
1855 } elseif ($third_upper_data == 'SET'
1856 || $third_upper_data == 'NO') {
1857 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1858 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1860 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1861 if ($clause == 'on_update'
1862 && $in_timestamp_options) {
1863 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1864 $seen_default = FALSE;
1867 } else {
1868 $value = '';
1870 if (!empty($value)) {
1871 $foreign[$foreign_key_number][$clause] = $value;
1873 unset($clause);
1874 } // endif (isset($clause))
1878 } // end of reserved words analysis
1881 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1882 $brackets_level++;
1883 if ($seen_create_table && $brackets_level == 1) {
1884 $in_create_table_fields = TRUE;
1889 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1890 $brackets_level--;
1891 if ($seen_references) {
1892 $seen_references = FALSE;
1894 if ($seen_create_table && $brackets_level == 0) {
1895 $in_create_table_fields = FALSE;
1899 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1900 $upper_data = strtoupper($arr[$i]['data']);
1901 if ($seen_create_table && $in_create_table_fields) {
1902 if ($upper_data == 'DEFAULT') {
1903 $seen_default = TRUE;
1904 $create_table_fields[$current_identifier]['default_value'] = $arr[$i + 1]['data'];
1910 * @see @todo 2005-10-16 note: the "or" part here is a workaround for a bug
1912 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1913 $upper_data = strtoupper($arr[$i]['data']);
1914 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1915 $create_table_fields[$current_identifier]['type'] = $upper_data;
1916 if ($upper_data == 'TIMESTAMP') {
1917 $arr[$i]['type'] = 'alpha_columnType';
1918 $in_timestamp_options = TRUE;
1919 } else {
1920 $in_timestamp_options = FALSE;
1921 if ($upper_data == 'CHAR') {
1922 $arr[$i]['type'] = 'alpha_columnType';
1929 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1931 if ($arr[$i]['type'] == 'quote_backtick') {
1932 // remove backquotes
1933 $identifier = PMA_unQuote($arr[$i]['data']);
1934 } else {
1935 $identifier = $arr[$i]['data'];
1938 if ($seen_create_table && $in_create_table_fields) {
1939 $current_identifier = $identifier;
1940 // warning: we set this one even for non TIMESTAMP type
1941 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1944 if ($seen_constraint) {
1945 $foreign[$foreign_key_number]['constraint'] = $identifier;
1948 if ($seen_foreign && $brackets_level > 0) {
1949 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1952 if ($seen_references) {
1953 if ($seen_alter && $brackets_level > 0) {
1954 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1955 // here, the first bracket level corresponds to the
1956 // bracket of CREATE TABLE
1957 // so if we are on level 2, it must be the index list
1958 // of the foreign key REFERENCES
1959 } elseif ($brackets_level > 1) {
1960 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1961 } elseif ($arr[$i+1]['type'] == 'punct_qualifier') {
1962 // identifier is `db`.`table`
1963 // the first pass will pick the db name
1964 // the next pass will pick the table name
1965 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1966 } else {
1967 // identifier is `table`
1968 $foreign[$foreign_key_number]['ref_table_name'] = $identifier;
1972 } // end for $i (loop #3)
1975 // Fill the $subresult array
1977 if (isset($create_table_fields)) {
1978 $subresult['create_table_fields'] = $create_table_fields;
1981 if (isset($foreign)) {
1982 $subresult['foreign_keys'] = $foreign;
1985 if (isset($select_expr_clause)) {
1986 $subresult['select_expr_clause'] = $select_expr_clause;
1988 if (isset($from_clause)) {
1989 $subresult['from_clause'] = $from_clause;
1991 if (isset($group_by_clause)) {
1992 $subresult['group_by_clause'] = $group_by_clause;
1994 if (isset($order_by_clause)) {
1995 $subresult['order_by_clause'] = $order_by_clause;
1997 if (isset($having_clause)) {
1998 $subresult['having_clause'] = $having_clause;
2000 if (isset($limit_clause)) {
2001 $subresult['limit_clause'] = $limit_clause;
2003 if (isset($where_clause)) {
2004 $subresult['where_clause'] = $where_clause;
2006 if (isset($unsorted_query) && !empty($unsorted_query)) {
2007 $subresult['unsorted_query'] = $unsorted_query;
2009 if (isset($where_clause_identifiers)) {
2010 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
2013 if (isset($position_of_first_select)) {
2014 $subresult['position_of_first_select'] = $position_of_first_select;
2015 $subresult['section_before_limit'] = $section_before_limit;
2016 $subresult['section_after_limit'] = $section_after_limit;
2019 // They are naughty and didn't have a trailing semi-colon,
2020 // then still handle it properly
2021 if ($subresult['querytype'] != '') {
2022 $result[] = $subresult;
2024 return $result;
2025 } // end of the "PMA_SQP_analyze()" function
2029 * Colorizes SQL queries html formatted
2031 * @todo check why adding a "\n" after the </span> would cause extra blanks
2032 * to be displayed: SELECT p . person_name
2033 * @param array The SQL queries html formatted
2035 * @return array The colorized SQL queries
2037 * @access public
2039 function PMA_SQP_formatHtml_colorize($arr)
2041 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
2042 $class = '';
2043 if ($i > 0) {
2044 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
2047 $class .= 'syntax_' . $arr['type'];
2049 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
2050 } // end of the "PMA_SQP_formatHtml_colorize()" function
2054 * Formats SQL queries to html
2056 * @param array The SQL queries
2057 * @param string mode
2058 * @param integer starting token
2059 * @param integer number of tokens to format, -1 = all
2061 * @return string The formatted SQL queries
2063 * @access public
2065 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
2066 $number_of_tokens=-1)
2068 //DEBUG echo 'in Format<pre>'; print_r($arr); echo '</pre>';
2069 // then check for an array
2070 if (!is_array($arr)) {
2071 return htmlspecialchars($arr);
2073 // first check for the SQL parser having hit an error
2074 if (PMA_SQP_isError()) {
2075 return htmlspecialchars($arr['raw']);
2077 // else do it properly
2078 switch ($mode) {
2079 case 'color':
2080 $str = '<span class="syntax">';
2081 $html_line_break = '<br />';
2082 $docu = TRUE;
2083 break;
2084 case 'query_only':
2085 $str = '';
2086 $html_line_break = "\n";
2087 $docu = FALSE;
2088 break;
2089 case 'text':
2090 $str = '';
2091 $html_line_break = '<br />';
2092 $docu = TRUE;
2093 break;
2094 } // end switch
2095 // inner_sql is a span that exists for all cases
2096 // of $cfg['SQP']['fmtType'] to make possible a replacement
2097 // for inline editing
2098 $str .= '<span class="inner_sql">';
2099 $close_docu_link = false;
2100 $indent = 0;
2101 $bracketlevel = 0;
2102 $functionlevel = 0;
2103 $infunction = FALSE;
2104 $space_punct_listsep = ' ';
2105 $space_punct_listsep_function_name = ' ';
2106 // $space_alpha_reserved_word = '<br />'."\n";
2107 $space_alpha_reserved_word = ' ';
2109 $keywords_with_brackets_1before = array(
2110 'INDEX',
2111 'KEY',
2112 'ON',
2113 'USING'
2115 $keywords_with_brackets_1before_cnt = 4;
2117 $keywords_with_brackets_2before = array(
2118 'IGNORE',
2119 'INDEX',
2120 'INTO',
2121 'KEY',
2122 'PRIMARY',
2123 'PROCEDURE',
2124 'REFERENCES',
2125 'UNIQUE',
2126 'USE'
2128 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
2129 $keywords_with_brackets_2before_cnt = 9;
2131 // These reserved words do NOT get a newline placed near them.
2132 $keywords_no_newline = array(
2133 'AS',
2134 'ASC',
2135 'DESC',
2136 'DISTINCT',
2137 'DUPLICATE',
2138 'HOUR',
2139 'INTERVAL',
2140 'IS',
2141 'LIKE',
2142 'NOT',
2143 'NULL',
2144 'ON',
2145 'REGEXP'
2147 $keywords_no_newline_cnt = 12;
2149 // These reserved words introduce a privilege list
2150 $keywords_priv_list = array(
2151 'GRANT',
2152 'REVOKE'
2154 $keywords_priv_list_cnt = 2;
2156 if ($number_of_tokens == -1) {
2157 $arraysize = $arr['len'];
2158 } else {
2159 $arraysize = $number_of_tokens;
2161 $typearr = array();
2162 if ($arraysize >= 0) {
2163 $typearr[0] = '';
2164 $typearr[1] = '';
2165 $typearr[2] = '';
2166 //$typearr[3] = $arr[0]['type'];
2167 $typearr[3] = $arr[$start_token]['type'];
2170 $in_priv_list = FALSE;
2171 for ($i = $start_token; $i < $arraysize; $i++) {
2172 // DEBUG echo "Loop format <strong>" . $arr[$i]['data'] . "</strong> " . $arr[$i]['type'] . "<br />";
2173 $before = '';
2174 $after = '';
2175 $indent = 0;
2176 // array_shift($typearr);
2178 0 prev2
2179 1 prev
2180 2 current
2181 3 next
2183 if (($i + 1) < $arraysize) {
2184 // array_push($typearr, $arr[$i + 1]['type']);
2185 $typearr[4] = $arr[$i + 1]['type'];
2186 } else {
2187 //array_push($typearr, null);
2188 $typearr[4] = '';
2191 for ($j=0; $j<4; $j++) {
2192 $typearr[$j] = $typearr[$j + 1];
2195 switch ($typearr[2]) {
2196 case 'alpha_bitfield_constant_introducer':
2197 $before = ' ';
2198 $after = '';
2199 break;
2200 case 'white_newline':
2201 $before = '';
2202 break;
2203 case 'punct_bracket_open_round':
2204 $bracketlevel++;
2205 $infunction = FALSE;
2206 // Make sure this array is sorted!
2207 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2208 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2209 || (($typearr[0] == 'alpha_reservedWord')
2210 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2211 || (($typearr[1] == 'alpha_reservedWord')
2212 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2214 $functionlevel++;
2215 $infunction = TRUE;
2216 $after .= ' ';
2217 } else {
2218 $indent++;
2219 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2221 break;
2222 case 'alpha_identifier':
2223 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2224 $after = '';
2225 $before = '';
2227 // for example SELECT 1 somealias
2228 if ($typearr[1] == 'digit_integer') {
2229 $before = ' ';
2231 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2232 $after .= ' ';
2234 break;
2235 case 'punct_user':
2236 case 'punct_qualifier':
2237 $before = '';
2238 $after = '';
2239 break;
2240 case 'punct_listsep':
2241 if ($infunction == TRUE) {
2242 $after .= $space_punct_listsep_function_name;
2243 } else {
2244 $after .= $space_punct_listsep;
2246 break;
2247 case 'punct_queryend':
2248 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2249 $after .= $html_line_break;
2250 $after .= $html_line_break;
2252 $space_punct_listsep = ' ';
2253 $space_punct_listsep_function_name = ' ';
2254 $space_alpha_reserved_word = ' ';
2255 $in_priv_list = FALSE;
2256 break;
2257 case 'comment_mysql':
2258 case 'comment_ansi':
2259 $after .= $html_line_break;
2260 break;
2261 case 'punct':
2262 $before .= ' ';
2263 // workaround for
2264 // select * from mytable limit 0,-1
2265 // (a side effect of this workaround is that
2266 // select 20 - 9
2267 // becomes
2268 // select 20 -9
2269 // )
2270 if ($typearr[3] != 'digit_integer') {
2271 $after .= ' ';
2273 break;
2274 case 'punct_bracket_close_round':
2275 $bracketlevel--;
2276 if ($infunction == TRUE) {
2277 $functionlevel--;
2278 $after .= ' ';
2279 $before .= ' ';
2280 } else {
2281 $indent--;
2282 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2284 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2285 break;
2286 case 'alpha_columnType':
2287 if ($docu) {
2288 switch ($arr[$i]['data']) {
2289 case 'tinyint':
2290 case 'smallint':
2291 case 'mediumint':
2292 case 'int':
2293 case 'bigint':
2294 case 'decimal':
2295 case 'float':
2296 case 'double':
2297 case 'real':
2298 case 'bit':
2299 case 'boolean':
2300 case 'serial':
2301 $before .= PMA_showMySQLDocu('data-types', 'numeric-types', false, '', true);
2302 $after = '</a>' . $after;
2303 break;
2304 case 'date':
2305 case 'datetime':
2306 case 'timestamp':
2307 case 'time':
2308 case 'year':
2309 $before .= PMA_showMySQLDocu('data-types', 'date-and-time-types', false, '', true);
2310 $after = '</a>' . $after;
2311 break;
2312 case 'char':
2313 case 'varchar':
2314 case 'tinytext':
2315 case 'text':
2316 case 'mediumtext':
2317 case 'longtext':
2318 case 'binary':
2319 case 'varbinary':
2320 case 'tinyblob':
2321 case 'mediumblob':
2322 case 'blob':
2323 case 'longblob':
2324 case 'enum':
2325 case 'set':
2326 $before .= PMA_showMySQLDocu('data-types', 'string-types', false, '', true);
2327 $after = '</a>' . $after;
2328 break;
2331 if ($typearr[3] == 'alpha_columnAttrib') {
2332 $after .= ' ';
2334 if ($typearr[1] == 'alpha_columnType') {
2335 $before .= ' ';
2337 break;
2338 case 'alpha_columnAttrib':
2340 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2341 // COLLATE LATIN1_GENERAL_CI DEFAULT
2342 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2343 $before .= ' ';
2345 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2346 $after .= ' ';
2348 // workaround for
2349 // AUTO_INCREMENT = 31DEFAULT_CHARSET = utf-8
2351 if ($typearr[2] == 'alpha_columnAttrib' && $typearr[3] == 'alpha_reservedWord') {
2352 $before .= ' ';
2354 // workaround for
2355 // select * from mysql.user where binary user="root"
2356 // binary is marked as alpha_columnAttrib
2357 // but should be marked as a reserved word
2358 if (strtoupper($arr[$i]['data']) == 'BINARY'
2359 && $typearr[3] == 'alpha_identifier') {
2360 $after .= ' ';
2362 break;
2363 case 'alpha_reservedWord':
2364 // do not uppercase the reserved word if we are calling
2365 // this function in query_only mode, because we need
2366 // the original query (otherwise we get problems with
2367 // semi-reserved words like "storage" which is legal
2368 // as an identifier name)
2370 if ($mode != 'query_only') {
2371 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2374 if ((($typearr[1] != 'alpha_reservedWord')
2375 || (($typearr[1] == 'alpha_reservedWord')
2376 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2377 && ($typearr[1] != 'punct_level_plus')
2378 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2379 // do not put a space before the first token, because
2380 // we use a lot of pattern matching checking for the
2381 // first reserved word at beginning of query
2382 // so do not put a newline before
2384 // also we must not be inside a privilege list
2385 if ($i > 0) {
2386 // the alpha_identifier exception is there to
2387 // catch cases like
2388 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2389 // (else, we get mydb.mytableTO)
2391 // the quote_single exception is there to
2392 // catch cases like
2393 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2395 * @todo fix all cases and find why this happens
2398 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2399 $before .= $space_alpha_reserved_word;
2401 } else {
2402 // on first keyword, check if it introduces a
2403 // privilege list
2404 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2405 $in_priv_list = TRUE;
2408 } else {
2409 $before .= ' ';
2412 switch ($arr[$i]['data']) {
2413 case 'CREATE':
2414 case 'ALTER':
2415 case 'DROP':
2416 case 'RENAME';
2417 case 'TRUNCATE':
2418 case 'ANALYZE':
2419 case 'ANALYSE':
2420 case 'OPTIMIZE':
2421 if ($docu) {
2422 switch ($arr[$i + 1]['data']) {
2423 case 'EVENT':
2424 case 'TABLE':
2425 case 'TABLESPACE':
2426 case 'FUNCTION':
2427 case 'INDEX':
2428 case 'PROCEDURE':
2429 case 'TRIGGER':
2430 case 'SERVER':
2431 case 'DATABASE':
2432 case 'VIEW':
2433 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_' . $arr[$i + 1]['data'], false, '', true);
2434 $close_docu_link = true;
2435 break;
2437 if ($arr[$i + 1]['data'] == 'LOGFILE' && $arr[$i + 2]['data'] == 'GROUP') {
2438 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'] . '_LOGFILE_GROUP', false, '', true);
2439 $close_docu_link = true;
2442 if (!$in_priv_list) {
2443 $space_punct_listsep = $html_line_break;
2444 $space_alpha_reserved_word = ' ';
2446 break;
2447 case 'EVENT':
2448 case 'TABLESPACE':
2449 case 'TABLE':
2450 case 'FUNCTION':
2451 case 'INDEX':
2452 case 'PROCEDURE':
2453 case 'SERVER':
2454 case 'TRIGGER':
2455 case 'DATABASE':
2456 case 'VIEW':
2457 case 'GROUP':
2458 if ($close_docu_link) {
2459 $after = '</a>' . $after;
2460 $close_docu_link = false;
2462 break;
2463 case 'SET':
2464 if ($docu && ($i == 0 || $arr[$i - 1]['data'] != 'CHARACTER')) {
2465 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2466 $after = '</a>' . $after;
2468 if (!$in_priv_list) {
2469 $space_punct_listsep = $html_line_break;
2470 $space_alpha_reserved_word = ' ';
2472 break;
2473 case 'EXPLAIN':
2474 case 'DESCRIBE':
2475 case 'DELETE':
2476 case 'SHOW':
2477 case 'UPDATE':
2478 if ($docu) {
2479 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2480 $after = '</a>' . $after;
2482 if (!$in_priv_list) {
2483 $space_punct_listsep = $html_line_break;
2484 $space_alpha_reserved_word = ' ';
2486 break;
2487 case 'INSERT':
2488 case 'REPLACE':
2489 if ($docu) {
2490 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2491 $after = '</a>' . $after;
2493 if (!$in_priv_list) {
2494 $space_punct_listsep = $html_line_break;
2495 $space_alpha_reserved_word = $html_line_break;
2497 break;
2498 case 'VALUES':
2499 $space_punct_listsep = ' ';
2500 $space_alpha_reserved_word = $html_line_break;
2501 break;
2502 case 'SELECT':
2503 if ($docu) {
2504 $before .= PMA_showMySQLDocu('SQL-Syntax', 'SELECT', false, '', true);
2505 $after = '</a>' . $after;
2507 $space_punct_listsep = ' ';
2508 $space_alpha_reserved_word = $html_line_break;
2509 break;
2510 case 'CALL':
2511 case 'DO':
2512 case 'HANDLER':
2513 if ($docu) {
2514 $before .= PMA_showMySQLDocu('SQL-Syntax', $arr[$i]['data'], false, '', true);
2515 $after = '</a>' . $after;
2517 break;
2518 default:
2519 break;
2520 } // end switch ($arr[$i]['data'])
2522 $after .= ' ';
2523 break;
2524 case 'digit_integer':
2525 case 'digit_float':
2526 case 'digit_hex':
2528 * @todo could there be other types preceding a digit?
2530 if ($typearr[1] == 'alpha_reservedWord') {
2531 $after .= ' ';
2533 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2534 $after .= ' ';
2536 if ($typearr[1] == 'alpha_columnAttrib') {
2537 $before .= ' ';
2539 break;
2540 case 'alpha_variable':
2541 $after = ' ';
2542 break;
2543 case 'quote_double':
2544 case 'quote_single':
2545 // workaround: for the query
2546 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2547 // the @ is incorrectly marked as alpha_variable
2548 // in the parser, and here, the '%' gets a blank before,
2549 // which is a syntax error
2550 if ($typearr[1] != 'punct_user' && $typearr[1] != 'alpha_bitfield_constant_introducer') {
2551 $before .= ' ';
2553 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2554 $after .= ' ';
2556 break;
2557 case 'quote_backtick':
2558 // here we check for punct_user to handle correctly
2559 // DEFINER = `username`@`%`
2560 // where @ is the punct_user and `%` is the quote_backtick
2561 if ($typearr[3] != 'punct_qualifier' && $typearr[3] != 'alpha_variable' && $typearr[3] != 'punct_user') {
2562 $after .= ' ';
2564 if ($typearr[1] != 'punct_qualifier' && $typearr[1] != 'alpha_variable' && $typearr[1] != 'punct_user') {
2565 $before .= ' ';
2567 break;
2568 default:
2569 break;
2570 } // end switch ($typearr[2])
2573 if ($typearr[3] != 'punct_qualifier') {
2574 $after .= ' ';
2576 $after .= "\n";
2578 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2579 } // end for
2580 /* End possibly unclosed documentation link */
2581 if ($close_docu_link) {
2582 $str .= '</a>';
2583 $close_docu_link = false;
2585 // close inner_sql span
2586 $str .= '</span>';
2587 if ($mode=='color') {
2588 // close syntax span
2589 $str .= '</span>';
2592 return $str;
2593 } // end of the "PMA_SQP_formatHtml()" function
2597 * Builds a CSS rule used for html formatted SQL queries
2599 * @param string The class name
2600 * @param string The property name
2601 * @param string The property value
2603 * @return string The CSS rule
2605 * @access public
2607 * @see PMA_SQP_buildCssData()
2609 function PMA_SQP_buildCssRule($classname, $property, $value)
2611 $str = '.' . $classname . ' {';
2612 if ($value != '') {
2613 $str .= $property . ': ' . $value . ';';
2615 $str .= '}' . "\n";
2617 return $str;
2618 } // end of the "PMA_SQP_buildCssRule()" function
2622 * Builds CSS rules used for html formatted SQL queries
2624 * @return string The CSS rules set
2626 * @access public
2628 * @global array The current PMA configuration
2630 * @see PMA_SQP_buildCssRule()
2632 function PMA_SQP_buildCssData()
2634 global $cfg;
2636 $css_string = '';
2637 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2638 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2641 for ($i = 0; $i < 8; $i++) {
2642 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2645 return $css_string;
2646 } // end of the "PMA_SQP_buildCssData()" function
2648 if (! defined('PMA_MINIMUM_COMMON')) {
2650 * Gets SQL queries with no format
2652 * @param array The SQL queries list
2654 * @return string The SQL queries with no format
2656 * @access public
2658 function PMA_SQP_formatNone($arr)
2660 $formatted_sql = htmlspecialchars($arr['raw']);
2661 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2663 return $formatted_sql;
2664 } // end of the "PMA_SQP_formatNone()" function
2668 * Gets SQL queries in text format
2670 * @todo WRITE THIS!
2671 * @param array The SQL queries list
2673 * @return string The SQL queries in text format
2675 * @access public
2677 function PMA_SQP_formatText($arr)
2679 return PMA_SQP_formatNone($arr);
2680 } // end of the "PMA_SQP_formatText()" function
2681 } // end if: minimal common.lib needed?