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