Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

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

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 839
    public function __construct(Query $query)
187
    {
188 839
        $this->query        = $query;
189 839
        $this->em           = $query->getEntityManager();
190 839
        $this->lexer        = new Lexer($query->getDQL());
191 839
        $this->parserResult = new ParserResult();
192 839
    }
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 839
    public function getAST()
257
    {
258
        // Parse & build AST
259 839
        $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 797
        $this->processDeferredIdentificationVariables();
264
265 795
        if ($this->deferredPartialObjectExpressions) {
266 11
            $this->processDeferredPartialObjectExpressions();
267
        }
268
269 793
        if ($this->deferredPathExpressions) {
270 594
            $this->processDeferredPathExpressions();
271
        }
272
273 790
        if ($this->deferredResultVariables) {
274 30
            $this->processDeferredResultVariables();
275
        }
276
277 790
        if ($this->deferredNewObjectExpressions) {
278 28
            $this->processDeferredNewObjectExpressions($AST);
279
        }
280
281 786
        $this->processRootEntityAliasSelected();
282
283
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
284 785
        $this->fixIdentificationVariableOrder($AST);
285
286 785
        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 850
    public function match($token)
302
    {
303 850
        $lookaheadType = $this->lexer->lookahead['type'];
304
305
        // Short-circuit on first condition, usually types match
306 850
        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 843
        $this->lexer->moveNext();
324 843
    }
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 839
    public function parse()
354
    {
355 839
        $AST = $this->getAST();
356
357 785
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
358 93
            $this->customTreeWalkers = $customWalkers;
359
        }
360
361 785
        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 785
        if ($this->customTreeWalkers) {
367 92
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
368
369 92
            foreach ($this->customTreeWalkers as $walker) {
370 92
                $treeWalkerChain->addTreeWalker($walker);
371
            }
372
373
            switch (true) {
374 92
                case ($AST instanceof AST\UpdateStatement):
375
                    $treeWalkerChain->walkUpdateStatement($AST);
376
                    break;
377
378 92
                case ($AST instanceof AST\DeleteStatement):
379
                    $treeWalkerChain->walkDeleteStatement($AST);
380
                    break;
381
382 92
                case ($AST instanceof AST\SelectStatement):
383
                default:
384 92
                    $treeWalkerChain->walkSelectStatement($AST);
385
            }
386
387 86
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
388
        }
389
390 779
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
391 779
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
392
393
        // Assign an SQL executor to the parser result
394 779
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
395
396 771
        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 785
    private function fixIdentificationVariableOrder($AST)
411
    {
412 785
        if (count($this->identVariableExpressions) <= 1) {
413 608
            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 157
    private function peekBeyondClosingParenthesis($resetPeek = true)
498
    {
499 157
        $token = $this->lexer->peek();
500 157
        $numUnmatched = 1;
501
502 157
        while ($numUnmatched > 0 && $token !== null) {
503 156
            switch ($token['type']) {
504 156
                case Lexer::T_OPEN_PARENTHESIS:
505 33
                    ++$numUnmatched;
506 33
                    break;
507
508 156
                case Lexer::T_CLOSE_PARENTHESIS:
509 156
                    --$numUnmatched;
510 156
                    break;
511
512
                default:
513
                    // Do nothing
514
            }
515
516 156
            $token = $this->lexer->peek();
517
        }
518
519 157
        if ($resetPeek) {
520 136
            $this->lexer->resetPeek();
521
        }
522
523 157
        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 345
    private function isMathOperator($token)
534
    {
535 345
        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 390
    private function isFunction()
544
    {
545 390
        $lookaheadType = $this->lexer->lookahead['type'];
546 390
        $peek          = $this->lexer->peek();
547
548 390
        $this->lexer->resetPeek();
549
550 390
        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 299
    private function isNextAllAnySome()
571
    {
572 299
        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 797 View Code Duplication
    private function processDeferredIdentificationVariables()
582
    {
583 797
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
584 785
            $identVariable = $deferredItem['expression'];
585
586
            // Check if IdentificationVariable exists in queryComponents
587 785
            if ( ! isset($this->queryComponents[$identVariable])) {
588 1
                $this->semanticalError(
589 1
                    "'$identVariable' is not defined.", $deferredItem['token']
590
                );
591
            }
592
593 785
            $qComp = $this->queryComponents[$identVariable];
594
595
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
596 785
            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 785
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
604 1
                $this->semanticalError(
605 785
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
606
                );
607
            }
608
        }
609 795
    }
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 30 View Code Duplication
    private function processDeferredResultVariables()
704
    {
705 30
        foreach ($this->deferredResultVariables as $deferredItem) {
706 30
            $resultVariable = $deferredItem['expression'];
707
708
            // Check if ResultVariable exists in queryComponents
709 30
            if ( ! isset($this->queryComponents[$resultVariable])) {
710
                $this->semanticalError(
711
                    "'$resultVariable' is not defined.", $deferredItem['token']
712
                );
713
            }
714
715 30
            $qComp = $this->queryComponents[$resultVariable];
716
717
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
718 30
            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 30
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
726
                $this->semanticalError(
727 30
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
728
                );
729
            }
730
        }
731 30
    }
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 594
    private function processDeferredPathExpressions()
745
    {
746 594
        foreach ($this->deferredPathExpressions as $deferredItem) {
747 594
            $pathExpression = $deferredItem['expression'];
748
749 594
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
750 594
            $class = $qComp['metadata'];
751
752 594
            if (($field = $pathExpression->field) === null) {
753 39
                $field = $pathExpression->field = $class->identifier[0];
754
            }
755
756
            // Check if field or association exists
757 594
            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 593
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
765
766 593
            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 593
            $expectedType = $pathExpression->expectedType;
776
777 593
            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 591
            $pathExpression->type = $fieldType;
807
        }
808 591
    }
809
810
    /**
811
     * @return void
812
     */
813 786
    private function processRootEntityAliasSelected()
814
    {
815 786
        if ( ! count($this->identVariableExpressions)) {
816 225
            return;
817
        }
818
819 572
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
820 572
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
821 572
                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 839
    public function QueryLanguage()
836
    {
837 839
        $this->lexer->moveNext();
838
839 839
        switch ($this->lexer->lookahead['type']) {
840 839
            case Lexer::T_SELECT:
841 774
                $statement = $this->SelectStatement();
842 736
                break;
843
844 72
            case Lexer::T_UPDATE:
845 32
                $statement = $this->UpdateStatement();
846 32
                break;
847
848 42
            case Lexer::T_DELETE:
849 41
                $statement = $this->DeleteStatement();
850 40
                break;
851
852
            default:
853 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
854
                break;
855
        }
856
857
        // Check for end of string
858 800
        if ($this->lexer->lookahead !== null) {
859 3
            $this->syntaxError('end of string');
860
        }
861
862 797
        return $statement;
863
    }
864
865
    /**
866
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
867
     *
868
     * @return \Doctrine\ORM\Query\AST\SelectStatement
869
     */
870 774
    public function SelectStatement()
871
    {
872 774
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
873
874 740
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
875 737
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
876 736
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
877 736
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
878
879 736
        return $selectStatement;
880
    }
881
882
    /**
883
     * UpdateStatement ::= UpdateClause [WhereClause]
884
     *
885
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
886
     */
887 32 View Code Duplication
    public function UpdateStatement()
888
    {
889 32
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
890
891 32
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
892
893 32
        return $updateStatement;
894
    }
895
896
    /**
897
     * DeleteStatement ::= DeleteClause [WhereClause]
898
     *
899
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
900
     */
901 41 View Code Duplication
    public function DeleteStatement()
902
    {
903 41
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
904
905 40
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
906
907 40
        return $deleteStatement;
908
    }
909
910
    /**
911
     * IdentificationVariable ::= identifier
912
     *
913
     * @return string
914
     */
915 816 View Code Duplication
    public function IdentificationVariable()
916
    {
917 816
        $this->match(Lexer::T_IDENTIFIER);
918
919 816
        $identVariable = $this->lexer->token['value'];
920
921 816
        $this->deferredIdentificationVariables[] = [
922 816
            'expression'   => $identVariable,
923 816
            'nestingLevel' => $this->nestingLevel,
924 816
            'token'        => $this->lexer->token,
925
        ];
926
927 816
        return $identVariable;
928
    }
929
930
    /**
931
     * AliasIdentificationVariable = identifier
932
     *
933
     * @return string
934
     */
935 808 View Code Duplication
    public function AliasIdentificationVariable()
936
    {
937 808
        $this->match(Lexer::T_IDENTIFIER);
938
939 808
        $aliasIdentVariable = $this->lexer->token['value'];
940 808
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
941
942 808
        if ($exists) {
943 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
944
        }
945
946 808
        return $aliasIdentVariable;
947
    }
948
949
    /**
950
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
951
     *
952
     * @return string
953
     */
954 829
    public function AbstractSchemaName()
955
    {
956 829
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
957 811
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
958
959 811
            $schemaName = $this->lexer->token['value'];
960 29
        } else if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
961 19
            $this->match(Lexer::T_IDENTIFIER);
962
963 19
            $schemaName = $this->lexer->token['value'];
964
        } else {
965 11
            $this->match(Lexer::T_ALIASED_NAME);
966
967 10
            list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']);
968
969 10
            $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
970
        }
971
972 828
        return $schemaName;
973
    }
974
975
    /**
976
     * Validates an AbstractSchemaName, making sure the class exists.
977
     *
978
     * @param string $schemaName The name to validate.
979
     *
980
     * @throws QueryException if the name does not exist.
981
     */
982 823
    private function validateAbstractSchemaName($schemaName)
983
    {
984 823
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
985 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
986
        }
987 808
    }
988
989
    /**
990
     * AliasResultVariable ::= identifier
991
     *
992
     * @return string
993
     */
994 119 View Code Duplication
    public function AliasResultVariable()
995
    {
996 119
        $this->match(Lexer::T_IDENTIFIER);
997
998 115
        $resultVariable = $this->lexer->token['value'];
999 115
        $exists = isset($this->queryComponents[$resultVariable]);
1000
1001 115
        if ($exists) {
1002 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1003
        }
1004
1005 115
        return $resultVariable;
1006
    }
1007
1008
    /**
1009
     * ResultVariable ::= identifier
1010
     *
1011
     * @return string
1012
     */
1013 30 View Code Duplication
    public function ResultVariable()
1014
    {
1015 30
        $this->match(Lexer::T_IDENTIFIER);
1016
1017 30
        $resultVariable = $this->lexer->token['value'];
1018
1019
        // Defer ResultVariable validation
1020 30
        $this->deferredResultVariables[] = [
1021 30
            'expression'   => $resultVariable,
1022 30
            'nestingLevel' => $this->nestingLevel,
1023 30
            'token'        => $this->lexer->token,
1024
        ];
1025
1026 30
        return $resultVariable;
1027
    }
1028
1029
    /**
1030
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1031
     *
1032
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1033
     */
1034 257
    public function JoinAssociationPathExpression()
1035
    {
1036 257
        $identVariable = $this->IdentificationVariable();
1037
1038 257
        if ( ! isset($this->queryComponents[$identVariable])) {
1039
            $this->semanticalError(
1040
                'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'
1041
            );
1042
        }
1043
1044 257
        $this->match(Lexer::T_DOT);
1045 257
        $this->match(Lexer::T_IDENTIFIER);
1046
1047 257
        $field = $this->lexer->token['value'];
1048
1049
        // Validate association field
1050 257
        $qComp = $this->queryComponents[$identVariable];
1051 257
        $class = $qComp['metadata'];
1052
1053 257
        if ( ! $class->hasAssociation($field)) {
1054
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1055
        }
1056
1057 257
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1058
    }
1059
1060
    /**
1061
     * Parses an arbitrary path expression and defers semantical validation
1062
     * based on expected types.
1063
     *
1064
     * PathExpression ::= IdentificationVariable {"." identifier}*
1065
     *
1066
     * @param integer $expectedTypes
1067
     *
1068
     * @return \Doctrine\ORM\Query\AST\PathExpression
1069
     */
1070 604
    public function PathExpression($expectedTypes)
1071
    {
1072 604
        $identVariable = $this->IdentificationVariable();
1073 604
        $field = null;
1074
1075 604
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1076 597
            $this->match(Lexer::T_DOT);
1077 597
            $this->match(Lexer::T_IDENTIFIER);
1078
1079 597
            $field = $this->lexer->token['value'];
1080
1081 597 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 2
                $this->match(Lexer::T_DOT);
1083 2
                $this->match(Lexer::T_IDENTIFIER);
1084 2
                $field .= '.'.$this->lexer->token['value'];
1085
            }
1086
        }
1087
1088
        // Creating AST node
1089 604
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1090
1091
        // Defer PathExpression validation if requested to be deferred
1092 604
        $this->deferredPathExpressions[] = [
1093 604
            'expression'   => $pathExpr,
1094 604
            'nestingLevel' => $this->nestingLevel,
1095 604
            'token'        => $this->lexer->token,
1096
        ];
1097
1098 604
        return $pathExpr;
1099
    }
