Cleanup #12440: Copy DBAL-only SQL parser methods from t3lib_sqlparser to ux_t3lib_sq...
authorXavier Perseguers <typo3@perseguers.ch>
Tue, 3 Nov 2009 12:29:06 +0000 (12:29 +0000)
committerXavier Perseguers <typo3@perseguers.ch>
Tue, 3 Nov 2009 12:29:06 +0000 (12:29 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Extensions/dbal/trunk@26118 735d13b6-9817-0410-8766-e36946ffe9aa

typo3/sysext/dbal/ChangeLog
typo3/sysext/dbal/class.ux_t3lib_sqlparser.php

index e35d695..134e867 100644 (file)
@@ -1,5 +1,6 @@
 2009-11-03  Xavier Perseguers  <typo3@perseguers.ch>
 
+       * Cleanup #12440: Copy DBAL-only SQL parser methods from t3lib_sqlparser to ux_t3lib_sqlparser
        * Fixed bug #12231: New caching framework (4.3-dev) does not work with DBAL
 
 2009-10-28  Xavier Perseguers  <typo3@perseguers.ch>
index 73c5d71..d0d1814 100644 (file)
  */
 class ux_t3lib_sqlparser extends t3lib_sqlparser {
 
+       // START: Methods originally belonging to t3lib_sqlparser but copied and fixed here as they were not used in Core
+
+       /**
+        * Parsing the tablenames in the "FROM [$parseString] WHERE" part of a query into an array.
+        * The success of this parsing determines if that part of the query is supported by TYPO3.
+        *
+        * @param       string          list of tables, eg. "pages, tt_content" or "pages A, pages B". NOTICE: passed by reference!
+        * @param       string          Regular expressing to STOP parsing, eg. '^(WHERE)([[:space:]]*)'
+        * @return      array           If successful parsing, returns an array, otherwise an error string.
+        * @see compileFromTables()
+        */
+       public function parseFromTables(&$parseString, $stopRegex = '') {
+
+                       // Prepare variables:
+               $parseString = $this->trimSQL($parseString);
+               $this->lastStopKeyWord = '';
+               $this->parse_error = '';
+
+               $stack = array();       // Contains the parsed content
+               $pnt = 0;                       // Pointer to positions in $stack
+               $loopExit = 0;          // Recursivity brake.
+
+                       // $parseString is continously shortend by the process and we keep parsing it till it is zero:
+               while (strlen($parseString)) {
+                               // Looking for the table:
+                       if ($stack[$pnt]['table'] = $this->nextPart($parseString,'^([[:alnum:]_]+)(,|[[:space:]]+)')) {
+                                       // Looking for stop-keywords before fetching potential table alias:
+                               if ($stopRegex && ($this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex))) {
+                                       $this->lastStopKeyWord = strtoupper(str_replace(array(' ',"\t","\r","\n"), '', $this->lastStopKeyWord));
+                                       return $stack;
+                               }
+                               if (!preg_match('/^(LEFT|JOIN)[[:space:]]+/i',$parseString)) {
+                                       $stack[$pnt]['as_keyword'] = $this->nextPart($parseString,'^(AS[[:space:]]+)');
+                                       $stack[$pnt]['as'] = $this->nextPart($parseString,'^([[:alnum:]_]+)[[:space:]]*');
+                               }
+                       } else return $this->parseError('No table name found as expected in parseFromTables()!', $parseString);
+
+                               // Looking for JOIN
+                       $joinCnt = 0;
+                       while ($join = $this->nextPart($parseString,'^(LEFT[[:space:]]+JOIN|LEFT[[:space:]]+OUTER[[:space:]]+JOIN|INNER[[:space:]]+JOIN|JOIN)[[:space:]]+')) {
+                               $stack[$pnt]['JOIN'][$joinCnt]['type'] = $join;
+                               if ($stack[$pnt]['JOIN'][$joinCnt]['withTable'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+', 1)) {
+                                       if (!preg_match('/^ON[[:space:]]+/i', $parseString)) {
+                                               $stack[$pnt]['JOIN'][$joinCnt]['as_keyword'] = $this->nextPart($parseString, '^(AS[[:space:]]+)');
+                                               $stack[$pnt]['JOIN'][$joinCnt]['as'] = $this->nextPart($parseString, '^([[:alnum:]_]+)[[:space:]]+');
+                                       }
+                                       if (!$this->nextPart($parseString, '^(ON[[:space:]]+)')) {
+                                               return $this->parseError('No join condition found in parseFromTables()!', $parseString);
+                                       }
+                                       $field1 = $this->nextPart($parseString,'^([[:alnum:]_.]+)[[:space:]]*=[[:space:]]*', 1);
+                                       $field2 = $this->nextPart($parseString,'^([[:alnum:]_.]+)[[:space:]]+');
+                                       if ($field1 && $field2) {
+
+                                               // Explode fields into field and table:
+                                               $tableField = explode('.', $field1, 2);
+                                               $field1 = array();
+                                               if (count($tableField) != 2) {
+                                                       $field1['table'] = '';
+                                                       $field1['field'] = $tableField[0];
+                                               } else {
+                                                       $field1['table'] = $tableField[0];
+                                                       $field1['field'] = $tableField[1];
+                                               }
+                                               $tableField = explode('.', $field2, 2);
+                                               $field2 = array();
+                                               if (count($tableField) != 2) {
+                                                       $field2['table'] = '';
+                                                       $field2['field'] = $tableField[0];
+                                               } else {
+                                                       $field2['table'] = $tableField[0];
+                                                       $field2['field'] = $tableField[1];
+                                               }
+                                               $stack[$pnt]['JOIN'][$joinCnt]['ON'] = array($field1, $field2);
+                                               $joinCnt++;
+                                       } else return $this->parseError('No join fields found in parseFromTables()!', $parseString);
+                               } else return $this->parseError('No join table found in parseFromTables()!', $parseString);
+                       }
+
+                               // Looking for stop-keywords:
+                       if ($stopRegex && $this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex)) {
+                               $this->lastStopKeyWord = strtoupper(str_replace(array(' ',"\t","\r","\n"), '', $this->lastStopKeyWord));
+                               return $stack;
+                       }
+
+                               // Looking for comma:
+                       if (strlen($parseString) && !$this->nextPart($parseString, '^(,)')) {
+                               return $this->parseError('No comma found as expected in parseFromTables()', $parseString);
+                       }
+
+                               // Increasing pointer:
+                       $pnt++;
+
+                               // Check recursivity brake:
+                       $loopExit++;
+                       if ($loopExit > 500) {
+                               return $this->parseError('More than 500 loops, exiting prematurely in parseFromTables()...', $parseString);
+                       }
+               }
+
+                       // Return result array:
+               return $stack;
+       }
+
+       /**
+        * Parsing the WHERE clause fields in the "WHERE [$parseString] ..." part of a query into a multidimensional array.
+        * The success of this parsing determines if that part of the query is supported by TYPO3.
+        *
+        * @param       string          WHERE clause to parse. NOTICE: passed by reference!
+        * @param       string          Regular expressing to STOP parsing, eg. '^(GROUP BY|ORDER BY|LIMIT)([[:space:]]*)'
+        * @return      mixed           If successful parsing, returns an array, otherwise an error string.
+        */
+       public function parseWhereClause(&$parseString, $stopRegex = '') {
+
+                       // Prepare variables:
+               $parseString = $this->trimSQL($parseString);
+               $this->lastStopKeyWord = '';
+               $this->parse_error = '';
+
+               $stack = array(0 => array());   // Contains the parsed content
+               $pnt = array(0 => 0);                   // Pointer to positions in $stack
+               $level = 0;                                             // Determines parenthesis level
+               $loopExit = 0;                                  // Recursivity brake.
+
+                       // $parseString is continously shortend by the process and we keep parsing it till it is zero:
+               while (strlen($parseString)) {
+
+                               // Look for next parenthesis level:
+                       $newLevel = $this->nextPart($parseString,'^([(])');
+                       if ($newLevel == '(') {                 // If new level is started, manage stack/pointers:
+                               $level++;                                       // Increase level
+                               $pnt[$level] = 0;                       // Reset pointer for this level
+                               $stack[$level] = array();       // Reset stack for this level
+                       } else {        // If no new level is started, just parse the current level:
+
+                                       // Find "modifyer", eg. "NOT or !"
+                               $stack[$level][$pnt[$level]]['modifier'] = trim($this->nextPart($parseString, '^(!|NOT[[:space:]]+)'));
+
+                                       // Support calculated value only for:
+                                       // - "&" (boolean AND)
+                                       // - "+" (addition)
+                                       // - "-" (substraction)
+                                       // - "*" (multiplication)
+                                       // - "/" (division)
+                                       // - "%" (modulo)
+                               $calcOperators = '&|\+|-|\*|\/|%';
+
+                                       // Fieldname:
+                               if ($fieldName = $this->nextPart($parseString, '^([[:alnum:]._]+)([[:space:]]+|' . $calcOperators . '|<=|>=|<|>|=|!=|IS)')) {
+
+                                               // Parse field name into field and table:
+                                       $tableField = explode('.', $fieldName, 2);
+                                       if (count($tableField) == 2) {
+                                               $stack[$level][$pnt[$level]]['table'] = $tableField[0];
+                                               $stack[$level][$pnt[$level]]['field'] = $tableField[1];
+                                       } else {
+                                               $stack[$level][$pnt[$level]]['table'] = '';
+                                               $stack[$level][$pnt[$level]]['field'] = $tableField[0];
+                                       }
+                               } else {
+                                       return $this->parseError('No field name found as expected in parseWhereClause()', $parseString);
+                               }
+
+                                       // See if the value is calculated:
+                               $stack[$level][$pnt[$level]]['calc'] = $this->nextPart($parseString, '^(' . $calcOperators . ')');
+                               if (strlen($stack[$level][$pnt[$level]]['calc'])) {
+                                               // Finding value for calculation:
+                                       $stack[$level][$pnt[$level]]['calc_value'] = $this->getValue($parseString);
+                               }
+
+                                       // Find "comparator":
+                               $stack[$level][$pnt[$level]]['comparator'] = $this->nextPart($parseString, '^(<=|>=|<|>|=|!=|NOT[[:space:]]+IN|IN|NOT[[:space:]]+LIKE|LIKE|IS[[:space:]]+NOT|IS)');
+                               if (strlen($stack[$level][$pnt[$level]]['comparator'])) {
+                                               // Finding value for comparator:
+                                       $stack[$level][$pnt[$level]]['value'] = $this->getValue($parseString, $stack[$level][$pnt[$level]]['comparator']);
+                                       if ($this->parse_error) {
+                                               return $this->parse_error;
+                                       }
+                               }
+
+                                       // Finished, increase pointer:
+                               $pnt[$level]++;
+
+                                       // Checking if the current level is ended, in that case do stack management:
+                               while ($this->nextPart($parseString,'^([)])')) {
+                                       $level--;               // Decrease level:
+                                       $stack[$level][$pnt[$level]]['sub'] = $stack[$level+1];         // Copy stack
+                                       $pnt[$level]++; // Increase pointer of the new level
+
+                                               // Make recursivity check:
+                                       $loopExit++;
+                                       if ($loopExit > 500) {
+                                               return $this->parseError('More than 500 loops (in search for exit parenthesis), exiting prematurely in parseWhereClause()...', $parseString);
+                                       }
+                               }
+
+                                       // Detecting the operator for the next level:
+                               $op = $this->nextPart($parseString, '^(AND[[:space:]]+NOT|&&[[:space:]]+NOT|OR[[:space:]]+NOT|OR[[:space:]]+NOT|\|\|[[:space:]]+NOT|AND|&&|OR|\|\|)(\(|[[:space:]]+)');
+                               if ($op) {
+                                               // Normalize boolean operator
+                                       $op = str_replace(array('&&', '||'), array('AND', 'OR'), $op);
+                                       $stack[$level][$pnt[$level]]['operator'] = $op;
+                               } elseif (strlen($parseString)) {
+
+                                               // Looking for stop-keywords:
+                                       if ($stopRegex && $this->lastStopKeyWord = $this->nextPart($parseString, $stopRegex)) {
+                                               $this->lastStopKeyWord = strtoupper(str_replace(array(' ',"\t","\r","\n"), '', $this->lastStopKeyWord));
+                                               return $stack[0];
+                                       } else {
+                                               return $this->parseError('No operator, but parsing not finished in parseWhereClause().', $parseString);
+                                       }
+                               }
+                       }
+
+                               // Make recursivity check:
+                       $loopExit++;
+                       if ($loopExit > 500) {
+                               return $this->parseError('More than 500 loops, exiting prematurely in parseWhereClause()...', $parseString);
+                       }
+               }
+
+                       // Return the stacks lowest level:
+               return $stack[0];
+       }
+
+       /**
+        * Compiles a "FROM [output] WHERE..:" table list based on input array (made with ->parseFromTables())
+        *
+        * @param       array           Array of table names, (made with ->parseFromTables())
+        * @return      string          Table name string
+        * @see parseFromTables()
+        */
+       public function compileFromTables($tablesArray) {
+
+                       // Prepare buffer variable:
+               $outputParts = array();
+
+                       // Traverse the table names:
+               if (is_array($tablesArray)) {
+                       foreach ($tablesArray as $k => $v) {
+
+                                       // Set table name:
+                               $outputParts[$k] = $v['table'];
+
+                                       // Add alias AS if there:
+                               if ($v['as']) {
+                                       $outputParts[$k] .= ' ' . $v['as_keyword'] . ' ' . $v['as'];
+                               }
+
+                               if (is_array($v['JOIN'])) {
+                                       foreach ($v['JOIN'] as $join) {
+                                               $outputParts[$k] .= ' ' . $join['type'] . ' ' . $join['withTable'];
+                                                       // Add alias AS if there:
+                                               if (isset($join['as']) && $join['as']) {
+                                                       $outputParts[$k] .= ' ' . $join['as_keyword'] . ' ' . $join['as'];
+                                               }
+                                               $outputParts[$k] .= ' ON ';
+                                               $outputParts[$k] .= ($join['ON'][0]['table']) ? $join['ON'][0]['table'] . '.' : '';
+                                               $outputParts[$k] .= $join['ON'][0]['field'];
+                                               $outputParts[$k] .= '=';
+                                               $outputParts[$k] .= ($join['ON'][1]['table']) ? $join['ON'][1]['table'] . '.' : '';
+                                               $outputParts[$k] .= $join['ON'][1]['field'];
+                                       }
+                               }
+                       }
+               }
+
+                       // Return imploded buffer:
+               return implode(', ', $outputParts);
+       }
+
+       /**
+        * Implodes an array of WHERE clause configuration into a WHERE clause.
+        * NOTICE: MIGHT BY A TEMPORARY FUNCTION. Use for debugging only!
+        * BUT IT IS NEEDED FOR DBAL - MAKE IT PERMANENT?!?!
+        *
+        * @param       array           WHERE clause configuration
+        * @return      string          WHERE clause as string.
+        * @see explodeWhereClause()
+        */
+       protected function nativeCompileWhereClause($clauseArray) {
+
+                       // Prepare buffer variable:
+               $output = '';
+
+                       // Traverse clause array:
+               if (is_array($clauseArray)) {
+                       foreach ($clauseArray as $k => $v) {
+
+                                       // Set operator:
+                               $output .= $v['operator'] ? ' ' . $v['operator'] : '';
+
+                                       // Look for sublevel:
+                               if (is_array($v['sub'])) {
+                                       $output .= ' (' . trim($this->nativeCompileWhereClause($v['sub'])) . ')';
+                               } else {
+
+                                               // Set field/table with modifying prefix if any:
+                                       $output .= ' ' . trim($v['modifier'] . ' ' . ($v['table'] ? $v['table'] . '.' : '') . $v['field']);
+
+                                               // Set calculation, if any:
+                                       if ($v['calc']) {
+                                               $output .= $v['calc'] . $v['calc_value'][1] . $this->compileAddslashes($v['calc_value'][0]) . $v['calc_value'][1];
+                                       }
+
+                                               // Set comparator:
+                                       if ($v['comparator']) {
+                                               $output .= ' ' . $v['comparator'];
+
+                                                       // Detecting value type; list or plain:
+                                               if (t3lib_div::inList('NOTIN,IN', strtoupper(str_replace(array(' ', "\t", "\r", "\n"), '', $v['comparator'])))) {
+                                                       $valueBuffer = array();
+                                                       foreach ($v['value'] as $realValue) {
+                                                               $valueBuffer[] = $realValue[1] . $this->compileAddslashes($realValue[0]) . $realValue[1];
+                                                       }
+                                                       $output .= ' (' . trim(implode(',', $valueBuffer)) . ')';
+                                               } else {
+                                                       $output .= ' ' . $v['value'][1] . $this->compileAddslashes($v['value'][0]) . $v['value'][1];
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+                       // Return output buffer:
+               return $output;
+       }
+       
+       // END: Methods originally belonging to t3lib_sqlparser but copied and fixed here as they were not used in Core
+       
        /*************************
         *
         * Compiling queries
@@ -338,10 +667,10 @@ class ux_t3lib_sqlparser extends t3lib_sqlparser {
         * @return string WHERE clause as string.
         * @see t3lib_sqlparser::parseWhereClause()
         */
-       function compileWhereClause($clauseArray, $functionMapping = TRUE) {
+       public function compileWhereClause($clauseArray, $functionMapping = TRUE) {
                switch ((string)$GLOBALS['TYPO3_DB']->handlerCfg[$GLOBALS['TYPO3_DB']->lastHandlerKey]['type']) {
                        case 'native':
-                               $output = parent::compileWhereClause($clauseArray);
+                               $output = $this->nativeCompileWhereClause($clauseArray);
                                break;
                        case 'adodb':
                                        // Prepare buffer variable: