Failed Conditions
Push — master ( a16dc6...b210c1 )
by Luís
09:00
created

lib/Doctrine/ORM/Query/Parser.php (1 issue)

Checks if the types of returned expressions are compatible with the documented types.

Best Practice Bug Major
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Query\AST\Functions;
25
26
/**
27
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
28
 * Parses a DQL query, reports any errors in it, and generates an AST.
29
 *
30
 * @since   2.0
31
 * @author  Guilherme Blanco <[email protected]>
32
 * @author  Jonathan Wage <[email protected]>
33
 * @author  Roman Borschel <[email protected]>
34
 * @author  Janne Vanhala <[email protected]>
35
 * @author  Fabio B. Silva <[email protected]>
36
 */
37
class Parser
38
{
39
    /**
40
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
41
     *
42
     * @var array
43
     */
44
    private static $_STRING_FUNCTIONS = [
45
        'concat'    => Functions\ConcatFunction::class,
46
        'substring' => Functions\SubstringFunction::class,
47
        'trim'      => Functions\TrimFunction::class,
48
        'lower'     => Functions\LowerFunction::class,
49
        'upper'     => Functions\UpperFunction::class,
50
        'identity'  => Functions\IdentityFunction::class,
51
    ];
52
53
    /**
54
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
55
     *
56
     * @var array
57
     */
58
    private static $_NUMERIC_FUNCTIONS = [
59
        'length'    => Functions\LengthFunction::class,
60
        'locate'    => Functions\LocateFunction::class,
61
        'abs'       => Functions\AbsFunction::class,
62
        'sqrt'      => Functions\SqrtFunction::class,
63
        'mod'       => Functions\ModFunction::class,
64
        'size'      => Functions\SizeFunction::class,
65
        'date_diff' => Functions\DateDiffFunction::class,
66
        'bit_and'   => Functions\BitAndFunction::class,
67
        'bit_or'    => Functions\BitOrFunction::class,
68
69
        // Aggregate functions
70
        'min'       => Functions\MinFunction::class,
71
        'max'       => Functions\MaxFunction::class,
72
        'avg'       => Functions\AvgFunction::class,
73
        'sum'       => Functions\SumFunction::class,
74
        'count'     => Functions\CountFunction::class,
75
    ];
76
77
    /**
78
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
79
     *
80
     * @var array
81
     */
82
    private static $_DATETIME_FUNCTIONS = [
83
        'current_date'      => Functions\CurrentDateFunction::class,
84
        'current_time'      => Functions\CurrentTimeFunction::class,
85
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
86
        'date_add'          => Functions\DateAddFunction::class,
87
        'date_sub'          => Functions\DateSubFunction::class,
88
    ];
89
90
    /*
91
     * Expressions that were encountered during parsing of identifiers and expressions
92
     * and still need to be validated.
93
     */
94
95
    /**
96
     * @var array
97
     */
98
    private $deferredIdentificationVariables = [];
99
100
    /**
101
     * @var array
102
     */
103
    private $deferredPartialObjectExpressions = [];
104
105
    /**
106
     * @var array
107
     */
108
    private $deferredPathExpressions = [];
109
110
    /**
111
     * @var array
112
     */
113
    private $deferredResultVariables = [];
114
115
    /**
116
     * @var array
117
     */
118
    private $deferredNewObjectExpressions = [];
119
120
    /**
121
     * The lexer.
122
     *
123
     * @var \Doctrine\ORM\Query\Lexer
124
     */
125
    private $lexer;
126
127
    /**
128
     * The parser result.
129
     *
130
     * @var \Doctrine\ORM\Query\ParserResult
131
     */
132
    private $parserResult;
133
134
    /**
135
     * The EntityManager.
136
     *
137
     * @var \Doctrine\ORM\EntityManager
138
     */
139
    private $em;
140
141
    /**
142
     * The Query to parse.
143
     *
144
     * @var Query
145
     */
146
    private $query;
147
148
    /**
149
     * Map of declared query components in the parsed query.
150
     *
151
     * @var array
152
     */
153
    private $queryComponents = [];
154
155
    /**
156
     * Keeps the nesting level of defined ResultVariables.
157
     *
158
     * @var integer
159
     */
160
    private $nestingLevel = 0;
161
162
    /**
163
     * Any additional custom tree walkers that modify the AST.
164
     *
165
     * @var array
166
     */
167
    private $customTreeWalkers = [];
168
169
    /**
170
     * The custom last tree walker, if any, that is responsible for producing the output.
171
     *
172
     * @var TreeWalker
173
     */
174
    private $customOutputWalker;
175
176
    /**
177
     * @var array
178
     */
179
    private $identVariableExpressions = [];
180
181
    /**
182
     * Creates a new query parser object.
183
     *
184
     * @param Query $query The Query to parse.
185
     */
186 854
    public function __construct(Query $query)
187
    {
188 854
        $this->query        = $query;
189 854
        $this->em           = $query->getEntityManager();
190 854
        $this->lexer        = new Lexer($query->getDQL());
191 854
        $this->parserResult = new ParserResult();
192 854
    }
193
194
    /**
195
     * Sets a custom tree walker that produces output.
196
     * This tree walker will be run last over the AST, after any other walkers.
197
     *
198
     * @param string $className
199
     *
200
     * @return void
201
     */
202 127
    public function setCustomOutputTreeWalker($className)
203
    {
204 127
        $this->customOutputWalker = $className;
205 127
    }
206
207
    /**
208
     * Adds a custom tree walker for modifying the AST.
209
     *
210
     * @param string $className
211
     *
212
     * @return void
213
     */
214
    public function addCustomTreeWalker($className)
215
    {
216
        $this->customTreeWalkers[] = $className;
217
    }
218
219
    /**
220
     * Gets the lexer used by the parser.
221
     *
222
     * @return \Doctrine\ORM\Query\Lexer
223
     */
224 28
    public function getLexer()
225
    {
226 28
        return $this->lexer;
227
    }
228
229
    /**
230
     * Gets the ParserResult that is being filled with information during parsing.
231
     *
232
     * @return \Doctrine\ORM\Query\ParserResult
233
     */
234
    public function getParserResult()
235
    {
236
        return $this->parserResult;
237
    }
238
239
    /**
240
     * Gets the EntityManager used by the parser.
241
     *
242
     * @return \Doctrine\ORM\EntityManager
243
     */
244
    public function getEntityManager()
245
    {
246
        return $this->em;
247
    }
248
249
    /**
250
     * Parses and builds AST for the given Query.
251
     *
252
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
253
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
254
     *         \Doctrine\ORM\Query\AST\DeleteStatement
255
     */
256 854
    public function getAST()
257
    {
258
        // Parse & build AST
259 854
        $AST = $this->QueryLanguage();
260
261
        // Process any deferred validations of some nodes in the AST.
262
        // This also allows post-processing of the AST for modification purposes.
263 812
        $this->processDeferredIdentificationVariables();
264
265 810
        if ($this->deferredPartialObjectExpressions) {
266 11
            $this->processDeferredPartialObjectExpressions();
267
        }
268
269 808
        if ($this->deferredPathExpressions) {
270 599
            $this->processDeferredPathExpressions();
271
        }
272
273 805
        if ($this->deferredResultVariables) {
274 32
            $this->processDeferredResultVariables();
275
        }
276
277 805
        if ($this->deferredNewObjectExpressions) {
278 28
            $this->processDeferredNewObjectExpressions($AST);
279
        }
280
281 801
        $this->processRootEntityAliasSelected();
282
283
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
284 800
        $this->fixIdentificationVariableOrder($AST);
285
286 800
        return $AST;
287
    }
288
289
    /**
290
     * Attempts to match the given token with the current lookahead token.
291
     *
292
     * If they match, updates the lookahead token; otherwise raises a syntax
293
     * error.
294
     *
295
     * @param int $token The token type.
296
     *
297
     * @return void
298
     *
299
     * @throws QueryException If the tokens don't match.
300
     */
301 865
    public function match($token)
302
    {
303 865
        $lookaheadType = $this->lexer->lookahead['type'];
304
305
        // Short-circuit on first condition, usually types match
306 865
        if ($lookaheadType !== $token) {
307
            // If parameter is not identifier (1-99) must be exact match
308 21
            if ($token < Lexer::T_IDENTIFIER) {
309 3
                $this->syntaxError($this->lexer->getLiteral($token));
310
            }
311
312
            // If parameter is keyword (200+) must be exact match
313 18
            if ($token > Lexer::T_IDENTIFIER) {
314 7
                $this->syntaxError($this->lexer->getLiteral($token));
315
            }
316
317
            // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
318 11
            if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
319 8
                $this->syntaxError($this->lexer->getLiteral($token));
320
            }
321
        }
322
323 858
        $this->lexer->moveNext();
324 858
    }
325
326
    /**
327
     * Frees this parser, enabling it to be reused.
328
     *
329
     * @param boolean $deep     Whether to clean peek and reset errors.
330
     * @param integer $position Position to reset.
331
     *
332
     * @return void
333
     */
334
    public function free($deep = false, $position = 0)
335
    {
336
        // WARNING! Use this method with care. It resets the scanner!
337
        $this->lexer->resetPosition($position);
338
339
        // Deep = true cleans peek and also any previously defined errors
340
        if ($deep) {
341
            $this->lexer->resetPeek();
342
        }
343
344
        $this->lexer->token = null;
345
        $this->lexer->lookahead = null;
346
    }
347
348
    /**
349
     * Parses a query string.
350
     *
351
     * @return ParserResult
352
     */
353 854
    public function parse()
354
    {
355 854
        $AST = $this->getAST();
356
357 800
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
358 96
            $this->customTreeWalkers = $customWalkers;
359
        }
360
361 800
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
362 79
            $this->customOutputWalker = $customOutputWalker;
363
        }
364
365
        // Run any custom tree walkers over the AST
366 800
        if ($this->customTreeWalkers) {
367 95
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
368
369 95
            foreach ($this->customTreeWalkers as $walker) {
370 95
                $treeWalkerChain->addTreeWalker($walker);
371
            }
372
373
            switch (true) {
374 95
                case ($AST instanceof AST\UpdateStatement):
375
                    $treeWalkerChain->walkUpdateStatement($AST);
376
                    break;
377
378 95
                case ($AST instanceof AST\DeleteStatement):
379
                    $treeWalkerChain->walkDeleteStatement($AST);
380
                    break;
381
382 95
                case ($AST instanceof AST\SelectStatement):
383
                default:
384 95
                    $treeWalkerChain->walkSelectStatement($AST);
385
            }
386
387 89
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
388
        }
389
390 794
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
391 794
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
392
393
        // Assign an SQL executor to the parser result
394 794
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
395
396 786
        return $this->parserResult;
397
    }
398
399
    /**
400
     * Fixes order of identification variables.
401
     *
402
     * They have to appear in the select clause in the same order as the
403
     * declarations (from ... x join ... y join ... z ...) appear in the query
404
     * as the hydration process relies on that order for proper operation.
405
     *
406
     * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
407
     *
408
     * @return void
409
     */
410 800
    private function fixIdentificationVariableOrder($AST)
411
    {
412 800
        if (count($this->identVariableExpressions) <= 1) {
413 623
            return;
414
        }
415
416 182
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
417 182
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
418 8
                continue;
419
            }
420
421 182
            $expr = $this->identVariableExpressions[$dqlAlias];
422 182
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
423
424 182
            unset($AST->selectClause->selectExpressions[$key]);
425
426 182
            $AST->selectClause->selectExpressions[] = $expr;
427
        }
428 182
    }
429
430
    /**
431
     * Generates a new syntax error.
432
     *
433
     * @param string     $expected Expected string.
434
     * @param array|null $token    Got token.
435
     *
436
     * @return void
437
     *
438
     * @throws \Doctrine\ORM\Query\QueryException
439
     */
440 18
    public function syntaxError($expected = '', $token = null)