1100
1101
    /**
1102
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1103
     *
1104
     * @return \Doctrine\ORM\Query\AST\PathExpression
1105
     */
1106
    public function AssociationPathExpression()
1107
    {
1108
        return $this->PathExpression(
1109
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1110
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1111
        );
1112
    }
1113
1114
    /**
1115
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1116
     *
1117
     * @return \Doctrine\ORM\Query\AST\PathExpression
1118
     */
1119 512
    public function SingleValuedPathExpression()
1120
    {
1121 512
        return $this->PathExpression(
1122 512
            AST\PathExpression::TYPE_STATE_FIELD |
1123 512
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1124
        );
1125
    }
1126
1127
    /**
1128
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1129
     *
1130
     * @return \Doctrine\ORM\Query\AST\PathExpression
1131
     */
1132 204
    public function StateFieldPathExpression()
1133
    {
1134 204
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1135
    }
1136
1137
    /**
1138
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1139
     *
1140
     * @return \Doctrine\ORM\Query\AST\PathExpression
1141
     */
1142 9
    public function SingleValuedAssociationPathExpression()
1143
    {
1144 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1145
    }
1146
1147
    /**
1148
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1149
     *
1150
     * @return \Doctrine\ORM\Query\AST\PathExpression
1151
     */
1152 21
    public function CollectionValuedPathExpression()
1153
    {
1154 21
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1155
    }
1156
1157
    /**
1158
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1159
     *
1160
     * @return \Doctrine\ORM\Query\AST\SelectClause
1161
     */
1162 774
    public function SelectClause()
1163
    {
1164 774
        $isDistinct = false;
1165 774
        $this->match(Lexer::T_SELECT);
1166
1167
        // Check for DISTINCT
1168 774
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1169 6
            $this->match(Lexer::T_DISTINCT);
1170
1171 6
            $isDistinct = true;
1172
        }
1173
1174
        // Process SelectExpressions (1..N)
1175 774
        $selectExpressions = [];
1176 774
        $selectExpressions[] = $this->SelectExpression();
1177
1178 766
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1179 285
            $this->match(Lexer::T_COMMA);
1180
1181 285
            $selectExpressions[] = $this->SelectExpression();
1182
        }
1183
1184 765
        return new AST\SelectClause($selectExpressions, $isDistinct);
1185
    }
1186
1187
    /**
1188
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1189
     *
1190
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1191
     */
1192 48 View Code Duplication
    public function SimpleSelectClause()
1193
    {
1194 48
        $isDistinct = false;
1195 48
        $this->match(Lexer::T_SELECT);
1196
1197 48
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1198
            $this->match(Lexer::T_DISTINCT);
1199
1200
            $isDistinct = true;
1201
        }
1202
1203 48
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1204
    }
1205
1206
    /**
1207
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1208
     *
1209
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1210
     */
1211 32
    public function UpdateClause()
1212
    {
1213 32
        $this->match(Lexer::T_UPDATE);
1214
1215 32
        $token = $this->lexer->lookahead;
1216 32
        $abstractSchemaName = $this->AbstractSchemaName();
1217
1218 32
        $this->validateAbstractSchemaName($abstractSchemaName);
1219
1220 32
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1221 2
            $this->match(Lexer::T_AS);
1222
        }
1223
1224 32
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1225
1226 32
        $class = $this->em->getClassMetadata($abstractSchemaName);
1227
1228
        // Building queryComponent
1229
        $queryComponent = [
1230 32
            'metadata'     => $class,
1231
            'parent'       => null,
1232
            'relation'     => null,
1233
            'map'          => null,
1234 32
            'nestingLevel' => $this->nestingLevel,
1235 32
            'token'        => $token,
1236
        ];
1237
1238 32
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1239
1240 32
        $this->match(Lexer::T_SET);
1241
1242 32
        $updateItems = [];
1243 32
        $updateItems[] = $this->UpdateItem();
1244
1245 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1246 5
            $this->match(Lexer::T_COMMA);
1247
1248 5
            $updateItems[] = $this->UpdateItem();
1249
        }
1250
1251 32
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1252 32
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1253
1254 32
        return $updateClause;
1255
    }
1256
1257
    /**
1258
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1259
     *
1260
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1261
     */
1262 41
    public function DeleteClause()
1263
    {
1264 41
        $this->match(Lexer::T_DELETE);
1265
1266 41
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1267 8
            $this->match(Lexer::T_FROM);
1268
        }
1269
1270 41
        $token = $this->lexer->lookahead;
1271 41
        $abstractSchemaName = $this->AbstractSchemaName();
1272
1273 41
        $this->validateAbstractSchemaName($abstractSchemaName);
1274
1275 41
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1276
1277 41
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1278 1
            $this->match(Lexer::T_AS);
1279
        }
1280
1281 41
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1282
1283 40
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1284 40
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1285
1286
        // Building queryComponent
1287
        $queryComponent = [
1288 40
            'metadata'     => $class,
1289
            'parent'       => null,
1290
            'relation'     => null,
1291
            'map'          => null,
1292 40
            'nestingLevel' => $this->nestingLevel,
1293 40
            'token'        => $token,
1294
        ];
1295
1296 40
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1297
1298 40
        return $deleteClause;
1299
    }
1300
1301
    /**
1302
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1303
     *
1304
     * @return \Doctrine\ORM\Query\AST\FromClause
1305
     */
1306 765
    public function FromClause()
1307
    {
1308 765
        $this->match(Lexer::T_FROM);
1309
1310 760
        $identificationVariableDeclarations = [];
1311 760
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1312
1313 740
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1314 6
            $this->match(Lexer::T_COMMA);
1315
1316 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1317
        }
1318
1319 740
        return new AST\FromClause($identificationVariableDeclarations);
1320
    }
1321
1322
    /**
1323
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1324
     *
1325
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1326
     */
1327 48
    public function SubselectFromClause()
1328
    {
1329 48
        $this->match(Lexer::T_FROM);
1330
1331 48
        $identificationVariables = [];
1332 48
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1333
1334 47
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1335
            $this->match(Lexer::T_COMMA);
1336
1337
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1338
        }
1339
1340 47
        return new AST\SubselectFromClause($identificationVariables);
1341
    }
1342
1343
    /**
1344
     * WhereClause ::= "WHERE" ConditionalExpression
1345
     *
1346
     * @return \Doctrine\ORM\Query\AST\WhereClause
1347
     */
1348 340
    public function WhereClause()
1349
    {
1350 340
        $this->match(Lexer::T_WHERE);
1351
1352 340
        return new AST\WhereClause($this->ConditionalExpression());
1353
    }
1354
1355
    /**
1356
     * HavingClause ::= "HAVING" ConditionalExpression
1357
     *
1358
     * @return \Doctrine\ORM\Query\AST\HavingClause
1359
     */
