Rev Author Line No. Line
250 kaklik 1 <?php
2 /* $Id: sqlparser.lib.php,v 2.47 2006/01/17 17:02:30 cybot_tm Exp $ */
3 // vim: expandtab sw=4 ts=4 sts=4:
4  
5 /** SQL Parser Functions for phpMyAdmin
6 *
7 * Copyright 2002 Robin Johnson <robbat2@users.sourceforge.net>
8 * http://www.orbis-terrarum.net/?l=people.robbat2
9 *
10 * These functions define an SQL parser system, capable of understanding and
11 * extracting data from a MySQL type SQL query.
12 *
13 * The basic procedure for using the new SQL parser:
14 * On any page that needs to extract data from a query or to pretty-print a
15 * query, you need code like this up at the top:
16 *
17 * ($sql contains the query)
18 * $parsed_sql = PMA_SQP_parse($sql);
19 *
20 * If you want to extract data from it then, you just need to run
21 * $sql_info = PMA_SQP_analyze($parsed_sql);
22 *
23 * lem9: See comments in PMA_SQP_analyze for the returned info
24 * from the analyzer.
25 *
26 * If you want a pretty-printed version of the query, do:
27 * $string = PMA_SQP_formatHtml($parsed_sql);
28 * (note that that you need to have syntax.css.php included somehow in your
29 * page for it to work, I recommend '<link rel="stylesheet" type="text/css"
30 * href="syntax.css.php" />' at the moment.)
31 */
32  
33  
34 /**
35 * Minimum inclusion? (i.e. for the stylesheet builder)
36 */
37 if ( ! defined( 'PMA_MINIMUM_COMMON' ) ) {
38 /**
39 * Include the string library as we use it heavily
40 */
41 require_once('./libraries/string.lib.php');
42  
43 /**
44 * Include data for the SQL Parser
45 */
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;
53 }
54  
55 if (!defined('DEBUG_TIMING')) {
56 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
57 {
58 $arr[] = array('type' => $type, 'data' => $data);
59 $arrsize++;
60 } // end of the "PMA_SQP_arrayAdd()" function
61 } else {
62 function PMA_SQP_arrayAdd(&$arr, $type, $data, &$arrsize)
63 {
64 global $timer;
65  
66 $t = $timer;
67 $arr[] = array('type' => $type, 'data' => $data, 'time' => $t);
68 $timer = microtime();
69 $arrsize++;
70 } // end of the "PMA_SQP_arrayAdd()" function
71 } // end if... else...
72  
73  
74 /**
75 * Reset the error variable for the SQL parser
76 *
77 * @access public
78 */
79 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
80 function PMA_SQP_resetError()
81 {
82 global $SQP_errorString;
83 $SQP_errorString = '';
84 unset($SQP_errorString);
85 }
86  
87 /**
88 * Get the contents of the error variable for the SQL parser
89 *
90 * @return string Error string from SQL parser
91 *
92 * @access public
93 */
94 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
95 function PMA_SQP_getErrorString()
96 {
97 global $SQP_errorString;
98 return isset($SQP_errorString) ? $SQP_errorString : '';
99 }
100  
101 /**
102 * Check if the SQL parser hit an error
103 *
104 * @return boolean error state
105 *
106 * @access public
107 */
108 // Added, Robbat2 - 13 Janurary 2003, 2:59PM
109 function PMA_SQP_isError()
110 {
111 global $SQP_errorString;
112 return isset($SQP_errorString) && !empty($SQP_errorString);
113 }
114  
115 /**
116 * Set an error message for the system
117 *
118 * @param string The error message
119 * @param string The failing SQL query
120 *
121 * @access private
122 * @scope SQL Parser internal
123 */
124 // Revised, Robbat2 - 13 Janurary 2003, 2:59PM
125 function PMA_SQP_throwError($message, $sql)
126 {
127  
128 global $SQP_errorString;
129 $SQP_errorString = '<p>'.$GLOBALS['strSQLParserUserError'] . '</p>' . "\n"
130 . '<pre>' . "\n"
131 . 'ERROR: ' . $message . "\n"
132 . 'SQL: ' . htmlspecialchars($sql) . "\n"
133 . '</pre>' . "\n";
134  
135 } // end of the "PMA_SQP_throwError()" function
136  
137  
138 /**
139 * Do display the bug report
140 *
141 * @param string The error message
142 * @param string The failing SQL query
143 *
144 * @access public
145 */
146 function PMA_SQP_bug($message, $sql)
147 {
148 global $SQP_errorString;
149 $debugstr = 'ERROR: ' . $message . "\n";
150 $debugstr .= 'CVS: $Id: sqlparser.lib.php,v 2.47 2006/01/17 17:02:30 cybot_tm Exp $' . "\n";
151 $debugstr .= 'MySQL: '.PMA_MYSQL_STR_VERSION . "\n";
152 $debugstr .= 'USR OS, AGENT, VER: ' . PMA_USR_OS . ' ' . PMA_USR_BROWSER_AGENT . ' ' . PMA_USR_BROWSER_VER . "\n";
153 $debugstr .= 'PMA: ' . PMA_VERSION . "\n";
154 $debugstr .= 'PHP VER,OS: ' . PMA_PHP_STR_VERSION . ' ' . PHP_OS . "\n";
155 $debugstr .= 'LANG: ' . $GLOBALS['lang'] . "\n";
156 $debugstr .= 'SQL: ' . htmlspecialchars($sql);
157  
158 $encodedstr = $debugstr;
159 if (@function_exists('gzcompress')) {
160 $encodedstr = gzcompress($debugstr, 9);
161 }
162 $encodedstr = preg_replace("/(\015\012)|(\015)|(\012)/", '<br />' . "\n", chunk_split(base64_encode($encodedstr)));
163  
164 $SQP_errorString .= $GLOBALS['strSQLParserBugMessage'] . '<br />' . "\n"
165 . '----' . $GLOBALS['strBeginCut'] . '----' . '<br />' . "\n"
166 . $encodedstr . "\n"
167 . '----' . $GLOBALS['strEndCut'] . '----' . '<br />' . "\n";
168  
169 $SQP_errorString .= '----' . $GLOBALS['strBeginRaw'] . '----<br />' . "\n"
170 . '<pre>' . "\n"
171 . $debugstr
172 . '</pre>' . "\n"
173 . '----' . $GLOBALS['strEndRaw'] . '----<br />' . "\n";
174  
175 } // end of the "PMA_SQP_bug()" function
176  
177  
178 /**
179 * Parses the SQL queries
180 *
181 * @param string The SQL query list
182 *
183 * @return mixed Most of times, nothing...
184 *
185 * @global array The current PMA configuration
186 * @global array MySQL column attributes
187 * @global array MySQL reserved words
188 * @global array MySQL column types
189 * @global array MySQL function names
190 * @global integer MySQL column attributes count
191 * @global integer MySQL reserved words count
192 * @global integer MySQL column types count
193 * @global integer MySQL function names count
194 * @global array List of available character sets
195 * @global array List of available collations
196 * @global integer Character sets count
197 * @global integer Collations count
198 *
199 * @access public
200 */
201 function PMA_SQP_parse($sql)
202 {
203 global $cfg;
204 global $PMA_SQPdata_column_attrib, $PMA_SQPdata_reserved_word, $PMA_SQPdata_column_type, $PMA_SQPdata_function_name,
205 $PMA_SQPdata_column_attrib_cnt, $PMA_SQPdata_reserved_word_cnt, $PMA_SQPdata_column_type_cnt, $PMA_SQPdata_function_name_cnt;
206 global $mysql_charsets, $mysql_collations_flat, $mysql_charsets_count, $mysql_collations_count;
207 global $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt;
208  
209 // rabus: Convert all line feeds to Unix style
210 $sql = str_replace("\r\n", "\n", $sql);
211 $sql = str_replace("\r", "\n", $sql);
212  
213 $len = PMA_strlen($sql);
214 if ($len == 0) {
215 return array();
216 }
217  
218 $sql_array = array();
219 $sql_array['raw'] = $sql;
220 $count1 = 0;
221 $count2 = 0;
222 $punct_queryend = ';';
223 $punct_qualifier = '.';
224 $punct_listsep = ',';
225 $punct_level_plus = '(';
226 $punct_level_minus = ')';
227 $digit_floatdecimal = '.';
228 $digit_hexset = 'x';
229 $bracket_list = '()[]{}';
230 $allpunct_list = '-,;:!?/.^~\*&%+<=>|';
231 $allpunct_list_pair = array (
232  
233 1 => '&&',
234 2 => ':=',
235 3 => '<<',
236 4 => '<=',
237 5 => '<=>',
238 6 => '<>',
239 7 => '>=',
240 8 => '>>',
241 9 => '||'
242 );
243 $allpunct_list_pair_size = 10; //count($allpunct_list_pair);
244 $quote_list = '\'"`';
245 $arraysize = 0;
246  
247 while ($count2 < $len) {
248 $c = PMA_substr($sql, $count2, 1);
249 $count1 = $count2;
250  
251 if (($c == "\n")) {
252 $count2++;
253 PMA_SQP_arrayAdd($sql_array, 'white_newline', '', $arraysize);
254 continue;
255 }
256  
257 // Checks for white space
258 if (PMA_STR_isSpace($c)) {
259 $count2++;
260 continue;
261 }
262  
263 // Checks for comment lines.
264 // MySQL style #
265 // C style /* */
266 // ANSI style --
267 if (($c == '#')
268 || (($count2 + 1 < $len) && ($c == '/') && (PMA_substr($sql, $count2 + 1, 1) == '*'))
269 || (($count2 + 2 == $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-'))
270 || (($count2 + 2 < $len) && ($c == '-') && (PMA_substr($sql, $count2 + 1, 1) == '-') && ((PMA_substr($sql, $count2 + 2, 1) <= ' ')))) {
271 $count2++;
272 $pos = 0;
273 $type = 'bad';
274 switch ($c) {
275 case '#':
276 $type = 'mysql';
277 case '-':
278 $type = 'ansi';
279 $pos = $GLOBALS['PMA_strpos']($sql, "\n", $count2);
280 break;
281 case '/':
282 $type = 'c';
283 $pos = $GLOBALS['PMA_strpos']($sql, '*/', $count2);
284 $pos += 2;
285 break;
286 default:
287 break;
288 } // end switch
289 $count2 = ($pos < $count2) ? $len : $pos;
290 $str = PMA_substr($sql, $count1, $count2 - $count1);
291 PMA_SQP_arrayAdd($sql_array, 'comment_' . $type, $str, $arraysize);
292 continue;
293 } // end if
294  
295 // Checks for something inside quotation marks
296 if (PMA_STR_strInStr($c, $quote_list)) {
297 $startquotepos = $count2;
298 $quotetype = $c;
299 $count2++;
300 $escaped = FALSE;
301 $escaped_escaped = FALSE;
302 $pos = $count2;
303 $oldpos = 0;
304 do {
305 $oldpos = $pos;
306 $pos = $GLOBALS['PMA_strpos'](' ' . $sql, $quotetype, $oldpos + 1) - 1;
307 // ($pos === FALSE)
308 if ($pos < 0) {
309 $debugstr = $GLOBALS['strSQPBugUnclosedQuote'] . ' @ ' . $startquotepos. "\n"
310 . 'STR: ' . htmlspecialchars($quotetype);
311 PMA_SQP_throwError($debugstr, $sql);
312 return $sql;
313 }
314  
315 // If the quote is the first character, it can't be
316 // escaped, so don't do the rest of the code
317 if ($pos == 0) {
318 break;
319 }
320  
321 // Checks for MySQL escaping using a \
322 // And checks for ANSI escaping using the $quotetype character
323 if (($pos < $len) && PMA_STR_charIsEscaped($sql, $pos)) {
324 $pos ++;
325 continue;
326 } elseif (($pos + 1 < $len) && (PMA_substr($sql, $pos, 1) == $quotetype) && (PMA_substr($sql, $pos + 1, 1) == $quotetype)) {
327 $pos = $pos + 2;
328 continue;
329 } else {
330 break;
331 }
332 } while ($len > $pos); // end do
333  
334 $count2 = $pos;
335 $count2++;
336 $type = 'quote_';
337 switch ($quotetype) {
338 case '\'':
339 $type .= 'single';
340 break;
341 case '"':
342 $type .= 'double';
343 break;
344 case '`':
345 $type .= 'backtick';
346 break;
347 default:
348 break;
349 } // end switch
350 $data = PMA_substr($sql, $count1, $count2 - $count1);
351 PMA_SQP_arrayAdd($sql_array, $type, $data, $arraysize);
352 continue;
353 }
354  
355 // Checks for brackets
356 if (PMA_STR_strInStr($c, $bracket_list)) {
357 // All bracket tokens are only one item long
358 $count2++;
359 $type_type = '';
360 if (PMA_STR_strInStr($c, '([{')) {
361 $type_type = 'open';
362 } else {
363 $type_type = 'close';
364 }
365  
366 $type_style = '';
367 if (PMA_STR_strInStr($c, '()')) {
368 $type_style = 'round';
369 } elseif (PMA_STR_strInStr($c, '[]')) {
370 $type_style = 'square';
371 } else {
372 $type_style = 'curly';
373 }
374  
375 $type = 'punct_bracket_' . $type_type . '_' . $type_style;
376 PMA_SQP_arrayAdd($sql_array, $type, $c, $arraysize);
377 continue;
378 }
379  
380 // Checks for identifier (alpha or numeric)
381 if (PMA_STR_isSqlIdentifier($c, FALSE) || ($c == '@') || ($c == '.' && PMA_STR_isDigit(PMA_substr($sql, $count2 + 1, 1)))) {
382 $count2 ++;
383  
384 //TODO: a @ can also be present in expressions like
385 // FROM 'user'@'%'
386 // or TO 'user'@'%'
387 // in this case, the @ is wrongly marked as alpha_variable
388  
389 $is_sql_variable = ($c == '@');
390 $is_digit = (!$is_sql_variable) && PMA_STR_isDigit($c);
391 $is_hex_digit = ($is_digit) && ($c == '.') && ($c == '0') && ($count2 < $len) && (PMA_substr($sql, $count2, 1) == 'x');
392 $is_float_digit = $c == '.';
393 $is_float_digit_exponent = FALSE;
394  
395 // Nijel: Fast skip is especially needed for huge BLOB data, requires PHP at least 4.3.0:
396 if (PMA_PHP_INT_VERSION >= 40300) {
397 if ($is_hex_digit) {
398 $count2++;
399 $pos = strspn($sql, '0123456789abcdefABCDEF', $count2);
400 if ($pos > $count2) {
401 $count2 = $pos;
402 }
403 unset($pos);
404 } elseif ($is_digit) {
405 $pos = strspn($sql, '0123456789', $count2);
406 if ($pos > $count2) {
407 $count2 = $pos;
408 }
409 unset($pos);
410 }
411 }
412  
413 while (($count2 < $len) && PMA_STR_isSqlIdentifier(PMA_substr($sql, $count2, 1), ($is_sql_variable || $is_digit))) {
414 $c2 = PMA_substr($sql, $count2, 1);
415 if ($is_sql_variable && ($c2 == '.')) {
416 $count2++;
417 continue;
418 }
419 if ($is_digit && (!$is_hex_digit) && ($c2 == '.')) {
420 $count2++;
421 if (!$is_float_digit) {
422 $is_float_digit = TRUE;
423 continue;
424 } else {
425 $debugstr = $GLOBALS['strSQPBugInvalidIdentifer'] . ' @ ' . ($count1+1) . "\n"
426 . 'STR: ' . htmlspecialchars(PMA_substr($sql, $count1, $count2 - $count1));
427 PMA_SQP_throwError($debugstr, $sql);
428 return $sql;
429 }
430 }
431 if ($is_digit && (!$is_hex_digit) && (($c2 == 'e') || ($c2 == 'E'))) {
432 if (!$is_float_digit_exponent) {
433 $is_float_digit_exponent = TRUE;
434 $is_float_digit = TRUE;
435 $count2++;
436 continue;
437 } else {
438 $is_digit = FALSE;
439 $is_float_digit = FALSE;
440 }
441 }
442 if (($is_hex_digit && PMA_STR_isHexDigit($c2)) || ($is_digit && PMA_STR_isDigit($c2))) {
443 $count2++;
444 continue;
445 } else {
446 $is_digit = FALSE;
447 $is_hex_digit = FALSE;
448 }
449  
450 $count2++;
451 } // end while
452  
453 $l = $count2 - $count1;
454 $str = PMA_substr($sql, $count1, $l);
455  
456 $type = '';
457 if ($is_digit) {
458 $type = 'digit';
459 if ($is_float_digit) {
460 $type .= '_float';
461 } elseif ($is_hex_digit) {
462 $type .= '_hex';
463 } else {
464 $type .= '_integer';
465 }
466 } else {
467 if ($is_sql_variable != FALSE) {
468 $type = 'alpha_variable';
469 } else {
470 $type = 'alpha';
471 }
472 } // end if... else....
473 PMA_SQP_arrayAdd($sql_array, $type, $str, $arraysize);
474  
475 continue;
476 }
477  
478 // Checks for punct
479 if (PMA_STR_strInStr($c, $allpunct_list)) {
480 while (($count2 < $len) && PMA_STR_strInStr(PMA_substr($sql, $count2, 1), $allpunct_list)) {
481 $count2++;
482 }
483 $l = $count2 - $count1;
484 if ($l == 1) {
485 $punct_data = $c;
486 } else {
487 $punct_data = PMA_substr($sql, $count1, $l);
488 }
489  
490 // Special case, sometimes, althought two characters are
491 // adjectent directly, they ACTUALLY need to be seperate
492 if ($l == 1) {
493 $t_suffix = '';
494 switch ($punct_data) {
495 case $punct_queryend:
496 $t_suffix = '_queryend';
497 break;
498 case $punct_qualifier:
499 $t_suffix = '_qualifier';
500 break;
501 case $punct_listsep:
502 $t_suffix = '_listsep';
503 break;
504 default:
505 break;
506 }
507 PMA_SQP_arrayAdd($sql_array, 'punct' . $t_suffix, $punct_data, $arraysize);
508 } elseif (PMA_STR_binarySearchInArr($punct_data, $allpunct_list_pair, $allpunct_list_pair_size)) {
509 // Ok, we have one of the valid combined punct expressions
510 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
511 } else {
512 // Bad luck, lets split it up more
513 $first = $punct_data[0];
514 $first2 = $punct_data[0] . $punct_data[1];
515 $last2 = $punct_data[$l - 2] . $punct_data[$l - 1];
516 $last = $punct_data[$l - 1];
517 if (($first == ',') || ($first == ';') || ($first == '.') || ($first == '*')) {
518 $count2 = $count1 + 1;
519 $punct_data = $first;
520 } elseif (($last2 == '/*') || (($last2 == '--') && ($count2 == $len || PMA_substr($sql, $count2, 1) <= ' ') )) {
521 $count2 -= 2;
522 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
523 } elseif (($last == '-') || ($last == '+') || ($last == '!')) {
524 $count2--;
525 $punct_data = PMA_substr($sql, $count1, $count2 - $count1);
526 // TODO: for negation operator, split in 2 tokens ?
527 // "select x&~1 from t"
528 // becomes "select x & ~ 1 from t" ?
529  
530 } elseif ($last != '~') {
531 $debugstr = $GLOBALS['strSQPBugUnknownPunctuation'] . ' @ ' . ($count1+1) . "\n"
532 . 'STR: ' . htmlspecialchars($punct_data);
533 PMA_SQP_throwError($debugstr, $sql);
534 return $sql;
535 }
536 PMA_SQP_arrayAdd($sql_array, 'punct', $punct_data, $arraysize);
537 continue;
538 } // end if... elseif... else
539 continue;
540 }
541  
542 // DEBUG
543 $count2++;
544  
545 $debugstr = 'C1 C2 LEN: ' . $count1 . ' ' . $count2 . ' ' . $len . "\n"
546 . 'STR: ' . PMA_substr($sql, $count1, $count2 - $count1) . "\n";
547 PMA_SQP_bug($debugstr, $sql);
548 return $sql;
549  
550 } // end while ($count2 < $len)
551  
552  
553 if ($arraysize > 0) {
554 $t_next = $sql_array[0]['type'];
555 $t_prev = '';
556 $t_bef_prev = '';
557 $t_cur = '';
558 $d_next = $sql_array[0]['data'];
559 $d_prev = '';
560 $d_bef_prev = '';
561 $d_cur = '';
562 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
563 $d_prev_upper = '';
564 $d_bef_prev_upper = '';
565 $d_cur_upper = '';
566 }
567  
568 for ($i = 0; $i < $arraysize; $i++) {
569 $t_bef_prev = $t_prev;
570 $t_prev = $t_cur;
571 $t_cur = $t_next;
572 $d_bef_prev = $d_prev;
573 $d_prev = $d_cur;
574 $d_cur = $d_next;
575 $d_bef_prev_upper = $d_prev_upper;
576 $d_prev_upper = $d_cur_upper;
577 $d_cur_upper = $d_next_upper;
578 if (($i + 1) < $arraysize) {
579 $t_next = $sql_array[$i + 1]['type'];
580 $d_next = $sql_array[$i + 1]['data'];
581 $d_next_upper = $t_next == 'alpha' ? strtoupper($d_next) : $d_next;
582 } else {
583 $t_next = '';
584 $d_next = '';
585 $d_next_upper = '';
586 }
587  
588 //DEBUG echo "[prev: <b>".$d_prev."</b> ".$t_prev."][cur: <b>".$d_cur."</b> ".$t_cur."][next: <b>".$d_next."</b> ".$t_next."]<br />";
589  
590 if ($t_cur == 'alpha') {
591 $t_suffix = '_identifier';
592 if (($t_next == 'punct_qualifier') || ($t_prev == 'punct_qualifier')) {
593 $t_suffix = '_identifier';
594 } elseif (($t_next == 'punct_bracket_open_round')
595 && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_function_name, $PMA_SQPdata_function_name_cnt)) {
596 // FIXME-2005-10-16: in the case of a CREATE TABLE containing a TIMESTAMP,
597 // since TIMESTAMP() is also a function, it's found here and
598 // the token is wrongly marked as alpha_functionName. But we
599 // compensate for this when analysing for timestamp_not_null
600 // later in this script.
601 $t_suffix = '_functionName';
602 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_type, $PMA_SQPdata_column_type_cnt)) {
603 $t_suffix = '_columnType';
604  
605 // Temporary fix for BUG #621357
606 //TODO FIX PROPERLY NEEDS OVERHAUL OF SQL TOKENIZER
607 if ($d_cur_upper == 'SET' && $t_next != 'punct_bracket_open_round') {
608 $t_suffix = '_reservedWord';
609 }
610 //END OF TEMPORARY FIX
611  
612 // CHARACTER is a synonym for CHAR, but can also be meant as
613 // CHARACTER SET. In this case, we have a reserved word.
614 if ($d_cur_upper == 'CHARACTER' && $d_next_upper == 'SET') {
615 $t_suffix = '_reservedWord';
616 }
617  
618 // experimental
619 // current is a column type, so previous must not be
620 // a reserved word but an identifier
621 // CREATE TABLE SG_Persons (first varchar(64))
622  
623 //if ($sql_array[$i-1]['type'] =='alpha_reservedWord') {
624 // $sql_array[$i-1]['type'] = 'alpha_identifier';
625 //}
626  
627 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_reserved_word, $PMA_SQPdata_reserved_word_cnt)) {
628 $t_suffix = '_reservedWord';
629 } elseif (PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_column_attrib, $PMA_SQPdata_column_attrib_cnt)) {
630 $t_suffix = '_columnAttrib';
631 // INNODB is a MySQL table type, but in "SHOW INNODB STATUS",
632 // it should be regarded as a reserved word.
633 if ($d_cur_upper == 'INNODB' && $d_prev_upper == 'SHOW' && $d_next_upper == 'STATUS') {
634 $t_suffix = '_reservedWord';
635 }
636  
637 if ($d_cur_upper == 'DEFAULT' && $d_next_upper == 'CHARACTER') {
638 $t_suffix = '_reservedWord';
639 }
640 // Binary as character set
641 if ($d_cur_upper == 'BINARY' && (
642 ($d_bef_prev_upper == 'CHARACTER' && $d_prev_upper == 'SET')
643 || ($d_bef_prev_upper == 'SET' && $d_prev_upper == '=')
644 || ($d_bef_prev_upper == 'CHARSET' && $d_prev_upper == '=')
645 || $d_prev_upper == 'CHARSET'
646 ) && PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, count($mysql_charsets))) {
647 $t_suffix = '_charset';
648 }
649 } elseif (PMA_STR_binarySearchInArr($d_cur, $mysql_charsets, $mysql_charsets_count)
650 || PMA_STR_binarySearchInArr($d_cur, $mysql_collations_flat, $mysql_collations_count)
651 || ($d_cur{0} == '_' && PMA_STR_binarySearchInArr(substr($d_cur, 1), $mysql_charsets, $mysql_charsets_count))) {
652 $t_suffix = '_charset';
653 } else {
654 // Do nothing
655 }
656 // check if present in the list of forbidden words
657 if ($t_suffix == '_reservedWord' && PMA_STR_binarySearchInArr($d_cur_upper, $PMA_SQPdata_forbidden_word, $PMA_SQPdata_forbidden_word_cnt)) {
658 $sql_array[$i]['forbidden'] = TRUE;
659 } else {
660 $sql_array[$i]['forbidden'] = FALSE;
661 }
662 $sql_array[$i]['type'] .= $t_suffix;
663 }
664 } // end for
665  
666 // Stores the size of the array inside the array, as count() is a slow
667 // operation.
668 $sql_array['len'] = $arraysize;
669  
670 // Sends the data back
671 return $sql_array;
672 } // end of the "PMA_SQP_parse()" function
673  
674 /**
675 * Checks for token types being what we want...
676 *
677 * @param string String of type that we have
678 * @param string String of type that we want
679 *
680 * @return boolean result of check
681 *
682 * @access private
683 */
684 function PMA_SQP_typeCheck($toCheck, $whatWeWant)
685 {
686 $typeSeperator = '_';
687 if (strcmp($whatWeWant, $toCheck) == 0) {
688 return TRUE;
689 } else {
690 if (strpos($whatWeWant, $typeSeperator) === FALSE) {
691 return strncmp($whatWeWant, $toCheck, strpos($toCheck, $typeSeperator)) == 0;
692 } else {
693 return FALSE;
694 }
695 }
696 }
697  
698  
699 /**
700 * Analyzes SQL queries
701 *
702 * @param array The SQL queries
703 *
704 * @return array The analyzed SQL queries
705 *
706 * @access public
707 */
708 function PMA_SQP_analyze($arr)
709 {
710 if ($arr == array()) {
711 return array();
712 }
713 $result = array();
714 $size = $arr['len'];
715 $subresult = array(
716 'querytype' => '',
717 'select_expr_clause'=> '', // the whole stuff between SELECT and FROM , except DISTINCT
718 'position_of_first_select' => '', // the array index
719 'from_clause'=> '',
720 'group_by_clause'=> '',
721 'order_by_clause'=> '',
722 'having_clause' => '',
723 'where_clause' => '',
724 'where_clause_identifiers' => array(),
725 'unsorted_query' => '',
726 'queryflags' => array(),
727 'select_expr' => array(),
728 'table_ref' => array(),
729 'foreign_keys' => array(),
730 'create_table_fields' => array()
731 );
732 $subresult_empty = $subresult;
733 $seek_queryend = FALSE;
734 $seen_end_of_table_ref = FALSE;
735 $number_of_brackets_in_extract = 0;
736 $number_of_brackets_in_group_concat = 0;
737  
738 // for SELECT EXTRACT(YEAR_MONTH FROM CURDATE())
739 // we must not use CURDATE as a table_ref
740 // so we track wether we are in the EXTRACT()
741 $in_extract = FALSE;
742  
743 // for GROUP_CONCAT( ... )
744 $in_group_concat = FALSE;
745  
746 /* Description of analyzer results by lem9
747 *
748 * db, table, column, alias
749 * ------------------------
750 *
751 * Inside the $subresult array, we create ['select_expr'] and ['table_ref'] arrays.
752 *
753 * The SELECT syntax (simplified) is
754 *
755 * SELECT
756 * select_expression,...
757 * [FROM [table_references]
758 *
759 *
760 * ['select_expr'] is filled with each expression, the key represents the
761 * expression position in the list (0-based) (so we don't lose track of
762 * multiple occurences of the same column).
763 *
764 * ['table_ref'] is filled with each table ref, same thing for the key.
765 *
766 * I create all sub-values empty, even if they are
767 * not present (for example no select_expression alias).
768 *
769 * There is a debug section at the end of loop #1, if you want to
770 * see the exact contents of select_expr and table_ref
771 *
772 * queryflags
773 * ----------
774 *
775 * In $subresult, array 'queryflags' is filled, according to what we
776 * find in the query.
777 *
778 * Currently, those are generated:
779 *
780 * ['queryflags']['need_confirm'] = 1; if the query needs confirmation
781 * ['queryflags']['select_from'] = 1; if this is a real SELECT...FROM
782 * ['queryflags']['distinct'] = 1; for a DISTINCT
783 * ['queryflags']['union'] = 1; for a UNION
784 * ['queryflags']['join'] = 1; for a JOIN
785 * ['queryflags']['offset'] = 1; for the presence of OFFSET
786 *
787 * query clauses
788 * -------------
789 *
790 * The select is splitted in those clauses:
791 * ['select_expr_clause']
792 * ['from_clause']
793 * ['group_by_clause']
794 * ['order_by_clause']
795 * ['having_clause']
796 * ['where_clause']
797 *
798 * The identifiers of the WHERE clause are put into the array
799 * ['where_clause_identifier']
800 *
801 * For a SELECT, the whole query without the ORDER BY clause is put into
802 * ['unsorted_query']
803 *
804 * foreign keys
805 * ------------
806 * The CREATE TABLE may contain FOREIGN KEY clauses, so they get
807 * analyzed and ['foreign_keys'] is an array filled with
808 * the constraint name, the index list,
809 * the REFERENCES table name and REFERENCES index list,
810 * and ON UPDATE | ON DELETE clauses
811 *
812 * position_of_first_select
813 * ------------------------
814 *
815 * The array index of the first SELECT we find. Will be used to
816 * insert a SQL_CALC_FOUND_ROWS.
817 *
818 * create_table_fields
819 * -------------------
820 *
821 * For now, mostly used to detect the DEFAULT CURRENT_TIMESTAMP and
822 * ON UPDATE CURRENT_TIMESTAMP clauses of the CREATE TABLE query.
823 * An array, each element is the identifier name.
824 * Note that for now, the timestamp_not_null element is created
825 * even for non-TIMESTAMP fields.
826 *
827 * Sub-elements: ['type'] which contains the column type
828 * optional (currently they are never false but can be absent):
829 * ['default_current_timestamp'] boolean
830 * ['on_update_current_timestamp'] boolean
831 * ['timestamp_not_null'] boolean
832 *
833 * section_before_limit, section_after_limit
834 * -----------------------------------------
835 *
836 * Marks the point of the query where we can insert a LIMIT clause;
837 * so the section_before_limit will contain the left part before
838 * a possible LIMIT clause
839 *
840 *
841 * End of description of analyzer results
842 */
843  
844 // must be sorted
845 // TODO: current logic checks for only one word, so I put only the
846 // first word of the reserved expressions that end a table ref;
847 // maybe this is not ok (the first word might mean something else)
848 // $words_ending_table_ref = array(
849 // 'FOR UPDATE',
850 // 'GROUP BY',
851 // 'HAVING',
852 // 'LIMIT',
853 // 'LOCK IN SHARE MODE',
854 // 'ORDER BY',
855 // 'PROCEDURE',
856 // 'UNION',
857 // 'WHERE'
858 // );
859 $words_ending_table_ref = array(
860 'FOR',
861 'GROUP',
862 'HAVING',
863 'LIMIT',
864 'LOCK',
865 'ORDER',
866 'PROCEDURE',
867 'UNION',
868 'WHERE'
869 );
870 $words_ending_table_ref_cnt = 9; //count($words_ending_table_ref);
871  
872 $words_ending_clauses = array(
873 'FOR',
874 'LIMIT',
875 'LOCK',
876 'PROCEDURE',
877 'UNION'
878 );
879 $words_ending_clauses_cnt = 5; //count($words_ending_clauses);
880  
881  
882  
883  
884 // must be sorted
885 $supported_query_types = array(
886 'SELECT'
887 /*
888 // Support for these additional query types will come later on.
889 'DELETE',
890 'INSERT',
891 'REPLACE',
892 'TRUNCATE',
893 'UPDATE'
894 'EXPLAIN',
895 'DESCRIBE',
896 'SHOW',
897 'CREATE',
898 'SET',
899 'ALTER'
900 */
901 );
902 $supported_query_types_cnt = count($supported_query_types);
903  
904 // loop #1 for each token: select_expr, table_ref for SELECT
905  
906 for ($i = 0; $i < $size; $i++) {
907 //DEBUG echo "trace loop1 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
908  
909 // High speed seek for locating the end of the current query
910 if ($seek_queryend == TRUE) {
911 if ($arr[$i]['type'] == 'punct_queryend') {
912 $seek_queryend = FALSE;
913 } else {
914 continue;
915 } // end if (type == punct_queryend)
916 } // end if ($seek_queryend)
917  
918 // TODO: when we find a UNION, should we split
919 // in another subresult?
920 // Note: do not split if this is a punct_queryend for the
921 // first and only query
922 if ($arr[$i]['type'] == 'punct_queryend' && ($i + 1 != $size)) {
923 $result[] = $subresult;
924 $subresult = $subresult_empty;
925 continue;
926 } // end if (type == punct_queryend)
927  
928 // ==============================================================
929 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
930 if ($in_extract) {
931 $number_of_brackets_in_extract++;
932 }
933 if ($in_group_concat) {
934 $number_of_brackets_in_group_concat++;
935 }
936 }
937 // ==============================================================
938 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
939 if ($in_extract) {
940 $number_of_brackets_in_extract--;
941 if ($number_of_brackets_in_extract == 0) {
942 $in_extract = FALSE;
943 }
944 }
945 if ($in_group_concat) {
946 $number_of_brackets_in_group_concat--;
947 if ($number_of_brackets_in_group_concat == 0) {
948 $in_group_concat = FALSE;
949 }
950 }
951 }
952 // ==============================================================
953 if ($arr[$i]['type'] == 'alpha_functionName') {
954 $upper_data = strtoupper($arr[$i]['data']);
955 if ($upper_data =='EXTRACT') {
956 $in_extract = TRUE;
957 $number_of_brackets_in_extract = 0;
958 }
959 if ($upper_data =='GROUP_CONCAT') {
960 $in_group_concat = TRUE;
961 $number_of_brackets_in_group_concat = 0;
962 }
963 }
964  
965 // ==============================================================
966 if ($arr[$i]['type'] == 'alpha_reservedWord'
967 // && $arr[$i]['forbidden'] == FALSE) {
968 ) {
969 // We don't know what type of query yet, so run this
970 if ($subresult['querytype'] == '') {
971 $subresult['querytype'] = strtoupper($arr[$i]['data']);
972 } // end if (querytype was empty)
973  
974 // Check if we support this type of query
975 if (!PMA_STR_binarySearchInArr($subresult['querytype'], $supported_query_types, $supported_query_types_cnt)) {
976 // Skip ahead to the next one if we don't
977 $seek_queryend = TRUE;
978 continue;
979 } // end if (query not supported)
980  
981 // upper once
982 $upper_data = strtoupper($arr[$i]['data']);
983 //TODO: reset for each query?
984  
985 if ($upper_data == 'SELECT') {
986 $seen_from = FALSE;
987 $previous_was_identifier = FALSE;
988 $current_select_expr = -1;
989 $seen_end_of_table_ref = FALSE;
990 } // end if ( data == SELECT)
991  
992 if ($upper_data =='FROM' && !$in_extract) {
993 $current_table_ref = -1;
994 $seen_from = TRUE;
995 $previous_was_identifier = FALSE;
996 $save_table_ref = TRUE;
997 } // end if (data == FROM)
998  
999 // here, do not 'continue' the loop, as we have more work for
1000 // reserved words below
1001 } // end if (type == alpha_reservedWord)
1002  
1003 // ==============================
1004 if ($arr[$i]['type'] == 'quote_backtick'
1005 || $arr[$i]['type'] == 'quote_double'
1006 || $arr[$i]['type'] == 'quote_single'
1007 || $arr[$i]['type'] == 'alpha_identifier'
1008 || ($arr[$i]['type'] == 'alpha_reservedWord'
1009 && $arr[$i]['forbidden'] == FALSE)) {
1010  
1011 switch ($arr[$i]['type']) {
1012 case 'alpha_identifier':
1013 case 'alpha_reservedWord':
1014 // this is not a real reservedWord, because
1015 // it's not present in the list of forbidden words,
1016 // for example "storage" which can be used as
1017 // an identifier
1018 //
1019 // TODO: avoid the pretty printing in color
1020 // in this case
1021  
1022 $identifier = $arr[$i]['data'];
1023 break;
1024  
1025 //TODO: check embedded double quotes or backticks?
1026 // and/or remove just the first and last character?
1027 case 'quote_backtick':
1028 $identifier = str_replace('`', '', $arr[$i]['data']);
1029 break;
1030 case 'quote_double':
1031 $identifier = str_replace('"', '', $arr[$i]['data']);
1032 break;
1033 case 'quote_single':
1034 $identifier = str_replace("'", "", $arr[$i]['data']);
1035 break;
1036 } // end switch
1037  
1038 if ($subresult['querytype'] == 'SELECT' && !$in_group_concat) {
1039 if (!$seen_from) {
1040 if ($previous_was_identifier && isset($chain)) {
1041 // found alias for this select_expr, save it
1042 // but only if we got something in $chain
1043 // (for example, SELECT COUNT(*) AS cnt
1044 // puts nothing in $chain, so we avoid
1045 // setting the alias)
1046 $alias_for_select_expr = $identifier;
1047 } else {
1048 $chain[] = $identifier;
1049 $previous_was_identifier = TRUE;
1050  
1051 } // end if !$previous_was_identifier
1052 } else {
1053 // ($seen_from)
1054 if ($save_table_ref && !$seen_end_of_table_ref) {
1055 if ($previous_was_identifier) {
1056 // found alias for table ref
1057 // save it for later
1058 $alias_for_table_ref = $identifier;
1059 } else {
1060 $chain[] = $identifier;
1061 $previous_was_identifier = TRUE;
1062  
1063 } // end if ($previous_was_identifier)
1064 } // end if ($save_table_ref &&!$seen_end_of_table_ref)
1065 } // end if (!$seen_from)
1066 } // end if (querytype SELECT)
1067 } // end if ( quote_backtick or double quote or alpha_identifier)
1068  
1069 // ===================================
1070 if ($arr[$i]['type'] == 'punct_qualifier') {
1071 // to be able to detect an identifier following another
1072 $previous_was_identifier = FALSE;
1073 continue;
1074 } // end if (punct_qualifier)
1075  
1076 // TODO: check if 3 identifiers following one another -> error
1077  
1078 // s a v e a s e l e c t e x p r
1079 // finding a list separator or FROM
1080 // means that we must save the current chain of identifiers
1081 // into a select expression
1082  
1083 // for now, we only save a select expression if it contains
1084 // at least one identifier, as we are interested in checking
1085 // the columns and table names, so in "select * from persons",
1086 // the "*" is not saved
1087  
1088 if (isset($chain) && !$seen_end_of_table_ref
1089 && ( (!$seen_from
1090 && $arr[$i]['type'] == 'punct_listsep')
1091 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data == 'FROM')) ) {
1092 $size_chain = count($chain);
1093 $current_select_expr++;
1094 $subresult['select_expr'][$current_select_expr] = array(
1095 'expr' => '',
1096 'alias' => '',
1097 'db' => '',
1098 'table_name' => '',
1099 'table_true_name' => '',
1100 'column' => ''
1101 );
1102  
1103 if (isset($alias_for_select_expr) && strlen($alias_for_select_expr)) {
1104 // we had found an alias for this select expression
1105 $subresult['select_expr'][$current_select_expr]['alias'] = $alias_for_select_expr;
1106 unset($alias_for_select_expr);
1107 }
1108 // there is at least a column
1109 $subresult['select_expr'][$current_select_expr]['column'] = $chain[$size_chain - 1];
1110 $subresult['select_expr'][$current_select_expr]['expr'] = $chain[$size_chain - 1];
1111  
1112 // maybe a table
1113 if ($size_chain > 1) {
1114 $subresult['select_expr'][$current_select_expr]['table_name'] = $chain[$size_chain - 2];
1115 // we assume for now that this is also the true name
1116 $subresult['select_expr'][$current_select_expr]['table_true_name'] = $chain[$size_chain - 2];
1117 $subresult['select_expr'][$current_select_expr]['expr']
1118 = $subresult['select_expr'][$current_select_expr]['table_name']
1119 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1120 } // end if ($size_chain > 1)
1121  
1122 // maybe a db
1123 if ($size_chain > 2) {
1124 $subresult['select_expr'][$current_select_expr]['db'] = $chain[$size_chain - 3];
1125 $subresult['select_expr'][$current_select_expr]['expr']
1126 = $subresult['select_expr'][$current_select_expr]['db']
1127 . '.' . $subresult['select_expr'][$current_select_expr]['expr'];
1128 } // end if ($size_chain > 2)
1129 unset($chain);
1130  
1131 // TODO: explain this:
1132 if (($arr[$i]['type'] == 'alpha_reservedWord')
1133 && ($upper_data != 'FROM')) {
1134 $previous_was_identifier = TRUE;
1135 }
1136  
1137 } // end if (save a select expr)
1138  
1139  
1140 //======================================
1141 // s a v e a t a b l e r e f
1142 //======================================
1143  
1144 // maybe we just saw the end of table refs
1145 // but the last table ref has to be saved
1146 // or we are at the last token (TODO: there could be another
1147 // query after this one)
1148 // or we just got a reserved word
1149  
1150 if (isset($chain) && $seen_from && $save_table_ref
1151 && ($arr[$i]['type'] == 'punct_listsep'
1152 || ($arr[$i]['type'] == 'alpha_reservedWord' && $upper_data!="AS")
1153 || $seen_end_of_table_ref
1154 || $i==$size-1 )) {
1155  
1156 $size_chain = count($chain);
1157 $current_table_ref++;
1158 $subresult['table_ref'][$current_table_ref] = array(
1159 'expr' => '',
1160 'db' => '',
1161 'table_name' => '',
1162 'table_alias' => '',
1163 'table_true_name' => ''
1164 );
1165 if (isset($alias_for_table_ref) && strlen($alias_for_table_ref)) {
1166 $subresult['table_ref'][$current_table_ref]['table_alias'] = $alias_for_table_ref;
1167 unset($alias_for_table_ref);
1168 }
1169 $subresult['table_ref'][$current_table_ref]['table_name'] = $chain[$size_chain - 1];
1170 // we assume for now that this is also the true name
1171 $subresult['table_ref'][$current_table_ref]['table_true_name'] = $chain[$size_chain - 1];
1172 $subresult['table_ref'][$current_table_ref]['expr']
1173 = $subresult['table_ref'][$current_table_ref]['table_name'];
1174 // maybe a db
1175 if ($size_chain > 1) {
1176 $subresult['table_ref'][$current_table_ref]['db'] = $chain[$size_chain - 2];
1177 $subresult['table_ref'][$current_table_ref]['expr']
1178 = $subresult['table_ref'][$current_table_ref]['db']
1179 . '.' . $subresult['table_ref'][$current_table_ref]['expr'];
1180 } // end if ($size_chain > 1)
1181  
1182 // add the table alias into the whole expression
1183 $subresult['table_ref'][$current_table_ref]['expr']
1184 .= ' ' . $subresult['table_ref'][$current_table_ref]['table_alias'];
1185  
1186 unset($chain);
1187 $previous_was_identifier = TRUE;
1188 //continue;
1189  
1190 } // end if (save a table ref)
1191  
1192  
1193 // when we have found all table refs,
1194 // for each table_ref alias, put the true name of the table
1195 // in the corresponding select expressions
1196  
1197 if (isset($current_table_ref) && ($seen_end_of_table_ref || $i == $size-1) && $subresult != $subresult_empty) {
1198 for ($tr=0; $tr <= $current_table_ref; $tr++) {
1199 $alias = $subresult['table_ref'][$tr]['table_alias'];
1200 $truename = $subresult['table_ref'][$tr]['table_true_name'];
1201 for ($se=0; $se <= $current_select_expr; $se++) {
1202 if (isset($alias) && strlen($alias) && $subresult['select_expr'][$se]['table_true_name']
1203 == $alias) {
1204 $subresult['select_expr'][$se]['table_true_name']
1205 = $truename;
1206 } // end if (found the alias)
1207 } // end for (select expressions)
1208  
1209 } // end for (table refs)
1210 } // end if (set the true names)
1211  
1212  
1213 // e n d i n g l o o p #1
1214 // set the $previous_was_identifier to FALSE if the current
1215 // token is not an identifier
1216 if (($arr[$i]['type'] != 'alpha_identifier')
1217 && ($arr[$i]['type'] != 'quote_double')
1218 && ($arr[$i]['type'] != 'quote_single')
1219 && ($arr[$i]['type'] != 'quote_backtick')) {
1220 $previous_was_identifier = FALSE;
1221 } // end if
1222  
1223 // however, if we are on AS, we must keep the $previous_was_identifier
1224 if (($arr[$i]['type'] == 'alpha_reservedWord')
1225 && ($upper_data == 'AS')) {
1226 $previous_was_identifier = TRUE;
1227 }
1228  
1229 if (($arr[$i]['type'] == 'alpha_reservedWord')
1230 && ($upper_data =='ON' || $upper_data =='USING')) {
1231 $save_table_ref = FALSE;
1232 } // end if (data == ON)
1233  
1234 if (($arr[$i]['type'] == 'alpha_reservedWord')
1235 && ($upper_data =='JOIN' || $upper_data =='FROM')) {
1236 $save_table_ref = TRUE;
1237 } // end if (data == JOIN)
1238  
1239 // no need to check the end of table ref if we already did
1240 // TODO: maybe add "&& $seen_from"
1241 if (!$seen_end_of_table_ref) {
1242 // if this is the last token, it implies that we have
1243 // seen the end of table references
1244 // Check for the end of table references
1245 //
1246 // Note: if we are analyzing a GROUP_CONCAT clause,
1247 // we might find a word that seems to indicate that
1248 // we have found the end of table refs (like ORDER)
1249 // but it's a modifier of the GROUP_CONCAT so
1250 // it's not the real end of table refs
1251 if (($i == $size-1)
1252 || ($arr[$i]['type'] == 'alpha_reservedWord'
1253 && !$in_group_concat
1254 && PMA_STR_binarySearchInArr($upper_data, $words_ending_table_ref, $words_ending_table_ref_cnt))) {
1255 $seen_end_of_table_ref = TRUE;
1256 // to be able to save the last table ref, but do not
1257 // set it true if we found a word like "ON" that has
1258 // already set it to false
1259 if (isset($save_table_ref) && $save_table_ref != FALSE) {
1260 $save_table_ref = TRUE;
1261 } //end if
1262  
1263 } // end if (check for end of table ref)
1264 } //end if (!$seen_end_of_table_ref)
1265  
1266 if ($seen_end_of_table_ref) {
1267 $save_table_ref = FALSE;
1268 } // end if
1269  
1270 } // end for $i (loop #1)
1271  
1272 // -------------------------------------------------------
1273 // This is a big hunk of debugging code by Marc for this.
1274 // -------------------------------------------------------
1275 /*
1276 if (isset($current_select_expr)) {
1277 for ($trace=0; $trace<=$current_select_expr; $trace++) {
1278 echo "<br />";
1279 reset ($subresult['select_expr'][$trace]);
1280 while (list ($key, $val) = each ($subresult['select_expr'][$trace]))
1281 echo "sel expr $trace $key => $val<br />\n";
1282 }
1283 }
1284  
1285 if (isset($current_table_ref)) {
1286 echo "current_table_ref = " . $current_table_ref . "<br>";
1287 for ($trace=0; $trace<=$current_table_ref; $trace++) {
1288  
1289 echo "<br />";
1290 reset ($subresult['table_ref'][$trace]);
1291 while (list ($key, $val) = each ($subresult['table_ref'][$trace]))
1292 echo "table ref $trace $key => $val<br />\n";
1293 }
1294 }
1295 */
1296 // -------------------------------------------------------
1297  
1298  
1299 // loop #2: - queryflags
1300 // - querytype (for queries != 'SELECT')
1301 // - section_before_limit, section_after_limit
1302 //
1303 // we will also need this queryflag in loop 2
1304 // so set it here
1305 if (isset($current_table_ref) && $current_table_ref > -1) {
1306 $subresult['queryflags']['select_from'] = 1;
1307 }
1308  
1309 $collect_section_before_limit = TRUE;
1310 $section_before_limit = '';
1311 $section_after_limit = '';
1312 $seen_reserved_word = FALSE;
1313 $seen_group = FALSE;
1314 $seen_order = FALSE;
1315 $in_group_by = FALSE; // true when we are inside the GROUP BY clause
1316 $in_order_by = FALSE; // true when we are inside the ORDER BY clause
1317 $in_having = FALSE; // true when we are inside the HAVING clause
1318 $in_select_expr = FALSE; // true when we are inside the select expr clause
1319 $in_where = FALSE; // true when we are inside the WHERE clause
1320 $in_from = FALSE;
1321 $in_group_concat = FALSE;
1322 $unsorted_query = '';
1323 $first_reserved_word = '';
1324 $current_identifier = '';
1325  
1326 for ($i = 0; $i < $size; $i++) {
1327 //DEBUG echo "trace loop2 <b>" . $arr[$i]['data'] . "</b> (" . $arr[$i]['type'] . ")<br />";
1328  
1329 // need_confirm
1330 //
1331 // check for reserved words that will have to generate
1332 // a confirmation request later in sql.php
1333 // the cases are:
1334 // DROP TABLE
1335 // DROP DATABASE
1336 // ALTER TABLE... DROP
1337 // DELETE FROM...
1338 //
1339 // this code is not used for confirmations coming from functions.js
1340  
1341 // TODO: check for punct_queryend
1342  
1343  
1344 // TODO: verify C-style comments?
1345 if ($arr[$i]['type'] == 'comment_ansi') {
1346 $collect_section_before_limit = FALSE;
1347 }
1348  
1349 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1350 $upper_data = strtoupper($arr[$i]['data']);
1351 if (!$seen_reserved_word) {
1352 $first_reserved_word = $upper_data;
1353 $subresult['querytype'] = $upper_data;
1354 $seen_reserved_word = TRUE;
1355  
1356 // if the first reserved word is DROP or DELETE,
1357 // we know this is a query that needs to be confirmed
1358 if ($first_reserved_word=='DROP'
1359 || $first_reserved_word == 'DELETE'
1360 || $first_reserved_word == 'TRUNCATE') {
1361 $subresult['queryflags']['need_confirm'] = 1;
1362 }
1363  
1364 if ($first_reserved_word=='SELECT'){
1365 $position_of_first_select = $i;
1366 }
1367  
1368 } else {
1369 if ($upper_data=='DROP' && $first_reserved_word=='ALTER') {
1370 $subresult['queryflags']['need_confirm'] = 1;
1371 }
1372 }
1373  
1374 if ($upper_data == 'PROCEDURE') {
1375 $collect_section_before_limit = FALSE;
1376 }
1377 // TODO: set also to FALSE if we find
1378 // FOR UPDATE
1379 // LOCK IN SHARE MODE
1380  
1381 if ($upper_data == 'SELECT') {
1382 $in_select_expr = TRUE;
1383 $select_expr_clause = '';
1384 }
1385 if ($upper_data == 'DISTINCT' && !$in_group_concat) {
1386 $subresult['queryflags']['distinct'] = 1;
1387 }
1388  
1389 if ($upper_data == 'UNION') {
1390 $subresult['queryflags']['union'] = 1;
1391 }
1392  
1393 if ($upper_data == 'JOIN') {
1394 $subresult['queryflags']['join'] = 1;
1395 }
1396  
1397 if ($upper_data == 'OFFSET') {
1398 $subresult['queryflags']['offset'] = 1;
1399 }
1400  
1401 // if this is a real SELECT...FROM
1402 if ($upper_data == 'FROM' && isset($subresult['queryflags']['select_from']) && $subresult['queryflags']['select_from'] == 1) {
1403 $in_from = TRUE;
1404 $from_clause = '';
1405 $in_select_expr = FALSE;
1406 }
1407  
1408  
1409 // (we could have less resetting of variables to FALSE
1410 // if we trust that the query respects the standard
1411 // MySQL order for clauses)
1412  
1413 // we use $seen_group and $seen_order because we are looking
1414 // for the BY
1415 if ($upper_data == 'GROUP') {
1416 $seen_group = TRUE;
1417 $seen_order = FALSE;
1418 $in_having = FALSE;
1419 $in_order_by = FALSE;
1420 $in_where = FALSE;
1421 $in_select_expr = FALSE;
1422 $in_from = FALSE;
1423 }
1424 if ($upper_data == 'ORDER' && !$in_group_concat) {
1425 $seen_order = TRUE;
1426 $seen_group = FALSE;
1427 $in_having = FALSE;
1428 $in_group_by = FALSE;
1429 $in_where = FALSE;
1430 $in_select_expr = FALSE;
1431 $in_from = FALSE;
1432 }
1433 if ($upper_data == 'HAVING') {
1434 $in_having = TRUE;
1435 $having_clause = '';
1436 $seen_group = FALSE;
1437 $seen_order = FALSE;
1438 $in_group_by = FALSE;
1439 $in_order_by = FALSE;
1440 $in_where = FALSE;
1441 $in_select_expr = FALSE;
1442 $in_from = FALSE;
1443 }
1444  
1445 if ($upper_data == 'WHERE') {
1446 $in_where = TRUE;
1447 $where_clause = '';
1448 $where_clause_identifiers = array();
1449 $seen_group = FALSE;
1450 $seen_order = FALSE;
1451 $in_group_by = FALSE;
1452 $in_order_by = FALSE;
1453 $in_having = FALSE;
1454 $in_select_expr = FALSE;
1455 $in_from = FALSE;
1456 }
1457  
1458 if ($upper_data == 'BY') {
1459 if ($seen_group) {
1460 $in_group_by = TRUE;
1461 $group_by_clause = '';
1462 }
1463 if ($seen_order) {
1464 $in_order_by = TRUE;
1465 $order_by_clause = '';
1466 }
1467 }
1468  
1469 // if we find one of the words that could end the clause
1470 if (PMA_STR_binarySearchInArr($upper_data, $words_ending_clauses, $words_ending_clauses_cnt)) {
1471  
1472 $in_group_by = FALSE;
1473 $in_order_by = FALSE;
1474 $in_having = FALSE;
1475 $in_where = FALSE;
1476 $in_select_expr = FALSE;
1477 $in_from = FALSE;
1478 }
1479  
1480 } // endif (reservedWord)
1481  
1482  
1483 // do not add a blank after a function name
1484 // TODO: can we combine loop 2 and loop 1?
1485 // some code is repeated here...
1486  
1487 $sep=' ';
1488 if ($arr[$i]['type'] == 'alpha_functionName') {
1489 $sep='';
1490 $upper_data = strtoupper($arr[$i]['data']);
1491 if ($upper_data =='GROUP_CONCAT') {
1492 $in_group_concat = TRUE;
1493 $number_of_brackets_in_group_concat = 0;
1494 }
1495 }
1496  
1497 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1498 if ($in_group_concat) {
1499 $number_of_brackets_in_group_concat++;
1500 }
1501 }
1502 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1503 if ($in_group_concat) {
1504 $number_of_brackets_in_group_concat--;
1505 if ($number_of_brackets_in_group_concat == 0) {
1506 $in_group_concat = FALSE;
1507 }
1508 }
1509 }
1510  
1511 if ($in_select_expr && $upper_data != 'SELECT' && $upper_data != 'DISTINCT') {
1512 $select_expr_clause .= $arr[$i]['data'] . $sep;
1513 }
1514 if ($in_from && $upper_data != 'FROM') {
1515 $from_clause .= $arr[$i]['data'] . $sep;
1516 }
1517 if ($in_group_by && $upper_data != 'GROUP' && $upper_data != 'BY') {
1518 $group_by_clause .= $arr[$i]['data'] . $sep;
1519 }
1520 if ($in_order_by && $upper_data != 'ORDER' && $upper_data != 'BY') {
1521 $order_by_clause .= $arr[$i]['data'] . $sep;
1522 }
1523 if ($in_having && $upper_data != 'HAVING') {
1524 $having_clause .= $arr[$i]['data'] . $sep;
1525 }
1526 if ($in_where && $upper_data != 'WHERE') {
1527 $where_clause .= $arr[$i]['data'] . $sep;
1528  
1529 if (($arr[$i]['type'] == 'quote_backtick')
1530 || ($arr[$i]['type'] == 'alpha_identifier')) {
1531 $where_clause_identifiers[] = $arr[$i]['data'];
1532 }
1533 }
1534  
1535 if (isset($subresult['queryflags']['select_from'])
1536 && $subresult['queryflags']['select_from'] == 1
1537 && !$seen_order) {
1538 $unsorted_query .= $arr[$i]['data'];
1539  
1540 if ($arr[$i]['type'] != 'punct_bracket_open_round'
1541 && $arr[$i]['type'] != 'punct_bracket_close_round'
1542 && $arr[$i]['type'] != 'punct') {
1543 $unsorted_query .= $sep;
1544 }
1545 }
1546  
1547 // clear $upper_data for next iteration
1548 $upper_data='';
1549  
1550 if ($collect_section_before_limit && $arr[$i]['type'] != 'punct_queryend') {
1551 $section_before_limit .= $arr[$i]['data'] . $sep;
1552 } else {
1553 $section_after_limit .= $arr[$i]['data'] . $sep;
1554 }
1555  
1556  
1557 } // end for $i (loop #2)
1558  
1559  
1560 // -----------------------------------------------------
1561 // loop #3: foreign keys and MySQL 4.1.2+ TIMESTAMP options
1562 // (for now, check only the first query)
1563 // (for now, identifiers are assumed to be backquoted)
1564  
1565 // If we find that we are dealing with a CREATE TABLE query,
1566 // we look for the next punct_bracket_open_round, which
1567 // introduces the fields list. Then, when we find a
1568 // quote_backtick, it must be a field, so we put it into
1569 // the create_table_fields array. Even if this field is
1570 // not a timestamp, it will be useful when logic has been
1571 // added for complete field attributes analysis.
1572  
1573 $seen_foreign = FALSE;
1574 $seen_references = FALSE;
1575 $seen_constraint = FALSE;
1576 $foreign_key_number = -1;
1577 $seen_create_table = FALSE;
1578 $seen_create = FALSE;
1579 $in_create_table_fields = FALSE;
1580 $brackets_level = 0;
1581 $in_timestamp_options = FALSE;
1582 $seen_default = FALSE;
1583  
1584 for ($i = 0; $i < $size; $i++) {
1585 // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1586  
1587 if ($arr[$i]['type'] == 'alpha_reservedWord') {
1588 $upper_data = strtoupper($arr[$i]['data']);
1589  
1590 if ($upper_data == 'NOT' && $in_timestamp_options) {
1591 $create_table_fields[$current_identifier]['timestamp_not_null'] = TRUE;
1592  
1593 }
1594  
1595 if ($upper_data == 'CREATE') {
1596 $seen_create = TRUE;
1597 }
1598  
1599 if ($upper_data == 'TABLE' && $seen_create) {
1600 $seen_create_table = TRUE;
1601 $create_table_fields = array();
1602 }
1603  
1604 if ($upper_data == 'CURRENT_TIMESTAMP') {
1605 if ($in_timestamp_options) {
1606 if ($seen_default) {
1607 $create_table_fields[$current_identifier]['default_current_timestamp'] = TRUE;
1608 }
1609 }
1610 }
1611  
1612 if ($upper_data == 'CONSTRAINT') {
1613 $foreign_key_number++;
1614 $seen_foreign = FALSE;
1615 $seen_references = FALSE;
1616 $seen_constraint = TRUE;
1617 }
1618 if ($upper_data == 'FOREIGN') {
1619 $seen_foreign = TRUE;
1620 $seen_references = FALSE;
1621 $seen_constraint = FALSE;
1622 }
1623 if ($upper_data == 'REFERENCES') {
1624 $seen_foreign = FALSE;
1625 $seen_references = TRUE;
1626 $seen_constraint = FALSE;
1627 }
1628  
1629  
1630 // Cases covered:
1631  
1632 // [ON DELETE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1633 // [ON UPDATE {CASCADE | SET NULL | NO ACTION | RESTRICT}]
1634  
1635 // but we set ['on_delete'] or ['on_cascade'] to
1636 // CASCADE | SET_NULL | NO_ACTION | RESTRICT
1637  
1638 // ON UPDATE CURRENT_TIMESTAMP
1639  
1640 if ($upper_data == 'ON') {
1641 if ($arr[$i+1]['type'] == 'alpha_reservedWord') {
1642 $second_upper_data = strtoupper($arr[$i+1]['data']);
1643 if ($second_upper_data == 'DELETE') {
1644 $clause = 'on_delete';
1645 }
1646 if ($second_upper_data == 'UPDATE') {
1647 $clause = 'on_update';
1648 }
1649 if (isset($clause)
1650 && ($arr[$i+2]['type'] == 'alpha_reservedWord'
1651  
1652 // ugly workaround because currently, NO is not
1653 // in the list of reserved words in sqlparser.data
1654 // (we got a bug report about not being able to use
1655 // 'no' as an identifier)
1656 || ($arr[$i+2]['type'] == 'alpha_identifier'
1657 && strtoupper($arr[$i+2]['data'])=='NO') )
1658 ) {
1659 $third_upper_data = strtoupper($arr[$i+2]['data']);
1660 if ($third_upper_data == 'CASCADE'
1661 || $third_upper_data == 'RESTRICT') {
1662 $value = $third_upper_data;
1663 } elseif ($third_upper_data == 'SET'
1664 || $third_upper_data == 'NO') {
1665 if ($arr[$i+3]['type'] == 'alpha_reservedWord') {
1666 $value = $third_upper_data . '_' . strtoupper($arr[$i+3]['data']);
1667 }
1668 } elseif ($third_upper_data == 'CURRENT_TIMESTAMP') {
1669 if ($clause == 'on_update'
1670 && $in_timestamp_options) {
1671 $create_table_fields[$current_identifier]['on_update_current_timestamp'] = TRUE;
1672 $seen_default = FALSE;
1673 }
1674  
1675 } else {
1676 $value = '';
1677 }
1678 if (!empty($value)) {
1679 $foreign[$foreign_key_number][$clause] = $value;
1680 }
1681 unset($clause);
1682 } // endif (isset($clause))
1683 }
1684 }
1685  
1686 } // end of reserved words analysis
1687  
1688  
1689 if ($arr[$i]['type'] == 'punct_bracket_open_round') {
1690 $brackets_level++;
1691 if ($seen_create_table && $brackets_level == 1) {
1692 $in_create_table_fields = TRUE;
1693 }
1694 }
1695  
1696  
1697 if ($arr[$i]['type'] == 'punct_bracket_close_round') {
1698 $brackets_level--;
1699 if ($seen_references) {
1700 $seen_references = FALSE;
1701 }
1702 if ($seen_create_table && $brackets_level == 0) {
1703 $in_create_table_fields = FALSE;
1704 }
1705 }
1706  
1707 if (($arr[$i]['type'] == 'alpha_columnAttrib')) {
1708 $upper_data = strtoupper($arr[$i]['data']);
1709 if ($seen_create_table && $in_create_table_fields) {
1710 if ($upper_data == 'DEFAULT') {
1711 $seen_default = TRUE;
1712 }
1713 }
1714 }
1715  
1716 // note: the "or" part here is a workaround for a bug
1717 // (see FIXME-2005-10-16)
1718 if (($arr[$i]['type'] == 'alpha_columnType') || ($arr[$i]['type'] == 'alpha_functionName' && $seen_create_table)) {
1719 $upper_data = strtoupper($arr[$i]['data']);
1720 if ($seen_create_table && $in_create_table_fields && isset($current_identifier)) {
1721 $create_table_fields[$current_identifier]['type'] = $upper_data;
1722 if ($upper_data == 'TIMESTAMP') {
1723 $in_timestamp_options = TRUE;
1724 } else {
1725 $in_timestamp_options = FALSE;
1726 }
1727 }
1728 }
1729  
1730  
1731 if ($arr[$i]['type'] == 'quote_backtick' || $arr[$i]['type'] == 'alpha_identifier') {
1732  
1733 if ($arr[$i]['type'] == 'quote_backtick') {
1734 // remove backquotes
1735 $identifier = str_replace('`', '', $arr[$i]['data']);
1736 } else {
1737 $identifier = $arr[$i]['data'];
1738 }
1739  
1740 if ($seen_create_table && $in_create_table_fields) {
1741 $current_identifier = $identifier;
1742 // warning: we set this one even for non TIMESTAMP type
1743 $create_table_fields[$current_identifier]['timestamp_not_null'] = FALSE;
1744 }
1745  
1746 if ($seen_constraint) {
1747 $foreign[$foreign_key_number]['constraint'] = $identifier;
1748 }
1749  
1750 if ($seen_foreign && $brackets_level > 0) {
1751 $foreign[$foreign_key_number]['index_list'][] = $identifier;
1752 }
1753  
1754 if ($seen_references) {
1755 // here, the first bracket level corresponds to the
1756 // bracket of CREATE TABLE
1757 // so if we are on level 2, it must be the index list
1758 // of the foreign key REFERENCES
1759 if ($brackets_level > 1) {
1760 $foreign[$foreign_key_number]['ref_index_list'][] = $identifier;
1761 } else {
1762 // for MySQL 4.0.18, identifier is
1763 // `table` or `db`.`table`
1764 // the first pass will pick the db name
1765 // the next pass will execute the else and pick the
1766 // db name in $db_table[0]
1767 if ($arr[$i+1]['type'] == 'punct_qualifier') {
1768 $foreign[$foreign_key_number]['ref_db_name'] = $identifier;
1769 } else {
1770 // for MySQL 4.0.16, identifier is
1771 // `table` or `db.table`
1772 $db_table = explode('.', $identifier);
1773 if (isset($db_table[1])) {
1774 $foreign[$foreign_key_number]['ref_db_name'] = $db_table[0];
1775 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[1];
1776 } else {
1777 $foreign[$foreign_key_number]['ref_table_name'] = $db_table[0];
1778 }
1779 }
1780 }
1781 }
1782 }
1783 } // end for $i (loop #3)
1784  
1785  
1786 // Fill the $subresult array
1787  
1788 if (isset($create_table_fields)) {
1789 $subresult['create_table_fields'] = $create_table_fields;
1790 }
1791  
1792 if (isset($foreign)) {
1793 $subresult['foreign_keys'] = $foreign;
1794 }
1795  
1796 if (isset($select_expr_clause)) {
1797 $subresult['select_expr_clause'] = $select_expr_clause;
1798 }
1799 if (isset($from_clause)) {
1800 $subresult['from_clause'] = $from_clause;
1801 }
1802 if (isset($group_by_clause)) {
1803 $subresult['group_by_clause'] = $group_by_clause;
1804 }
1805 if (isset($order_by_clause)) {
1806 $subresult['order_by_clause'] = $order_by_clause;
1807 }
1808 if (isset($having_clause)) {
1809 $subresult['having_clause'] = $having_clause;
1810 }
1811 if (isset($where_clause)) {
1812 $subresult['where_clause'] = $where_clause;
1813 }
1814 if (isset($unsorted_query) && !empty($unsorted_query)) {
1815 $subresult['unsorted_query'] = $unsorted_query;
1816 }
1817 if (isset($where_clause_identifiers)) {
1818 $subresult['where_clause_identifiers'] = $where_clause_identifiers;
1819 }
1820  
1821 if (isset($position_of_first_select)) {
1822 $subresult['position_of_first_select'] = $position_of_first_select;
1823 $subresult['section_before_limit'] = $section_before_limit;
1824 $subresult['section_after_limit'] = $section_after_limit;
1825 }
1826  
1827 // They are naughty and didn't have a trailing semi-colon,
1828 // then still handle it properly
1829 if ($subresult['querytype'] != '') {
1830 $result[] = $subresult;
1831 }
1832 return $result;
1833 } // end of the "PMA_SQP_analyze()" function
1834  
1835  
1836 /**
1837 * Colorizes SQL queries html formatted
1838 *
1839 * @param array The SQL queries html formatted
1840 *
1841 * @return array The colorized SQL queries
1842 *
1843 * @access public
1844 */
1845 function PMA_SQP_formatHtml_colorize($arr)
1846 {
1847 $i = $GLOBALS['PMA_strpos']($arr['type'], '_');
1848 $class = '';
1849 if ($i > 0) {
1850 $class = 'syntax_' . PMA_substr($arr['type'], 0, $i) . ' ';
1851 }
1852  
1853 $class .= 'syntax_' . $arr['type'];
1854  
1855 //TODO: check why adding a "\n" after the </span> would cause extra
1856 // blanks to be displayed:
1857 // SELECT p . person_name
1858  
1859 return '<span class="' . $class . '">' . htmlspecialchars($arr['data']) . '</span>';
1860 } // end of the "PMA_SQP_formatHtml_colorize()" function
1861  
1862  
1863 /**
1864 * Formats SQL queries to html
1865 *
1866 * @param array The SQL queries
1867 * @param string mode
1868 * @param integer starting token
1869 * @param integer number of tokens to format, -1 = all
1870 *
1871 * @return string The formatted SQL queries
1872 *
1873 * @access public
1874 */
1875 function PMA_SQP_formatHtml($arr, $mode='color', $start_token=0,
1876 $number_of_tokens=-1)
1877 {
1878 // then check for an array
1879 if (!is_array($arr)) {
1880 return htmlspecialchars($arr);
1881 }
1882 // first check for the SQL parser having hit an error
1883 if (PMA_SQP_isError()) {
1884 return htmlspecialchars($arr['raw']);
1885 }
1886 // else do it properly
1887 switch ($mode) {
1888 case 'color':
1889 $str = '<span class="syntax">';
1890 $html_line_break = '<br />';
1891 break;
1892 case 'query_only':
1893 $str = '';
1894 $html_line_break = "\n";
1895 break;
1896 case 'text':
1897 $str = '';
1898 $html_line_break = '<br />';
1899 break;
1900 } // end switch
1901 $indent = 0;
1902 $bracketlevel = 0;
1903 $functionlevel = 0;
1904 $infunction = FALSE;
1905 $space_punct_listsep = ' ';
1906 $space_punct_listsep_function_name = ' ';
1907 // $space_alpha_reserved_word = '<br />'."\n";
1908 $space_alpha_reserved_word = ' ';
1909  
1910 $keywords_with_brackets_1before = array(
1911 'INDEX',
1912 'KEY',
1913 'ON',
1914 'USING'
1915 );
1916 $keywords_with_brackets_1before_cnt = 4;
1917  
1918 $keywords_with_brackets_2before = array(
1919 'IGNORE',
1920 'INDEX',
1921 'INTO',
1922 'KEY',
1923 'PRIMARY',
1924 'PROCEDURE',
1925 'REFERENCES',
1926 'UNIQUE',
1927 'USE'
1928 );
1929 // $keywords_with_brackets_2before_cnt = count($keywords_with_brackets_2before);
1930 $keywords_with_brackets_2before_cnt = 9;
1931  
1932 // These reserved words do NOT get a newline placed near them.
1933 $keywords_no_newline = array(
1934 'AS',
1935 'ASC',
1936 'DESC',
1937 'DISTINCT',
1938 'HOUR',
1939 'INTERVAL',
1940 'IS',
1941 'LIKE',
1942 'NOT',
1943 'NULL',
1944 'ON',
1945 'REGEXP'
1946 );
1947 $keywords_no_newline_cnt = 12;
1948  
1949 // These reserved words introduce a privilege list
1950 $keywords_priv_list = array(
1951 'GRANT',
1952 'REVOKE'
1953 );
1954 $keywords_priv_list_cnt = 2;
1955  
1956 if ($number_of_tokens == -1) {
1957 $arraysize = $arr['len'];
1958 } else {
1959 $arraysize = $number_of_tokens;
1960 }
1961 $typearr = array();
1962 if ($arraysize >= 0) {
1963 $typearr[0] = '';
1964 $typearr[1] = '';
1965 $typearr[2] = '';
1966 //$typearr[3] = $arr[0]['type'];
1967 $typearr[3] = $arr[$start_token]['type'];
1968 }
1969  
1970 $in_priv_list = FALSE;
1971 for ($i = $start_token; $i < $arraysize; $i++) {
1972 // DEBUG echo "<b>" . $arr[$i]['data'] . "</b> " . $arr[$i]['type'] . "<br />";
1973 $before = '';
1974 $after = '';
1975 $indent = 0;
1976 // array_shift($typearr);
1977 /*
1978  
1979 1 prev
1980 2 current
1981 3 next
1982 */
1983 if (($i + 1) < $arraysize) {
1984 // array_push($typearr, $arr[$i + 1]['type']);
1985 $typearr[4] = $arr[$i + 1]['type'];
1986 } else {
1987 //array_push($typearr, null);
1988 $typearr[4] = '';
1989 }
1990  
1991 for ($j=0; $j<4; $j++) {
1992 $typearr[$j] = $typearr[$j + 1];
1993 }
1994  
1995 switch ($typearr[2]) {
1996 case 'white_newline':
1997 $before = '';
1998 break;
1999 case 'punct_bracket_open_round':
2000 $bracketlevel++;
2001 $infunction = FALSE;
2002 // Make sure this array is sorted!
2003 if (($typearr[1] == 'alpha_functionName') || ($typearr[1] == 'alpha_columnType') || ($typearr[1] == 'punct')
2004 || ($typearr[3] == 'digit_integer') || ($typearr[3] == 'digit_hex') || ($typearr[3] == 'digit_float')
2005 || (($typearr[0] == 'alpha_reservedWord')
2006 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 2]['data']), $keywords_with_brackets_2before, $keywords_with_brackets_2before_cnt))
2007 || (($typearr[1] == 'alpha_reservedWord')
2008 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_with_brackets_1before, $keywords_with_brackets_1before_cnt))
2009 ) {
2010 $functionlevel++;
2011 $infunction = TRUE;
2012 $after .= ' ';
2013 } else {
2014 $indent++;
2015 $after .= ($mode != 'query_only' ? '<div class="syntax_indent' . $indent . '">' : ' ');
2016 }
2017 break;
2018 case 'alpha_identifier':
2019 if (($typearr[1] == 'punct_qualifier') || ($typearr[3] == 'punct_qualifier')) {
2020 $after = '';
2021 $before = '';
2022 }
2023 if (($typearr[3] == 'alpha_columnType') || ($typearr[3] == 'alpha_identifier')) {
2024 $after .= ' ';
2025 }
2026 break;
2027 case 'punct_qualifier':
2028 $before = '';
2029 $after = '';
2030 break;
2031 case 'punct_listsep':
2032 if ($infunction == TRUE) {
2033 $after .= $space_punct_listsep_function_name;
2034 } else {
2035 $after .= $space_punct_listsep;
2036 }
2037 break;
2038 case 'punct_queryend':
2039 if (($typearr[3] != 'comment_mysql') && ($typearr[3] != 'comment_ansi') && $typearr[3] != 'comment_c') {
2040 $after .= $html_line_break;
2041 $after .= $html_line_break;
2042 }
2043 $space_punct_listsep = ' ';
2044 $space_punct_listsep_function_name = ' ';
2045 $space_alpha_reserved_word = ' ';
2046 $in_priv_list = FALSE;
2047 break;
2048 case 'comment_mysql':
2049 case 'comment_ansi':
2050 $after .= $html_line_break;
2051 break;
2052 case 'punct':
2053 $before .= ' ';
2054 // workaround for
2055 // select * from mytable limit 0,-1
2056 // (a side effect of this workaround is that
2057 // select 20 - 9
2058 // becomes
2059 // select 20 -9
2060 // )
2061 if ($typearr[3] != 'digit_integer') {
2062 $after .= ' ';
2063 }
2064 break;
2065 case 'punct_bracket_close_round':
2066 $bracketlevel--;
2067 if ($infunction == TRUE) {
2068 $functionlevel--;
2069 $after .= ' ';
2070 $before .= ' ';
2071 } else {
2072 $indent--;
2073 $before .= ($mode != 'query_only' ? '</div>' : ' ');
2074 }
2075 $infunction = ($functionlevel > 0) ? TRUE : FALSE;
2076 break;
2077 case 'alpha_columnType':
2078 if ($typearr[3] == 'alpha_columnAttrib') {
2079 $after .= ' ';
2080 }
2081 if ($typearr[1] == 'alpha_columnType') {
2082 $before .= ' ';
2083 }
2084 break;
2085 case 'alpha_columnAttrib':
2086  
2087 // ALTER TABLE tbl_name AUTO_INCREMENT = 1
2088 // COLLATE LATIN1_GENERAL_CI DEFAULT
2089 if ($typearr[1] == 'alpha_identifier' || $typearr[1] == 'alpha_charset') {
2090 $before .= ' ';
2091 }
2092 if (($typearr[3] == 'alpha_columnAttrib') || ($typearr[3] == 'quote_single') || ($typearr[3] == 'digit_integer')) {
2093 $after .= ' ';
2094 }
2095 // workaround for
2096 // select * from mysql.user where binary user="root"
2097 // binary is marked as alpha_columnAttrib
2098 // but should be marked as a reserved word
2099 if (strtoupper($arr[$i]['data']) == 'BINARY'
2100 && $typearr[3] == 'alpha_identifier') {
2101 $after .= ' ';
2102 }
2103 break;
2104 case 'alpha_reservedWord':
2105 // do not uppercase the reserved word if we are calling
2106 // this function in query_only mode, because we need
2107 // the original query (otherwise we get problems with
2108 // semi-reserved words like "storage" which is legal
2109 // as an identifier name)
2110  
2111 if ($mode != 'query_only') {
2112 $arr[$i]['data'] = strtoupper($arr[$i]['data']);
2113 }
2114  
2115 if ((($typearr[1] != 'alpha_reservedWord')
2116 || (($typearr[1] == 'alpha_reservedWord')
2117 && PMA_STR_binarySearchInArr(strtoupper($arr[$i - 1]['data']), $keywords_no_newline, $keywords_no_newline_cnt)))
2118 && ($typearr[1] != 'punct_level_plus')
2119 && (!PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_no_newline, $keywords_no_newline_cnt))) {
2120 // do not put a space before the first token, because
2121 // we use a lot of eregi() checking for the first
2122 // reserved word at beginning of query
2123 // so do not put a newline before
2124 //
2125 // also we must not be inside a privilege list
2126 if ($i > 0) {
2127 // the alpha_identifier exception is there to
2128 // catch cases like
2129 // GRANT SELECT ON mydb.mytable TO myuser@localhost
2130 // (else, we get mydb.mytableTO )
2131 //
2132 // the quote_single exception is there to
2133 // catch cases like
2134 // GRANT ... TO 'marc'@'domain.com' IDENTIFIED...
2135 //
2136 // TODO: fix all cases and find why this happens
2137  
2138 if (!$in_priv_list || $typearr[1] == 'alpha_identifier' || $typearr[1] == 'quote_single' || $typearr[1] == 'white_newline') {
2139 $before .= $space_alpha_reserved_word;
2140 }
2141 } else {
2142 // on first keyword, check if it introduces a
2143 // privilege list
2144 if (PMA_STR_binarySearchInArr($arr[$i]['data'], $keywords_priv_list, $keywords_priv_list_cnt)) {
2145 $in_priv_list = TRUE;
2146 }
2147 }
2148 } else {
2149 $before .= ' ';
2150 }
2151  
2152 switch ($arr[$i]['data']) {
2153 case 'CREATE':
2154 if (!$in_priv_list) {
2155 $space_punct_listsep = $html_line_break;
2156 $space_alpha_reserved_word = ' ';
2157 }
2158 break;
2159 case 'EXPLAIN':
2160 case 'DESCRIBE':
2161 case 'SET':
2162 case 'ALTER':
2163 case 'DELETE':
2164 case 'SHOW':
2165 case 'DROP':
2166 case 'UPDATE':
2167 case 'TRUNCATE':
2168 case 'ANALYZE':
2169 case 'ANALYSE':
2170 if (!$in_priv_list) {
2171 $space_punct_listsep = $html_line_break;
2172 $space_alpha_reserved_word = ' ';
2173 }
2174 break;
2175 case 'INSERT':
2176 case 'REPLACE':
2177 if (!$in_priv_list) {
2178 $space_punct_listsep = $html_line_break;
2179 $space_alpha_reserved_word = $html_line_break;
2180 }
2181 break;
2182 case 'VALUES':
2183 $space_punct_listsep = ' ';
2184 $space_alpha_reserved_word = $html_line_break;
2185 break;
2186 case 'SELECT':
2187 $space_punct_listsep = ' ';
2188 $space_alpha_reserved_word = $html_line_break;
2189 break;
2190 default:
2191 break;
2192 } // end switch ($arr[$i]['data'])
2193  
2194 $after .= ' ';
2195 break;
2196 case 'digit_integer':
2197 case 'digit_float':
2198 case 'digit_hex':
2199 //TODO: could there be other types preceding a digit?
2200 if ($typearr[1] == 'alpha_reservedWord') {
2201 $after .= ' ';
2202 }
2203 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2204 $after .= ' ';
2205 }
2206 if ($typearr[1] == 'alpha_columnAttrib') {
2207 $before .= ' ';
2208 }
2209 break;
2210 case 'alpha_variable':
2211 // other workaround for a problem similar to the one
2212 // explained below for quote_single
2213 if (!$in_priv_list) {
2214 $after = ' ';
2215 }
2216 break;
2217 case 'quote_double':
2218 case 'quote_single':
2219 // workaround: for the query
2220 // REVOKE SELECT ON `base2\_db`.* FROM 'user'@'%'
2221 // the @ is incorrectly marked as alpha_variable
2222 // in the parser, and here, the '%' gets a blank before,
2223 // which is a syntax error
2224 if ($typearr[1] !='alpha_variable') {
2225 $before .= ' ';
2226 }
2227 if ($infunction && $typearr[3] == 'punct_bracket_close_round') {
2228 $after .= ' ';
2229 }
2230 break;
2231 case 'quote_backtick':
2232 if ($typearr[3] != 'punct_qualifier') {
2233 $after .= ' ';
2234 }
2235 if ($typearr[1] != 'punct_qualifier') {
2236 $before .= ' ';
2237 }
2238 break;
2239 default:
2240 break;
2241 } // end switch ($typearr[2])
2242  
2243 /*
2244 if ($typearr[3] != 'punct_qualifier') {
2245 $after .= ' ';
2246 }
2247 $after .= "\n";
2248 */
2249 $str .= $before . ($mode=='color' ? PMA_SQP_formatHTML_colorize($arr[$i]) : $arr[$i]['data']). $after;
2250 } // end for
2251 if ($mode=='color') {
2252 $str .= '</span>';
2253 }
2254  
2255 return $str;
2256 } // end of the "PMA_SQP_formatHtml()" function
2257 }
2258  
2259 /**
2260 * Builds a CSS rule used for html formatted SQL queries
2261 *
2262 * @param string The class name
2263 * @param string The property name
2264 * @param string The property value
2265 *
2266 * @return string The CSS rule
2267 *
2268 * @access public
2269 *
2270 * @see PMA_SQP_buildCssData()
2271 */
2272 function PMA_SQP_buildCssRule($classname, $property, $value)
2273 {
2274 $str = '.' . $classname . ' {';
2275 if ($value != '') {
2276 $str .= $property . ': ' . $value . ';';
2277 }
2278 $str .= '}' . "\n";
2279  
2280 return $str;
2281 } // end of the "PMA_SQP_buildCssRule()" function
2282  
2283  
2284 /**
2285 * Builds CSS rules used for html formatted SQL queries
2286 *
2287 * @return string The CSS rules set
2288 *
2289 * @access public
2290 *
2291 * @global array The current PMA configuration
2292 *
2293 * @see PMA_SQP_buildCssRule()
2294 */
2295 function PMA_SQP_buildCssData()
2296 {
2297 global $cfg;
2298  
2299 $css_string = '';
2300 foreach ($cfg['SQP']['fmtColor'] AS $key => $col) {
2301 $css_string .= PMA_SQP_buildCssRule('syntax_' . $key, 'color', $col);
2302 }
2303  
2304 for ($i = 0; $i < 8; $i++) {
2305 $css_string .= PMA_SQP_buildCssRule('syntax_indent' . $i, 'margin-left', ($i * $cfg['SQP']['fmtInd']) . $cfg['SQP']['fmtIndUnit']);
2306 }
2307  
2308 return $css_string;
2309 } // end of the "PMA_SQP_buildCssData()" function
2310  
2311 if ( ! defined( 'PMA_MINIMUM_COMMON' ) ) {
2312 /**
2313 * Gets SQL queries with no format
2314 *
2315 * @param array The SQL queries list
2316 *
2317 * @return string The SQL queries with no format
2318 *
2319 * @access public
2320 */
2321 function PMA_SQP_formatNone($arr)
2322 {
2323 $formatted_sql = htmlspecialchars($arr['raw']);
2324 $formatted_sql = preg_replace("@((\015\012)|(\015)|(\012)){3,}@", "\n\n", $formatted_sql);
2325  
2326 return $formatted_sql;
2327 } // end of the "PMA_SQP_formatNone()" function
2328  
2329  
2330 /**
2331 * Gets SQL queries in text format
2332 *
2333 * @param array The SQL queries list
2334 *
2335 * @return string The SQL queries in text format
2336 *
2337 * @access public
2338 */
2339 function PMA_SQP_formatText($arr)
2340 {
2341 /**
2342 * TODO WRITE THIS!
2343 */
2344 return PMA_SQP_formatNone($arr);
2345 } // end of the "PMA_SQP_formatText()" function
2346 } // end if: minimal common.lib needed?
2347  
2348 ?>