441
    {
442 18
        if ($token === null) {
443 15
            $token = $this->lexer->lookahead;
444
        }
445
446 18
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
447
448 18
        $message  = "line 0, col {$tokenPos}: Error: ";
449 18
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
450 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
451
452 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
453
    }
454
455
    /**
456
     * Generates a new semantical error.
457
     *
458
     * @param string     $message Optional message.
459
     * @param array|null $token   Optional token.
460
     *
461
     * @return void
462
     *
463
     * @throws \Doctrine\ORM\Query\QueryException
464
     */
465 35
    public function semanticalError($message = '', $token = null)
466
    {
467 35
        if ($token === null) {
468 2
            $token = $this->lexer->lookahead;
469
        }
470
471
        // Minimum exposed chars ahead of token
472 35
        $distance = 12;
473
474
        // Find a position of a final word to display in error string
475 35
        $dql    = $this->query->getDQL();
476 35
        $length = strlen($dql);
477 35
        $pos    = $token['position'] + $distance;
478 35
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
479 35
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
480
481 35
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
482 35
        $tokenStr = substr($dql, $token['position'], $length);
483
484
        // Building informative message
485 35
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
486
487 35
        throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
488
    }
489
490
    /**
491
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
492
     *
493
     * @param boolean $resetPeek Reset peek after finding the closing parenthesis.
494
     *
495
     * @return array
496
     */
497 168
    private function peekBeyondClosingParenthesis($resetPeek = true)
498
    {
499 168
        $token = $this->lexer->peek();
500 168
        $numUnmatched = 1;
501
502 168
        while ($numUnmatched > 0 && $token !== null) {
503 167
            switch ($token['type']) {
504 167
                case Lexer::T_OPEN_PARENTHESIS:
505 42
                    ++$numUnmatched;
506 42
                    break;
507
508 167
                case Lexer::T_CLOSE_PARENTHESIS:
509 167
                    --$numUnmatched;
510 167
                    break;
511
512
                default:
513
                    // Do nothing
514
            }
515
516 167
            $token = $this->lexer->peek();
517
        }
518
519 168
        if ($resetPeek) {
520 147
            $this->lexer->resetPeek();
521
        }
522
523 168
        return $token;
524
    }
525
526
    /**
527
     * Checks if the given token indicates a mathematical operator.
528
     *
529
     * @param array $token
530
     *
531
     * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
532
     */
533 356
    private function isMathOperator($token)
534
    {
535 356
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
536
    }
537
538
    /**
539
     * Checks if the next-next (after lookahead) token starts a function.
540
     *
541
     * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
542
     */
543 402
    private function isFunction()
544
    {
545 402
        $lookaheadType = $this->lexer->lookahead['type'];
546 402
        $peek          = $this->lexer->peek();
547
548 402
        $this->lexer->resetPeek();
549
550 402
        return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
551
    }
552
553
    /**
554
     * Checks whether the given token type indicates an aggregate function.
555
     *
556
     * @param int $tokenType
557
     *
558
     * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
559
     */
560 1
    private function isAggregateFunction($tokenType)
561
    {
562 1
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]);
563
    }
564
565
    /**
566
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
567
     *
568
     * @return boolean
569
     */
570 302
    private function isNextAllAnySome()
571
    {
572 302
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]);
573
    }
574
575
    /**
576
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
577
     * It must exist in query components list.
578
     *
579
     * @return void
580
     */
581 812 View Code Duplication
    private function processDeferredIdentificationVariables()
582
    {
583 812
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
584 791
            $identVariable = $deferredItem['expression'];
585
586
            // Check if IdentificationVariable exists in queryComponents
587 791
            if ( ! isset($this->queryComponents[$identVariable])) {
588 1
                $this->semanticalError(
589 1
                    "'$identVariable' is not defined.", $deferredItem['token']
590
                );
591
            }
592
593 791
            $qComp = $this->queryComponents[$identVariable];
594
595
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
596 791
            if ( ! isset($qComp['metadata'])) {
597
                $this->semanticalError(
598
                    "'$identVariable' does not point to a Class.", $deferredItem['token']
599
                );
600
            }
601
602
            // Validate if identification variable nesting level is lower or equal than the current one
603 791
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
604 1
                $this->semanticalError(
605 791
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
606
                );
607
            }
608
        }
609 810
    }
610
611
    /**
612
     * Validates that the given <tt>NewObjectExpression</tt>.
613
     *
614
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
615
     *
616
     * @return void
617
     */
618 28
    private function processDeferredNewObjectExpressions($AST)
619
    {
620 28
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
621 28
            $expression     = $deferredItem['expression'];
622 28
            $token          = $deferredItem['token'];
623 28
            $className      = $expression->className;
624 28
            $args           = $expression->args;
625 28
            $fromClassName  = isset($AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName)
626 28
                ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName
627 28
                : null;
628
629
            // If the namespace is not given then assumes the first FROM entity namespace
630 28
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
631 11
                $namespace  = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
632 11
                $fqcn       = $namespace . '\\' . $className;
633
634 11
                if (class_exists($fqcn)) {
635 11
                    $expression->className  = $fqcn;
636 11
                    $className              = $fqcn;
637
                }
638
            }
639
640 28
            if ( ! class_exists($className)) {
641 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
642
            }
643
644 27
            $class = new \ReflectionClass($className);
645
646 27
            if ( ! $class->isInstantiable()) {
647 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
648
            }
649
650 26
            if ($class->getConstructor() === null) {
651 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
652
            }
653
654 25
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
655 25
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
656
            }
657
        }
658 24
    }
659
660
    /**
661
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
662
     * It must exist in query components list.
663
     *
664
     * @return void
665
     */
666 11
    private function processDeferredPartialObjectExpressions()
667
    {
668 11
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
669 11
            $expr = $deferredItem['expression'];
670 11
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
671
672 11
            foreach ($expr->partialFieldSet as $field) {
673 11
                if (isset($class->fieldMappings[$field])) {
674 10
                    continue;
675
                }
676
677 3
                if (isset($class->associationMappings[$field]) &&
678 3
                    $class->associationMappings[$field]['isOwningSide'] &&
679 3
                    $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
680 2
                    continue;
681
                }
682
683 1
                $this->semanticalError(
684 1
                    "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token']
685
                );
686
            }
687
688 10
            if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
689 1
                $this->semanticalError(
690 1
                    "The partial field selection of class " . $class->name . " must contain the identifier.",
691 10
                    $deferredItem['token']
692
                );
693
            }
694
        }
695 9
    }
696
697
    /**
698
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
699
     * It must exist in query components list.
700
     *
701
     * @return void
702
     */
703 32 View Code Duplication
    private function processDeferredResultVariables()
704
    {
705 32
        foreach ($this->deferredResultVariables as $deferredItem) {
706 32
            $resultVariable = $deferredItem['expression'];
707
708
            // Check if ResultVariable exists in queryComponents
709 32
            if ( ! isset($this->queryComponents[$resultVariable])) {
710
                $this->semanticalError(
711
                    "'$resultVariable' is not defined.", $deferredItem['token']
712
                );
713
            }
714
715 32
            $qComp = $this->queryComponents[$resultVariable];
716
717
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
718 32
            if ( ! isset($qComp['resultVariable'])) {
719
                $this->semanticalError(
720
                    "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token']
721
                );
722
            }
723
724
            // Validate if identification variable nesting level is lower or equal than the current one
725 32
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
726
                $this->semanticalError(
727 32
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
728
                );
729
            }
730
        }
731 32
    }
732
733
    /**
734
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
735
     *
736
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
737
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
738
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
739
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
740
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
741
     *
742
     * @return void
743
     */
744 599
    private function processDeferredPathExpressions()
745
    {
746 599
        foreach ($this->deferredPathExpressions as $deferredItem) {
747 599
            $pathExpression = $deferredItem['expression'];
748
749 599
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
750 599
            $class = $qComp['metadata'];
751
752 599
            if (($field = $pathExpression->field) === null) {
753 39
                $field = $pathExpression->field = $class->identifier[0];
754
            }
755
756
            // Check if field or association exists
757 599
            if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
758 1
                $this->semanticalError(
759 1
                    'Class ' . $class->name . ' has no field or association named ' . $field,
760 1
                    $deferredItem['token']
761
                );
762
            }
763
764 598
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
765
766 598
            if (isset($class->associationMappings[$field])) {
767 87
                $assoc = $class->associationMappings[$field];
768
769 87
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
770 66
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
771 87
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
772
            }
773
774
            // Validate if PathExpression is one of the expected types
775 598
            $expectedType = $pathExpression->expectedType;
776
777 598
            if ( ! ($expectedType & $fieldType)) {
778
                // We need to recognize which was expected type(s)
779 2
                $expectedStringTypes = [];
780
781
                // Validate state field type
782 2
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
783 1
                    $expectedStringTypes[] = 'StateFieldPathExpression';
784
                }
785
786
                // Validate single valued association (*-to-one)
787 2
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
788 2
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
789
                }
790
791
                // Validate single valued association (*-to-many)
792 2
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
793
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
794
                }
795
796
                // Build the error message
797 2
                $semanticalError  = 'Invalid PathExpression. ';
798 2
                $semanticalError .= (count($expectedStringTypes) == 1)
799 1
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
800 2
                    : implode(' or ', $expectedStringTypes) . ' expected.';
801
802 2
                $this->semanticalError($semanticalError, $deferredItem['token']);
803
            }
804
805
            // We need to force the type in PathExpression
806 596
            $pathExpression->type = $fieldType;
807
        }
808 596
    }
809
810
    /**
811
     * @return void
812
     */
813 801
    private function processRootEntityAliasSelected()
814
    {
815 801
        if ( ! count($this->identVariableExpressions)) {
816 234
            return;
817
        }
818
819 578
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
820 578
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
821 578
                return;
822
            }
823
        }
824
825 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
826
    }
827
828
    /**
829
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
830
     *
831
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
832
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
833
     *         \Doctrine\ORM\Query\AST\DeleteStatement
834
     */
835 854
    public function QueryLanguage()
836
    {
837 854
        $statement = null;
838
839 854
        $this->lexer->moveNext();
840
841 854
        switch ($this->lexer->lookahead['type']) {
842 854
            case Lexer::T_SELECT:
843 789
                $statement = $this->SelectStatement();
844 751
                break;
845
846 72
            case Lexer::T_UPDATE:
847 32
                $statement = $this->UpdateStatement();
848 32
                break;
849
850 42
            case Lexer::T_DELETE:
851 41
                $statement = $this->DeleteStatement();
852 40
                break;
853
854
            default:
855 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
856
                break;
857
        }
858
859
        // Check for end of string
860 815
        if ($this->lexer->lookahead !== null) {
861 3
            $this->syntaxError('end of string');
862
        }
863
864 812
        return $statement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statement also could return the type Doctrine\ORM\Query\AST\U...ery\AST\DeleteStatement which is incompatible with the documented return type Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
865
    }
866
867
    /**
868
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
869
     *
870
     * @return \Doctrine\ORM\Query\AST\SelectStatement
871
     */
872 789
    public function SelectStatement()
873
    {
874 789
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
875
876 755
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
877 752
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
878 751
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
879 751
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
880
881 751
        return $selectStatement;
882
    }
883
884
    /**
885
     * UpdateStatement ::= UpdateClause [WhereClause]
886
     *
887
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
888
     */
889 32 View Code Duplication
    public function UpdateStatement()
890
    {
891 32
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
892
893 32
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
894
895 32
        return $updateStatement;
896
    }
897
898
    /**
899
     * DeleteStatement ::= DeleteClause [WhereClause]
900
     *
901
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
902
     */
903 41 View Code Duplication
    public function DeleteStatement()
904
    {
905 41
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
906
907 40
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
908
909 40
        return $deleteStatement;
910
    }
911
912
    /**
913
     * IdentificationVariable ::= identifier
914
     *
915
     * @return string
916
     */
917 822 View Code Duplication
    public function IdentificationVariable()
918
    {
919 822
        $this->match(Lexer::T_IDENTIFIER);
920
921 822
        $identVariable = $this->lexer->token['value'];
922
923 822
        $this->deferredIdentificationVariables[] = [
924 822
            'expression'   => $identVariable,
925 822
            'nestingLevel' => $this->nestingLevel,
926 822
            'token'        => $this->lexer->token,
927
        ];
928
929 822
        return $identVariable;
930
    }
931
932
    /**
933
     * AliasIdentificationVariable = identifier
934
     *
935
     * @return string
936
     */
937 823 View Code Duplication
    public function AliasIdentificationVariable()
938
    {
939 823
        $this->match(Lexer::T_IDENTIFIER);
940
941 823
        $aliasIdentVariable = $this->lexer->token['value'];
942 823
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
943
944 823
        if ($exists) {
945 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
946
        }
947
948 823
        return $aliasIdentVariable;
949
    }
950
951
    /**
952
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
953
     *
954
     * @return string
955
     */
956 844
    public function AbstractSchemaName()
957
    {
958 844
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
959 826
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
960
961 826
            $schemaName = $this->lexer->token['value'];
962 29
        } else if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
963 19
            $this->match(Lexer::T_IDENTIFIER);
964
965 19
            $schemaName = $this->lexer->token['value'];
966
        } else {
967 11
            $this->match(Lexer::T_ALIASED_NAME);
968
969 10
            list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']);
970
971 10
            $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
972
        }
973
974 843
        return $schemaName;
975
    }
976
977
    /**
978
     * Validates an AbstractSchemaName, making sure the class exists.
979
     *
980
     * @param string $schemaName The name to validate.
981
     *
982
     * @throws QueryException if the name does not exist.
983
     */
984 838
    private function validateAbstractSchemaName($schemaName)
985
    {
986 838
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
987 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
988
        }
989 823
    }
990
991
    /**
992
     * AliasResultVariable ::= identifier
993
     *
994
     * @return string
995
     */
996 130 View Code Duplication
    public function AliasResultVariable()
997
    {
998 130
        $this->match(Lexer::T_IDENTIFIER);
999
1000 126
        $resultVariable = $this->lexer->token['value'];
1001 126
        $exists = isset($this->queryComponents[$resultVariable]);
1002
1003 126
        if ($exists) {
1004 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1005
        }
1006
1007 126
        return $resultVariable;
1008
    }
1009
1010
    /**
1011
     * ResultVariable ::= identifier
1012
     *
1013
     * @return string
1014
     */
1015 32 View Code Duplication
    public function ResultVariable()
1016
    {
1017 32
        $this->match(Lexer::T_IDENTIFIER);
1018
1019 32
        $resultVariable = $this->lexer->token['value'];
1020
1021
        // Defer ResultVariable validation
1022 32
        $this->deferredResultVariables[] = [
1023 32
            'expression'   => $resultVariable,
1024 32
            'nestingLevel' => $this->nestingLevel,
1025 32
            'token'        => $this->lexer->token,
1026
        ];
1027
1028 32
        return $resultVariable;
1029
    }
1030
1031
    /**
1032
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1033
     *
1034
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1035
     */
1036 259
    public function JoinAssociationPathExpression()
1037
    {
1038 259
        $identVariable = $this->IdentificationVariable();
1039
1040 259
        if ( ! isset($this->queryComponents[$identVariable])) {
1041
            $this->semanticalError(
1042
                'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'
1043
            );
1044
        }
1045
1046 259
        $this->match(Lexer::T_DOT);
1047 259
        $this->match(Lexer::T_IDENTIFIER);
1048
1049 259
        $field = $this->lexer->token['value'];
1050
1051
        // Validate association field
1052 259
        $qComp = $this->queryComponents[$identVariable];
1053 259
        $class = $qComp['metadata'];
1054
1055 259
        if ( ! $class->hasAssociation($field)) {
1056
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1057
        }
1058
1059 259
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1060
    }
1061
1062
    /**
1063
     * Parses an arbitrary path expression and defers semantical validation
1064
     * based on expected types.
1065
     *
1066
     * PathExpression ::= IdentificationVariable {"." identifier}*
1067
     *
1068
     * @param integer $expectedTypes
1069
     *
1070
     * @return \Doctrine\ORM\Query\AST\PathExpression
1071
     */
1072 609
    public function PathExpression($expectedTypes)
1073
    {
1074 609
        $identVariable = $this->IdentificationVariable();
1075 609
        $field = null;
1076
1077 609
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1078 602
            $this->match(Lexer::T_DOT);
1079 602
            $this->match(Lexer::T_IDENTIFIER);
1080
1081 602
            $field = $this->lexer->token['value'];
1082
1083 602 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1084 2
                $this->match(Lexer::T_DOT);
1085 2
                $this->match(Lexer::T_IDENTIFIER);
1086 2
                $field .= '.'.$this->lexer->token['value'];
1087
            }
1088
        }
1089
1090
        // Creating AST node
1091 609
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1092
1093
        // Defer PathExpression validation if requested to be deferred
1094 609
        $this->deferredPathExpressions[] = [
1095 609
            'expression'   => $pathExpr,
1096 609
            'nestingLevel' => $this->nestingLevel,
1097 609
            'token'        => $this->lexer->token,
1098
        ];
1099
1100 609
        return $pathExpr;
1101
    }
1102
1103
    /**
1104
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1105
     *
1106
     * @return \Doctrine\ORM\Query\AST\PathExpression
1107
     */
1108
    public function AssociationPathExpression()
1109
    {
1110
        return $this->PathExpression(
1111
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1112
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1113
        );
1114
    }
1115
1116
    /**
1117
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1118
     *
1119
     * @return \Doctrine\ORM\Query\AST\PathExpression
1120
     */
1121 517
    public function SingleValuedPathExpression()
1122
    {
1123 517
        return $this->PathExpression(
1124 517
            AST\PathExpression::TYPE_STATE_FIELD |
1125 517
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1126
        );
1127
    }
1128
1129
    /**
1130
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1131
     *
1132
     * @return \Doctrine\ORM\Query\AST\PathExpression
1133
     */
1134 204
    public function StateFieldPathExpression()
1135
    {
1136 204
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1137
    }
1138
1139
    /**
1140
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1141
     *
1142
     * @return \Doctrine\ORM\Query\AST\PathExpression
1143
     */
1144 9
    public function SingleValuedAssociationPathExpression()
1145
    {
1146 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1147
    }
1148
1149
    /**
1150
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1151
     *
1152
     * @return \Doctrine\ORM\Query\AST\PathExpression
1153
     */
1154 21
    public function CollectionValuedPathExpression()
1155
    {
1156 21
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1157
    }
1158
1159
    /**
1160
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1161
     *
1162
     * @return \Doctrine\ORM\Query\AST\SelectClause
1163
     */
1164 789
    public function SelectClause()
1165
    {
1166 789
        $isDistinct = false;
1167 789
        $this->match(Lexer::T_SELECT);
1168
1169
        // Check for DISTINCT
1170 789
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1171 6
            $this->match(Lexer::T_DISTINCT);
1172
1173 6
            $isDistinct = true;
1174
        }
1175
1176
        // Process SelectExpressions (1..N)
1177 789
        $selectExpressions = [];
1178 789
        $selectExpressions[] = $this->SelectExpression();
1179
1180 781
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1181 298
            $this->match(Lexer::T_COMMA);
1182
1183 298
            $selectExpressions[] = $this->SelectExpression();
1184
        }
1185
1186 780
        return new AST\SelectClause($selectExpressions, $isDistinct);
1187
    }
1188
1189
    /**
1190
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1191
     *
1192
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1193
     */
1194 49 View Code Duplication
    public function SimpleSelectClause()
1195
    {
1196 49
        $isDistinct = false;
1197 49
        $this->match(Lexer::T_SELECT);
1198
1199 49
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1200
            $this->match(Lexer::T_DISTINCT);
1201
1202
            $isDistinct = true;
1203
        }
1204
1205 49
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1206
    }
1207
1208
    /**
1209
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1210
     *
1211
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1212
     */
1213 32
    public function UpdateClause()
1214
    {
1215 32
        $this->match(Lexer::T_UPDATE);
1216
1217 32
        $token = $this->lexer->lookahead;
1218 32
        $abstractSchemaName = $this->AbstractSchemaName();
1219
1220 32
        $this->validateAbstractSchemaName($abstractSchemaName);
1221
1222 32
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1223 2
            $this->match(Lexer::T_AS);
1224
        }
1225
1226 32
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1227
1228 32
        $class = $this->em->getClassMetadata($abstractSchemaName);
1229
1230
        // Building queryComponent
1231
        $queryComponent = [
1232 32
            'metadata'     => $class,
1233
            'parent'       => null,
1234
            'relation'     => null,
1235
            'map'          => null,
1236 32
            'nestingLevel' => $this->nestingLevel,
1237 32
            'token'        => $token,
1238
        ];
1239
1240 32
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1241
1242 32
        $this->match(Lexer::T_SET);
1243
1244 32
        $updateItems = [];
1245 32
        $updateItems[] = $this->UpdateItem();
1246
1247 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1248 5
            $this->match(Lexer::T_COMMA);
1249
1250 5
            $updateItems[] = $this->UpdateItem();
1251
        }
1252
1253 32
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1254 32
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1255
1256 32
        return $updateClause;
1257
    }
1258
1259
    /**
1260
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1261
     *
1262
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1263
     */
1264 41
    public function DeleteClause()
1265
    {
1266 41
        $this->match(Lexer::T_DELETE);
1267
1268 41
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1269 8
            $this->match(Lexer::T_FROM);
1270
        }
1271
1272 41
        $token = $this->lexer->lookahead;
1273 41
        $abstractSchemaName = $this->AbstractSchemaName();
1274
1275 41
        $this->validateAbstractSchemaName($abstractSchemaName);
1276
1277 41
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1278
1279 41
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1280 1
            $this->match(Lexer::T_AS);
1281
        }
1282
1283 41
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1284
1285 40
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1286 40
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1287
1288
        // Building queryComponent
1289
        $queryComponent = [
1290 40
            'metadata'     => $class,
1291
            'parent'       => null,
1292
            'relation'     => null,
1293
            'map'          => null,
1294 40
            'nestingLevel' => $this->nestingLevel,
1295 40
            'token'        => $token,
1296
        ];
1297
1298 40
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1299
1300 40
        return $deleteClause;
1301
    }
1302
1303
    /**
1304
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1305
     *
1306
     * @return \Doctrine\ORM\Query\AST\FromClause
1307
     */
1308 780
    public function FromClause()
1309
    {
1310 780
        $this->match(Lexer::T_FROM);
1311
1312 775
        $identificationVariableDeclarations = [];
1313 775
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1314
1315 755
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1316 6
            $this->match(Lexer::T_COMMA);
1317
1318 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1319
        }
1320
1321 755
        return new AST\FromClause($identificationVariableDeclarations);
1322
    }
1323
1324
    /**
1325
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1326
     *
1327
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1328
     */
1329 49
    public function SubselectFromClause()
1330
    {
1331 49
        $this->match(Lexer::T_FROM);
1332
1333 49
        $identificationVariables = [];
1334 49
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1335
1336 48
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1337
            $this->match(Lexer::T_COMMA);
1338
1339
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1340
        }
1341
1342 48
        return new AST\SubselectFromClause($identificationVariables);
1343
    }
1344
1345
    /**
1346
     * WhereClause ::= "WHERE" ConditionalExpression
1347
     *
1348
     * @return \Doctrine\ORM\Query\AST\WhereClause
1349
     */
1350 343
    public function WhereClause()
1351
    {
1352 343
        $this->match(Lexer::T_WHERE);
1353
1354 343
        return new AST\WhereClause($this->ConditionalExpression());
1355
    }
1356
1357
    /**
1358
     * HavingClause ::= "HAVING" ConditionalExpression
1359
     *
1360
     * @return \Doctrine\ORM\Query\AST\HavingClause
1361
     */
1362 21
    public function HavingClause()
1363
    {
1364 21
        $this->match(Lexer::T_HAVING);
1365
1366 21
        return new AST\HavingClause($this->ConditionalExpression());
1367
    }
1368
1369
    /**
1370
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1371
     *
1372
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1373
     */
1374 33
    public function GroupByClause()
1375
    {
1376 33
        $this->match(Lexer::T_GROUP);
1377 33
        $this->match(Lexer::T_BY);
1378
1379 33
        $groupByItems = [$this->GroupByItem()];
1380
1381 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1382 8
            $this->match(Lexer::T_COMMA);
1383
1384 8
            $groupByItems[] = $this->GroupByItem();
1385
        }
1386
1387 32
        return new AST\GroupByClause($groupByItems);
1388
    }
1389
1390
    /**
1391
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1392
     *
1393
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1394
     */
1395 182
    public function OrderByClause()
1396
    {
1397 182
        $this->match(Lexer::T_ORDER);
1398 182
        $this->match(Lexer::T_BY);
1399
1400 182
        $orderByItems = [];
1401 182
        $orderByItems[] = $this->OrderByItem();
1402
1403 182
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1404 15
            $this->match(Lexer::T_COMMA);
1405
1406 15
            $orderByItems[] = $this->OrderByItem();
1407
        }
1408
1409 182
        return new AST\OrderByClause($orderByItems);
1410
    }
1411
1412
    /**
1413
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1414
     *
1415
     * @return \Doctrine\ORM\Query\AST\Subselect
1416
     */
1417 49
    public function Subselect()
1418
    {
1419
        // Increase query nesting level
1420 49
        $this->nestingLevel++;
1421
1422 49
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1423
1424 48
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1425 48
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1426 48
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1427 48
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1428
1429
        // Decrease query nesting level
1430 48
        $this->nestingLevel--;
1431
1432 48
        return $subselect;
1433
    }
1434
1435
    /**
1436
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1437
     *
1438
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1439
     */
1440 32
    public function UpdateItem()
1441
    {
1442 32
        $pathExpr = $this->SingleValuedPathExpression();
1443
1444 32
        $this->match(Lexer::T_EQUALS);
1445
1446 32
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1447
1448 32
        return $updateItem;
1449
    }
1450
1451
    /**
1452
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1453
     *
1454
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1455
     */
1456 33
    public function GroupByItem()
1457
    {
1458
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1459 33
        $glimpse = $this->lexer->glimpse();
1460
1461 33
        if ($glimpse['type'] === Lexer::T_DOT) {
1462 14
            return $this->SingleValuedPathExpression();
1463
        }
1464
1465
        // Still need to decide between IdentificationVariable or ResultVariable
1466 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1467
1468 19
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1469 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1470
        }
1471
1472 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1473 16
            ? $this->IdentificationVariable()
1474 18
            : $this->ResultVariable();
1475
    }
1476
1477
    /**
1478
     * OrderByItem ::= (
1479
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1480
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1481
     * ) ["ASC" | "DESC"]
1482
     *
1483
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1484
     */
1485 182
    public function OrderByItem()
1486
    {
1487 182
        $this->lexer->peek(); // lookahead => '.'
1488 182
        $this->lexer->peek(); // lookahead => token after '.'
1489
1490 182
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1491
1492 182
        $this->lexer->resetPeek();
1493
1494 182
        $glimpse = $this->lexer->glimpse();
1495
1496
        switch (true) {
1497 182
            case ($this->isFunction()):
1498 2
                $expr = $this->FunctionDeclaration();
1499 2
                break;
1500
1501 180
            case ($this->isMathOperator($peek)):
1502 25
                $expr = $this->SimpleArithmeticExpression();
1503 25
                break;
1504
1505 156
            case ($glimpse['type'] === Lexer::T_DOT):
1506 141
                $expr = $this->SingleValuedPathExpression();
1507 141
                break;
1508
1509 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1510 2
                $expr = $this->ScalarExpression();
1511 2
                break;
1512
1513
            default:
1514 17
                $expr = $this->ResultVariable();
1515 17
                break;
1516
        }
1517
1518 182
        $type = 'ASC';
1519 182
        $item = new AST\OrderByItem($expr);
1520
1521
        switch (true) {
1522 182
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1523 95
                $this->match(Lexer::T_DESC);
1524 95
                $type = 'DESC';
1525 95
                break;
1526
1527 154
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1528 97
                $this->match(Lexer::T_ASC);
1529 97
                break;
1530
1531
            default:
1532
                // Do nothing
1533
        }
1534
1535 182
        $item->type = $type;
1536
1537 182
        return $item;
1538
    }
1539
1540
    /**
1541
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1542
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1543
     *
1544
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1545
     * grammar that needs to be supported:
1546
     *
1547
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1548
     *
1549
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1550
     *
1551
     * @return AST\ArithmeticExpression
1552
     */
1553 32
    public function NewValue()
1554
    {
1555 32
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1556 1
            $this->match(Lexer::T_NULL);
1557
1558 1
            return null;
1559
        }
1560
1561 31 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1562 19
            $this->match(Lexer::T_INPUT_PARAMETER);
1563
1564 19
            return new AST\InputParameter($this->lexer->token['value']);
1565
        }
1566
1567 12
        return $this->ArithmeticExpression();
1568
    }
1569
1570
    /**
1571
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1572
     *
1573
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1574
     */
1575 777
    public function IdentificationVariableDeclaration()
1576
    {
1577 777
        $joins                    = [];
1578 777
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1579 760
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1580 8
            ? $this->IndexBy()
1581 760
            : null;
1582
1583 760
        $rangeVariableDeclaration->isRoot = true;
1584
1585
        while (
1586 760
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1587 760
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1588 760
            $this->lexer->isNextToken(Lexer::T_JOIN)
1589
        ) {
1590 281
            $joins[] = $this->Join();
1591
        }
1592
1593 757
        return new AST\IdentificationVariableDeclaration(
1594 757
            $rangeVariableDeclaration, $indexBy, $joins
1595
        );
1596
    }
1597
1598
    /**
1599
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1600
     *
1601
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1602
     * Desired EBNF support:
1603
     *
1604
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1605
     *
1606
     * It demands that entire SQL generation to become programmatical. This is
1607
     * needed because association based subselect requires "WHERE" conditional
1608
     * expressions to be injected, but there is no scope to do that. Only scope
1609
     * accessible is "FROM", prohibiting an easy implementation without larger
1610
     * changes.}
1611
     *
1612
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1613
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1614
     */
1615 49
    public function SubselectIdentificationVariableDeclaration()
1616
    {
1617
        /*
1618
        NOT YET IMPLEMENTED!
1619
1620
        $glimpse = $this->lexer->glimpse();
1621
1622
        if ($glimpse['type'] == Lexer::T_DOT) {
1623
            $associationPathExpression = $this->AssociationPathExpression();
1624
1625
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1626
                $this->match(Lexer::T_AS);
1627
            }
1628
1629
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1630
            $identificationVariable      = $associationPathExpression->identificationVariable;
1631
            $field                       = $associationPathExpression->associationField;
1632
1633
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1634
            $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1635
1636
            // Building queryComponent
1637
            $joinQueryComponent = array(
1638
                'metadata'     => $targetClass,
1639
                'parent'       => $identificationVariable,
1640
                'relation'     => $class->getAssociationMapping($field),
1641
                'map'          => null,
1642
                'nestingLevel' => $this->nestingLevel,
1643
                'token'        => $this->lexer->lookahead
1644
            );
1645
1646
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1647
1648
            return new AST\SubselectIdentificationVariableDeclaration(
1649
                $associationPathExpression, $aliasIdentificationVariable
1650
            );
1651
        }
1652
        */
1653
1654 49
        return $this->IdentificationVariableDeclaration();
1655
    }
1656
1657
    /**
1658
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1659
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1660
     *          ["WITH" ConditionalExpression]
1661
     *
1662
     * @return \Doctrine\ORM\Query\AST\Join
1663
     */
1664 281
    public function Join()
1665
    {
1666
        // Check Join type
1667 281
        $joinType = AST\Join::JOIN_TYPE_INNER;
1668
1669
        switch (true) {
1670 281
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1671 68
                $this->match(Lexer::T_LEFT);
1672
1673 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1674
1675
                // Possible LEFT OUTER join
1676 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1677
                    $this->match(Lexer::T_OUTER);
1678
1679
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1680
                }
1681 68
                break;
1682
1683 217
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1684 21
                $this->match(Lexer::T_INNER);
1685 21
                break;
1686
1687
            default:
1688
                // Do nothing
1689
        }
1690
1691 281
        $this->match(Lexer::T_JOIN);
1692
1693 281
        $next            = $this->lexer->glimpse();
1694 281
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1695 278
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1696 278
        $join            = new AST\Join($joinType, $joinDeclaration);
1697
1698
        // Describe non-root join declaration
1699 278
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1700 22
            $joinDeclaration->isRoot = false;
1701
        }
1702
1703
        // Check for ad-hoc Join conditions
1704 278
        if ($adhocConditions) {
1705 24
            $this->match(Lexer::T_WITH);
1706
1707 24
            $join->conditionalExpression = $this->ConditionalExpression();
1708
        }
1709
1710 278
        return $join;
1711
    }
1712
1713
    /**
1714
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1715
     *
1716
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1717
     *
1718
     * @throws QueryException
1719
     */
1720 777
    public function RangeVariableDeclaration()
1721
    {
1722 777
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1723 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1724
        }
1725
1726 776
        $abstractSchemaName = $this->AbstractSchemaName();
1727
1728 775
        $this->validateAbstractSchemaName($abstractSchemaName);
1729
1730 760
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1731 6
            $this->match(Lexer::T_AS);
1732
        }
1733
1734 760
        $token = $this->lexer->lookahead;
1735 760
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1736 760
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1737
1738
        // Building queryComponent
1739
        $queryComponent = [
1740 760
            'metadata'     => $classMetadata,
1741
            'parent'       => null,
1742
            'relation'     => null,
1743
            'map'          => null,
1744 760
            'nestingLevel' => $this->nestingLevel,
1745 760
            'token'        => $token
1746
        ];
1747
1748 760
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1749
1750 760
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1751
    }
1752
1753
    /**
1754
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1755
     *
1756
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1757
     */
1758 259
    public function JoinAssociationDeclaration()
1759
    {
1760 259
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1761
1762 259
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1763 5
            $this->match(Lexer::T_AS);
1764
        }
1765
1766 259
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1767 257
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1768
1769 257
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1770 257
        $field                  = $joinAssociationPathExpression->associationField;
1771
1772 257
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1773 257
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1774
1775
        // Building queryComponent
1776
        $joinQueryComponent = [
1777 257
            'metadata'     => $targetClass,
1778 257
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1779 257
            'relation'     => $class->getAssociationMapping($field),
1780
            'map'          => null,
1781 257
            'nestingLevel' => $this->nestingLevel,
1782 257
            'token'        => $this->lexer->lookahead
1783
        ];
1784
1785 257
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1786
1787 257
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1788
    }
1789
1790
    /**
1791
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1792
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1793
     *
1794
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1795
     */
1796 11
    public function PartialObjectExpression()
1797
    {
1798 11
        $this->match(Lexer::T_PARTIAL);
1799
1800 11
        $partialFieldSet = [];
1801
1802 11
        $identificationVariable = $this->IdentificationVariable();
1803
1804 11
        $this->match(Lexer::T_DOT);
1805 11
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1806 11
        $this->match(Lexer::T_IDENTIFIER);
1807
1808 11
        $field = $this->lexer->token['value'];
1809
1810
        // First field in partial expression might be embeddable property
1811 11 View Code Duplication
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1812 1
            $this->match(Lexer::T_DOT);
1813 1
            $this->match(Lexer::T_IDENTIFIER);
1814 1
            $field .= '.'.$this->lexer->token['value'];
1815
        }