1360 21
    public function HavingClause()
1361
    {
1362 21
        $this->match(Lexer::T_HAVING);
1363
1364 21
        return new AST\HavingClause($this->ConditionalExpression());
1365
    }
1366
1367
    /**
1368
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1369
     *
1370
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1371
     */
1372 32
    public function GroupByClause()
1373
    {
1374 32
        $this->match(Lexer::T_GROUP);
1375 32
        $this->match(Lexer::T_BY);
1376
1377 32
        $groupByItems = [$this->GroupByItem()];
1378
1379 31
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1380 8
            $this->match(Lexer::T_COMMA);
1381
1382 8
            $groupByItems[] = $this->GroupByItem();
1383
        }
1384
1385 31
        return new AST\GroupByClause($groupByItems);
1386
    }
1387
1388
    /**
1389
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1390
     *
1391
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1392
     */
1393 179
    public function OrderByClause()
1394
    {
1395 179
        $this->match(Lexer::T_ORDER);
1396 179
        $this->match(Lexer::T_BY);
1397
1398 179
        $orderByItems = [];
1399 179
        $orderByItems[] = $this->OrderByItem();
1400
1401 179
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1402 14
            $this->match(Lexer::T_COMMA);
1403
1404 14
            $orderByItems[] = $this->OrderByItem();
1405
        }
1406
1407 179
        return new AST\OrderByClause($orderByItems);
1408
    }
1409
1410
    /**
1411
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1412
     *
1413
     * @return \Doctrine\ORM\Query\AST\Subselect
1414
     */
1415 48
    public function Subselect()
1416
    {
1417
        // Increase query nesting level
1418 48
        $this->nestingLevel++;
1419
1420 48
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1421
1422 47
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1423 47
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1424 47
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1425 47
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1426
1427
        // Decrease query nesting level
1428 47
        $this->nestingLevel--;
1429
1430 47
        return $subselect;
1431
    }
1432
1433
    /**
1434
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1435
     *
1436
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1437
     */
1438 32
    public function UpdateItem()
1439
    {
1440 32
        $pathExpr = $this->SingleValuedPathExpression();
1441
1442 32
        $this->match(Lexer::T_EQUALS);
1443
1444 32
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1445
1446 32
        return $updateItem;
1447
    }
1448
1449
    /**
1450
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1451
     *
1452
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1453
     */
1454 32
    public function GroupByItem()
1455
    {
1456
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1457 32
        $glimpse = $this->lexer->glimpse();
1458
1459 32
        if ($glimpse['type'] === Lexer::T_DOT) {
1460 13
            return $this->SingleValuedPathExpression();
1461
        }
1462
1463
        // Still need to decide between IdentificationVariable or ResultVariable
1464 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1465
1466 19
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1467 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1468
        }
1469
1470 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1471 16
            ? $this->IdentificationVariable()
1472 18
            : $this->ResultVariable();
1473
    }
1474
1475
    /**
1476
     * OrderByItem ::= (
1477
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1478
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1479
     * ) ["ASC" | "DESC"]
1480
     *
1481
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1482
     */
1483 179
    public function OrderByItem()
1484
    {
1485 179
        $this->lexer->peek(); // lookahead => '.'
1486 179
        $this->lexer->peek(); // lookahead => token after '.'
1487
1488 179
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1489
1490 179
        $this->lexer->resetPeek();
1491
1492 179
        $glimpse = $this->lexer->glimpse();
1493
1494
        switch (true) {
1495 179
            case ($this->isFunction()):
1496 1
                $expr = $this->FunctionDeclaration();
1497 1
                break;
1498
1499 178
            case ($this->isMathOperator($peek)):
1500 25
                $expr = $this->SimpleArithmeticExpression();
1501 25
                break;
1502
1503 154
            case ($glimpse['type'] === Lexer::T_DOT):
1504 141
                $expr = $this->SingleValuedPathExpression();
1505 141
                break;
1506
1507 17
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1508 2
                $expr = $this->ScalarExpression();
1509 2
                break;
1510
1511
            default:
1512 15
                $expr = $this->ResultVariable();
1513 15
                break;
1514
        }
1515
1516 179
        $type = 'ASC';
1517 179
        $item = new AST\OrderByItem($expr);
1518
1519
        switch (true) {
1520 179
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1521 93
                $this->match(Lexer::T_DESC);
1522 93
                $type = 'DESC';
1523 93
                break;
1524
1525 152
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1526 96
                $this->match(Lexer::T_ASC);
1527 96
                break;
1528
1529
            default:
1530
                // Do nothing
1531
        }
1532
1533 179
        $item->type = $type;
1534
1535 179
        return $item;
1536
    }
1537
1538
    /**
1539
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1540
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1541
     *
1542
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1543
     * grammar that needs to be supported:
1544
     *
1545
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1546
     *
1547
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1548
     *
1549
     * @return AST\ArithmeticExpression
1550
     */
1551 32
    public function NewValue()
1552
    {
1553 32
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1554 1
            $this->match(Lexer::T_NULL);
1555
1556 1
            return null;
1557
        }
1558
1559 31 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1560 19
            $this->match(Lexer::T_INPUT_PARAMETER);
1561
1562 19
            return new AST\InputParameter($this->lexer->token['value']);
1563
        }
1564
1565 12
        return $this->ArithmeticExpression();
1566
    }
1567
1568
    /**
1569
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1570
     *
1571
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1572
     */
1573 762
    public function IdentificationVariableDeclaration()
1574
    {
1575 762
        $joins                    = [];
1576 762
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1577 745
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1578 8
            ? $this->IndexBy()
1579 745
            : null;
1580
1581 745
        $rangeVariableDeclaration->isRoot = true;
1582
1583
        while (
1584 745
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1585 745
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1586 745
            $this->lexer->isNextToken(Lexer::T_JOIN)
1587
        ) {
1588 280
            $joins[] = $this->Join();
1589
        }
1590
1591 742
        return new AST\IdentificationVariableDeclaration(
1592 742
            $rangeVariableDeclaration, $indexBy, $joins
1593
        );
1594
    }
1595
1596
    /**
1597
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1598
     *
1599
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1600
     * Desired EBNF support:
1601
     *
1602
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1603
     *
1604
     * It demands that entire SQL generation to become programmatical. This is
1605
     * needed because association based subselect requires "WHERE" conditional
1606
     * expressions to be injected, but there is no scope to do that. Only scope
1607
     * accessible is "FROM", prohibiting an easy implementation without larger
1608
     * changes.}
1609
     *
1610
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1611
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1612
     */
1613 48
    public function SubselectIdentificationVariableDeclaration()
1614
    {
1615
        /*
1616
        NOT YET IMPLEMENTED!
1617
1618
        $glimpse = $this->lexer->glimpse();
1619
1620
        if ($glimpse['type'] == Lexer::T_DOT) {
1621
            $associationPathExpression = $this->AssociationPathExpression();
1622
1623
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1624
                $this->match(Lexer::T_AS);
1625
            }
1626
1627
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1628
            $identificationVariable      = $associationPathExpression->identificationVariable;
1629
            $field                       = $associationPathExpression->associationField;
1630
1631
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1632
            $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1633
1634
            // Building queryComponent
1635
            $joinQueryComponent = array(
1636
                'metadata'     => $targetClass,
1637
                'parent'       => $identificationVariable,
1638
                'relation'     => $class->getAssociationMapping($field),
1639
                'map'          => null,
1640
                'nestingLevel' => $this->nestingLevel,
1641
                'token'        => $this->lexer->lookahead
1642
            );
1643
1644
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1645
1646
            return new AST\SubselectIdentificationVariableDeclaration(
1647
                $associationPathExpression, $aliasIdentificationVariable
1648
            );
1649
        }
1650
        */
1651
1652 48
        return $this->IdentificationVariableDeclaration();
1653
    }
1654
1655
    /**
1656
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1657
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1658
     *          ["WITH" ConditionalExpression]
1659
     *
1660
     * @return \Doctrine\ORM\Query\AST\Join
1661
     */
1662 280
    public function Join()
1663
    {
1664
        // Check Join type
1665 280
        $joinType = AST\Join::JOIN_TYPE_INNER;
1666
1667
        switch (true) {
1668 280
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1669 68
                $this->match(Lexer::T_LEFT);
1670
1671 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1672
1673
                // Possible LEFT OUTER join
1674 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1675
                    $this->match(Lexer::T_OUTER);
1676
1677
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1678
                }
1679 68
                break;
1680
1681 216
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1682 21
                $this->match(Lexer::T_INNER);
1683 21
                break;
1684
1685
            default:
1686
                // Do nothing
1687
        }
1688
1689 280
        $this->match(Lexer::T_JOIN);
1690
1691 280
        $next            = $this->lexer->glimpse();
1692 280
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1693 277
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1694 277
        $join            = new AST\Join($joinType, $joinDeclaration);
1695
1696
        // Describe non-root join declaration
1697 277
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1698 22
            $joinDeclaration->isRoot = false;
1699
        }
1700
1701
        // Check for ad-hoc Join conditions
1702 277
        if ($adhocConditions) {
1703 24
            $this->match(Lexer::T_WITH);
1704
1705 24
            $join->conditionalExpression = $this->ConditionalExpression();
1706
        }
1707
1708 277
        return $join;
1709
    }
1710
1711
    /**
1712
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1713
     *
1714
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1715
     *
1716
     * @throws QueryException
1717
     */
1718 762
    public function RangeVariableDeclaration()
1719
    {
1720 762
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1721 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1722
        }
1723
1724 761
        $abstractSchemaName = $this->AbstractSchemaName();
1725
1726 760
        $this->validateAbstractSchemaName($abstractSchemaName);
1727
1728 745
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1729 6
            $this->match(Lexer::T_AS);
1730
        }
1731
1732 745
        $token = $this->lexer->lookahead;
1733 745
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1734 745
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1735
1736
        // Building queryComponent
1737
        $queryComponent = [
1738 745
            'metadata'     => $classMetadata,
1739
            'parent'       => null,
1740
            'relation'     => null,
1741
            'map'          => null,
1742 745
            'nestingLevel' => $this->nestingLevel,
1743 745
            'token'        => $token
1744
        ];
1745
1746 745
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1747
1748 745
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1749
    }
1750
1751
    /**
1752
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1753
     *
1754
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1755
     */
1756 257
    public function JoinAssociationDeclaration()
1757
    {
1758 257
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1759
1760 257
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1761 5
            $this->match(Lexer::T_AS);
1762
        }
1763
1764 257
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1765 255
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1766
1767 255
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1768 255
        $field                  = $joinAssociationPathExpression->associationField;
1769
1770 255
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1771 255
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1772
1773
        // Building queryComponent
1774
        $joinQueryComponent = [
1775 255
            'metadata'     => $targetClass,
1776 255
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1777 255
            'relation'     => $class->getAssociationMapping($field),
1778
            'map'          => null,
1779 255
            'nestingLevel' => $this->nestingLevel,
1780 255
            'token'        => $this->lexer->lookahead
1781
        ];
1782
1783 255
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1784
1785 255
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1786
    }
1787
1788
    /**
1789
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1790
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1791
     *
1792
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1793
     */
1794 11
    public function PartialObjectExpression()
1795
    {
1796 11
        $this->match(Lexer::T_PARTIAL);
1797
1798 11
        $partialFieldSet = [];
1799
1800 11
        $identificationVariable = $this->IdentificationVariable();
1801
1802 11
        $this->match(Lexer::T_DOT);
1803 11
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1804 11
        $this->match(Lexer::T_IDENTIFIER);
1805
1806 11
        $field = $this->lexer->token['value'];
1807
1808
        // First field in partial expression might be embeddable property
1809 11 View Code Duplication
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1810 1
            $this->match(Lexer::T_DOT);
1811 1
            $this->match(Lexer::T_IDENTIFIER);
1812 1
            $field .= '.'.$this->lexer->token['value'];
1813
        }
1814
1815 11
        $partialFieldSet[] = $field;
1816
1817 11
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1818 9
            $this->match(Lexer::T_COMMA);
1819 9
            $this->match(Lexer::T_IDENTIFIER);
1820
1821 9
            $field = $this->lexer->token['value'];
1822
1823 9 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1824 2
                $this->match(Lexer::T_DOT);
1825 2
                $this->match(Lexer::T_IDENTIFIER);
1826 2
                $field .= '.'.$this->lexer->token['value'];
1827
            }
1828
1829 9
            $partialFieldSet[] = $field;
1830
        }
1831
1832 11
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1833
1834 11
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1835
1836
        // Defer PartialObjectExpression validation
1837 11
        $this->deferredPartialObjectExpressions[] = [
1838 11
            'expression'   => $partialObjectExpression,
1839 11
            'nestingLevel' => $this->nestingLevel,
1840 11
            'token'        => $this->lexer->token,
1841
        ];
1842
1843 11
        return $partialObjectExpression;
1844
    }
1845
1846
    /**
1847
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1848
     *
1849
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1850
     */
1851 28
    public function NewObjectExpression()
1852
    {
1853 28
        $this->match(Lexer::T_NEW);
1854
1855 28
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1856 28
        $token = $this->lexer->token;
1857
1858 28
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1859
1860 28
        $args[] = $this->NewObjectArg();
1861
1862 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1863 24
            $this->match(Lexer::T_COMMA);
1864
1865 24
            $args[] = $this->NewObjectArg();
1866
        }
1867
1868 28
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1869
1870 28
        $expression = new AST\NewObjectExpression($className, $args);
1871
1872
        // Defer NewObjectExpression validation
1873 28
        $this->deferredNewObjectExpressions[] = [
1874 28
            'token'        => $token,
1875 28
            'expression'   => $expression,
1876 28
            'nestingLevel' => $this->nestingLevel,
1877
        ];
1878
1879 28
        return $expression;
1880
    }
1881
1882
    /**
1883
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1884
     *
1885
     * @return mixed
1886
     */
1887 28
    public function NewObjectArg()
1888
    {
1889 28
        $token = $this->lexer->lookahead;
1890 28
        $peek  = $this->lexer->glimpse();
1891
1892 28
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1893 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1894 2
            $expression = $this->Subselect();
1895 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1896
1897 2
            return $expression;
1898
        }
1899
1900 28
        return $this->ScalarExpression();
1901
    }
1902
1903
    /**
1904
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1905
     *
1906
     * @return \Doctrine\ORM\Query\AST\IndexBy
1907
     */
1908 12
    public function IndexBy()
1909
    {
1910 12
        $this->match(Lexer::T_INDEX);
1911 12
        $this->match(Lexer::T_BY);
1912 12
        $pathExpr = $this->StateFieldPathExpression();
1913
1914
        // Add the INDEX BY info to the query component
1915 12
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1916
1917 12
        return new AST\IndexBy($pathExpr);
1918
    }
1919
1920
    /**
1921
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1922
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1923
     *                      InstanceOfExpression
1924
     *
1925
     * @return mixed One of the possible expressions or subexpressions.
1926
     */
1927 161
    public function ScalarExpression()
1928
    {
1929 161
        $lookahead = $this->lexer->lookahead['type'];
1930 161
        $peek      = $this->lexer->glimpse();
1931
1932
        switch (true) {
1933 161
            case ($lookahead === Lexer::T_INTEGER):
1934 158
            case ($lookahead === Lexer::T_FLOAT):
1935
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1936 158
            case ($lookahead === Lexer::T_MINUS):
1937 158
            case ($lookahead === Lexer::T_PLUS):
1938 17
                return $this->SimpleArithmeticExpression();
1939
1940 158
            case ($lookahead === Lexer::T_STRING):
1941 13
                return $this->StringPrimary();
1942
1943 156
            case ($lookahead === Lexer::T_TRUE):
1944 156 View Code Duplication
            case ($lookahead === Lexer::T_FALSE):
1945 3
                $this->match($lookahead);
1946
1947 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1948
1949 156
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1950
                switch (true) {
1951 1
                    case $this->isMathOperator($peek):
1952
                        // :param + u.value
1953 1
                        return $this->SimpleArithmeticExpression();
1954
                    default:
1955
                        return $this->InputParameter();
1956
                }
1957
1958 156
            case ($lookahead === Lexer::T_CASE):
1959 152
            case ($lookahead === Lexer::T_COALESCE):
1960 152
            case ($lookahead === Lexer::T_NULLIF):
1961
                // Since NULLIF and COALESCE can be identified as a function,
1962
                // we need to check these before checking for FunctionDeclaration
1963 8
                return $this->CaseExpression();
1964
1965 152
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1966 4
                return $this->SimpleArithmeticExpression();
1967
1968
            // this check must be done before checking for a filed path expression
1969 149
            case ($this->isFunction()):
1970 26
                $this->lexer->peek(); // "("
1971
1972
                switch (true) {
1973 26
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1974
                        // SUM(u.id) + COUNT(u.id)
1975 7
                        return $this->SimpleArithmeticExpression();
1976
1977
                    default:
1978
                        // IDENTITY(u)
1979 21
                        return $this->FunctionDeclaration();
1980
                }
1981
1982
                break;
1983
            // it is no function, so it must be a field path
1984 131
            case ($lookahead === Lexer::T_IDENTIFIER):
1985 131
                $this->lexer->peek(); // lookahead => '.'
1986 131
                $this->lexer->peek(); // lookahead => token after '.'
1987 131
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1988 131
                $this->lexer->resetPeek();
1989
1990 131
                if ($this->isMathOperator($peek)) {
1991 7
                    return $this->SimpleArithmeticExpression();
1992
                }
1993
1994 126
                return $this->StateFieldPathExpression();
1995
1996
            default:
1997
                $this->syntaxError();
1998
        }
1999
    }
2000
2001
    /**
2002
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
2003
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2004
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2005
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2006
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2007
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2008
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2009
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2010
     *
2011
     * @return mixed One of the possible expressions or subexpressions.
2012
     */
2013 19
    public function CaseExpression()
2014
    {
2015 19
        $lookahead = $this->lexer->lookahead['type'];
2016
2017
        switch ($lookahead) {
2018 19
            case Lexer::T_NULLIF:
2019 5
                return $this->NullIfExpression();
2020
2021 16
            case Lexer::T_COALESCE:
2022 2
                return $this->CoalesceExpression();
2023
2024 14
            case Lexer::T_CASE:
2025 14
                $this->lexer->resetPeek();
2026 14
                $peek = $this->lexer->peek();
2027
2028 14
                if ($peek['type'] === Lexer::T_WHEN) {
2029 9
                    return $this->GeneralCaseExpression();
2030
                }
2031
2032 5
                return $this->SimpleCaseExpression();
2033
2034
            default:
2035
                // Do nothing
2036
                break;
2037
        }
2038
2039
        $this->syntaxError();
2040
    }
2041
2042
    /**
2043
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2044
     *
2045
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2046
     */
2047 3
    public function CoalesceExpression()
2048
    {
2049 3
        $this->match(Lexer::T_COALESCE);
2050 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2051
2052
        // Process ScalarExpressions (1..N)
2053 3
        $scalarExpressions = [];
2054 3
        $scalarExpressions[] = $this->ScalarExpression();
2055
2056 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2057 3
            $this->match(Lexer::T_COMMA);
2058
2059 3
            $scalarExpressions[] = $this->ScalarExpression();
2060
        }
2061
2062 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2063
2064 3
        return new AST\CoalesceExpression($scalarExpressions);
2065
    }
2066
2067
    /**
2068
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2069
     *
2070
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2071
     */
2072 5
    public function NullIfExpression()
