1<?php 2/** 3 * ExpressionListProcessor.php 4 * 5 * This file implements the processor for expression lists. 6 * 7 * PHP version 5 8 * 9 * LICENSE: 10 * Copyright (c) 2010-2014 Justin Swanhart and André Rothe 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. The name of the author may not be used to endorse or promote products 22 * derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 27 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * @author André Rothe <andre.rothe@phosco.info> 36 * @copyright 2010-2014 Justin Swanhart and André Rothe 37 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 38 * @version SVN: $Id$ 39 * 40 */ 41 42namespace PHPSQLParser\processors; 43use PHPSQLParser\utils\ExpressionType; 44use PHPSQLParser\utils\ExpressionToken; 45use PHPSQLParser\utils\PHPSQLParserConstants; 46 47/** 48 * This class processes expression lists. 49 * 50 * @author André Rothe <andre.rothe@phosco.info> 51 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 52 * 53 */ 54class ExpressionListProcessor extends AbstractProcessor { 55 56 public function process($tokens) { 57 $resultList = array(); 58 $skip_next = false; 59 $prev = new ExpressionToken(); 60 61 foreach ($tokens as $k => $v) { 62 63 64 if ($this->isCommentToken($v)) { 65 $resultList[] = parent::processComment($v); 66 continue; 67 } 68 69 $curr = new ExpressionToken($k, $v); 70 71 if ($curr->isWhitespaceToken()) { 72 continue; 73 } 74 75 if ($skip_next) { 76 // skip the next non-whitespace token 77 $skip_next = false; 78 continue; 79 } 80 81 /* is it a subquery? */ 82 if ($curr->isSubQueryToken()) { 83 84 $processor = new DefaultProcessor($this->options); 85 $curr->setSubTree($processor->process($this->removeParenthesisFromStart($curr->getTrim()))); 86 $curr->setTokenType(ExpressionType::SUBQUERY); 87 88 } elseif ($curr->isEnclosedWithinParenthesis()) { 89 /* is it an in-list? */ 90 91 $localTokenList = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($curr->getTrim())); 92 93 if ($prev->getUpper() === 'IN') { 94 95 foreach ($localTokenList as $k => $v) { 96 $tmpToken = new ExpressionToken($k, $v); 97 if ($tmpToken->isCommaToken()) { 98 unset($localTokenList[$k]); 99 } 100 } 101 102 $localTokenList = array_values($localTokenList); 103 $curr->setSubTree($this->process($localTokenList)); 104 $curr->setTokenType(ExpressionType::IN_LIST); 105 } elseif ($prev->getUpper() === 'AGAINST') { 106 107 $match_mode = false; 108 foreach ($localTokenList as $k => $v) { 109 110 $tmpToken = new ExpressionToken($k, $v); 111 switch ($tmpToken->getUpper()) { 112 case 'WITH': 113 $match_mode = 'WITH QUERY EXPANSION'; 114 break; 115 case 'IN': 116 $match_mode = 'IN BOOLEAN MODE'; 117 break; 118 119 default: 120 } 121 122 if ($match_mode !== false) { 123 unset($localTokenList[$k]); 124 } 125 } 126 127 $tmpToken = $this->process($localTokenList); 128 129 if ($match_mode !== false) { 130 $match_mode = new ExpressionToken(0, $match_mode); 131 $match_mode->setTokenType(ExpressionType::MATCH_MODE); 132 $tmpToken[] = $match_mode->toArray(); 133 } 134 135 $curr->setSubTree($tmpToken); 136 $curr->setTokenType(ExpressionType::MATCH_ARGUMENTS); 137 $prev->setTokenType(ExpressionType::SIMPLE_FUNCTION); 138 139 } elseif ($prev->isColumnReference() || $prev->isFunction() || $prev->isAggregateFunction() 140 || $prev->isCustomFunction()) { 141 142 // if we have a colref followed by a parenthesis pair, 143 // it isn't a colref, it is a user-function 144 145 // TODO: this should be a method, because we need the same code 146 // below for unspecified tokens (expressions). 147 148 $localExpr = new ExpressionToken(); 149 $tmpExprList = array(); 150 151 foreach ($localTokenList as $k => $v) { 152 $tmpToken = new ExpressionToken($k, $v); 153 if (!$tmpToken->isCommaToken()) { 154 $localExpr->addToken($v); 155 $tmpExprList[] = $v; 156 } else { 157 // an expression could have multiple parts split by operands 158 // if we have a comma, it is a split-point for expressions 159 $tmpExprList = array_values($tmpExprList); 160 $localExprList = $this->process($tmpExprList); 161 162 if (count($localExprList) > 1) { 163 $localExpr->setSubTree($localExprList); 164 $localExpr->setTokenType(ExpressionType::EXPRESSION); 165 $localExprList = $localExpr->toArray(); 166 $localExprList['alias'] = false; 167 $localExprList = array($localExprList); 168 } 169 170 if (!$curr->getSubTree()) { 171 if (!empty($localExprList)) { 172 $curr->setSubTree($localExprList); 173 } 174 } else { 175 $tmpExprList = $curr->getSubTree(); 176 $curr->setSubTree(array_merge($tmpExprList, $localExprList)); 177 } 178 179 $tmpExprList = array(); 180 $localExpr = new ExpressionToken(); 181 } 182 } 183 184 $tmpExprList = array_values($tmpExprList); 185 $localExprList = $this->process($tmpExprList); 186 187 if (count($localExprList) > 1) { 188 $localExpr->setSubTree($localExprList); 189 $localExpr->setTokenType(ExpressionType::EXPRESSION); 190 $localExprList = $localExpr->toArray(); 191 $localExprList['alias'] = false; 192 $localExprList = array($localExprList); 193 } 194 195 if (!$curr->getSubTree()) { 196 if (!empty($localExprList)) { 197 $curr->setSubTree($localExprList); 198 } 199 } else { 200 $tmpExprList = $curr->getSubTree(); 201 $curr->setSubTree(array_merge($tmpExprList, $localExprList)); 202 } 203 204 $prev->setSubTree($curr->getSubTree()); 205 if ($prev->isColumnReference()) { 206 if (PHPSQLParserConstants::getInstance()->isCustomFunction($prev->getUpper())) { 207 $prev->setTokenType(ExpressionType::CUSTOM_FUNCTION); 208 } else { 209 $prev->setTokenType(ExpressionType::SIMPLE_FUNCTION); 210 } 211 $prev->setNoQuotes(null, null, $this->options); 212 } 213 214 array_pop($resultList); 215 $curr = $prev; 216 } 217 218 // we have parenthesis, but it seems to be an expression 219 if ($curr->isUnspecified()) { 220 $tmpExprList = array_values($localTokenList); 221 $localExprList = $this->process($tmpExprList); 222 223 $curr->setTokenType(ExpressionType::BRACKET_EXPRESSION); 224 if (!$curr->getSubTree()) { 225 if (!empty($localExprList)) { 226 $curr->setSubTree($localExprList); 227 } 228 } else { 229 $tmpExprList = $curr->getSubTree(); 230 $curr->setSubTree(array_merge($tmpExprList, $localExprList)); 231 } 232 } 233 234 } elseif ($curr->isVariableToken()) { 235 236 # a variable 237 # it can be quoted 238 239 $curr->setTokenType($this->getVariableType($curr->getUpper())); 240 $curr->setSubTree(false); 241 $curr->setNoQuotes(trim(trim($curr->getToken()), '@'), "`'\"", $this->options); 242 243 } else { 244 /* it is either an operator, a colref or a constant */ 245 switch ($curr->getUpper()) { 246 247 case '*': 248 $curr->setSubTree(false); // o subtree 249 250 // single or first element of expression list -> all-column-alias 251 if (empty($resultList)) { 252 $curr->setTokenType(ExpressionType::COLREF); 253 break; 254 } 255 256 // if the last token is colref, const or expression 257 // then * is an operator 258 // but if the previous colref ends with a dot, the * is the all-columns-alias 259 if (!$prev->isColumnReference() && !$prev->isConstant() && !$prev->isExpression() 260 && !$prev->isBracketExpression() && !$prev->isAggregateFunction() && !$prev->isVariable()) { 261 $curr->setTokenType(ExpressionType::COLREF); 262 break; 263 } 264 265 if ($prev->isColumnReference() && $prev->endsWith(".")) { 266 $prev->addToken('*'); // tablealias dot * 267 continue 2; // skip the current token 268 } 269 270 $curr->setTokenType(ExpressionType::OPERATOR); 271 break; 272 273 case ':=': 274 case 'AND': 275 case '&&': 276 case 'BETWEEN': 277 case 'BINARY': 278 case '&': 279 case '~': 280 case '|': 281 case '^': 282 case 'DIV': 283 case '/': 284 case '<=>': 285 case '=': 286 case '>=': 287 case '>': 288 case 'IS': 289 case 'NOT': 290 case '<<': 291 case '<=': 292 case '<': 293 case 'LIKE': 294 case '%': 295 case '!=': 296 case '<>': 297 case 'REGEXP': 298 case '!': 299 case '||': 300 case 'OR': 301 case '>>': 302 case 'RLIKE': 303 case 'SOUNDS': 304 case 'XOR': 305 case 'IN': 306 $curr->setSubTree(false); 307 $curr->setTokenType(ExpressionType::OPERATOR); 308 break; 309 310 case 'NULL': 311 $curr->setSubTree(false); 312 $curr->setTokenType(ExpressionType::CONSTANT); 313 break; 314 315 case '-': 316 case '+': 317 // differ between preceding sign and operator 318 $curr->setSubTree(false); 319 320 if ($prev->isColumnReference() || $prev->isFunction() || $prev->isAggregateFunction() 321 || $prev->isConstant() || $prev->isSubQuery() || $prev->isExpression() 322 || $prev->isBracketExpression() || $prev->isVariable() || $prev->isCustomFunction()) { 323 $curr->setTokenType(ExpressionType::OPERATOR); 324 } else { 325 $curr->setTokenType(ExpressionType::SIGN); 326 } 327 break; 328 329 default: 330 $curr->setSubTree(false); 331 332 switch ($curr->getToken(0)) { 333 case "'": 334 // it is a string literal 335 $curr->setTokenType(ExpressionType::CONSTANT); 336 break; 337 case '"': 338 if (!$this->options->getANSIQuotes()) { 339 // If we're not using ANSI quotes, this is a string literal. 340 $curr->setTokenType(ExpressionType::CONSTANT); 341 break; 342 } 343 // Otherwise continue to the next case 344 case '`': 345 // it is an escaped colum name 346 $curr->setTokenType(ExpressionType::COLREF); 347 $curr->setNoQuotes($curr->getToken(), null, $this->options); 348 break; 349 350 default: 351 if (is_numeric($curr->getToken())) { 352 353 if ($prev->isSign()) { 354 $prev->addToken($curr->getToken()); // it is a negative numeric constant 355 $prev->setTokenType(ExpressionType::CONSTANT); 356 continue 3; 357 // skip current token 358 } else { 359 $curr->setTokenType(ExpressionType::CONSTANT); 360 } 361 } else { 362 $curr->setTokenType(ExpressionType::COLREF); 363 $curr->setNoQuotes($curr->getToken(), null, $this->options); 364 } 365 break; 366 } 367 } 368 } 369 370 /* is a reserved word? */ 371 if (!$curr->isOperator() && !$curr->isInList() && !$curr->isFunction() && !$curr->isAggregateFunction() 372 && !$curr->isCustomFunction() && PHPSQLParserConstants::getInstance()->isReserved($curr->getUpper())) { 373 374 $next = isset( $tokens[ $k + 1 ] ) ? new ExpressionToken( $k + 1, $tokens[ $k + 1 ] ) : new ExpressionToken(); 375 $isEnclosedWithinParenthesis = $next->isEnclosedWithinParenthesis(); 376 if ($isEnclosedWithinParenthesis && PHPSQLParserConstants::getInstance()->isCustomFunction($curr->getUpper())) { 377 $curr->setTokenType(ExpressionType::CUSTOM_FUNCTION); 378 $curr->setNoQuotes(null, null, $this->options); 379 380 } elseif ($isEnclosedWithinParenthesis && PHPSQLParserConstants::getInstance()->isAggregateFunction($curr->getUpper())) { 381 $curr->setTokenType(ExpressionType::AGGREGATE_FUNCTION); 382 $curr->setNoQuotes(null, null, $this->options); 383 384 } elseif ($curr->getUpper() === 'NULL') { 385 // it is a reserved word, but we would like to set it as constant 386 $curr->setTokenType(ExpressionType::CONSTANT); 387 388 } else { 389 if ($isEnclosedWithinParenthesis && PHPSQLParserConstants::getInstance()->isParameterizedFunction($curr->getUpper())) { 390 // issue 60: check functions with parameters 391 // -> colref (we check parameters later) 392 // -> if there is no parameter, we leave the colref 393 $curr->setTokenType(ExpressionType::COLREF); 394 395 } elseif ($isEnclosedWithinParenthesis && PHPSQLParserConstants::getInstance()->isFunction($curr->getUpper())) { 396 $curr->setTokenType(ExpressionType::SIMPLE_FUNCTION); 397 $curr->setNoQuotes(null, null, $this->options); 398 399 } elseif (!$isEnclosedWithinParenthesis && PHPSQLParserConstants::getInstance()->isFunction($curr->getUpper())) { 400 // Colname using function name. 401 $curr->setTokenType(ExpressionType::COLREF); 402 } else { 403 $curr->setTokenType(ExpressionType::RESERVED); 404 $curr->setNoQuotes(null, null, $this->options); 405 } 406 } 407 } 408 409 // issue 94, INTERVAL 1 MONTH 410 if ($curr->isConstant() && PHPSQLParserConstants::getInstance()->isParameterizedFunction($prev->getUpper())) { 411 $prev->setTokenType(ExpressionType::RESERVED); 412 $prev->setNoQuotes(null, null, $this->options); 413 } 414 415 if ($prev->isConstant() && PHPSQLParserConstants::getInstance()->isParameterizedFunction($curr->getUpper())) { 416 $curr->setTokenType(ExpressionType::RESERVED); 417 $curr->setNoQuotes(null, null, $this->options); 418 } 419 420 if ($curr->isUnspecified()) { 421 $curr->setTokenType(ExpressionType::EXPRESSION); 422 $curr->setNoQuotes(null, null, $this->options); 423 $curr->setSubTree($this->process($this->splitSQLIntoTokens($curr->getTrim()))); 424 } 425 426 $resultList[] = $curr; 427 $prev = $curr; 428 } // end of for-loop 429 430 return $this->toArray($resultList); 431 } 432} 433?> 434