TYPO3 CMS  TYPO3_6-2
SqlParser.php
Go to the documentation of this file.
1 <?php
3 
22 class SqlParser {
23 
24  // Parser:
25  // Parsing error string
29  public $parse_error = '';
30 
31  // Last stop keyword used.
35  public $lastStopKeyWord = '';
36 
40  public function __construct() {
41  }
42 
43  /*************************************
44  *
45  * SQL Parsing, full queries
46  *
47  **************************************/
55  public function parseSQL($parseString) {
56  // Prepare variables:
57  $parseString = $this->trimSQL($parseString);
58  $this->parse_error = '';
59  $result = array();
60  // Finding starting keyword of string:
61  $_parseString = $parseString;
62  // Protecting original string...
63  $keyword = $this->nextPart($_parseString, '^(SELECT|UPDATE|INSERT[[:space:]]+INTO|DELETE[[:space:]]+FROM|EXPLAIN|DROP[[:space:]]+TABLE|CREATE[[:space:]]+TABLE|CREATE[[:space:]]+DATABASE|ALTER[[:space:]]+TABLE|TRUNCATE[[:space:]]+TABLE)[[:space:]]+');
64  $keyword = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $keyword));
65  switch ($keyword) {
66  case 'SELECT':
67  // Parsing SELECT query:
68  $result = $this->parseSELECT($parseString);
69  break;
70  case 'UPDATE':
71  // Parsing UPDATE query:
72  $result = $this->parseUPDATE($parseString);
73  break;
74  case 'INSERTINTO':
75  // Parsing INSERT query:
76  $result = $this->parseINSERT($parseString);
77  break;
78  case 'DELETEFROM':
79  // Parsing DELETE query:
80  $result = $this->parseDELETE($parseString);
81  break;
82  case 'EXPLAIN':
83  // Parsing EXPLAIN SELECT query:
84  $result = $this->parseEXPLAIN($parseString);
85  break;
86  case 'DROPTABLE':
87  // Parsing DROP TABLE query:
88  $result = $this->parseDROPTABLE($parseString);
89  break;
90  case 'ALTERTABLE':
91  // Parsing ALTER TABLE query:
92  $result = $this->parseALTERTABLE($parseString);
93  break;
94  case 'CREATETABLE':
95  // Parsing CREATE TABLE query:
96  $result = $this->parseCREATETABLE($parseString);
97  break;
98  case 'CREATEDATABASE':
99  // Parsing CREATE DATABASE query:
100  $result = $this->parseCREATEDATABASE($parseString);
101  break;
102  case 'TRUNCATETABLE':
103  // Parsing TRUNCATE TABLE query:
104  $result = $this->parseTRUNCATETABLE($parseString);
105  break;
106  default:
107  $result = $this->parseError('"' . $keyword . '" is not a keyword', $parseString);
108  }
109  return $result;
110  }
111 
120  protected function parseSELECT($parseString, &$parameterReferences = NULL) {
121  // Removing SELECT:
122  $parseString = $this->trimSQL($parseString);
123  $parseString = ltrim(substr($parseString, 6));
124  // Init output variable:
125  $result = array();
126  if ($parameterReferences === NULL) {
127  $result['parameters'] = array();
128  $parameterReferences = &$result['parameters'];
129  }
130  $result['type'] = 'SELECT';
131  // Looking for STRAIGHT_JOIN keyword:
132  $result['STRAIGHT_JOIN'] = $this->nextPart($parseString, '^(STRAIGHT_JOIN)[[:space:]]+');
133  // Select fields:
134  $result['SELECT'] = $this->parseFieldList($parseString, '^(FROM)[[:space:]]+');
135  if ($this->parse_error) {
136  return $this->parse_error;
137  }
138  // Continue if string is not ended:
139  if ($parseString) {
140  // Get table list:
141  $result['FROM'] = $this->parseFromTables($parseString, '^(WHERE)[[:space:]]+');
142  if ($this->parse_error) {
143  return $this->parse_error;
144  }
145  // If there are more than just the tables (a WHERE clause that would be...)
146  if ($parseString) {
147  // Get WHERE clause:
148  $result['WHERE'] = $this->parseWhereClause($parseString, '^(GROUP[[:space:]]+BY|ORDER[[:space:]]+BY|LIMIT)[[:space:]]+', $parameterReferences);
149  if ($this->parse_error) {
150  return $this->parse_error;
151  }
152  // If the WHERE clause parsing was stopped by GROUP BY, ORDER BY or LIMIT, then proceed with parsing:
153  if ($this->lastStopKeyWord) {
154  // GROUP BY parsing:
155  if ($this->lastStopKeyWord == 'GROUPBY') {
156  $result['GROUPBY'] = $this->parseFieldList($parseString, '^(ORDER[[:space:]]+BY|LIMIT)[[:space:]]+');
157  if ($this->parse_error) {
158  return $this->parse_error;
159  }
160  }
161  // ORDER BY parsing:
162  if ($this->lastStopKeyWord == 'ORDERBY') {
163  $result['ORDERBY'] = $this->parseFieldList($parseString, '^(LIMIT)[[:space:]]+');
164  if ($this->parse_error) {
165  return $this->parse_error;
166  }
167  }
168  // LIMIT parsing:
169  if ($this->lastStopKeyWord == 'LIMIT') {
170  if (preg_match('/^([0-9]+|[0-9]+[[:space:]]*,[[:space:]]*[0-9]+)$/', trim($parseString))) {
171  $result['LIMIT'] = $parseString;
172  } else {
173  return $this->parseError('No value for limit!', $parseString);
174  }
175  }
176  }
177  }
178  } else {
179  return $this->parseError('No table to select from!', $parseString);
180  }
181  // Store current parseString in the result array for possible further processing (e.g., subquery support by DBAL)
182  $result['parseString'] = $parseString;
183  // Return result:
184  return $result;
185  }
186 
194  protected function parseUPDATE($parseString) {
195  // Removing UPDATE
196  $parseString = $this->trimSQL($parseString);
197  $parseString = ltrim(substr($parseString, 6));
198  // Init output variable:
199  $result = array();
200  $result['type'] = 'UPDATE';
201  // Get table:
202  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
203  // Continue if string is not ended:
204  if ($result['TABLE']) {
205  if ($parseString && $this->nextPart($parseString, '^(SET)[[:space:]]+')) {
206  $comma = TRUE;
207  // Get field/value pairs:
208  while ($comma) {
209  if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]*=')) {
210  // Strip off "=" sign.
211  $this->nextPart($parseString, '^(=)');
212  $value = $this->getValue($parseString);
213  $result['FIELDS'][$fieldName] = $value;
214  } else {
215  return $this->parseError('No fieldname found', $parseString);
216  }
217  $comma = $this->nextPart($parseString, '^(,)');
218  }
219  // WHERE
220  if ($this->nextPart($parseString, '^(WHERE)')) {
221  $result['WHERE'] = $this->parseWhereClause($parseString);
222  if ($this->parse_error) {
223  return $this->parse_error;
224  }
225  }
226  } else {
227  return $this->parseError('Query missing SET...', $parseString);
228  }
229  } else {
230  return $this->parseError('No table found!', $parseString);
231  }
232  // Should be no more content now:
233  if ($parseString) {
234  return $this->parseError('Still content in clause after parsing!', $parseString);
235  }
236  // Return result:
237  return $result;
238  }
239 
247  protected function parseINSERT($parseString) {
248  // Removing INSERT
249  $parseString = $this->trimSQL($parseString);
250  $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 4));
251  // Init output variable:
252  $result = array();
253  $result['type'] = 'INSERT';
254  // Get table:
255  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)([[:space:]]+|\\()');
256  if ($result['TABLE']) {
257  // In this case there are no field names mentioned in the SQL!
258  if ($this->nextPart($parseString, '^(VALUES)([[:space:]]+|\\()')) {
259  // Get values/fieldnames (depending...)
260  $result['VALUES_ONLY'] = $this->getValue($parseString, 'IN');
261  if ($this->parse_error) {
262  return $this->parse_error;
263  }
264  if (preg_match('/^,/', $parseString)) {
265  $result['VALUES_ONLY'] = array($result['VALUES_ONLY']);
266  $result['EXTENDED'] = '1';
267  while ($this->nextPart($parseString, '^(,)') === ',') {
268  $result['VALUES_ONLY'][] = $this->getValue($parseString, 'IN');
269  if ($this->parse_error) {
270  return $this->parse_error;
271  }
272  }
273  }
274  } else {
275  // There are apparently fieldnames listed:
276  $fieldNames = $this->getValue($parseString, '_LIST');
277  if ($this->parse_error) {
278  return $this->parse_error;
279  }
280  // "VALUES" keyword binds the fieldnames to values:
281  if ($this->nextPart($parseString, '^(VALUES)([[:space:]]+|\\()')) {
282  $result['FIELDS'] = array();
283  do {
284  // Using the "getValue" function to get the field list...
285  $values = $this->getValue($parseString, 'IN');
286  if ($this->parse_error) {
287  return $this->parse_error;
288  }
289  $insertValues = array();
290  foreach ($fieldNames as $k => $fN) {
291  if (preg_match('/^[[:alnum:]_]+$/', $fN)) {
292  if (isset($values[$k])) {
293  if (!isset($insertValues[$fN])) {
294  $insertValues[$fN] = $values[$k];
295  } else {
296  return $this->parseError('Fieldname ("' . $fN . '") already found in list!', $parseString);
297  }
298  } else {
299  return $this->parseError('No value set!', $parseString);
300  }
301  } else {
302  return $this->parseError('Invalid fieldname ("' . $fN . '")', $parseString);
303  }
304  }
305  if (isset($values[$k + 1])) {
306  return $this->parseError('Too many values in list!', $parseString);
307  }
308  $result['FIELDS'][] = $insertValues;
309  } while ($this->nextPart($parseString, '^(,)') === ',');
310  if (count($result['FIELDS']) === 1) {
311  $result['FIELDS'] = $result['FIELDS'][0];
312  } else {
313  $result['EXTENDED'] = '1';
314  }
315  } else {
316  return $this->parseError('VALUES keyword expected', $parseString);
317  }
318  }
319  } else {
320  return $this->parseError('No table found!', $parseString);
321  }
322  // Should be no more content now:
323  if ($parseString) {
324  return $this->parseError('Still content after parsing!', $parseString);
325  }
326  // Return result
327  return $result;
328  }
329 
337  protected function parseDELETE($parseString) {
338  // Removing DELETE
339  $parseString = $this->trimSQL($parseString);
340  $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 4));
341  // Init output variable:
342  $result = array();
343  $result['type'] = 'DELETE';
344  // Get table:
345  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
346  if ($result['TABLE']) {
347  // WHERE
348  if ($this->nextPart($parseString, '^(WHERE)')) {
349  $result['WHERE'] = $this->parseWhereClause($parseString);
350  if ($this->parse_error) {
351  return $this->parse_error;
352  }
353  }
354  } else {
355  return $this->parseError('No table found!', $parseString);
356  }
357  // Should be no more content now:
358  if ($parseString) {
359  return $this->parseError('Still content in clause after parsing!', $parseString);
360  }
361  // Return result:
362  return $result;
363  }
364 
372  protected function parseEXPLAIN($parseString) {
373  // Removing EXPLAIN
374  $parseString = $this->trimSQL($parseString);
375  $parseString = ltrim(substr($parseString, 6));
376  // Init output variable:
377  $result = $this->parseSELECT($parseString);
378  if (is_array($result)) {
379  $result['type'] = 'EXPLAIN';
380  }
381  return $result;
382  }
383 
391  protected function parseCREATETABLE($parseString) {
392  // Removing CREATE TABLE
393  $parseString = $this->trimSQL($parseString);
394  $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 5));
395  // Init output variable:
396  $result = array();
397  $result['type'] = 'CREATETABLE';
398  // Get table:
399  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]*\\(', TRUE);
400  if ($result['TABLE']) {
401  // While the parseString is not yet empty:
402  while (strlen($parseString) > 0) {
403  // Getting key
404  if ($key = $this->nextPart($parseString, '^(KEY|PRIMARY KEY|UNIQUE KEY|UNIQUE)([[:space:]]+|\\()')) {
405  $key = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $key));
406  switch ($key) {
407  case 'PRIMARYKEY':
408  $result['KEYS']['PRIMARYKEY'] = $this->getValue($parseString, '_LIST');
409  if ($this->parse_error) {
410  return $this->parse_error;
411  }
412  break;
413  case 'UNIQUE':
414 
415  case 'UNIQUEKEY':
416  if ($keyName = $this->nextPart($parseString, '^([[:alnum:]_]+)([[:space:]]+|\\()')) {
417  $result['KEYS']['UNIQUE'] = array($keyName => $this->getValue($parseString, '_LIST'));
418  if ($this->parse_error) {
419  return $this->parse_error;
420  }
421  } else {
422  return $this->parseError('No keyname found', $parseString);
423  }
424  break;
425  case 'KEY':
426  if ($keyName = $this->nextPart($parseString, '^([[:alnum:]_]+)([[:space:]]+|\\()')) {
427  $result['KEYS'][$keyName] = $this->getValue($parseString, '_LIST', 'INDEX');
428  if ($this->parse_error) {
429  return $this->parse_error;
430  }
431  } else {
432  return $this->parseError('No keyname found', $parseString);
433  }
434  break;
435  }
436  } elseif ($fieldName = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+')) {
437  // Getting field:
438  $result['FIELDS'][$fieldName]['definition'] = $this->parseFieldDef($parseString);
439  if ($this->parse_error) {
440  return $this->parse_error;
441  }
442  }
443  // Finding delimiter:
444  $delim = $this->nextPart($parseString, '^(,|\\))');
445  if (!$delim) {
446  return $this->parseError('No delimiter found', $parseString);
447  } elseif ($delim == ')') {
448  break;
449  }
450  }
451  // Finding what is after the table definition - table type in MySQL
452  if ($delim == ')') {
453  if ($this->nextPart($parseString, '^((ENGINE|TYPE)[[:space:]]*=)')) {
454  $result['engine'] = $parseString;
455  $parseString = '';
456  }
457  } else {
458  return $this->parseError('No fieldname found!', $parseString);
459  }
460  } else {
461  return $this->parseError('No table found!', $parseString);
462  }
463  // Should be no more content now:
464  if ($parseString) {
465  return $this->parseError('Still content in clause after parsing!', $parseString);
466  }
467  return $result;
468  }
469 
477  protected function parseALTERTABLE($parseString) {
478  // Removing ALTER TABLE
479  $parseString = $this->trimSQL($parseString);
480  $parseString = ltrim(substr(ltrim(substr($parseString, 5)), 5));
481  // Init output variable:
482  $result = array();
483  $result['type'] = 'ALTERTABLE';
484  // Get table:
485  $hasBackquote = $this->nextPart($parseString, '^(`)') === '`';
486  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)' . ($hasBackquote ? '`' : '') . '[[:space:]]+');
487  if ($hasBackquote && $this->nextPart($parseString, '^(`)') !== '`') {
488  return $this->parseError('No end backquote found!', $parseString);
489  }
490  if ($result['TABLE']) {
491  if ($result['action'] = $this->nextPart($parseString, '^(CHANGE|DROP[[:space:]]+KEY|DROP[[:space:]]+PRIMARY[[:space:]]+KEY|ADD[[:space:]]+KEY|ADD[[:space:]]+PRIMARY[[:space:]]+KEY|ADD[[:space:]]+UNIQUE|DROP|ADD|RENAME|DEFAULT[[:space:]]+CHARACTER[[:space:]]+SET|ENGINE)([[:space:]]+|\\(|=)')) {
492  $actionKey = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $result['action']));
493  // Getting field:
494  if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('ADDPRIMARYKEY,DROPPRIMARYKEY,ENGINE', $actionKey) || ($fieldKey = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+'))) {
495  switch ($actionKey) {
496  case 'ADD':
497  $result['FIELD'] = $fieldKey;
498  $result['definition'] = $this->parseFieldDef($parseString);
499  if ($this->parse_error) {
500  return $this->parse_error;
501  }
502  break;
503  case 'DROP':
504 
505  case 'RENAME':
506  $result['FIELD'] = $fieldKey;
507  break;
508  case 'CHANGE':
509  $result['FIELD'] = $fieldKey;
510  if ($result['newField'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+')) {
511  $result['definition'] = $this->parseFieldDef($parseString);
512  if ($this->parse_error) {
513  return $this->parse_error;
514  }
515  } else {
516  return $this->parseError('No NEW field name found', $parseString);
517  }
518  break;
519  case 'ADDKEY':
520 
521  case 'ADDPRIMARYKEY':
522 
523  case 'ADDUNIQUE':
524  $result['KEY'] = $fieldKey;
525  $result['fields'] = $this->getValue($parseString, '_LIST', 'INDEX');
526  if ($this->parse_error) {
527  return $this->parse_error;
528  }
529  break;
530  case 'DROPKEY':
531  $result['KEY'] = $fieldKey;
532  break;
533  case 'DROPPRIMARYKEY':
534  // ??? todo!
535  break;
536  case 'DEFAULTCHARACTERSET':
537  $result['charset'] = $fieldKey;
538  break;
539  case 'ENGINE':
540  $result['engine'] = $this->nextPart($parseString, '^=[[:space:]]*([[:alnum:]]+)[[:space:]]+', TRUE);
541  break;
542  }
543  } else {
544  return $this->parseError('No field name found', $parseString);
545  }
546  } else {
547  return $this->parseError('No action CHANGE, DROP or ADD found!', $parseString);
548  }
549  } else {
550  return $this->parseError('No table found!', $parseString);
551  }
552  // Should be no more content now:
553  if ($parseString) {
554  return $this->parseError('Still content in clause after parsing!', $parseString);
555  }
556  return $result;
557  }
558 
565  protected function parseDROPTABLE($parseString) {
566  // Removing DROP TABLE
567  $parseString = $this->trimSQL($parseString);
568  $parseString = ltrim(substr(ltrim(substr($parseString, 4)), 5));
569  // Init output variable:
570  $result = array();
571  $result['type'] = 'DROPTABLE';
572  // IF EXISTS
573  $result['ifExists'] = $this->nextPart($parseString, '^(IF[[:space:]]+EXISTS[[:space:]]+)');
574  // Get table:
575  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
576  if ($result['TABLE']) {
577  // Should be no more content now:
578  if ($parseString) {
579  return $this->parseError('Still content in clause after parsing!', $parseString);
580  }
581  return $result;
582  } else {
583  return $this->parseError('No table found!', $parseString);
584  }
585  }
586 
593  protected function parseCREATEDATABASE($parseString) {
594  // Removing CREATE DATABASE
595  $parseString = $this->trimSQL($parseString);
596  $parseString = ltrim(substr(ltrim(substr($parseString, 6)), 8));
597  // Init output variable:
598  $result = array();
599  $result['type'] = 'CREATEDATABASE';
600  // Get table:
601  $result['DATABASE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
602  if ($result['DATABASE']) {
603  // Should be no more content now:
604  if ($parseString) {
605  return $this->parseError('Still content in clause after parsing!', $parseString);
606  }
607  return $result;
608  } else {
609  return $this->parseError('No database found!', $parseString);
610  }
611  }
612 
619  protected function parseTRUNCATETABLE($parseString) {
620  // Removing TRUNCATE TABLE
621  $parseString = $this->trimSQL($parseString);
622  $parseString = ltrim(substr(ltrim(substr($parseString, 8)), 5));
623  // Init output variable:
624  $result = array();
625  $result['type'] = 'TRUNCATETABLE';
626  // Get table:
627  $result['TABLE'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
628  if ($result['TABLE']) {
629  // Should be no more content now:
630  if ($parseString) {
631  return $this->parseError('Still content in clause after parsing!', $parseString);
632  }
633  return $result;
634  } else {
635  return $this->parseError('No table found!', $parseString);
636  }
637  }
638 
639  /**************************************
640  *
641  * SQL Parsing, helper functions for parts of queries
642  *
643  **************************************/
654  public function parseFieldList(&$parseString, $stopRegex = '') {
655  $stack = array();
656  // Contains the parsed content
657  if (strlen($parseString) == 0) {
658  return $stack;
659  }
660  // FIXME - should never happen, why does it?
661  // Pointer to positions in $stack
662  $pnt = 0;
663  // Indicates the parenthesis level we are at.
664  $level = 0;
665  // Recursivity brake.
666  $loopExit = 0;
667  // Prepare variables:
668  $parseString = $this->trimSQL($parseString);
669  $this->lastStopKeyWord = '';
670  $this->parse_error = '';
671  // Parse any SQL hint / comments
672  $stack[$pnt]['comments'] = $this->nextPart($parseString, '^(\\/\\*.*\\*\\/)');
673  // $parseString is continuously shortened by the process and we keep parsing it till it is zero:
674  while (strlen($parseString)) {
675  // Checking if we are inside / outside parenthesis (in case of a function like count(), max(), min() etc...):
676  // Inside parenthesis here (does NOT detect if values in quotes are used, the only token is ")" or "("):
677  if ($level > 0) {
678  // Accumulate function content until next () parenthesis:
679  $funcContent = $this->nextPart($parseString, '^([^()]*.)');
680  $stack[$pnt]['func_content.'][] = array(
681  'level' => $level,
682  'func_content' => substr($funcContent, 0, -1)
683  );
684  $stack[$pnt]['func_content'] .= $funcContent;
685  // Detecting ( or )
686  switch (substr($stack[$pnt]['func_content'], -1)) {
687  case '(':
688  $level++;
689  break;
690  case ')':
691  $level--;
692  // If this was the last parenthesis:
693  if (!$level) {
694  $stack[$pnt]['func_content'] = substr($stack[$pnt]['func_content'], 0, -1);
695  // Remove any whitespace after the parenthesis.
696  $parseString = ltrim($parseString);
697  }
698  break;
699  }
700  } else {
701  // Outside parenthesis, looking for next field:
702  // Looking for a flow-control construct (only known constructs supported)
703  if (preg_match('/^case([[:space:]][[:alnum:]\\*._]+)?[[:space:]]when/i', $parseString)) {
704  $stack[$pnt]['type'] = 'flow-control';
705  $stack[$pnt]['flow-control'] = $this->parseCaseStatement($parseString);
706  // Looking for "AS" alias:
707  if ($as = $this->nextPart($parseString, '^(AS)[[:space:]]+')) {
708  $stack[$pnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)(,|[[:space:]]+)');
709  $stack[$pnt]['as_keyword'] = $as;
710  }
711  } else {
712  // Looking for a known function (only known functions supported)
713  $func = $this->nextPart($parseString, '^(count|max|min|floor|sum|avg)[[:space:]]*\\(');
714  if ($func) {
715  // Strip off "("
716  $parseString = trim(substr($parseString, 1));
717  $stack[$pnt]['type'] = 'function';
718  $stack[$pnt]['function'] = $func;
719  // increse parenthesis level counter.
720  $level++;
721  } else {
722  $stack[$pnt]['distinct'] = $this->nextPart($parseString, '^(distinct[[:space:]]+)');
723  // Otherwise, look for regular fieldname:
724  if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]\\*._]+)(,|[[:space:]]+)')) !== '') {
725  $stack[$pnt]['type'] = 'field';
726  // Explode fieldname into field and table:
727  $tableField = explode('.', $fieldName, 2);
728  if (count($tableField) == 2) {
729  $stack[$pnt]['table'] = $tableField[0];
730  $stack[$pnt]['field'] = $tableField[1];
731  } else {
732  $stack[$pnt]['table'] = '';
733  $stack[$pnt]['field'] = $tableField[0];
734  }
735  } else {
736  return $this->parseError('No field name found as expected in parseFieldList()', $parseString);
737  }
738  }
739  }
740  }
741  // After a function or field we look for "AS" alias and a comma to separate to the next field in the list:
742  if (!$level) {
743  // Looking for "AS" alias:
744  if ($as = $this->nextPart($parseString, '^(AS)[[:space:]]+')) {
745  $stack[$pnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)(,|[[:space:]]+)');
746  $stack[$pnt]['as_keyword'] = $as;
747  }
748  // Looking for "ASC" or "DESC" keywords (for ORDER BY)
749  if ($sDir = $this->nextPart($parseString, '^(ASC|DESC)([[:space:]]+|,)')) {
750  $stack[$pnt]['sortDir'] = $sDir;
751  }
752  // Looking for stop-keywords:
753  if ($stopRegex && ($this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex))) {
754  $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord));
755  return $stack;
756  }
757  // Looking for comma (since the stop-keyword did not trigger a return...)
758  if (strlen($parseString) && !$this->nextPart($parseString, '^(,)')) {
759  return $this->parseError('No comma found as expected in parseFieldList()', $parseString);
760  }
761  // Increasing pointer:
762  $pnt++;
763  }
764  // Check recursivity brake:
765  $loopExit++;
766  if ($loopExit > 500) {
767  return $this->parseError('More than 500 loops, exiting prematurely in parseFieldList()...', $parseString);
768  }
769  }
770  // Return result array:
771  return $stack;
772  }
773 
782  protected function parseCaseStatement(&$parseString) {
783  $result = array();
784  $result['type'] = $this->nextPart($parseString, '^(case)[[:space:]]+');
785  if (!preg_match('/^when[[:space:]]+/i', $parseString)) {
786  $value = $this->getValue($parseString);
787  if (!(isset($value[1]) || is_numeric($value[0]))) {
788  $result['case_field'] = $value[0];
789  } else {
790  $result['case_value'] = $value;
791  }
792  }
793  $result['when'] = array();
794  while ($this->nextPart($parseString, '^(when)[[:space:]]')) {
795  $when = array();
796  $when['when_value'] = $this->parseWhereClause($parseString, '^(then)[[:space:]]+');
797  $when['then_value'] = $this->getValue($parseString);
798  $result['when'][] = $when;
799  }
800  if ($this->nextPart($parseString, '^(else)[[:space:]]+')) {
801  $result['else'] = $this->getValue($parseString);
802  }
803  if (!$this->nextPart($parseString, '^(end)[[:space:]]+')) {
804  return $this->parseError('No "end" keyword found as expected in parseCaseStatement()', $parseString);
805  }
806  return $result;
807  }
808 
818  public function parseFromTables(&$parseString, $stopRegex = '') {
819  // Prepare variables:
820  $parseString = $this->trimSQL($parseString);
821  $this->lastStopKeyWord = '';
822  $this->parse_error = '';
823  // Contains the parsed content
824  $stack = array();
825  // Pointer to positions in $stack
826  $pnt = 0;
827  // Recursivity brake.
828  $loopExit = 0;
829  // $parseString is continously shortend by the process and we keep parsing it till it is zero:
830  while (strlen($parseString)) {
831  // Looking for the table:
832  if ($stack[$pnt]['table'] = $this->nextPart($parseString, '^([[:alnum:]_]+)(,|[[:space:]]+)')) {
833  // Looking for stop-keywords before fetching potential table alias:
834  if ($stopRegex && ($this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex))) {
835  $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord));
836  return $stack;
837  }
838  if (!preg_match('/^(LEFT|RIGHT|JOIN|INNER)[[:space:]]+/i', $parseString)) {
839  $stack[$pnt]['as_keyword'] = $this->nextPart($parseString, '^(AS[[:space:]]+)');
840  $stack[$pnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]*');
841  }
842  } else {
843  return $this->parseError('No table name found as expected in parseFromTables()!', $parseString);
844  }
845  // Looking for JOIN
846  $joinCnt = 0;
847  while ($join = $this->nextPart($parseString, '^(LEFT[[:space:]]+JOIN|LEFT[[:space:]]+OUTER[[:space:]]+JOIN|RIGHT[[:space:]]+JOIN|RIGHT[[:space:]]+OUTER[[:space:]]+JOIN|INNER[[:space:]]+JOIN|JOIN)[[:space:]]+')) {
848  $stack[$pnt]['JOIN'][$joinCnt]['type'] = $join;
849  if ($stack[$pnt]['JOIN'][$joinCnt]['withTable'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+', 1)) {
850  if (!preg_match('/^ON[[:space:]]+/i', $parseString)) {
851  $stack[$pnt]['JOIN'][$joinCnt]['as_keyword'] = $this->nextPart($parseString, '^(AS[[:space:]]+)');
852  $stack[$pnt]['JOIN'][$joinCnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
853  }
854  if (!$this->nextPart($parseString, '^(ON[[:space:]]+)')) {
855  return $this->parseError('No join condition found in parseFromTables()!', $parseString);
856  }
857  $stack[$pnt]['JOIN'][$joinCnt]['ON'] = array();
858  $condition = array('operator' => '');
859  $parseCondition = TRUE;
860  while ($parseCondition) {
861  if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)[[:space:]]*(<=|>=|<|>|=|!=)')) !== '') {
862  // Parse field name into field and table:
863  $tableField = explode('.', $fieldName, 2);
864  $condition['left'] = array();
865  if (count($tableField) == 2) {
866  $condition['left']['table'] = $tableField[0];
867  $condition['left']['field'] = $tableField[1];
868  } else {
869  $condition['left']['table'] = '';
870  $condition['left']['field'] = $tableField[0];
871  }
872  } else {
873  return $this->parseError('No join field found in parseFromTables()!', $parseString);
874  }
875  // Find "comparator":
876  $condition['comparator'] = $this->nextPart($parseString, '^(<=|>=|<|>|=|!=)');
877  if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)')) !== '') {
878  // Parse field name into field and table:
879  $tableField = explode('.', $fieldName, 2);
880  $condition['right'] = array();
881  if (count($tableField) == 2) {
882  $condition['right']['table'] = $tableField[0];
883  $condition['right']['field'] = $tableField[1];
884  } else {
885  $condition['right']['table'] = '';
886  $condition['right']['field'] = $tableField[0];
887  }
888  } elseif ($value = $this->getValue($parseString)) {
889  $condition['right']['value'] = $value;
890  } else {
891  return $this->parseError('No join field found in parseFromTables()!', $parseString);
892  }
893  $stack[$pnt]['JOIN'][$joinCnt]['ON'][] = $condition;
894  if (($operator = $this->nextPart($parseString, '^(AND|OR)')) !== '') {
895  $condition = array('operator' => $operator);
896  } else {
897  $parseCondition = FALSE;
898  }
899  }
900  $joinCnt++;
901  } else {
902  return $this->parseError('No join table found in parseFromTables()!', $parseString);
903  }
904  }
905  // Looking for stop-keywords:
906  if ($stopRegex && ($this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex))) {
907  $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord));
908  return $stack;
909  }
910  // Looking for comma:
911  if (strlen($parseString) && !$this->nextPart($parseString, '^(,)')) {
912  return $this->parseError('No comma found as expected in parseFromTables()', $parseString);
913  }
914  // Increasing pointer:
915  $pnt++;
916  // Check recursivity brake:
917  $loopExit++;
918  if ($loopExit > 500) {
919  return $this->parseError('More than 500 loops, exiting prematurely in parseFromTables()...', $parseString);
920  }
921  }
922  // Return result array:
923  return $stack;
924  }
925 
935  public function parseWhereClause(&$parseString, $stopRegex = '', array &$parameterReferences = array()) {
936  // Prepare variables:
937  $parseString = $this->trimSQL($parseString);
938  $this->lastStopKeyWord = '';
939  $this->parse_error = '';
940  // Contains the parsed content
941  $stack = array(0 => array());
942  // Pointer to positions in $stack
943  $pnt = array(0 => 0);
944  // Determines parenthesis level
945  $level = 0;
946  // Recursivity brake.
947  $loopExit = 0;
948  // $parseString is continuously shortened by the process and we keep parsing it till it is zero:
949  while (strlen($parseString)) {
950  // Look for next parenthesis level:
951  $newLevel = $this->nextPart($parseString, '^([(])');
952  // If new level is started, manage stack/pointers:
953  if ($newLevel == '(') {
954  // Increase level
955  $level++;
956  // Reset pointer for this level
957  $pnt[$level] = 0;
958  // Reset stack for this level
959  $stack[$level] = array();
960  } else {
961  // If no new level is started, just parse the current level:
962  // Find "modifier", eg. "NOT or !"
963  $stack[$level][$pnt[$level]]['modifier'] = trim($this->nextPart($parseString, '^(!|NOT[[:space:]]+)'));
964  // See if condition is EXISTS with a subquery
965  if (preg_match('/^EXISTS[[:space:]]*[(]/i', $parseString)) {
966  $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(EXISTS)[[:space:]]*');
967  // Strip off "("
968  $parseString = trim(substr($parseString, 1));
969  $stack[$level][$pnt[$level]]['func']['subquery'] = $this->parseSELECT($parseString, $parameterReferences);
970  // Seek to new position in parseString after parsing of the subquery
971  $parseString = $stack[$level][$pnt[$level]]['func']['subquery']['parseString'];
972  unset($stack[$level][$pnt[$level]]['func']['subquery']['parseString']);
973  if (!$this->nextPart($parseString, '^([)])')) {
974  return 'No ) parenthesis at end of subquery';
975  }
976  } else {
977  // See if LOCATE function is found
978  if (preg_match('/^LOCATE[[:space:]]*[(]/i', $parseString)) {
979  $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(LOCATE)[[:space:]]*');
980  // Strip off "("
981  $parseString = trim(substr($parseString, 1));
982  $stack[$level][$pnt[$level]]['func']['substr'] = $this->getValue($parseString);
983  if (!$this->nextPart($parseString, '^(,)')) {
984  return $this->parseError('No comma found as expected in parseWhereClause()', $parseString);
985  }
986  if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]\\*._]+)[[:space:]]*')) {
987  // Parse field name into field and table:
988  $tableField = explode('.', $fieldName, 2);
989  if (count($tableField) == 2) {
990  $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0];
991  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1];
992  } else {
993  $stack[$level][$pnt[$level]]['func']['table'] = '';
994  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0];
995  }
996  } else {
997  return $this->parseError('No field name found as expected in parseWhereClause()', $parseString);
998  }
999  if ($this->nextPart($parseString, '^(,)')) {
1000  $stack[$level][$pnt[$level]]['func']['pos'] = $this->getValue($parseString);
1001  }
1002  if (!$this->nextPart($parseString, '^([)])')) {
1003  return $this->parseError('No ) parenthesis at end of function', $parseString);
1004  }
1005  } elseif (preg_match('/^IFNULL[[:space:]]*[(]/i', $parseString)) {
1006  $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(IFNULL)[[:space:]]*');
1007  $parseString = trim(substr($parseString, 1));
1008  // Strip off "("
1009  if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]\\*._]+)[[:space:]]*')) {
1010  // Parse field name into field and table:
1011  $tableField = explode('.', $fieldName, 2);
1012  if (count($tableField) == 2) {
1013  $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0];
1014  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1];
1015  } else {
1016  $stack[$level][$pnt[$level]]['func']['table'] = '';
1017  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0];
1018  }
1019  } else {
1020  return $this->parseError('No field name found as expected in parseWhereClause()', $parseString);
1021  }
1022  if ($this->nextPart($parseString, '^(,)')) {
1023  $stack[$level][$pnt[$level]]['func']['default'] = $this->getValue($parseString);
1024  }
1025  if (!$this->nextPart($parseString, '^([)])')) {
1026  return $this->parseError('No ) parenthesis at end of function', $parseString);
1027  }
1028  } elseif (preg_match('/^CAST[[:space:]]*[(]/i', $parseString)) {
1029  $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(CAST)[[:space:]]*');
1030  $parseString = trim(substr($parseString, 1));
1031  // Strip off "("
1032  if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]\\*._]+)[[:space:]]*')) {
1033  // Parse field name into field and table:
1034  $tableField = explode('.', $fieldName, 2);
1035  if (count($tableField) === 2) {
1036  $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0];
1037  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1];
1038  } else {
1039  $stack[$level][$pnt[$level]]['func']['table'] = '';
1040  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0];
1041  }
1042  } else {
1043  return $this->parseError('No field name found as expected in parseWhereClause()', $parseString);
1044  }
1045  if ($this->nextPart($parseString, '^([[:space:]]*AS[[:space:]]*)')) {
1046  $stack[$level][$pnt[$level]]['func']['datatype'] = $this->getValue($parseString);
1047  }
1048  if (!$this->nextPart($parseString, '^([)])')) {
1049  return $this->parseError('No ) parenthesis at end of function', $parseString);
1050  }
1051  } elseif (preg_match('/^FIND_IN_SET[[:space:]]*[(]/i', $parseString)) {
1052  $stack[$level][$pnt[$level]]['func']['type'] = $this->nextPart($parseString, '^(FIND_IN_SET)[[:space:]]*');
1053  // Strip off "("
1054  $parseString = trim(substr($parseString, 1));
1055  if ($str = $this->getValue($parseString)) {
1056  $stack[$level][$pnt[$level]]['func']['str'] = $str;
1057  if ($fieldName = $this->nextPart($parseString, '^,[[:space:]]*([[:alnum:]._]+)[[:space:]]*', TRUE)) {
1058  // Parse field name into field and table:
1059  $tableField = explode('.', $fieldName, 2);
1060  if (count($tableField) == 2) {
1061  $stack[$level][$pnt[$level]]['func']['table'] = $tableField[0];
1062  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[1];
1063  } else {
1064  $stack[$level][$pnt[$level]]['func']['table'] = '';
1065  $stack[$level][$pnt[$level]]['func']['field'] = $tableField[0];
1066  }
1067  } else {
1068  return $this->parseError('No field name found as expected in parseWhereClause()', $parseString);
1069  }
1070  if (!$this->nextPart($parseString, '^([)])')) {
1071  return $this->parseError('No ) parenthesis at end of function', $parseString);
1072  }
1073  } else {
1074  return $this->parseError('No item to look for found as expected in parseWhereClause()', $parseString);
1075  }
1076  } else {
1077  // Support calculated value only for:
1078  // - "&" (boolean AND)
1079  // - "+" (addition)
1080  // - "-" (substraction)
1081  // - "*" (multiplication)
1082  // - "/" (division)
1083  // - "%" (modulo)
1084  $calcOperators = '&|\\+|-|\\*|\\/|%';
1085  // Fieldname:
1086  if (($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)([[:space:]]+|' . $calcOperators . '|<=|>=|<|>|=|!=|IS)')) !== '') {
1087  // Parse field name into field and table:
1088  $tableField = explode('.', $fieldName, 2);
1089  if (count($tableField) == 2) {
1090  $stack[$level][$pnt[$level]]['table'] = $tableField[0];
1091  $stack[$level][$pnt[$level]]['field'] = $tableField[1];
1092  } else {
1093  $stack[$level][$pnt[$level]]['table'] = '';
1094  $stack[$level][$pnt[$level]]['field'] = $tableField[0];
1095  }
1096  } else {
1097  return $this->parseError('No field name found as expected in parseWhereClause()', $parseString);
1098  }
1099  // See if the value is calculated:
1100  $stack[$level][$pnt[$level]]['calc'] = $this->nextPart($parseString, '^(' . $calcOperators . ')');
1101  if (strlen($stack[$level][$pnt[$level]]['calc'])) {
1102  // Finding value for calculation:
1103  $calc_value = $this->getValue($parseString);
1104  $stack[$level][$pnt[$level]]['calc_value'] = $calc_value;
1105  if (count($calc_value) == 1 && is_string($calc_value[0])) {
1106  // Value is a field, store it to allow DBAL to post-process it (quoting, remapping)
1107  $tableField = explode('.', $calc_value[0], 2);
1108  if (count($tableField) == 2) {
1109  $stack[$level][$pnt[$level]]['calc_table'] = $tableField[0];
1110  $stack[$level][$pnt[$level]]['calc_field'] = $tableField[1];
1111  } else {
1112  $stack[$level][$pnt[$level]]['calc_table'] = '';
1113  $stack[$level][$pnt[$level]]['calc_field'] = $tableField[0];
1114  }
1115  }
1116  }
1117  }
1118  // Find "comparator":
1119  $comparatorPatterns = array(
1120  '<=',
1121  '>=',
1122  '<>',
1123  '<',
1124  '>',
1125  '=',
1126  '!=',
1127  'NOT[[:space:]]+IN',
1128  'IN',
1129  'NOT[[:space:]]+LIKE[[:space:]]+BINARY',
1130  'LIKE[[:space:]]+BINARY',
1131  'NOT[[:space:]]+LIKE',
1132  'LIKE',
1133  'IS[[:space:]]+NOT',
1134  'IS',
1135  'BETWEEN',
1136  'NOT[[:space]]+BETWEEN'
1137  );
1138  $stack[$level][$pnt[$level]]['comparator'] = $this->nextPart($parseString, '^(' . implode('|', $comparatorPatterns) . ')');
1139  if (strlen($stack[$level][$pnt[$level]]['comparator'])) {
1140  if (preg_match('/^CONCAT[[:space:]]*\\(/', $parseString)) {
1141  $this->nextPart($parseString, '^(CONCAT[[:space:]]?[(])');
1142  $values = array(
1143  'operator' => 'CONCAT',
1144  'args' => array()
1145  );
1146  $cnt = 0;
1147  while ($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)')) {
1148  // Parse field name into field and table:
1149  $tableField = explode('.', $fieldName, 2);
1150  if (count($tableField) == 2) {
1151  $values['args'][$cnt]['table'] = $tableField[0];
1152  $values['args'][$cnt]['field'] = $tableField[1];
1153  } else {
1154  $values['args'][$cnt]['table'] = '';
1155  $values['args'][$cnt]['field'] = $tableField[0];
1156  }
1157  // Looking for comma:
1158  $this->nextPart($parseString, '^(,)');
1159  $cnt++;
1160  }
1161  // Look for ending parenthesis:
1162  $this->nextPart($parseString, '([)])');
1163  $stack[$level][$pnt[$level]]['value'] = $values;
1164  } else {
1165  if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('IN,NOT IN', $stack[$level][$pnt[$level]]['comparator']) && preg_match('/^[(][[:space:]]*SELECT[[:space:]]+/', $parseString)) {
1166  $this->nextPart($parseString, '^([(])');
1167  $stack[$level][$pnt[$level]]['subquery'] = $this->parseSELECT($parseString, $parameterReferences);
1168  // Seek to new position in parseString after parsing of the subquery
1169  $parseString = $stack[$level][$pnt[$level]]['subquery']['parseString'];
1170  unset($stack[$level][$pnt[$level]]['subquery']['parseString']);
1171  if (!$this->nextPart($parseString, '^([)])')) {
1172  return 'No ) parenthesis at end of subquery';
1173  }
1174  } else {
1175  if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('BETWEEN,NOT BETWEEN', $stack[$level][$pnt[$level]]['comparator'])) {
1176  $stack[$level][$pnt[$level]]['values'] = array();
1177  $stack[$level][$pnt[$level]]['values'][0] = $this->getValue($parseString);
1178  if (!$this->nextPart($parseString, '^(AND)')) {
1179  return $this->parseError('No AND operator found as expected in parseWhereClause()', $parseString);
1180  }
1181  $stack[$level][$pnt[$level]]['values'][1] = $this->getValue($parseString);
1182  } else {
1183  // Finding value for comparator:
1184  $stack[$level][$pnt[$level]]['value'] = &$this->getValueOrParameter($parseString, $stack[$level][$pnt[$level]]['comparator'], '', $parameterReferences);
1185  if ($this->parse_error) {
1186  return $this->parse_error;
1187  }
1188  }
1189  }
1190  }
1191  }
1192  }
1193  // Finished, increase pointer:
1194  $pnt[$level]++;
1195  // Checking if we are back to level 0 and we should still decrease level,
1196  // meaning we were probably parsing as subquery and should return here:
1197  if ($level === 0 && preg_match('/^[)]/', $parseString)) {
1198  // Return the stacks lowest level:
1199  return $stack[0];
1200  }
1201  // Checking if we are back to level 0 and we should still decrease level,
1202  // meaning we were probably parsing a subquery and should return here:
1203  if ($level === 0 && preg_match('/^[)]/', $parseString)) {
1204  // Return the stacks lowest level:
1205  return $stack[0];
1206  }
1207  // Checking if the current level is ended, in that case do stack management:
1208  while ($this->nextPart($parseString, '^([)])')) {
1209  $level--;
1210  // Decrease level:
1211  // Copy stack
1212  $stack[$level][$pnt[$level]]['sub'] = $stack[$level + 1];
1213  // Increase pointer of the new level
1214  $pnt[$level]++;
1215  // Make recursivity check:
1216  $loopExit++;
1217  if ($loopExit > 500) {
1218  return $this->parseError('More than 500 loops (in search for exit parenthesis), exiting prematurely in parseWhereClause()...', $parseString);
1219  }
1220  }
1221  // Detecting the operator for the next level:
1222  $op = $this->nextPart($parseString, '^(AND[[:space:]]+NOT|&&[[:space:]]+NOT|OR[[:space:]]+NOT|OR[[:space:]]+NOT|\\|\\|[[:space:]]+NOT|AND|&&|OR|\\|\\|)(\\(|[[:space:]]+)');
1223  if ($op) {
1224  // Normalize boolean operator
1225  $op = str_replace(array('&&', '||'), array('AND', 'OR'), $op);
1226  $stack[$level][$pnt[$level]]['operator'] = $op;
1227  } elseif (strlen($parseString)) {
1228  // Looking for stop-keywords:
1229  if ($stopRegex && ($this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex))) {
1230  $this->lastStopKeyWord = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $this->lastStopKeyWord));
1231  return $stack[0];
1232  } else {
1233  return $this->parseError('No operator, but parsing not finished in parseWhereClause().', $parseString);
1234  }
1235  }
1236  }
1237  // Make recursivity check:
1238  $loopExit++;
1239  if ($loopExit > 500) {
1240  return $this->parseError('More than 500 loops, exiting prematurely in parseWhereClause()...', $parseString);
1241  }
1242  }
1243  // Return the stacks lowest level:
1244  return $stack[0];
1245  }
1246 
1255  public function parseFieldDef(&$parseString, $stopRegex = '') {
1256  // Prepare variables:
1257  $parseString = $this->trimSQL($parseString);
1258  $this->lastStopKeyWord = '';
1259  $this->parse_error = '';
1260  $result = array();
1261  // Field type:
1262  if ($result['fieldType'] = $this->nextPart($parseString, '^(int|smallint|tinyint|mediumint|bigint|double|numeric|decimal|float|varchar|char|text|tinytext|mediumtext|longtext|blob|tinyblob|mediumblob|longblob)([[:space:],]+|\\()')) {
1263  // Looking for value:
1264  if ($parseString[0] === '(') {
1265  $parseString = substr($parseString, 1);
1266  if ($result['value'] = $this->nextPart($parseString, '^([^)]*)')) {
1267  $parseString = ltrim(substr($parseString, 1));
1268  } else {
1269  return $this->parseError('No end-parenthesis for value found in parseFieldDef()!', $parseString);
1270  }
1271  }
1272  // Looking for keywords
1273  while ($keyword = $this->nextPart($parseString, '^(DEFAULT|NOT[[:space:]]+NULL|AUTO_INCREMENT|UNSIGNED)([[:space:]]+|,|\\))')) {
1274  $keywordCmp = strtoupper(str_replace(array(' ', TAB, CR, LF), '', $keyword));
1275  $result['featureIndex'][$keywordCmp]['keyword'] = $keyword;
1276  switch ($keywordCmp) {
1277  case 'DEFAULT':
1278  $result['featureIndex'][$keywordCmp]['value'] = $this->getValue($parseString);
1279  break;
1280  }
1281  }
1282  } else {
1283  return $this->parseError('Field type unknown in parseFieldDef()!', $parseString);
1284  }
1285  return $result;
1286  }
1287 
1288  /************************************
1289  *
1290  * Parsing: Helper functions
1291  *
1292  ************************************/
1302  protected function nextPart(&$parseString, $regex, $trimAll = FALSE) {
1303  $reg = array();
1304  // Adding space char because [[:space:]]+ is often a requirement in regex's
1305  if (preg_match('/' . $regex . '/i', $parseString . ' ', $reg)) {
1306  $parseString = ltrim(substr($parseString, strlen($reg[$trimAll ? 0 : 1])));
1307  return $reg[1];
1308  }
1309  // No match found
1310  return '';
1311  }
1312 
1324  protected function &getValueOrParameter(&$parseString, $comparator = '', $mode = '', array &$parameterReferences = array()) {
1325  $parameter = $this->nextPart($parseString, '^(\\:[[:alnum:]_]+|\\?)');
1326  if ($parameter === '?') {
1327  if (!isset($parameterReferences['?'])) {
1328  $parameterReferences['?'] = array();
1329  }
1330  $value = array('?');
1331  $parameterReferences['?'][] = &$value;
1332  } elseif ($parameter !== '') {
1333  // named parameter
1334  if (isset($parameterReferences[$parameter])) {
1335  // Use the same reference as last time we encountered this parameter
1336  $value = &$parameterReferences[$parameter];
1337  } else {
1338  $value = array($parameter);
1339  $parameterReferences[$parameter] = &$value;
1340  }
1341  } else {
1342  $value = $this->getValue($parseString, $comparator, $mode);
1343  }
1344  return $value;
1345  }
1346 
1355  protected function getValue(&$parseString, $comparator = '', $mode = '') {
1356  $value = '';
1357  if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('NOTIN,IN,_LIST', strtoupper(str_replace(array(' ', LF, CR, TAB), '', $comparator)))) {
1358  // List of values:
1359  if ($this->nextPart($parseString, '^([(])')) {
1360  $listValues = array();
1361  $comma = ',';
1362  while ($comma == ',') {
1363  $listValues[] = $this->getValue($parseString);
1364  if ($mode === 'INDEX') {
1365  // Remove any length restriction on INDEX definition
1366  $this->nextPart($parseString, '^([(]\\d+[)])');
1367  }
1368  $comma = $this->nextPart($parseString, '^([,])');
1369  }
1370  $out = $this->nextPart($parseString, '^([)])');
1371  if ($out) {
1372  if ($comparator == '_LIST') {
1373  $kVals = array();
1374  foreach ($listValues as $vArr) {
1375  $kVals[] = $vArr[0];
1376  }
1377  return $kVals;
1378  } else {
1379  return $listValues;
1380  }
1381  } else {
1382  return array($this->parseError('No ) parenthesis in list', $parseString));
1383  }
1384  } else {
1385  return array($this->parseError('No ( parenthesis starting the list', $parseString));
1386  }
1387  } else {
1388  // Just plain string value, in quotes or not:
1389  // Quote?
1390  $firstChar = $parseString[0];
1391  switch ($firstChar) {
1392  case '"':
1393  $value = array($this->getValueInQuotes($parseString, '"'), '"');
1394  break;
1395  case '\'':
1396  $value = array($this->getValueInQuotes($parseString, '\''), '\'');
1397  break;
1398  default:
1399  $reg = array();
1400  if (preg_match('/^([[:alnum:]._-]+)/i', $parseString, $reg)) {
1401  $parseString = ltrim(substr($parseString, strlen($reg[0])));
1402  $value = array($reg[1]);
1403  }
1404  }
1405  }
1406  return $value;
1407  }
1408 
1417  protected function getValueInQuotes(&$parseString, $quote) {
1418  $parts = explode($quote, substr($parseString, 1));
1419  $buffer = '';
1420  foreach ($parts as $k => $v) {
1421  $buffer .= $v;
1422  $reg = array();
1423  preg_match('/\\\\$/', $v, $reg);
1424  if ($reg and strlen($reg[0]) % 2) {
1425  $buffer .= $quote;
1426  } else {
1427  $parseString = ltrim(substr($parseString, strlen($buffer) + 2));
1428  return $this->parseStripslashes($buffer);
1429  }
1430  }
1431  }
1432 
1440  protected function parseStripslashes($str) {
1441  $search = array('\\\\', '\\\'', '\\"', '\0', '\n', '\r', '\Z');
1442  $replace = array('\\', '\'', '"', "\x00", "\x0a", "\x0d", "\x1a");
1443 
1444  return str_replace($search, $replace, $str);
1445  }
1446 
1454  protected function compileAddslashes($str) {
1455  $search = array('\\', '\'', '"', "\x00", "\x0a", "\x0d", "\x1a");
1456  $replace = array('\\\\', '\\\'', '\\"', '\0', '\n', '\r', '\Z');
1457 
1458  return str_replace($search, $replace, $str);
1459  }
1460 
1468  protected function parseError($msg, $restQuery) {
1469  $this->parse_error = 'SQL engine parse ERROR: ' . $msg . ': near "' . substr($restQuery, 0, 50) . '"';
1470  return $this->parse_error;
1471  }
1472 
1482  protected function trimSQL($str) {
1483  return rtrim(rtrim(trim($str), ';')) . ' ';
1484  }
1485 
1486  /*************************
1487  *
1488  * Compiling queries
1489  *
1490  *************************/
1498  public function compileSQL($components) {
1499  switch ($components['type']) {
1500  case 'SELECT':
1501  $query = $this->compileSELECT($components);
1502  break;
1503  case 'UPDATE':
1504  $query = $this->compileUPDATE($components);
1505  break;
1506  case 'INSERT':
1507  $query = $this->compileINSERT($components);
1508  break;
1509  case 'DELETE':
1510  $query = $this->compileDELETE($components);
1511  break;
1512  case 'EXPLAIN':
1513  $query = 'EXPLAIN ' . $this->compileSELECT($components);
1514  break;
1515  case 'DROPTABLE':
1516  $query = 'DROP TABLE' . ($components['ifExists'] ? ' IF EXISTS' : '') . ' ' . $components['TABLE'];
1517  break;
1518  case 'CREATETABLE':
1519  $query = $this->compileCREATETABLE($components);
1520  break;
1521  case 'ALTERTABLE':
1522  $query = $this->compileALTERTABLE($components);
1523  break;
1524  case 'TRUNCATETABLE':
1525  $query = $this->compileTRUNCATETABLE($components);
1526  break;
1527  }
1528  return $query;
1529  }
1530 
1538  protected function compileSELECT($components) {
1539  // Initialize:
1540  $where = $this->compileWhereClause($components['WHERE']);
1541  $groupBy = $this->compileFieldList($components['GROUPBY']);
1542  $orderBy = $this->compileFieldList($components['ORDERBY']);
1543  $limit = $components['LIMIT'];
1544  // Make query:
1545  $query = 'SELECT ' . ($components['STRAIGHT_JOIN'] ?: '') . ' ' .
1546  $this->compileFieldList($components['SELECT']) .
1547  ' FROM ' . $this->compileFromTables($components['FROM']) . (strlen($where) ?
1548  ' WHERE ' . $where : '') . (strlen($groupBy) ?
1549  ' GROUP BY ' . $groupBy : '') . (strlen($orderBy) ?
1550  ' ORDER BY ' . $orderBy : '') . (strlen($limit) ?
1551  ' LIMIT ' . $limit : '');
1552  return $query;
1553  }
1554 
1562  protected function compileUPDATE($components) {
1563  // Where clause:
1564  $where = $this->compileWhereClause($components['WHERE']);
1565  // Fields
1566  $fields = array();
1567  foreach ($components['FIELDS'] as $fN => $fV) {
1568  $fields[] = $fN . '=' . $fV[1] . $this->compileAddslashes($fV[0]) . $fV[1];
1569  }
1570  // Make query:
1571  $query = 'UPDATE ' . $components['TABLE'] . ' SET ' . implode(',', $fields) .
1572  (strlen($where) ? ' WHERE ' . $where : '');
1573 
1574  return $query;
1575  }
1576 
1584  protected function compileINSERT($components) {
1585  $values = array();
1586  if (isset($components['VALUES_ONLY']) && is_array($components['VALUES_ONLY'])) {
1587  $valuesComponents = $components['EXTENDED'] === '1' ? $components['VALUES_ONLY'] : array($components['VALUES_ONLY']);
1588  $tableFields = array();
1589  } else {
1590  $valuesComponents = $components['EXTENDED'] === '1' ? $components['FIELDS'] : array($components['FIELDS']);
1591  $tableFields = array_keys($valuesComponents[0]);
1592  }
1593  foreach ($valuesComponents as $valuesComponent) {
1594  $fields = array();
1595  foreach ($valuesComponent as $fV) {
1596  $fields[] = $fV[1] . $this->compileAddslashes($fV[0]) . $fV[1];
1597  }
1598  $values[] = '(' . implode(',', $fields) . ')';
1599  }
1600  // Make query:
1601  $query = 'INSERT INTO ' . $components['TABLE'];
1602  if (count($tableFields)) {
1603  $query .= ' (' . implode(',', $tableFields) . ')';
1604  }
1605  $query .= ' VALUES ' . implode(',', $values);
1606 
1607  return $query;
1608  }
1609 
1617  protected function compileDELETE($components) {
1618  // Where clause:
1619  $where = $this->compileWhereClause($components['WHERE']);
1620  // Make query:
1621  $query = 'DELETE FROM ' . $components['TABLE'] . (strlen($where) ? ' WHERE ' . $where : '');
1622 
1623  return $query;
1624  }
1625 
1633  protected function compileCREATETABLE($components) {
1634  // Create fields and keys:
1635  $fieldsKeys = array();
1636  foreach ($components['FIELDS'] as $fN => $fCfg) {
1637  $fieldsKeys[] = $fN . ' ' . $this->compileFieldCfg($fCfg['definition']);
1638  }
1639  foreach ($components['KEYS'] as $kN => $kCfg) {
1640  if ($kN === 'PRIMARYKEY') {
1641  $fieldsKeys[] = 'PRIMARY KEY (' . implode(',', $kCfg) . ')';
1642  } elseif ($kN === 'UNIQUE') {
1643  $key = key($kCfg);
1644  $fields = current($kCfg);
1645  $fieldsKeys[] = 'UNIQUE KEY ' . $key . ' (' . implode(',', $fields) . ')';
1646  } else {
1647  $fieldsKeys[] = 'KEY ' . $kN . ' (' . implode(',', $kCfg) . ')';
1648  }
1649  }
1650  // Make query:
1651  $query = 'CREATE TABLE ' . $components['TABLE'] . ' (' .
1652  implode(',', $fieldsKeys) . ')' .
1653  ($components['engine'] ? ' ENGINE=' . $components['engine'] : '');
1654 
1655  return $query;
1656  }
1657 
1665  protected function compileALTERTABLE($components) {
1666  // Make query:
1667  $query = 'ALTER TABLE ' . $components['TABLE'] . ' ' . $components['action'] . ' ' . ($components['FIELD'] ?: $components['KEY']);
1668  // Based on action, add the final part:
1669  switch (strtoupper(str_replace(array(' ', TAB, CR, LF), '', $components['action']))) {
1670  case 'ADD':
1671  $query .= ' ' . $this->compileFieldCfg($components['definition']);
1672  break;
1673  case 'CHANGE':
1674  $query .= ' ' . $components['newField'] . ' ' . $this->compileFieldCfg($components['definition']);
1675  break;
1676  case 'DROP':
1677 
1678  case 'DROPKEY':
1679  break;
1680  case 'ADDKEY':
1681 
1682  case 'ADDPRIMARYKEY':
1683 
1684  case 'ADDUNIQUE':
1685  $query .= ' (' . implode(',', $components['fields']) . ')';
1686  break;
1687  case 'DEFAULTCHARACTERSET':
1688  $query .= $components['charset'];
1689  break;
1690  case 'ENGINE':
1691  $query .= '= ' . $components['engine'];
1692  break;
1693  }
1694  // Return query
1695  return $query;
1696  }
1697 
1705  protected function compileTRUNCATETABLE(array $components) {
1706  // Make query:
1707  $query = 'TRUNCATE TABLE ' . $components['TABLE'];
1708  // Return query
1709  return $query;
1710  }
1711 
1712  /**************************************
1713  *
1714  * Compiling queries, helper functions for parts of queries
1715  *
1716  **************************************/
1726  public function compileFieldList($selectFields, $compileComments = TRUE) {
1727  // Prepare buffer variable:
1728  $fields = '';
1729  // Traverse the selectFields if any:
1730  if (is_array($selectFields)) {
1731  $outputParts = array();
1732  foreach ($selectFields as $k => $v) {
1733  // Detecting type:
1734  switch ($v['type']) {
1735  case 'function':
1736  $outputParts[$k] = $v['function'] . '(' . $v['func_content'] . ')';
1737  break;
1738  case 'flow-control':
1739  if ($v['flow-control']['type'] === 'CASE') {
1740  $outputParts[$k] = $this->compileCaseStatement($v['flow-control']);
1741  }
1742  break;
1743  case 'field':
1744  $outputParts[$k] = ($v['distinct'] ? $v['distinct'] : '') . ($v['table'] ? $v['table'] . '.' : '') . $v['field'];
1745  break;
1746  }
1747  // Alias:
1748  if ($v['as']) {
1749  $outputParts[$k] .= ' ' . $v['as_keyword'] . ' ' . $v['as'];
1750  }
1751  // Specifically for ORDER BY and GROUP BY field lists:
1752  if ($v['sortDir']) {
1753  $outputParts[$k] .= ' ' . $v['sortDir'];
1754  }
1755  }
1756  if ($compileComments && $selectFields[0]['comments']) {
1757  $fields = $selectFields[0]['comments'] . ' ';
1758  }
1759  $fields .= implode(', ', $outputParts);
1760  }
1761  return $fields;
1762  }
1763 
1771  protected function compileCaseStatement(array $components) {
1772  $statement = 'CASE';
1773  if (isset($components['case_field'])) {
1774  $statement .= ' ' . $components['case_field'];
1775  } elseif (isset($components['case_value'])) {
1776  $statement .= ' ' . $components['case_value'][1] . $components['case_value'][0] . $components['case_value'][1];
1777  }
1778  foreach ($components['when'] as $when) {
1779  $statement .= ' WHEN ';
1780  $statement .= $this->compileWhereClause($when['when_value']);
1781  $statement .= ' THEN ';
1782  $statement .= $when['then_value'][1] . $when['then_value'][0] . $when['then_value'][1];
1783  }
1784  if (isset($components['else'])) {
1785  $statement .= ' ELSE ';
1786  $statement .= $components['else'][1] . $components['else'][0] . $components['else'][1];
1787  }
1788  $statement .= ' END';
1789  return $statement;
1790  }
1791 
1799  public function compileFromTables($tablesArray) {
1800  // Prepare buffer variable:
1801  $outputParts = array();
1802  // Traverse the table names:
1803  if (is_array($tablesArray)) {
1804  foreach ($tablesArray as $k => $v) {
1805  // Set table name:
1806  $outputParts[$k] = $v['table'];
1807  // Add alias AS if there:
1808  if ($v['as']) {
1809  $outputParts[$k] .= ' ' . $v['as_keyword'] . ' ' . $v['as'];
1810  }
1811  if (is_array($v['JOIN'])) {
1812  foreach ($v['JOIN'] as $join) {
1813  $outputParts[$k] .= ' ' . $join['type'] . ' ' . $join['withTable'];
1814  // Add alias AS if there:
1815  if (isset($join['as']) && $join['as']) {
1816  $outputParts[$k] .= ' ' . $join['as_keyword'] . ' ' . $join['as'];
1817  }
1818  $outputParts[$k] .= ' ON ';
1819  foreach ($join['ON'] as $condition) {
1820  if ($condition['operator'] !== '') {
1821  $outputParts[$k] .= ' ' . $condition['operator'] . ' ';
1822  }
1823  $outputParts[$k] .= $condition['left']['table'] ? $condition['left']['table'] . '.' : '';
1824  $outputParts[$k] .= $condition['left']['field'];
1825  $outputParts[$k] .= $condition['comparator'];
1826  if (!empty($condition['right']['value'])) {
1827  $value = $condition['right']['value'];
1828  $outputParts[$k] .= $value[1] . $this->compileAddslashes($value[0]) . $value[1];
1829  } else {
1830  $outputParts[$k] .= $condition['right']['table'] ? $condition['right']['table'] . '.' : '';
1831  $outputParts[$k] .= $condition['right']['field'];
1832  }
1833  }
1834  }
1835  }
1836  }
1837  }
1838  // Return imploded buffer:
1839  return implode(', ', $outputParts);
1840  }
1841 
1849  public function compileWhereClause($clauseArray) {
1850  // Prepare buffer variable:
1851  $output = '';
1852  // Traverse clause array:
1853  if (is_array($clauseArray)) {
1854  foreach ($clauseArray as $k => $v) {
1855  // Set operator:
1856  $output .= $v['operator'] ? ' ' . $v['operator'] : '';
1857  // Look for sublevel:
1858  if (is_array($v['sub'])) {
1859  $output .= ' (' . trim($this->compileWhereClause($v['sub'])) . ')';
1860  } elseif (isset($v['func']) && $v['func']['type'] === 'EXISTS') {
1861  $output .= ' ' . trim($v['modifier']) . ' EXISTS (' . $this->compileSELECT($v['func']['subquery']) . ')';
1862  } else {
1863  if (isset($v['func']) && $v['func']['type'] === 'LOCATE') {
1864  $output .= ' ' . trim($v['modifier']) . ' LOCATE(';
1865  $output .= $v['func']['substr'][1] . $v['func']['substr'][0] . $v['func']['substr'][1];
1866  $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
1867  $output .= isset($v['func']['pos']) ? ', ' . $v['func']['pos'][0] : '';
1868  $output .= ')';
1869  } elseif (isset($v['func']) && $v['func']['type'] === 'IFNULL') {
1870  $output .= ' ' . trim($v['modifier']) . ' IFNULL(';
1871  $output .= ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
1872  $output .= ', ' . $v['func']['default'][1] . $this->compileAddslashes($v['func']['default'][0]) . $v['func']['default'][1];
1873  $output .= ')';
1874  } elseif (isset($v['func']) && $v['func']['type'] === 'CAST') {
1875  $output .= ' ' . trim($v['modifier']) . ' CAST(';
1876  $output .= ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
1877  $output .= ' AS ' . $v['func']['datatype'][0];
1878  $output .= ')';
1879  } elseif (isset($v['func']) && $v['func']['type'] === 'FIND_IN_SET') {
1880  $output .= ' ' . trim($v['modifier']) . ' FIND_IN_SET(';
1881  $output .= $v['func']['str'][1] . $v['func']['str'][0] . $v['func']['str'][1];
1882  $output .= ', ' . ($v['func']['table'] ? $v['func']['table'] . '.' : '') . $v['func']['field'];
1883  $output .= ')';
1884  } else {
1885  // Set field/table with modifying prefix if any:
1886  $output .= ' ' . trim(($v['modifier'] . ' ' . ($v['table'] ? $v['table'] . '.' : '') . $v['field']));
1887  // Set calculation, if any:
1888  if ($v['calc']) {
1889  $output .= $v['calc'] . $v['calc_value'][1] . $this->compileAddslashes($v['calc_value'][0]) . $v['calc_value'][1];
1890  }
1891  }
1892  // Set comparator:
1893  if ($v['comparator']) {
1894  $output .= ' ' . $v['comparator'];
1895  // Detecting value type; list or plain:
1896  if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('NOTIN,IN', strtoupper(str_replace(array(' ', TAB, CR, LF), '', $v['comparator'])))) {
1897  if (isset($v['subquery'])) {
1898  $output .= ' (' . $this->compileSELECT($v['subquery']) . ')';
1899  } else {
1900  $valueBuffer = array();
1901  foreach ($v['value'] as $realValue) {
1902  $valueBuffer[] = $realValue[1] . $this->compileAddslashes($realValue[0]) . $realValue[1];
1903  }
1904  $output .= ' (' . trim(implode(',', $valueBuffer)) . ')';
1905  }
1906  } else {
1907  if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('BETWEEN,NOT BETWEEN', $v['comparator'])) {
1908  $lbound = $v['values'][0];
1909  $ubound = $v['values'][1];
1910  $output .= ' ' . $lbound[1] . $this->compileAddslashes($lbound[0]) . $lbound[1];
1911  $output .= ' AND ';
1912  $output .= $ubound[1] . $this->compileAddslashes($ubound[0]) . $ubound[1];
1913  } else {
1914  if (isset($v['value']['operator'])) {
1915  $values = array();
1916  foreach ($v['value']['args'] as $fieldDef) {
1917  $values[] = ($fieldDef['table'] ? $fieldDef['table'] . '.' : '') . $fieldDef['field'];
1918  }
1919  $output .= ' ' . $v['value']['operator'] . '(' . implode(',', $values) . ')';
1920  } else {
1921  $output .= ' ' . $v['value'][1] . $this->compileAddslashes($v['value'][0]) . $v['value'][1];
1922  }
1923  }
1924  }
1925  }
1926  }
1927  }
1928  }
1929  // Return output buffer:
1930  return $output;
1931  }
1932 
1939  public function compileFieldCfg($fieldCfg) {
1940  // Set type:
1941  $cfg = $fieldCfg['fieldType'];
1942  // Add value, if any:
1943  if (strlen($fieldCfg['value'])) {
1944  $cfg .= '(' . $fieldCfg['value'] . ')';
1945  }
1946  // Add additional features:
1947  if (is_array($fieldCfg['featureIndex'])) {
1948  foreach ($fieldCfg['featureIndex'] as $featureDef) {
1949  $cfg .= ' ' . $featureDef['keyword'];
1950  // Add value if found:
1951  if (is_array($featureDef['value'])) {
1952  $cfg .= ' ' . $featureDef['value'][1] . $this->compileAddslashes($featureDef['value'][0]) . $featureDef['value'][1];
1953  }
1954  }
1955  }
1956  // Return field definition string:
1957  return $cfg;
1958  }
1959 
1960  /*************************
1961  *
1962  * Debugging
1963  *
1964  *************************/
1972  public function debug_parseSQLpart($part, $str) {
1973  $retVal = FALSE;
1974  switch ($part) {
1975  case 'SELECT':
1976  $retVal = $this->debug_parseSQLpartCompare($str, $this->compileFieldList($this->parseFieldList($str)));
1977  break;
1978  case 'FROM':
1979  $retVal = $this->debug_parseSQLpartCompare($str, $this->compileFromTables($this->parseFromTables($str)));
1980  break;
1981  case 'WHERE':
1982  $retVal = $this->debug_parseSQLpartCompare($str, $this->compileWhereClause($this->parseWhereClause($str)));
1983  break;
1984  }
1985  return $retVal;
1986  }
1987 
1996  public function debug_parseSQLpartCompare($str, $newStr, $caseInsensitive = FALSE) {
1997  if ($caseInsensitive) {
1998  $str1 = strtoupper($str);
1999  $str2 = strtoupper($newStr);
2000  } else {
2001  $str1 = $str;
2002  $str2 = $newStr;
2003  }
2004 
2005  // Fixing escaped chars:
2006  $search = array('\0', '\n', '\r', '\Z');
2007  $replace = array("\x00", "\x0a", "\x0d", "\x1a");
2008  $str1 = str_replace($search, $replace, $str1);
2009  $str2 = str_replace($search, $replace, $str2);
2010 
2011  $search = array(' ', TAB, CR, LF);
2012  if (str_replace($search, '', $this->trimSQL($str1)) !== str_replace($search, '', $this->trimSQL($str2))) {
2013  return array(
2014  str_replace($search, ' ', $str),
2015  str_replace($search, ' ', $newStr),
2016  );
2017  }
2018  }
2019 }
parseWhereClause(&$parseString, $stopRegex='', array &$parameterReferences=array())
Definition: SqlParser.php:935
parseFieldDef(&$parseString, $stopRegex='')
Definition: SqlParser.php:1255
compileFieldList($selectFields, $compileComments=TRUE)
Definition: SqlParser.php:1726
getValueInQuotes(&$parseString, $quote)
Definition: SqlParser.php:1417
parseFromTables(&$parseString, $stopRegex='')
Definition: SqlParser.php:818
nextPart(&$parseString, $regex, $trimAll=FALSE)
Definition: SqlParser.php:1302
compileTRUNCATETABLE(array $components)
Definition: SqlParser.php:1705
getValue(&$parseString, $comparator='', $mode='')
Definition: SqlParser.php:1355
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
compileCaseStatement(array $components)
Definition: SqlParser.php:1771
parseSELECT($parseString, &$parameterReferences=NULL)
Definition: SqlParser.php:120
parseFieldList(&$parseString, $stopRegex='')
Definition: SqlParser.php:654
& getValueOrParameter(&$parseString, $comparator='', $mode='', array &$parameterReferences=array())
Definition: SqlParser.php:1324
debug_parseSQLpartCompare($str, $newStr, $caseInsensitive=FALSE)
Definition: SqlParser.php:1996