1816
1817 11
        $partialFieldSet[] = $field;
1818
1819 11
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1820 9
            $this->match(Lexer::T_COMMA);
1821 9
            $this->match(Lexer::T_IDENTIFIER);
1822
1823 9
            $field = $this->lexer->token['value'];
1824
1825 9 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1826 2
                $this->match(Lexer::T_DOT);
1827 2
                $this->match(Lexer::T_IDENTIFIER);
1828 2
                $field .= '.'.$this->lexer->token['value'];
1829
            }
1830
1831 9
            $partialFieldSet[] = $field;
1832
        }
1833
1834 11
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1835
1836 11
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1837
1838
        // Defer PartialObjectExpression validation
1839 11
        $this->deferredPartialObjectExpressions[] = [
1840 11
            'expression'   => $partialObjectExpression,
1841 11
            'nestingLevel' => $this->nestingLevel,
1842 11
            'token'        => $this->lexer->token,
1843
        ];
1844
1845 11
        return $partialObjectExpression;
1846
    }
1847
1848
    /**
1849
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1850
     *
1851
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1852
     */
1853 28
    public function NewObjectExpression()
1854
    {
1855 28
        $this->match(Lexer::T_NEW);
1856
1857 28
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1858 28
        $token = $this->lexer->token;
1859
1860 28
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1861
1862 28
        $args[] = $this->NewObjectArg();
1863
1864 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1865 24
            $this->match(Lexer::T_COMMA);
1866
1867 24
            $args[] = $this->NewObjectArg();
1868
        }
1869
1870 28
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1871
1872 28
        $expression = new AST\NewObjectExpression($className, $args);
1873
1874
        // Defer NewObjectExpression validation
1875 28
        $this->deferredNewObjectExpressions[] = [
1876 28
            'token'        => $token,
1877 28
            'expression'   => $expression,
1878 28
            'nestingLevel' => $this->nestingLevel,
1879
        ];
1880
1881 28
        return $expression;
1882
    }
1883
1884
    /**
1885
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1886
     *
1887
     * @return mixed
1888
     */
1889 28
    public function NewObjectArg()
1890
    {
1891 28
        $token = $this->lexer->lookahead;
1892 28
        $peek  = $this->lexer->glimpse();
1893
1894 28
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1895 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1896 2
            $expression = $this->Subselect();
1897 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1898
1899 2
            return $expression;
1900
        }
1901
1902 28
        return $this->ScalarExpression();
1903
    }
1904
1905
    /**
1906
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1907
     *
1908
     * @return \Doctrine\ORM\Query\AST\IndexBy
1909
     */
1910 12
    public function IndexBy()
1911
    {
1912 12
        $this->match(Lexer::T_INDEX);
1913 12
        $this->match(Lexer::T_BY);
1914 12
        $pathExpr = $this->StateFieldPathExpression();
1915
1916
        // Add the INDEX BY info to the query component
1917 12
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1918
1919 12
        return new AST\IndexBy($pathExpr);
1920
    }
1921
1922
    /**
1923
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1924
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1925
     *                      InstanceOfExpression
1926
     *
1927
     * @return mixed One of the possible expressions or subexpressions.
1928
     */
1929 162
    public function ScalarExpression()
1930
    {
1931 162
        $lookahead = $this->lexer->lookahead['type'];
1932 162
        $peek      = $this->lexer->glimpse();
1933
1934
        switch (true) {
1935 162
            case ($lookahead === Lexer::T_INTEGER):
1936 159
            case ($lookahead === Lexer::T_FLOAT):
1937
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1938 159
            case ($lookahead === Lexer::T_MINUS):
1939 159
            case ($lookahead === Lexer::T_PLUS):
1940 17
                return $this->SimpleArithmeticExpression();
1941
1942 159
            case ($lookahead === Lexer::T_STRING):
1943 13
                return $this->StringPrimary();
1944
1945 157
            case ($lookahead === Lexer::T_TRUE):
1946 157 View Code Duplication
            case ($lookahead === Lexer::T_FALSE):
1947 3
                $this->match($lookahead);
1948
1949 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1950
1951 157
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1952
                switch (true) {
1953 1
                    case $this->isMathOperator($peek):
1954
                        // :param + u.value
1955 1
                        return $this->SimpleArithmeticExpression();
1956
                    default:
1957
                        return $this->InputParameter();
1958
                }
1959
1960 157
            case ($lookahead === Lexer::T_CASE):
1961 153
            case ($lookahead === Lexer::T_COALESCE):
1962 153
            case ($lookahead === Lexer::T_NULLIF):
1963
                // Since NULLIF and COALESCE can be identified as a function,
1964
                // we need to check these before checking for FunctionDeclaration
1965 8
                return $this->CaseExpression();
1966
1967 153
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1968 4
                return $this->SimpleArithmeticExpression();
1969
1970
            // this check must be done before checking for a filed path expression
1971 150
            case ($this->isFunction()):
1972 27
                $this->lexer->peek(); // "("
1973
1974
                switch (true) {
1975 27
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1976
                        // SUM(u.id) + COUNT(u.id)
1977 7
                        return $this->SimpleArithmeticExpression();
1978
1979
                    default:
1980
                        // IDENTITY(u)
1981 22
                        return $this->FunctionDeclaration();
1982
                }
1983
1984
                break;
1985
            // it is no function, so it must be a field path
1986 131
            case ($lookahead === Lexer::T_IDENTIFIER):
1987 131
                $this->lexer->peek(); // lookahead => '.'
1988 131
                $this->lexer->peek(); // lookahead => token after '.'
1989 131
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1990 131
                $this->lexer->resetPeek();
1991
1992 131
                if ($this->isMathOperator($peek)) {
1993 7
                    return $this->SimpleArithmeticExpression();
1994
                }
1995
1996 126
                return $this->StateFieldPathExpression();
1997
1998
            default:
1999
                $this->syntaxError();
2000
        }
2001
    }
2002
2003
    /**
2004
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
2005
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2006
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2007
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2008
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2009
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2010
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2011
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2012
     *
2013
     * @return mixed One of the possible expressions or subexpressions.
2014
     */
2015 19
    public function CaseExpression()
2016
    {
2017 19
        $lookahead = $this->lexer->lookahead['type'];
2018
2019
        switch ($lookahead) {
2020 19
            case Lexer::T_NULLIF:
2021 5
                return $this->NullIfExpression();
2022
2023 16
            case Lexer::T_COALESCE:
2024 2
                return $this->CoalesceExpression();
2025
2026 14
            case Lexer::T_CASE:
2027 14
                $this->lexer->resetPeek();
2028 14
                $peek = $this->lexer->peek();
2029
2030 14
                if ($peek['type'] === Lexer::T_WHEN) {
2031 9
                    return $this->GeneralCaseExpression();
2032
                }
2033
2034 5
                return $this->SimpleCaseExpression();
2035
2036
            default:
2037
                // Do nothing
2038
                break;
2039
        }
2040
2041
        $this->syntaxError();
2042
    }
2043
2044
    /**
2045
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2046
     *
2047
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2048
     */
2049 3
    public function CoalesceExpression()
2050
    {
2051 3
        $this->match(Lexer::T_COALESCE);
2052 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2053
2054
        // Process ScalarExpressions (1..N)
2055 3
        $scalarExpressions = [];
2056 3
        $scalarExpressions[] = $this->ScalarExpression();
2057
2058 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2059 3
            $this->match(Lexer::T_COMMA);
2060
2061 3
            $scalarExpressions[] = $this->ScalarExpression();
2062
        }
2063
2064 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2065
2066 3
        return new AST\CoalesceExpression($scalarExpressions);
2067
    }
2068
2069
    /**
2070
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2071
     *
2072
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2073
     */
2074 5
    public function NullIfExpression()
2075
    {
2076 5
        $this->match(Lexer::T_NULLIF);
2077 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2078
2079 5
        $firstExpression = $this->ScalarExpression();
2080 5
        $this->match(Lexer::T_COMMA);
2081 5
        $secondExpression = $this->ScalarExpression();
2082
2083 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2084
2085 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2086
    }
2087
2088
    /**
2089
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2090
     *
2091
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2092
     */
2093 9 View Code Duplication
    public function GeneralCaseExpression()
2094
    {
2095 9
        $this->match(Lexer::T_CASE);
2096
2097
        // Process WhenClause (1..N)
2098 9
        $whenClauses = [];
2099
2100
        do {
2101 9
            $whenClauses[] = $this->WhenClause();
2102 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2103
2104 9
        $this->match(Lexer::T_ELSE);
2105 9
        $scalarExpression = $this->ScalarExpression();
2106 9
        $this->match(Lexer::T_END);
2107
2108 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2109
    }
2110
2111
    /**
2112
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2113
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2114
     *
2115
     * @return AST\SimpleCaseExpression
2116
     */
2117 5 View Code Duplication
    public function SimpleCaseExpression()
2118
    {
2119 5
        $this->match(Lexer::T_CASE);
2120 5
        $caseOperand = $this->StateFieldPathExpression();
2121
2122
        // Process SimpleWhenClause (1..N)
2123 5
        $simpleWhenClauses = [];
2124
2125
        do {
2126 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2127 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2128
2129 5
        $this->match(Lexer::T_ELSE);
2130 5
        $scalarExpression = $this->ScalarExpression();
2131 5
        $this->match(Lexer::T_END);
2132
2133 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2134
    }
2135
2136
    /**
2137
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2138
     *
2139
     * @return \Doctrine\ORM\Query\AST\WhenClause
2140
     */
2141 9
    public function WhenClause()
2142
    {
2143 9
        $this->match(Lexer::T_WHEN);
2144 9
        $conditionalExpression = $this->ConditionalExpression();
2145 9
        $this->match(Lexer::T_THEN);
2146
2147 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2148
    }
2149
2150
    /**
2151
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2152
     *
2153
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2154
     */
2155 5
    public function SimpleWhenClause()
2156
    {
2157 5
        $this->match(Lexer::T_WHEN);
2158 5
        $conditionalExpression = $this->ScalarExpression();
2159 5
        $this->match(Lexer::T_THEN);
2160
2161 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2162
    }
2163
2164
    /**
2165
     * SelectExpression ::= (
2166
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2167
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2168
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2169
     *
2170
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2171
     */
2172 789
    public function SelectExpression()
2173
    {
2174 789
        $expression    = null;
2175 789
        $identVariable = null;
2176 789
        $peek          = $this->lexer->glimpse();
2177 789
        $lookaheadType = $this->lexer->lookahead['type'];
2178
2179
        switch (true) {
2180
            // ScalarExpression (u.name)
2181 789
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2182 103
                $expression = $this->ScalarExpression();
2183 103
                break;
2184
2185
            // IdentificationVariable (u)
2186 729
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2187 604
                $expression = $identVariable = $this->IdentificationVariable();
2188 604
                break;
2189
2190
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
2191 188
            case ($lookaheadType === Lexer::T_CASE):
2192 183
            case ($lookaheadType === Lexer::T_COALESCE):
2193 181
            case ($lookaheadType === Lexer::T_NULLIF):
2194 9
                $expression = $this->CaseExpression();
2195 9
                break;
2196
2197
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2198 179
            case ($this->isFunction()):
2199 99
                $this->lexer->peek(); // "("
2200
2201
                switch (true) {
2202 99
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2203
                        // SUM(u.id) + COUNT(u.id)
2204 2
                        $expression = $this->ScalarExpression();
2205 2
                        break;
2206
2207
                    default:
2208
                        // IDENTITY(u)
2209 97
                        $expression = $this->FunctionDeclaration();
2210 97
                        break;
2211
                }
2212
2213 99
                break;
2214
2215
            // PartialObjectExpression (PARTIAL u.{id, name})
2216 81
            case ($lookaheadType === Lexer::T_PARTIAL):
2217 11
                $expression    = $this->PartialObjectExpression();
2218 11
                $identVariable = $expression->identificationVariable;
2219 11
                break;
2220
2221
            // Subselect
2222 70
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2223 23
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2224 23
                $expression = $this->Subselect();
2225 23
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2226 23
                break;
2227
2228
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2229 47
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2230 43
            case ($lookaheadType === Lexer::T_INTEGER):
2231 41
            case ($lookaheadType === Lexer::T_STRING):
2232 32
            case ($lookaheadType === Lexer::T_FLOAT):
2233
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2234 32
            case ($lookaheadType === Lexer::T_MINUS):
2235 32
            case ($lookaheadType === Lexer::T_PLUS):
2236 16
                $expression = $this->SimpleArithmeticExpression();
2237 16
                break;
2238
2239
            // NewObjectExpression (New ClassName(id, name))
2240 31
            case ($lookaheadType === Lexer::T_NEW):
2241 28
                $expression = $this->NewObjectExpression();
2242 28
                break;
2243
2244
            default:
2245 3
                $this->syntaxError(
2246 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2247 3
                    $this->lexer->lookahead
2248
                );
2249
        }
2250
2251
        // [["AS"] ["HIDDEN"] AliasResultVariable]
2252 786
        $mustHaveAliasResultVariable = false;
2253
2254 786
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2255 121
            $this->match(Lexer::T_AS);
2256
2257 121
            $mustHaveAliasResultVariable = true;
2258
        }
2259
2260 786
        $hiddenAliasResultVariable = false;
2261
2262 786
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2263 10
            $this->match(Lexer::T_HIDDEN);
2264
2265 10
            $hiddenAliasResultVariable = true;
2266
        }
2267
2268 786
        $aliasResultVariable = null;
2269
2270 786 View Code Duplication
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2271 130
            $token = $this->lexer->lookahead;
2272 130
            $aliasResultVariable = $this->AliasResultVariable();
2273
2274
            // Include AliasResultVariable in query components.
2275 125
            $this->queryComponents[$aliasResultVariable] = [
2276 125
                'resultVariable' => $expression,
2277 125
                'nestingLevel'   => $this->nestingLevel,
2278 125
                'token'          => $token,
2279
            ];
2280
        }
2281
2282
        // AST
2283
2284 781
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2285
2286 781
        if ($identVariable) {
2287 612
            $this->identVariableExpressions[$identVariable] = $expr;
2288
        }
2289
2290 781
        return $expr;
2291
    }
2292
2293
    /**
2294
     * SimpleSelectExpression ::= (
2295
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2296
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2297
     * ) [["AS"] AliasResultVariable]
2298
     *
2299
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2300
     */
2301 49
    public function SimpleSelectExpression()
2302
    {
2303 49
        $peek = $this->lexer->glimpse();
2304
2305 49
        switch ($this->lexer->lookahead['type']) {
2306 49
            case Lexer::T_IDENTIFIER:
2307
                switch (true) {
2308 19
                    case ($peek['type'] === Lexer::T_DOT):
2309 16
                        $expression = $this->StateFieldPathExpression();
2310
2311 16
                        return new AST\SimpleSelectExpression($expression);
2312
2313 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2314 2
                        $expression = $this->IdentificationVariable();
2315
2316 2
                        return new AST\SimpleSelectExpression($expression);
2317
2318 1
                    case ($this->isFunction()):
2319
                        // SUM(u.id) + COUNT(u.id)
2320 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2321
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2322
                        }
2323
                        // COUNT(u.id)
2324 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2325
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2326
                        }
2327
                        // IDENTITY(u)
2328 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2329
2330
                    default:
2331
                        // Do nothing
2332
                }
2333
                break;
2334
2335 31
            case Lexer::T_OPEN_PARENTHESIS:
2336 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2337
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2338 3
                    $expression = $this->SimpleArithmeticExpression();
2339
2340 3
                    return new AST\SimpleSelectExpression($expression);
2341
                }
2342
2343
                // Subselect
2344
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2345
                $expression = $this->Subselect();
2346
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2347
2348
                return new AST\SimpleSelectExpression($expression);
2349
2350
            default:
2351
                // Do nothing
2352
        }
2353
2354 28
        $this->lexer->peek();
2355
2356 28
        $expression = $this->ScalarExpression();
2357 28
        $expr       = new AST\SimpleSelectExpression($expression);
2358
2359 28
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2360 1
            $this->match(Lexer::T_AS);
2361
        }
2362
2363 28 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2364 2
            $token = $this->lexer->lookahead;
2365 2
            $resultVariable = $this->AliasResultVariable();
2366 2
            $expr->fieldIdentificationVariable = $resultVariable;
2367
2368
            // Include AliasResultVariable in query components.
2369 2
            $this->queryComponents[$resultVariable] = [
2370 2
                'resultvariable' => $expr,
2371 2
                'nestingLevel'   => $this->nestingLevel,
2372 2
                'token'          => $token,
2373
            ];
2374
        }
2375
2376 28
        return $expr;
2377
    }
2378
2379
    /**
2380
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2381
     *
2382
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2383
     */
2384 386 View Code Duplication
    public function ConditionalExpression()
2385
    {
2386 386
        $conditionalTerms = [];
2387 386
        $conditionalTerms[] = $this->ConditionalTerm();
2388
2389 383
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2390 16
            $this->match(Lexer::T_OR);
2391
2392 16
            $conditionalTerms[] = $this->ConditionalTerm();
2393
        }
2394
2395
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2396
        // if only one AST\ConditionalTerm is defined
2397 383
        if (count($conditionalTerms) == 1) {
2398 375
            return $conditionalTerms[0];
2399
        }
2400
2401 16
        return new AST\ConditionalExpression($conditionalTerms);
2402
    }
2403
2404
    /**
2405
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2406
     *
2407
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2408
     */
2409 386 View Code Duplication
    public function ConditionalTerm()
2410
    {
2411 386
        $conditionalFactors = [];
2412 386
        $conditionalFactors[] = $this->ConditionalFactor();
2413
2414 383
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2415 33
            $this->match(Lexer::T_AND);
2416
2417 33
            $conditionalFactors[] = $this->ConditionalFactor();
2418
        }
2419
2420
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2421
        // if only one AST\ConditionalFactor is defined
2422 383
        if (count($conditionalFactors) == 1) {
2423 364
            return $conditionalFactors[0];
2424
        }
2425
2426 33
        return new AST\ConditionalTerm($conditionalFactors);
2427
    }
2428
2429
    /**
2430
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2431
     *
2432
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2433
     */
2434 386
    public function ConditionalFactor()
2435
    {
2436 386
        $not = false;
2437
2438 386
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2439 6
            $this->match(Lexer::T_NOT);
2440
2441 6
            $not = true;
2442
        }
2443
2444 386
        $conditionalPrimary = $this->ConditionalPrimary();
2445
2446
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2447
        // if only one AST\ConditionalPrimary is defined
2448 383
        if ( ! $not) {
2449 381
            return $conditionalPrimary;
2450
        }
2451
2452 6
        $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary);
2453 6
        $conditionalFactor->not = $not;
2454
2455 6
        return $conditionalFactor;
2456
    }
2457
2458
    /**
2459
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2460
     *
2461
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2462
     */
2463 386
    public function ConditionalPrimary()
2464
    {
2465 386
        $condPrimary = new AST\ConditionalPrimary;
2466
2467 386
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2468 377
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2469
2470 374
            return $condPrimary;
2471
        }
2472
2473
        // Peek beyond the matching closing parenthesis ')'
2474 25
        $peek = $this->peekBeyondClosingParenthesis();
2475
2476 25
        if (in_array($peek['value'], ["=",  "<", "<=", "<>", ">", ">=", "!="]) ||
2477 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2478 25
            $this->isMathOperator($peek)) {
2479 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2480
2481 15
            return $condPrimary;
2482
        }
2483
2484 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2485 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2486 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2487
2488 21
        return $condPrimary;
2489
    }
2490
2491
    /**
2492
     * SimpleConditionalExpression ::=
2493
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2494
     *      InExpression | NullComparisonExpression | ExistsExpression |
2495
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2496
     *      InstanceOfExpression
2497
     */
2498 386
    public function SimpleConditionalExpression()
2499
    {
2500 386
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2501 7
            return $this->ExistsExpression();
2502
        }
2503
2504 386
        $token      = $this->lexer->lookahead;
2505 386
        $peek       = $this->lexer->glimpse();
2506 386
        $lookahead  = $token;
2507
2508 386
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2509
            $token = $this->lexer->glimpse();
2510
        }
2511
2512 386
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2513
            // Peek beyond the matching closing parenthesis.
2514 362
            $beyond = $this->lexer->peek();
2515
2516 362
            switch ($peek['value']) {
2517 362
                case '(':
2518
                    // Peeks beyond the matched closing parenthesis.
2519 33
                    $token = $this->peekBeyondClosingParenthesis(false);
2520
2521 33
                    if ($token['type'] === Lexer::T_NOT) {
2522 3
                        $token = $this->lexer->peek();
2523
                    }
2524
2525 33
                    if ($token['type'] === Lexer::T_IS) {
2526 2
                        $lookahead = $this->lexer->peek();
2527
                    }
2528 33
                    break;
2529
2530
                default:
2531
                    // Peek beyond the PathExpression or InputParameter.
2532 335
                    $token = $beyond;
2533
2534 335
                    while ($token['value'] === '.') {
2535 291
                        $this->lexer->peek();
2536
2537 291
                        $token = $this->lexer->peek();
2538
                    }
2539
2540
                    // Also peek beyond a NOT if there is one.
2541 335
                    if ($token['type'] === Lexer::T_NOT) {
2542 11
                        $token = $this->lexer->peek();
2543
                    }
2544
2545
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2546 335
                    $lookahead = $this->lexer->peek();
2547
            }
2548
2549
            // Also peek beyond a NOT if there is one.
2550 362
            if ($lookahead['type'] === Lexer::T_NOT) {
2551 7
                $lookahead = $this->lexer->peek();
2552
            }
2553
2554 362
            $this->lexer->resetPeek();
2555
        }
2556
2557 386
        if ($token['type'] === Lexer::T_BETWEEN) {
2558 8
            return $this->BetweenExpression();
2559
        }
2560
2561 380
        if ($token['type'] === Lexer::T_LIKE) {
2562 14
            return $this->LikeExpression();
2563
        }
2564
2565 367
        if ($token['type'] === Lexer::T_IN) {
2566 35
            return $this->InExpression();
2567
        }
2568
2569 341
        if ($token['type'] === Lexer::T_INSTANCE) {
2570 17
            return $this->InstanceOfExpression();
2571
        }
2572
2573 324
        if ($token['type'] === Lexer::T_MEMBER) {
2574 7
            return $this->CollectionMemberExpression();
2575
        }
2576
2577 317
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2578 14
            return $this->NullComparisonExpression();
2579
        }
2580
2581 306
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2582 4
            return $this->EmptyCollectionComparisonExpression();
2583
        }
2584
2585 302
        return $this->ComparisonExpression();
2586
    }
2587
2588
    /**
2589
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2590
     *
2591
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2592
     */
2593 4 View Code Duplication
    public function EmptyCollectionComparisonExpression()
2594
    {
2595 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2596 4
            $this->CollectionValuedPathExpression()
2597
        );
2598 4
        $this->match(Lexer::T_IS);
2599
2600 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2601 2
            $this->match(Lexer::T_NOT);
2602 2
            $emptyCollectionCompExpr->not = true;
2603
        }
2604
2605 4
        $this->match(Lexer::T_EMPTY);
2606
2607 4
        return $emptyCollectionCompExpr;
2608
    }
2609
2610
    /**
2611
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2612
     *
2613
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2614
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2615
     *
2616
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2617
     */
2618 7 View Code Duplication
    public function CollectionMemberExpression()
2619
    {
2620 7
        $not        = false;
2621 7
        $entityExpr = $this->EntityExpression();
2622
2623 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2624
            $this->match(Lexer::T_NOT);
2625
2626
            $not = true;
2627
        }
2628
2629 7
        $this->match(Lexer::T_MEMBER);
2630
2631 7
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2632 7
            $this->match(Lexer::T_OF);
2633
        }
2634
2635 7
        $collMemberExpr = new AST\CollectionMemberExpression(
2636 7
            $entityExpr, $this->CollectionValuedPathExpression()
2637
        );
2638 7
        $collMemberExpr->not = $not;
2639
2640 7
        return $collMemberExpr;
2641
    }
2642
2643
    /**
2644
     * Literal ::= string | char | integer | float | boolean
2645
     *
2646
     * @return \Doctrine\ORM\Query\AST\Literal
2647
     */
2648 186
    public function Literal()
2649
    {
2650 186
        switch ($this->lexer->lookahead['type']) {
2651 186 View Code Duplication
            case Lexer::T_STRING:
2652 48
                $this->match(Lexer::T_STRING);
2653
2654 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2655 146
            case Lexer::T_INTEGER:
2656 9 View Code Duplication
            case Lexer::T_FLOAT:
2657 138
                $this->match(
2658 138
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2659
                );
2660
2661 138
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2662 8
            case Lexer::T_TRUE:
2663 4 View Code Duplication
            case Lexer::T_FALSE:
2664 8
                $this->match(
2665 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2666
                );
2667
2668 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2669
            default:
2670
                $this->syntaxError('Literal');
2671
        }
2672
    }
2673
2674
    /**
2675
     * InParameter ::= Literal | InputParameter
2676
     *
2677
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2678
     */
2679 26
    public function InParameter()
2680
    {
2681 26
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2682 14
            return $this->InputParameter();
2683
        }
2684
2685 12
        return $this->Literal();
2686
    }
2687
2688
    /**
2689
     * InputParameter ::= PositionalParameter | NamedParameter
2690
     *
2691
     * @return \Doctrine\ORM\Query\AST\InputParameter
2692
     */
2693 169
    public function InputParameter()
2694
    {
2695 169
        $this->match(Lexer::T_INPUT_PARAMETER);
2696
2697 169
        return new AST\InputParameter($this->lexer->token['value']);
2698
    }
2699
2700
    /**
2701
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2702
     *
2703
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2704
     */
2705 336
    public function ArithmeticExpression()
2706
    {
2707 336
        $expr = new AST\ArithmeticExpression;
2708
2709 336
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2710 19
            $peek = $this->lexer->glimpse();
2711
2712 19
            if ($peek['type'] === Lexer::T_SELECT) {
2713 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2714 7
                $expr->subselect = $this->Subselect();
2715 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2716
2717 7
                return $expr;
2718
            }
2719
        }
2720
2721 336
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2722
2723 336
        return $expr;
2724
    }
2725
2726
    /**
2727
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2728
     *
2729
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2730
     */
2731 442 View Code Duplication
    public function SimpleArithmeticExpression()
2732
    {
2733 442
        $terms = [];
2734 442
        $terms[] = $this->ArithmeticTerm();
2735
2736 442
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2737 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2738
2739 21
            $terms[] = $this->lexer->token['value'];
2740 21
            $terms[] = $this->ArithmeticTerm();
2741
        }
2742
2743
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2744
        // if only one AST\ArithmeticTerm is defined
2745 442
        if (count($terms) == 1) {
2746 437
            return $terms[0];
2747
        }
2748
2749 21
        return new AST\SimpleArithmeticExpression($terms);
2750
    }
2751
2752
    /**
2753
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2754
     *
2755
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2756
     */
2757 442 View Code Duplication
    public function ArithmeticTerm()
2758
    {
2759 442
        $factors = [];
2760 442
        $factors[] = $this->ArithmeticFactor();
2761
2762 442
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2763 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2764
2765 53
            $factors[] = $this->lexer->token['value'];
2766 53
            $factors[] = $this->ArithmeticFactor();
2767
        }
2768
2769
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2770
        // if only one AST\ArithmeticFactor is defined
2771 442
        if (count($factors) == 1) {
2772 414
            return $factors[0];
2773
        }
2774
2775 53
        return new AST\ArithmeticTerm($factors);
2776
    }
2777
2778
    /**
2779
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2780
     *
2781
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2782
     */
2783 442
    public function ArithmeticFactor()
2784
    {
2785 442
        $sign = null;
2786
2787 442
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2788 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2789 3
            $sign = $isPlus;
2790
        }
2791
2792 442
        $primary = $this->ArithmeticPrimary();
2793
2794
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2795
        // if only one AST\ArithmeticPrimary is defined
2796 442
        if ($sign === null) {
2797 441
            return $primary;
2798
        }
2799
2800 3
        return new AST\ArithmeticFactor($primary, $sign);
2801
    }
2802
2803
    /**
2804
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2805
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2806
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2807
     *          | InputParameter | CaseExpression
2808
     */
2809 455
    public function ArithmeticPrimary()
2810
    {
2811 455 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2812 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2813
2814 25
            $expr = $this->SimpleArithmeticExpression();
2815
2816 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2817
2818 25
            return new AST\ParenthesisExpression($expr);
2819
        }
2820
2821 455
        switch ($this->lexer->lookahead['type']) {
2822 455
            case Lexer::T_COALESCE:
2823 455
            case Lexer::T_NULLIF:
2824 455
            case Lexer::T_CASE:
2825 4
                return $this->CaseExpression();
2826
2827 455
            case Lexer::T_IDENTIFIER:
2828 425
                $peek = $this->lexer->glimpse();
2829
2830 425
                if ($peek['value'] == '(') {
2831 37
                    return $this->FunctionDeclaration();
2832
                }
2833
2834 396
                if ($peek['value'] == '.') {
2835 385
                    return $this->SingleValuedPathExpression();
2836
                }
2837
2838 46
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2839 10
                    return $this->ResultVariable();
2840
                }
2841
2842 38
                return $this->StateFieldPathExpression();
2843
2844 322
            case Lexer::T_INPUT_PARAMETER:
2845 150
                return $this->InputParameter();
2846
2847
            default:
2848 180
                $peek = $this->lexer->glimpse();
2849
2850 180
                if ($peek['value'] == '(') {
2851 18
                    return $this->FunctionDeclaration();
2852
                }
2853
2854 176
                return $this->Literal();
2855
        }
2856
    }
2857
2858
    /**
2859
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2860
     *
2861
     * @return \Doctrine\ORM\Query\AST\Subselect |
2862
     *         string
2863
     */
2864 14
    public function StringExpression()
2865
    {
2866 14
        $peek = $this->lexer->glimpse();
2867
2868
        // Subselect
2869 14 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2870
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2871
            $expr = $this->Subselect();
2872
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2873
2874
            return $expr;
2875
        }
2876
2877
        // ResultVariable (string)
2878 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2879 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2880 2
            return $this->ResultVariable();
2881
        }
2882
2883 12
        return $this->StringPrimary();
2884
    }
2885
2886
    /**
2887
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2888
     */
2889 60
    public function StringPrimary()
2890
    {
2891 60
        $lookaheadType = $this->lexer->lookahead['type'];
2892
2893
        switch ($lookaheadType) {
2894 60
            case Lexer::T_IDENTIFIER:
2895 32
                $peek = $this->lexer->glimpse();
2896
2897 32
                if ($peek['value'] == '.') {
2898 32
                    return $this->StateFieldPathExpression();
2899
                }
2900
2901 8
                if ($peek['value'] == '(') {
2902
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2903 8
                    return $this->FunctionDeclaration();
2904
                }
2905
2906
                $this->syntaxError("'.' or '('");
2907
                break;
2908
2909 41 View Code Duplication
            case Lexer::T_STRING:
2910 41
                $this->match(Lexer::T_STRING);
2911
2912 41
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2913
2914 2
            case Lexer::T_INPUT_PARAMETER:
2915 2
                return $this->InputParameter();
2916
2917
            case Lexer::T_CASE:
2918
            case Lexer::T_COALESCE:
2919
            case Lexer::T_NULLIF:
2920
                return $this->CaseExpression();
2921
        }
2922
2923
        $this->syntaxError(
2924
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2925
        );
2926
    }
2927
2928
    /**
2929
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2930
     *
2931
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2932
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2933
     */
2934 7
    public function EntityExpression()
2935
    {
2936 7
        $glimpse = $this->lexer->glimpse();
2937
2938 7
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2939 1
            return $this->SingleValuedAssociationPathExpression();
2940
        }
2941
2942 6
        return $this->SimpleEntityExpression();
2943
    }
2944
2945
    /**
2946
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2947
     *
2948
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2949
     */
2950 6
    public function SimpleEntityExpression()
2951
    {
2952 6
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2953 5
            return $this->InputParameter();
2954
        }
2955
2956 1
        return $this->StateFieldPathExpression();
2957
    }
2958
2959
    /**
2960
     * AggregateExpression ::=
2961
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2962
     *
2963
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2964
     */
2965 88
    public function AggregateExpression()
2966
    {
2967 88
        $lookaheadType = $this->lexer->lookahead['type'];
2968 88
        $isDistinct = false;
2969
2970 88
        if ( ! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) {
2971
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2972
        }
2973
2974 88
        $this->match($lookaheadType);
2975 88
        $functionName = $this->lexer->token['value'];
2976 88
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2977
2978 88
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2979 3
            $this->match(Lexer::T_DISTINCT);
2980 3
            $isDistinct = true;
2981
        }
2982
2983 88
        $pathExp = $this->SimpleArithmeticExpression();
2984
2985 88
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2986
2987 88
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2988
    }
2989
2990
    /**
2991
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2992
     *
2993
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2994
     */
2995 3
    public function QuantifiedExpression()
2996
    {
2997 3
        $lookaheadType = $this->lexer->lookahead['type'];
2998 3
        $value = $this->lexer->lookahead['value'];
2999
3000 3
        if ( ! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) {
3001
            $this->syntaxError('ALL, ANY or SOME');
3002
        }
3003
3004 3
        $this->match($lookaheadType);
3005 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3006
3007 3
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
3008 3
        $qExpr->type = $value;
3009
3010 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3011
3012 3
        return $qExpr;
3013
    }
3014
3015
    /**
3016
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3017
     *
3018
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3019
     */
3020 8
    public function BetweenExpression()
3021
    {
3022 8
        $not = false;
3023 8
        $arithExpr1 = $this->ArithmeticExpression();
3024
3025 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3026 3
            $this->match(Lexer::T_NOT);
3027 3
            $not = true;
3028
        }
3029
3030 8
        $this->match(Lexer::T_BETWEEN);
3031 8
        $arithExpr2 = $this->ArithmeticExpression();
3032 8
        $this->match(Lexer::T_AND);
3033 8
        $arithExpr3 = $this->ArithmeticExpression();
3034
3035 8
        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3036 8
        $betweenExpr->not = $not;
3037
3038 8
        return $betweenExpr;
3039
    }
3040
3041
    /**
3042
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3043
     *
3044
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3045
     */
3046 302
    public function ComparisonExpression()
3047
    {
3048 302
        $this->lexer->glimpse();
3049
3050 302
        $leftExpr  = $this->ArithmeticExpression();
3051 302
        $operator  = $this->ComparisonOperator();
3052 302
        $rightExpr = ($this->isNextAllAnySome())
3053 3
            ? $this->QuantifiedExpression()
3054 302
            : $this->ArithmeticExpression();
3055
3056 300
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3057
    }
3058
3059
    /**
3060
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3061
     *
3062
     * @return \Doctrine\ORM\Query\AST\InExpression
3063
     */