2073
    {
2074 5
        $this->match(Lexer::T_NULLIF);
2075 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2076
2077 5
        $firstExpression = $this->ScalarExpression();
2078 5
        $this->match(Lexer::T_COMMA);
2079 5
        $secondExpression = $this->ScalarExpression();
2080
2081 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2082
2083 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2084
    }
2085
2086
    /**
2087
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2088
     *
2089
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2090
     */
2091 9 View Code Duplication
    public function GeneralCaseExpression()
2092
    {
2093 9
        $this->match(Lexer::T_CASE);
2094
2095
        // Process WhenClause (1..N)
2096 9
        $whenClauses = [];
2097
2098
        do {
2099 9
            $whenClauses[] = $this->WhenClause();
2100 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2101
2102 9
        $this->match(Lexer::T_ELSE);
2103 9
        $scalarExpression = $this->ScalarExpression();
2104 9
        $this->match(Lexer::T_END);
2105
2106 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2107
    }
2108
2109
    /**
2110
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2111
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2112
     *
2113
     * @return AST\SimpleCaseExpression
2114
     */
2115 5 View Code Duplication
    public function SimpleCaseExpression()
2116
    {
2117 5
        $this->match(Lexer::T_CASE);
2118 5
        $caseOperand = $this->StateFieldPathExpression();
2119
2120
        // Process SimpleWhenClause (1..N)
2121 5
        $simpleWhenClauses = [];
2122
2123
        do {
2124 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2125 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2126
2127 5
        $this->match(Lexer::T_ELSE);
2128 5
        $scalarExpression = $this->ScalarExpression();
2129 5
        $this->match(Lexer::T_END);
2130
2131 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2132
    }
2133
2134
    /**
2135
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2136
     *
2137
     * @return \Doctrine\ORM\Query\AST\WhenClause
2138
     */
2139 9
    public function WhenClause()
2140
    {
2141 9
        $this->match(Lexer::T_WHEN);
2142 9
        $conditionalExpression = $this->ConditionalExpression();
2143 9
        $this->match(Lexer::T_THEN);
2144
2145 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2146
    }
2147
2148
    /**
2149
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2150
     *
2151
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2152
     */
2153 5
    public function SimpleWhenClause()
2154
    {
2155 5
        $this->match(Lexer::T_WHEN);
2156 5
        $conditionalExpression = $this->ScalarExpression();
2157 5
        $this->match(Lexer::T_THEN);
2158
2159 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2160
    }
2161
2162
    /**
2163
     * SelectExpression ::= (
2164
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2165
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2166
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2167
     *
2168
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2169
     */
2170 774
    public function SelectExpression()
2171
    {
2172 774
        $expression    = null;
2173 774
        $identVariable = null;
2174 774
        $peek          = $this->lexer->glimpse();
2175 774
        $lookaheadType = $this->lexer->lookahead['type'];
2176
2177
        switch (true) {
2178
            // ScalarExpression (u.name)
2179 774
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2180 103
                $expression = $this->ScalarExpression();
2181 103
                break;
2182
2183
            // IdentificationVariable (u)
2184 714
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2185 598
                $expression = $identVariable = $this->IdentificationVariable();
2186 598
                break;
2187
2188
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
2189 177
            case ($lookaheadType === Lexer::T_CASE):
2190 172
            case ($lookaheadType === Lexer::T_COALESCE):
2191 170
            case ($lookaheadType === Lexer::T_NULLIF):
2192 9
                $expression = $this->CaseExpression();
2193 9
                break;
2194
2195
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2196 168
            case ($this->isFunction()):
2197 88
                $this->lexer->peek(); // "("
2198
2199
                switch (true) {
2200 88
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2201
                        // SUM(u.id) + COUNT(u.id)
2202 2
                        $expression = $this->ScalarExpression();
2203 2
                        break;
2204
2205
                    default:
2206
                        // IDENTITY(u)
2207 86
                        $expression = $this->FunctionDeclaration();
2208 86
                        break;
2209
                }
2210
2211 88
                break;
2212
2213
            // PartialObjectExpression (PARTIAL u.{id, name})
2214 80
            case ($lookaheadType === Lexer::T_PARTIAL):
2215 11
                $expression    = $this->PartialObjectExpression();
2216 11
                $identVariable = $expression->identificationVariable;
2217 11
                break;
2218
2219
            // Subselect
2220 69
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2221 22
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2222 22
                $expression = $this->Subselect();
2223 22
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2224 22
                break;
2225
2226
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2227 47
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2228 43
            case ($lookaheadType === Lexer::T_INTEGER):
2229 41
            case ($lookaheadType === Lexer::T_STRING):
2230 32
            case ($lookaheadType === Lexer::T_FLOAT):
2231
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2232 32
            case ($lookaheadType === Lexer::T_MINUS):
2233 32
            case ($lookaheadType === Lexer::T_PLUS):
2234 16
                $expression = $this->SimpleArithmeticExpression();
2235 16
                break;
2236
2237
            // NewObjectExpression (New ClassName(id, name))
2238 31
            case ($lookaheadType === Lexer::T_NEW):
2239 28
                $expression = $this->NewObjectExpression();
2240 28
                break;
2241
2242
            default:
2243 3
                $this->syntaxError(
2244 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2245 3
                    $this->lexer->lookahead
2246
                );
2247
        }
2248
2249
        // [["AS"] ["HIDDEN"] AliasResultVariable]
2250 771
        $mustHaveAliasResultVariable = false;
2251
2252 771
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2253 110
            $this->match(Lexer::T_AS);
2254
2255 110
            $mustHaveAliasResultVariable = true;
2256
        }
2257
2258 771
        $hiddenAliasResultVariable = false;
2259
2260 771
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2261 10
            $this->match(Lexer::T_HIDDEN);
2262
2263 10
            $hiddenAliasResultVariable = true;
2264
        }
2265
2266 771
        $aliasResultVariable = null;
2267
2268 771 View Code Duplication
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2269 119
            $token = $this->lexer->lookahead;
2270 119
            $aliasResultVariable = $this->AliasResultVariable();
2271
2272
            // Include AliasResultVariable in query components.
2273 114
            $this->queryComponents[$aliasResultVariable] = [
2274 114
                'resultVariable' => $expression,
2275 114
                'nestingLevel'   => $this->nestingLevel,
2276 114
                'token'          => $token,
2277
            ];
2278
        }
2279
2280
        // AST
2281
2282 766
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2283
2284 766
        if ($identVariable) {
2285 606
            $this->identVariableExpressions[$identVariable] = $expr;
2286
        }
2287
2288 766
        return $expr;
2289
    }
2290
2291
    /**
2292
     * SimpleSelectExpression ::= (
2293
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2294
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2295
     * ) [["AS"] AliasResultVariable]
2296
     *
2297
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2298
     */
2299 48
    public function SimpleSelectExpression()
2300
    {
2301 48
        $peek = $this->lexer->glimpse();
2302
2303 48
        switch ($this->lexer->lookahead['type']) {
2304 48
            case Lexer::T_IDENTIFIER:
2305
                switch (true) {
2306 19
                    case ($peek['type'] === Lexer::T_DOT):
2307 16
                        $expression = $this->StateFieldPathExpression();
2308
2309 16
                        return new AST\SimpleSelectExpression($expression);
2310
2311 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2312 2
                        $expression = $this->IdentificationVariable();
2313
2314 2
                        return new AST\SimpleSelectExpression($expression);
2315
2316 1
                    case ($this->isFunction()):
2317
                        // SUM(u.id) + COUNT(u.id)
2318 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2319
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2320
                        }
2321
                        // COUNT(u.id)
2322 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2323
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2324
                        }
2325
                        // IDENTITY(u)
2326 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2327
2328
                    default:
2329
                        // Do nothing
2330
                }
2331
                break;
2332
2333 30
            case Lexer::T_OPEN_PARENTHESIS:
2334 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2335
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2336 3
                    $expression = $this->SimpleArithmeticExpression();
2337
2338 3
                    return new AST\SimpleSelectExpression($expression);
2339
                }
2340
2341
                // Subselect
2342
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2343
                $expression = $this->Subselect();
2344
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2345
2346
                return new AST\SimpleSelectExpression($expression);
2347
2348
            default:
2349
                // Do nothing
2350
        }
2351
2352 27
        $this->lexer->peek();
2353
2354 27
        $expression = $this->ScalarExpression();
2355 27
        $expr       = new AST\SimpleSelectExpression($expression);
2356
2357 27
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2358 1
            $this->match(Lexer::T_AS);
2359
        }
2360
2361 27 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2362 2
            $token = $this->lexer->lookahead;
2363 2
            $resultVariable = $this->AliasResultVariable();
2364 2
            $expr->fieldIdentificationVariable = $resultVariable;
2365
2366
            // Include AliasResultVariable in query components.
2367 2
            $this->queryComponents[$resultVariable] = [
2368 2
                'resultvariable' => $expr,
2369 2
                'nestingLevel'   => $this->nestingLevel,
2370 2
                'token'          => $token,
2371
            ];
2372
        }
2373
2374 27
        return $expr;
2375
    }
2376
2377
    /**
2378
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2379
     *
2380
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2381
     */
2382 383 View Code Duplication
    public function ConditionalExpression()
2383
    {
2384 383
        $conditionalTerms = [];
2385 383
        $conditionalTerms[] = $this->ConditionalTerm();
2386
2387 380
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2388 16
            $this->match(Lexer::T_OR);
2389
2390 16
            $conditionalTerms[] = $this->ConditionalTerm();
2391
        }
2392
2393
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2394
        // if only one AST\ConditionalTerm is defined
2395 380
        if (count($conditionalTerms) == 1) {
2396 372
            return $conditionalTerms[0];
2397
        }
2398
2399 16
        return new AST\ConditionalExpression($conditionalTerms);
2400
    }
2401
2402
    /**
2403
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2404
     *
2405
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2406
     */
2407 383 View Code Duplication
    public function ConditionalTerm()