3064 35
    public function InExpression()
3065
    {
3066 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3067
3068 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3069 6
            $this->match(Lexer::T_NOT);
3070 6
            $inExpression->not = true;
3071
        }
3072
3073 35
        $this->match(Lexer::T_IN);
3074 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3075
3076 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3077 9
            $inExpression->subselect = $this->Subselect();
3078
        } else {
3079 26
            $literals = [];
3080 26
            $literals[] = $this->InParameter();
3081
3082 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3083 16
                $this->match(Lexer::T_COMMA);
3084 16
                $literals[] = $this->InParameter();
3085
            }
3086
3087 26
            $inExpression->literals = $literals;
3088
        }
3089
3090 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3091
3092 34
        return $inExpression;
3093
    }
3094
3095
    /**
3096
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3097
     *
3098
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3099
     */
3100 17
    public function InstanceOfExpression()
3101
    {
3102 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3103
3104 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3105 1
            $this->match(Lexer::T_NOT);
3106 1
            $instanceOfExpression->not = true;
3107
        }
3108
3109 17
        $this->match(Lexer::T_INSTANCE);
3110 17
        $this->match(Lexer::T_OF);
3111
3112 17
        $exprValues = [];
3113
3114 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3115 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3116
3117 2
            $exprValues[] = $this->InstanceOfParameter();
3118
3119 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3120 2
                $this->match(Lexer::T_COMMA);
3121
3122 2
                $exprValues[] = $this->InstanceOfParameter();
3123
            }
3124
3125 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3126
3127 2
            $instanceOfExpression->value = $exprValues;
3128
3129 2
            return $instanceOfExpression;
3130
        }
3131
3132 15
        $exprValues[] = $this->InstanceOfParameter();
3133
3134 15
        $instanceOfExpression->value = $exprValues;
3135
3136 15
        return $instanceOfExpression;
3137
    }
3138
3139
    /**
3140
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3141
     *
3142
     * @return mixed
3143
     */
3144 17
    public function InstanceOfParameter()
3145
    {
3146 17 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3147 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3148
3149 6
            return new AST\InputParameter($this->lexer->token['value']);
3150
        }
3151
3152 11
        $abstractSchemaName = $this->AbstractSchemaName();
3153
3154 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3155
3156 11
        return $abstractSchemaName;
3157
    }
3158
3159
    /**
3160
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3161
     *
3162
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3163
     */
3164 14
    public function LikeExpression()
3165
    {
3166 14
        $stringExpr = $this->StringExpression();
3167 14
        $not = false;
3168
3169 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3170 3
            $this->match(Lexer::T_NOT);
3171 3
            $not = true;
3172
        }
3173
3174 14
        $this->match(Lexer::T_LIKE);
3175
3176 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3177 4
            $this->match(Lexer::T_INPUT_PARAMETER);
3178 4
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3179
        } else {
3180 11
            $stringPattern = $this->StringPrimary();
3181
        }
3182
3183 14
        $escapeChar = null;
3184
3185 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3186 2
            $this->match(Lexer::T_ESCAPE);
3187 2
            $this->match(Lexer::T_STRING);
3188
3189 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3190
        }
3191
3192 14
        $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3193 14
        $likeExpr->not = $not;
3194
3195 14
        return $likeExpr;
3196
    }
3197
3198
    /**
3199
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3200
     *
3201
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3202
     */
3203 14
    public function NullComparisonExpression()
3204
    {
3205
        switch (true) {
3206 14
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3207
                $this->match(Lexer::T_INPUT_PARAMETER);
3208
3209
                $expr = new AST\InputParameter($this->lexer->token['value']);
3210
                break;
3211
3212 14
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3213 1
                $expr = $this->NullIfExpression();
3214 1
                break;
3215
3216 14
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3217 1
                $expr = $this->CoalesceExpression();
3218 1
                break;
3219
3220 14
            case $this->isFunction():
3221 2
                $expr = $this->FunctionDeclaration();
3222 2
                break;
3223
3224
            default:
3225
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3226 13
                $glimpse = $this->lexer->glimpse();
3227
3228 13
                if ($glimpse['type'] === Lexer::T_DOT) {
3229 9
                    $expr = $this->SingleValuedPathExpression();
3230
3231
                    // Leave switch statement
3232 9
                    break;
3233
                }
3234
3235 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3236
3237
                // Validate existing component
3238 4
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3239
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3240
                }
3241
3242
                // Validate SingleValuedPathExpression (ie.: "product")
3243 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3244 1
                    $expr = $this->SingleValuedPathExpression();
3245 1
                    break;
3246
                }
3247
3248
                // Validating ResultVariable
3249 3
                if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3250
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3251
                }
3252
3253 3
                $expr = $this->ResultVariable();
3254 3
                break;
3255
        }
3256
3257 14
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3258
3259 14
        $this->match(Lexer::T_IS);
3260
3261 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3262 5
            $this->match(Lexer::T_NOT);
3263
3264 5
            $nullCompExpr->not = true;
3265
        }
3266
3267 14
        $this->match(Lexer::T_NULL);
3268
3269 14
        return $nullCompExpr;
3270
    }
3271
3272
    /**
3273
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3274
     *
3275
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3276
     */
3277 7 View Code Duplication
    public function ExistsExpression()
3278
    {
3279 7
        $not = false;
3280
3281 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3282
            $this->match(Lexer::T_NOT);
3283
            $not = true;
3284
        }
3285
3286 7
        $this->match(Lexer::T_EXISTS);
3287 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3288
3289 7
        $existsExpression = new AST\ExistsExpression($this->Subselect());
3290 7
        $existsExpression->not = $not;
3291
3292 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3293
3294 7
        return $existsExpression;
3295
    }
3296
3297
    /**
3298
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3299
     *
3300
     * @return string
3301
     */
3302 302
    public function ComparisonOperator()
3303
    {
3304 302
        switch ($this->lexer->lookahead['value']) {
3305 302
            case '=':
3306 251
                $this->match(Lexer::T_EQUALS);
3307
3308 251
                return '=';
3309
3310 62
            case '<':
3311 17
                $this->match(Lexer::T_LOWER_THAN);
3312 17
                $operator = '<';
3313
3314 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3315 5
                    $this->match(Lexer::T_EQUALS);
3316 5
                    $operator .= '=';
3317 12
                } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3318 3
                    $this->match(Lexer::T_GREATER_THAN);
3319 3
                    $operator .= '>';
3320
                }
3321
3322 17
                return $operator;
3323
3324 53
            case '>':
3325 47
                $this->match(Lexer::T_GREATER_THAN);
3326 47
                $operator = '>';
3327
3328 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3329 6
                    $this->match(Lexer::T_EQUALS);
3330 6
                    $operator .= '=';
3331
                }
3332
3333 47
                return $operator;
3334
3335 6
            case '!':
3336 6
                $this->match(Lexer::T_NEGATE);
3337 6
                $this->match(Lexer::T_EQUALS);
3338
3339 6
                return '<>';
3340
3341
            default:
3342
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3343
        }
3344
    }
3345
3346
    /**
3347
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3348
     *
3349
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3350
     */
3351 153
    public function FunctionDeclaration()
3352
    {
3353 153
        $token = $this->lexer->lookahead;
3354 153
        $funcName = strtolower($token['value']);
3355
3356 153
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3357
3358
        // Check for custom functions functions first!
3359
        switch (true) {
3360 153
            case $customFunctionDeclaration !== null:
3361 4
                return $customFunctionDeclaration;
3362
3363 149
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3364 30
                return $this->FunctionsReturningStrings();
3365
3366 124
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3367 109
                return $this->FunctionsReturningNumerics();
3368
3369 16
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3370 16
                return $this->FunctionsReturningDatetime();
3371
3372
            default:
3373
                $this->syntaxError('known function', $token);
3374
        }
3375
    }
3376
3377
    /**
3378
     * Helper function for FunctionDeclaration grammar rule.
3379
     *
3380
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3381
     */
3382 153
    private function CustomFunctionDeclaration()
3383
    {
3384 153
        $token = $this->lexer->lookahead;
3385 153
        $funcName = strtolower($token['value']);
3386
3387
        // Check for custom functions afterwards
3388 153
        $config = $this->em->getConfiguration();
3389
3390
        switch (true) {
3391 153
            case ($config->getCustomStringFunction($funcName) !== null):
3392 3
                return $this->CustomFunctionsReturningStrings();
3393
3394 151
            case ($config->getCustomNumericFunction($funcName) !== null):
3395 2
                return $this->CustomFunctionsReturningNumerics();
3396
3397 149
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3398
                return $this->CustomFunctionsReturningDatetime();
3399
3400
            default:
3401 149
                return null;
3402
        }
3403
    }
3404
3405
    /**
3406
     * FunctionsReturningNumerics ::=
3407
     *      "LENGTH" "(" StringPrimary ")" |
3408
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3409
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3410
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3411
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3412
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3413
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3414
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3415
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3416
     *
3417
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3418
     */
3419 109 View Code Duplication
    public function FunctionsReturningNumerics()
3420
    {
3421 109
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3422 109
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3423
3424 109
        $function = new $funcClass($funcNameLower);
3425 109
        $function->parse($this);
3426
3427 109
        return $function;
3428
    }
3429
3430
    /**
3431
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3432
     */
3433 2 View Code Duplication
    public function CustomFunctionsReturningNumerics()
3434
    {
3435
        // getCustomNumericFunction is case-insensitive
3436 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3437 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3438
3439 2
        $function = is_string($functionClass)
3440 1
            ? new $functionClass($functionName)
3441 2
            : call_user_func($functionClass, $functionName);
3442
3443 2
        $function->parse($this);
3444
3445 2
        return $function;
3446
    }
3447
3448
    /**
3449
     * FunctionsReturningDateTime ::=
3450
     *     "CURRENT_DATE" |
3451
     *     "CURRENT_TIME" |
3452
     *     "CURRENT_TIMESTAMP" |
3453
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3454
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3455
     *
3456
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3457
     */
3458 16 View Code Duplication
    public function FunctionsReturningDatetime()
3459
    {
3460 16
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3461 16
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3462
3463 16
        $function = new $funcClass($funcNameLower);
3464 16
        $function->parse($this);
3465
3466 16
        return $function;
3467
    }
3468
3469
    /**
3470
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3471
     */
3472 View Code Duplication
    public function CustomFunctionsReturningDatetime()
3473
    {
3474
        // getCustomDatetimeFunction is case-insensitive
3475
        $functionName  = $this->lexer->lookahead['value'];
3476
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3477
3478
        $function = is_string($functionClass)
3479
            ? new $functionClass($functionName)
3480
            : call_user_func($functionClass, $functionName);
3481
3482
        $function->parse($this);
3483
3484
        return $function;
3485
    }
3486
3487
    /**
3488
     * FunctionsReturningStrings ::=
3489
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3490
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3491
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3492
     *   "LOWER" "(" StringPrimary ")" |
3493
     *   "UPPER" "(" StringPrimary ")" |
3494
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3495
     *
3496
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3497
     */
3498 30 View Code Duplication
    public function FunctionsReturningStrings()
3499
    {
3500 30
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3501 30
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3502
3503 30
        $function = new $funcClass($funcNameLower);
3504 30
        $function->parse($this);
3505
3506 30
        return $function;
3507
    }
3508
3509
    /**
3510
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3511
     */
3512 3 View Code Duplication
    public function CustomFunctionsReturningStrings()
3513
    {
3514
        // getCustomStringFunction is case-insensitive
3515 3
        $functionName  = $this->lexer->lookahead['value'];
3516 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3517
3518 3
        $function = is_string($functionClass)
3519 2
            ? new $functionClass($functionName)
3520 3
            : call_user_func($functionClass, $functionName);
3521
3522 3
        $function->parse($this);
3523
3524 3
        return $function;
3525
    }
3526
}
3527