2408
    {
2409 383
        $conditionalFactors = [];
2410 383
        $conditionalFactors[] = $this->ConditionalFactor();
2411
2412 380
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2413 32
            $this->match(Lexer::T_AND);
2414
2415 32
            $conditionalFactors[] = $this->ConditionalFactor();
2416
        }
2417
2418
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2419
        // if only one AST\ConditionalFactor is defined
2420 380
        if (count($conditionalFactors) == 1) {
2421 362
            return $conditionalFactors[0];
2422
        }
2423
2424 32
        return new AST\ConditionalTerm($conditionalFactors);
2425
    }
2426
2427
    /**
2428
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2429
     *
2430
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2431
     */
2432 383
    public function ConditionalFactor()
2433
    {
2434 383
        $not = false;
2435
2436 383
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2437 6
            $this->match(Lexer::T_NOT);
2438
2439 6
            $not = true;
2440
        }
2441
2442 383
        $conditionalPrimary = $this->ConditionalPrimary();
2443
2444
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2445
        // if only one AST\ConditionalPrimary is defined
2446 380
        if ( ! $not) {
2447 378
            return $conditionalPrimary;
2448
        }
2449
2450 6
        $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary);
2451 6
        $conditionalFactor->not = $not;
2452
2453 6
        return $conditionalFactor;
2454
    }
2455
2456
    /**
2457
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2458
     *
2459
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2460
     */
2461 383
    public function ConditionalPrimary()
2462
    {
2463 383
        $condPrimary = new AST\ConditionalPrimary;
2464
2465 383
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2466 374
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2467
2468 371
            return $condPrimary;
2469
        }
2470
2471
        // Peek beyond the matching closing parenthesis ')'
2472 25
        $peek = $this->peekBeyondClosingParenthesis();
2473
2474 25
        if (in_array($peek['value'], ["=",  "<", "<=", "<>", ">", ">=", "!="]) ||
2475 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2476 25
            $this->isMathOperator($peek)) {
2477 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2478
2479 15
            return $condPrimary;
2480
        }
2481
2482 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2483 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2484 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2485
2486 21
        return $condPrimary;
2487
    }
2488
2489
    /**
2490
     * SimpleConditionalExpression ::=
2491
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2492
     *      InExpression | NullComparisonExpression | ExistsExpression |
2493
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2494
     *      InstanceOfExpression
2495
     */
2496 383
    public function SimpleConditionalExpression()
2497
    {
2498 383
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2499 7
            return $this->ExistsExpression();
2500
        }
2501
2502 383
        $token      = $this->lexer->lookahead;
2503 383
        $peek       = $this->lexer->glimpse();
2504 383
        $lookahead  = $token;
2505
2506 383
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2507
            $token = $this->lexer->glimpse();
2508
        }
2509
2510 383
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2511
            // Peek beyond the matching closing parenthesis.
2512 359
            $beyond = $this->lexer->peek();
2513
2514 359
            switch ($peek['value']) {
2515 359
                case '(':
2516
                    // Peeks beyond the matched closing parenthesis.
2517 33
                    $token = $this->peekBeyondClosingParenthesis(false);
2518
2519 33
                    if ($token['type'] === Lexer::T_NOT) {
2520 3
                        $token = $this->lexer->peek();
2521
                    }
2522
2523 33
                    if ($token['type'] === Lexer::T_IS) {
2524 2
                        $lookahead = $this->lexer->peek();
2525
                    }
2526 33
                    break;
2527
2528
                default:
2529
                    // Peek beyond the PathExpression or InputParameter.
2530 332
                    $token = $beyond;
2531
2532 332
                    while ($token['value'] === '.') {
2533 288
                        $this->lexer->peek();
2534
2535 288
                        $token = $this->lexer->peek();
2536
                    }
2537
2538
                    // Also peek beyond a NOT if there is one.
2539 332
                    if ($token['type'] === Lexer::T_NOT) {
2540 11
                        $token = $this->lexer->peek();
2541
                    }
2542
2543
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2544 332
                    $lookahead = $this->lexer->peek();
2545
            }
2546
2547
            // Also peek beyond a NOT if there is one.
2548 359
            if ($lookahead['type'] === Lexer::T_NOT) {
2549 7
                $lookahead = $this->lexer->peek();
2550
            }
2551
2552 359
            $this->lexer->resetPeek();
2553
        }
2554
2555 383
        if ($token['type'] === Lexer::T_BETWEEN) {
2556 8
            return $this->BetweenExpression();
2557
        }
2558
2559 377
        if ($token['type'] === Lexer::T_LIKE) {
2560 14
            return $this->LikeExpression();
2561
        }
2562
2563 364
        if ($token['type'] === Lexer::T_IN) {
2564 35
            return $this->InExpression();
2565
        }
2566
2567 338
        if ($token['type'] === Lexer::T_INSTANCE) {
2568 17
            return $this->InstanceOfExpression();
2569
        }
2570
2571 321
        if ($token['type'] === Lexer::T_MEMBER) {
2572 7
            return $this->CollectionMemberExpression();
2573
        }
2574
2575 314
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2576 14
            return $this->NullComparisonExpression();
2577
        }
2578
2579 303
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2580 4
            return $this->EmptyCollectionComparisonExpression();
2581
        }
2582
2583 299
        return $this->ComparisonExpression();
2584
    }
2585
2586
    /**
2587
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2588
     *
2589
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2590
     */
2591 4 View Code Duplication
    public function EmptyCollectionComparisonExpression()
2592
    {
2593 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2594 4
            $this->CollectionValuedPathExpression()
2595
        );
2596 4
        $this->match(Lexer::T_IS);
2597
2598 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2599 2
            $this->match(Lexer::T_NOT);
2600 2
            $emptyCollectionCompExpr->not = true;
2601
        }
2602
2603 4
        $this->match(Lexer::T_EMPTY);
2604
2605 4
        return $emptyCollectionCompExpr;
2606
    }
2607
2608
    /**
2609
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2610
     *
2611
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2612
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2613
     *
2614
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2615
     */
2616 7 View Code Duplication
    public function CollectionMemberExpression()
2617
    {
2618 7
        $not        = false;
2619 7
        $entityExpr = $this->EntityExpression();
2620
2621 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2622
            $this->match(Lexer::T_NOT);
2623
2624
            $not = true;
2625
        }
2626
2627 7
        $this->match(Lexer::T_MEMBER);
2628
2629 7
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2630 7
            $this->match(Lexer::T_OF);
2631
        }
2632
2633 7
        $collMemberExpr = new AST\CollectionMemberExpression(
2634 7
            $entityExpr, $this->CollectionValuedPathExpression()
2635
        );
2636 7
        $collMemberExpr->not = $not;
2637
2638 7
        return $collMemberExpr;
2639
    }
2640
2641
    /**
2642
     * Literal ::= string | char | integer | float | boolean
2643
     *
2644
     * @return \Doctrine\ORM\Query\AST\Literal
2645
     */
2646 177
    public function Literal()
2647
    {
2648 177
        switch ($this->lexer->lookahead['type']) {
2649 177 View Code Duplication
            case Lexer::T_STRING:
2650 48
                $this->match(Lexer::T_STRING);
2651
2652 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2653 137
            case Lexer::T_INTEGER:
2654 9 View Code Duplication
            case Lexer::T_FLOAT:
2655 129
                $this->match(
2656 129
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2657
                );
2658
2659 129
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2660 8
            case Lexer::T_TRUE:
2661 4 View Code Duplication
            case Lexer::T_FALSE:
2662 8
                $this->match(
2663 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2664
                );
2665
2666 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2667
            default:
2668
                $this->syntaxError('Literal');
2669
        }
2670
    }
2671
2672
    /**
2673
     * InParameter ::= Literal | InputParameter
2674
     *
2675
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2676
     */
2677 26
    public function InParameter()
2678
    {
2679 26
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2680 14
            return $this->InputParameter();
2681
        }
2682
2683 12
        return $this->Literal();
2684
    }
2685
2686
    /**
2687
     * InputParameter ::= PositionalParameter | NamedParameter
2688
     *
2689
     * @return \Doctrine\ORM\Query\AST\InputParameter
2690
     */
2691 167
    public function InputParameter()
2692
    {
2693 167
        $this->match(Lexer::T_INPUT_PARAMETER);
2694
2695 167
        return new AST\InputParameter($this->lexer->token['value']);
2696
    }
2697
2698
    /**
2699
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2700
     *
2701
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2702
     */
2703 333
    public function ArithmeticExpression()
2704
    {
2705 333
        $expr = new AST\ArithmeticExpression;
2706
2707 333
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2708 19
            $peek = $this->lexer->glimpse();
2709
2710 19
            if ($peek['type'] === Lexer::T_SELECT) {
2711 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2712 7
                $expr->subselect = $this->Subselect();
2713 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2714
2715 7
                return $expr;
2716
            }
2717
        }
2718
2719 333
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2720
2721 333
        return $expr;
2722
    }
2723
2724
    /**
2725
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2726
     *
2727
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2728
     */
2729 437 View Code Duplication
    public function SimpleArithmeticExpression()
2730
    {
2731 437
        $terms = [];
2732 437
        $terms[] = $this->ArithmeticTerm();
2733
2734 437
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2735 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2736
2737 21
            $terms[] = $this->lexer->token['value'];
2738 21
            $terms[] = $this->ArithmeticTerm();
2739
        }
2740
2741
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2742
        // if only one AST\ArithmeticTerm is defined
2743 437
        if (count($terms) == 1) {
2744 432
            return $terms[0];
2745
        }
2746
2747 21
        return new AST\SimpleArithmeticExpression($terms);
2748
    }
2749
2750
    /**
2751
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2752
     *
2753
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2754
     */
2755 437 View Code Duplication
    public function ArithmeticTerm()
2756
    {
2757 437
        $factors = [];
2758 437
        $factors[] = $this->ArithmeticFactor();
2759
2760 437
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2761 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2762
2763 53
            $factors[] = $this->lexer->token['value'];
2764 53
            $factors[] = $this->ArithmeticFactor();
2765
        }
2766
2767
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2768
        // if only one AST\ArithmeticFactor is defined
2769 437
        if (count($factors) == 1) {
2770 409
            return $factors[0];
2771
        }
2772
2773 53
        return new AST\ArithmeticTerm($factors);
2774
    }
2775
2776
    /**
2777
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2778
     *
2779
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2780
     */
2781 437
    public function ArithmeticFactor()
2782
    {
2783 437
        $sign = null;
2784
2785 437
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2786 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2787 3
            $sign = $isPlus;
2788
        }
2789
2790 437
        $primary = $this->ArithmeticPrimary();
2791
2792
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2793
        // if only one AST\ArithmeticPrimary is defined
2794 437
        if ($sign === null) {
2795 436
            return $primary;
2796
        }
2797
2798 3
        return new AST\ArithmeticFactor($primary, $sign);
2799
    }
2800
2801
    /**
2802
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2803
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2804
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2805
     *          | InputParameter | CaseExpression
2806
     */
2807 441
    public function ArithmeticPrimary()
2808
    {
2809 441 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2810 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2811
2812 25
            $expr = $this->SimpleArithmeticExpression();
2813
2814 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2815
2816 25
            return new AST\ParenthesisExpression($expr);
2817
        }
2818
2819 441
        switch ($this->lexer->lookahead['type']) {
2820 441
            case Lexer::T_COALESCE:
2821 441
            case Lexer::T_NULLIF:
2822 441
            case Lexer::T_CASE:
2823 4
                return $this->CaseExpression();
2824
2825 441
            case Lexer::T_IDENTIFIER:
2826 411
                $peek = $this->lexer->glimpse();
2827
2828 411
                if ($peek['value'] == '(') {
2829 28
                    return $this->FunctionDeclaration();
2830
                }
2831
2832 391
                if ($peek['value'] == '.') {
2833 380
                    return $this->SingleValuedPathExpression();
2834
                }
2835
2836 46
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2837 10
                    return $this->ResultVariable();
2838
                }
2839
2840 38
                return $this->StateFieldPathExpression();
2841
2842 311
            case Lexer::T_INPUT_PARAMETER:
2843 148
                return $this->InputParameter();
2844
2845
            default:
2846 171
                $peek = $this->lexer->glimpse();
2847
2848 171
                if ($peek['value'] == '(') {
2849 18
                    return $this->FunctionDeclaration();
2850
                }
2851
2852 167
                return $this->Literal();
2853
        }
2854
    }
2855
2856
    /**
2857
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2858
     *
2859
     * @return \Doctrine\ORM\Query\AST\Subselect |
2860
     *         string
2861
     */
2862 14
    public function StringExpression()
2863
    {
2864 14
        $peek = $this->lexer->glimpse();
2865
2866
        // Subselect
2867 14 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2868
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2869
            $expr = $this->Subselect();
2870
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2871
2872
            return $expr;
2873
        }
2874
2875
        // ResultVariable (string)
2876 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2877 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2878 2
            return $this->ResultVariable();
2879
        }
2880
2881 12
        return $this->StringPrimary();
2882
    }
2883
2884
    /**
2885
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2886
     */
2887 51
    public function StringPrimary()
2888
    {
2889 51
        $lookaheadType = $this->lexer->lookahead['type'];
2890
2891
        switch ($lookaheadType) {
2892 51
            case Lexer::T_IDENTIFIER:
2893 32
                $peek = $this->lexer->glimpse();
2894
2895 32
                if ($peek['value'] == '.') {
2896 32
                    return $this->StateFieldPathExpression();
2897
                }
2898
2899 8
                if ($peek['value'] == '(') {
2900
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2901 8
                    return $this->FunctionDeclaration();
2902
                }
2903
2904
                $this->syntaxError("'.' or '('");
2905
                break;
2906
2907 32 View Code Duplication
            case Lexer::T_STRING:
2908 32
                $this->match(Lexer::T_STRING);
2909
2910 32
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2911
2912 2
            case Lexer::T_INPUT_PARAMETER:
2913 2
                return $this->InputParameter();
2914
2915
            case Lexer::T_CASE:
2916
            case Lexer::T_COALESCE:
2917
            case Lexer::T_NULLIF:
2918
                return $this->CaseExpression();
2919
        }
2920
2921
        $this->syntaxError(
2922
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2923
        );
2924
    }
2925
2926
    /**
2927
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2928
     *
2929
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2930
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2931
     */
2932 7
    public function EntityExpression()
2933
    {
2934 7
        $glimpse = $this->lexer->glimpse();
2935
2936 7
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2937 1
            return $this->SingleValuedAssociationPathExpression();
2938
        }
2939
2940 6
        return $this->SimpleEntityExpression();
2941
    }
2942
2943
    /**
2944
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2945
     *
2946
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2947
     */
2948 6
    public function SimpleEntityExpression()
2949
    {
2950 6
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2951 5
            return $this->InputParameter();
2952
        }
2953
2954 1
        return $this->StateFieldPathExpression();
2955
    }
2956
2957
    /**
2958
     * AggregateExpression ::=
2959
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2960
     *
2961
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2962
     */
2963 85
    public function AggregateExpression()
2964
    {
2965 85
        $lookaheadType = $this->lexer->lookahead['type'];
2966 85
        $isDistinct = false;
2967
2968 85
        if ( ! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) {
2969
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2970
        }
2971
2972 85
        $this->match($lookaheadType);
2973 85
        $functionName = $this->lexer->token['value'];
2974 85
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2975
2976 85
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2977 3
            $this->match(Lexer::T_DISTINCT);
2978 3
            $isDistinct = true;
2979
        }
2980
2981 85
        $pathExp = $this->SimpleArithmeticExpression();
2982
2983 85
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2984
2985 85
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2986
    }
2987
2988
    /**
2989
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2990
     *
2991
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2992
     */
2993 3
    public function QuantifiedExpression()
2994
    {
2995 3
        $lookaheadType = $this->lexer->lookahead['type'];
2996 3
        $value = $this->lexer->lookahead['value'];
2997
2998 3
        if ( ! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) {
2999
            $this->syntaxError('ALL, ANY or SOME');
3000
        }
3001
3002 3
        $this->match($lookaheadType);
3003 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3004
3005 3
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
3006 3
        $qExpr->type = $value;
3007
3008 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3009
3010 3
        return $qExpr;
3011
    }
3012
3013
    /**
3014
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3015
     *
3016
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3017
     */
3018 8
    public function BetweenExpression()
3019
    {
3020 8
        $not = false;
3021 8
        $arithExpr1 = $this->ArithmeticExpression();
3022
3023 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3024 3
            $this->match(Lexer::T_NOT);
3025 3
            $not = true;
3026
        }
3027
3028 8
        $this->match(Lexer::T_BETWEEN);
3029 8
        $arithExpr2 = $this->ArithmeticExpression();
3030 8
        $this->match(Lexer::T_AND);
3031 8
        $arithExpr3 = $this->ArithmeticExpression();
3032
3033 8
        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3034 8
        $betweenExpr->not = $not;
3035
3036 8
        return $betweenExpr;
3037
    }
3038
3039
    /**
3040
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3041
     *
3042
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3043
     */
3044 299
    public function ComparisonExpression()
3045
    {
3046 299
        $this->lexer->glimpse();
3047
3048 299
        $leftExpr  = $this->ArithmeticExpression();
3049 299
        $operator  = $this->ComparisonOperator();
3050 299
        $rightExpr = ($this->isNextAllAnySome())
3051 3
            ? $this->QuantifiedExpression()
3052 299
            : $this->ArithmeticExpression();
3053
3054 297
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3055
    }
3056
3057
    /**
3058
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3059
     *
3060
     * @return \Doctrine\ORM\Query\AST\InExpression
3061
     */
3062 35
    public function InExpression()
3063
    {
3064 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3065
3066 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3067 6
            $this->match(Lexer::T_NOT);
3068 6
            $inExpression->not = true;
3069
        }
3070
3071 35
        $this->match(Lexer::T_IN);
3072 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3073
3074 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3075 9
            $inExpression->subselect = $this->Subselect();
3076
        } else {
3077 26
            $literals = [];
3078 26
            $literals[] = $this->InParameter();
3079
3080 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3081 16
                $this->match(Lexer::T_COMMA);
3082 16
                $literals[] = $this->InParameter();
3083
            }
3084
3085 26
            $inExpression->literals = $literals;
3086
        }
3087
3088 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3089
3090 34
        return $inExpression;
3091
    }
3092
3093
    /**
3094
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3095
     *
3096
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3097
     */
3098 17
    public function InstanceOfExpression()
3099
    {
3100 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3101
3102 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3103 1
            $this->match(Lexer::T_NOT);
3104 1
            $instanceOfExpression->not = true;
3105
        }
3106
3107 17
        $this->match(Lexer::T_INSTANCE);
3108 17
        $this->match(Lexer::T_OF);
3109
3110 17
        $exprValues = [];
3111
3112 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3113 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3114
3115 2
            $exprValues[] = $this->InstanceOfParameter();
3116
3117 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3118 2
                $this->match(Lexer::T_COMMA);
3119
3120 2
                $exprValues[] = $this->InstanceOfParameter();
3121
            }
3122
3123 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3124
3125 2
            $instanceOfExpression->value = $exprValues;
3126
3127 2
            return $instanceOfExpression;
3128
        }
3129
3130 15
        $exprValues[] = $this->InstanceOfParameter();
3131
3132 15
        $instanceOfExpression->value = $exprValues;
3133
3134 15
        return $instanceOfExpression;
3135
    }
3136
3137
    /**
3138
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3139
     *
3140
     * @return mixed
3141
     */
3142 17
    public function InstanceOfParameter()
3143
    {
3144 17 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3145 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3146
3147 6
            return new AST\InputParameter($this->lexer->token['value']);
3148
        }
3149
3150 11
        $abstractSchemaName = $this->AbstractSchemaName();
3151
3152 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3153
3154 11
        return $abstractSchemaName;
3155
    }
3156
3157
    /**
3158
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3159
     *
3160
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3161
     */
3162 14
    public function LikeExpression()
3163
    {
3164 14
        $stringExpr = $this->StringExpression();
3165 14
        $not = false;
3166
3167 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3168 3
            $this->match(Lexer::T_NOT);
3169 3
            $not = true;
3170
        }
3171
3172 14
        $this->match(Lexer::T_LIKE);
3173
3174 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3175 4
            $this->match(Lexer::T_INPUT_PARAMETER);
3176 4
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3177
        } else {
3178 11
            $stringPattern = $this->StringPrimary();
3179
        }
3180
3181 14
        $escapeChar = null;
3182
3183 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3184 2
            $this->match(Lexer::T_ESCAPE);
3185 2
            $this->match(Lexer::T_STRING);
3186
3187 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3188
        }
3189
3190 14
        $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3191 14
        $likeExpr->not = $not;
3192
3193 14
        return $likeExpr;
3194
    }
3195
3196
    /**
3197
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3198
     *
3199
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3200
     */
3201 14
    public function NullComparisonExpression()
3202
    {
3203
        switch (true) {
3204 14
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3205
                $this->match(Lexer::T_INPUT_PARAMETER);
3206
3207
                $expr = new AST\InputParameter($this->lexer->token['value']);
3208
                break;
3209
3210 14
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3211 1
                $expr = $this->NullIfExpression();
3212 1
                break;
3213
3214 14
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3215 1
                $expr = $this->CoalesceExpression();
3216 1
                break;
3217
3218 14
            case $this->isFunction():
3219 2
                $expr = $this->FunctionDeclaration();
3220 2
                break;
3221
3222
            default:
3223
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3224 13
                $glimpse = $this->lexer->glimpse();
3225
3226 13
                if ($glimpse['type'] === Lexer::T_DOT) {
3227 9
                    $expr = $this->SingleValuedPathExpression();
3228
3229
                    // Leave switch statement
3230 9
                    break;
3231
                }
3232
3233 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3234
3235
                // Validate existing component
3236 4
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3237
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3238
                }
3239
3240
                // Validate SingleValuedPathExpression (ie.: "product")
3241 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3242 1
                    $expr = $this->SingleValuedPathExpression();
3243 1
                    break;
3244
                }
3245
3246
                // Validating ResultVariable
3247 3
                if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3248
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3249
                }
3250
3251 3
                $expr = $this->ResultVariable();
3252 3
                break;
3253
        }
3254
3255 14
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3256
3257 14
        $this->match(Lexer::T_IS);
3258
3259 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3260 5
            $this->match(Lexer::T_NOT);
3261
3262 5
            $nullCompExpr->not = true;
3263
        }
3264
3265 14
        $this->match(Lexer::T_NULL);
3266
3267 14
        return $nullCompExpr;
3268
    }
3269
3270
    /**
3271
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3272
     *
3273
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3274
     */
3275 7 View Code Duplication
    public function ExistsExpression()
3276
    {
3277 7
        $not = false;
3278
3279 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3280
            $this->match(Lexer::T_NOT);
3281
            $not = true;
3282
        }
3283
3284 7
        $this->match(Lexer::T_EXISTS);
3285 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3286
3287 7
        $existsExpression = new AST\ExistsExpression($this->Subselect());
3288 7
        $existsExpression->not = $not;
3289
3290 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3291
3292 7
        return $existsExpression;
3293
    }
3294
3295
    /**
3296
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3297
     *
3298
     * @return string
3299
     */
3300 299
    public function ComparisonOperator()
3301
    {
3302 299
        switch ($this->lexer->lookahead['value']) {
3303 299
            case '=':
3304 248
                $this->match(Lexer::T_EQUALS);
3305
3306 248
                return '=';
3307
3308 62
            case '<':
3309 17
                $this->match(Lexer::T_LOWER_THAN);
3310 17
                $operator = '<';
3311
3312 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3313 5
                    $this->match(Lexer::T_EQUALS);
3314 5
                    $operator .= '=';
3315 12
                } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3316 3
                    $this->match(Lexer::T_GREATER_THAN);
3317 3
                    $operator .= '>';
3318
                }
3319
3320 17
                return $operator;
3321
3322 53
            case '>':
3323 47
                $this->match(Lexer::T_GREATER_THAN);
3324 47
                $operator = '>';
3325
3326 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3327 6
                    $this->match(Lexer::T_EQUALS);
3328 6
                    $operator .= '=';
3329
                }
3330
3331 47
                return $operator;
3332
3333 6
            case '!':
3334 6
                $this->match(Lexer::T_NEGATE);
3335 6
                $this->match(Lexer::T_EQUALS);
3336
3337 6
                return '<>';
3338
3339
            default:
3340
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3341
        }
3342
    }
3343
3344
    /**
3345
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3346
     *
3347
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3348
     */
3349 141
    public function FunctionDeclaration()
3350
    {
3351 141
        $token = $this->lexer->lookahead;
3352 141
        $funcName = strtolower($token['value']);
3353
3354 141
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3355
3356
        // Check for custom functions functions first!
3357
        switch (true) {
3358 141
            case $customFunctionDeclaration !== null:
3359 4
                return $customFunctionDeclaration;
3360
            
3361 137
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3362 30
                return $this->FunctionsReturningStrings();
3363
3364 112
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3365 106
                return $this->FunctionsReturningNumerics();
3366
3367 7
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3368 7
                return $this->FunctionsReturningDatetime();
3369
3370
            default:
3371
                $this->syntaxError('known function', $token);
3372
        }
3373
    }
3374
3375
    /**
3376
     * Helper function for FunctionDeclaration grammar rule.
3377
     *
3378
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3379
     */
3380 141
    private function CustomFunctionDeclaration()
3381
    {
3382 141
        $token = $this->lexer->lookahead;
3383 141
        $funcName = strtolower($token['value']);
3384
3385
        // Check for custom functions afterwards
3386 141
        $config = $this->em->getConfiguration();
3387
3388
        switch (true) {
3389 141
            case ($config->getCustomStringFunction($funcName) !== null):
3390 3
                return $this->CustomFunctionsReturningStrings();
3391
3392 139
            case ($config->getCustomNumericFunction($funcName) !== null):
3393 2
                return $this->CustomFunctionsReturningNumerics();
3394
3395 137
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3396
                return $this->CustomFunctionsReturningDatetime();
3397
3398
            default:
3399 137
                return null;
3400
        }
3401
    }
3402
3403
    /**
3404
     * FunctionsReturningNumerics ::=
3405
     *      "LENGTH" "(" StringPrimary ")" |
3406
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3407
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3408
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3409
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3410
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3411
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3412
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3413
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3414
     *
3415
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3416
     */
3417 106 View Code Duplication
    public function FunctionsReturningNumerics()
3418
    {
3419 106
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3420 106
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3421
3422 106
        $function = new $funcClass($funcNameLower);
3423 106
        $function->parse($this);
3424
3425 106
        return $function;
3426
    }
3427
3428
    /**
3429
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3430
     */
3431 2 View Code Duplication
    public function CustomFunctionsReturningNumerics()
3432
    {
3433
        // getCustomNumericFunction is case-insensitive
3434 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3435 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3436
3437 2
        $function = is_string($functionClass)
3438 1
            ? new $functionClass($functionName)
3439 2
            : call_user_func($functionClass, $functionName);
3440
3441 2
        $function->parse($this);
3442
3443 2
        return $function;
3444
    }
3445
3446
    /**
3447
     * FunctionsReturningDateTime ::=
3448
     *     "CURRENT_DATE" |
3449
     *     "CURRENT_TIME" |
3450
     *     "CURRENT_TIMESTAMP" |
3451
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3452
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3453
     *
3454
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3455
     */
3456 7 View Code Duplication
    public function FunctionsReturningDatetime()
3457
    {
3458 7
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3459 7
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3460
3461 7
        $function = new $funcClass($funcNameLower);
3462 7
        $function->parse($this);
3463
3464 7
        return $function;
3465
    }
3466
3467
    /**
3468
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3469
     */
3470 View Code Duplication
    public function CustomFunctionsReturningDatetime()
3471
    {
3472
        // getCustomDatetimeFunction is case-insensitive
3473
        $functionName  = $this->lexer->lookahead['value'];
3474
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3475
3476
        $function = is_string($functionClass)
3477
            ? new $functionClass($functionName)
3478
            : call_user_func($functionClass, $functionName);
3479
3480
        $function->parse($this);
3481
3482
        return $function;
3483
    }
3484
3485
    /**
3486
     * FunctionsReturningStrings ::=
3487
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3488
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3489
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3490
     *   "LOWER" "(" StringPrimary ")" |
3491
     *   "UPPER" "(" StringPrimary ")" |
3492
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3493
     *
3494
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3495
     */
3496 30 View Code Duplication
    public function FunctionsReturningStrings()
3497
    {
3498 30
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3499 30
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3500
3501 30
        $function = new $funcClass($funcNameLower);
3502 30
        $function->parse($this);
3503
3504 30
        return $function;
3505
    }
3506
3507
    /**
3508
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3509
     */
3510 3 View Code Duplication
    public function CustomFunctionsReturningStrings()
3511
    {
3512
        // getCustomStringFunction is case-insensitive
3513 3
        $functionName  = $this->lexer->lookahead['value'];
3514 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3515
3516 3
        $function = is_string($functionClass)
3517 2
            ? new $functionClass($functionName)
3518 3
            : call_user_func($functionClass, $functionName);
3519
3520 3
        $function->parse($this);
3521
3522 3
        return $function;
3523
    }
3524
}
3525