Failed Conditions
Pull Request — master (#6743)
by Grégoire
18:17 queued 12:33
created

Parser::GeneralCaseExpression()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 9
nop 0
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
nc 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 168 and the first side effect is on line 19.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use Doctrine\Common\Persistence\Mapping\MappingException;
8
use Doctrine\ORM\Mapping\AssociationMetadata;
9
use Doctrine\ORM\Mapping\FieldMetadata;
10
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
11
use Doctrine\ORM\Query;
12
use Doctrine\ORM\Query\AST\Functions;
13
use Doctrine\ORM\Query\AST\Node;
14
15
/**
16
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
17
 * Parses a DQL query, reports any errors in it, and generates an AST.
18
 */
19
class Parser
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
20
{
21
    /**
22
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
23
     *
24
     * @var string[]
25
     */
26
    private static $_STRING_FUNCTIONS = [
27
        'concat'    => Functions\ConcatFunction::class,
28
        'substring' => Functions\SubstringFunction::class,
29
        'trim'      => Functions\TrimFunction::class,
30
        'lower'     => Functions\LowerFunction::class,
31
        'upper'     => Functions\UpperFunction::class,
32
        'identity'  => Functions\IdentityFunction::class,
33
    ];
34
35
    /**
36
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
37
     *
38
     * @var string[]
39
     */
40
    private static $_NUMERIC_FUNCTIONS = [
41
        'length'    => Functions\LengthFunction::class,
42
        'locate'    => Functions\LocateFunction::class,
43
        'abs'       => Functions\AbsFunction::class,
44
        'sqrt'      => Functions\SqrtFunction::class,
45
        'mod'       => Functions\ModFunction::class,
46
        'size'      => Functions\SizeFunction::class,
47
        'date_diff' => Functions\DateDiffFunction::class,
48
        'bit_and'   => Functions\BitAndFunction::class,
49
        'bit_or'    => Functions\BitOrFunction::class,
50
51
        // Aggregate functions
52
        'min'       => Functions\MinFunction::class,
53
        'max'       => Functions\MaxFunction::class,
54
        'avg'       => Functions\AvgFunction::class,
55
        'sum'       => Functions\SumFunction::class,
56
        'count'     => Functions\CountFunction::class,
57
    ];
58
59
    /**
60
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
61
     *
62
     * @var string[]
63
     */
64
    private static $_DATETIME_FUNCTIONS = [
65
        'current_date'      => Functions\CurrentDateFunction::class,
66
        'current_time'      => Functions\CurrentTimeFunction::class,
67
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
68
        'date_add'          => Functions\DateAddFunction::class,
69
        'date_sub'          => Functions\DateSubFunction::class,
70
    ];
71
72
    /*
73
     * Expressions that were encountered during parsing of identifiers and expressions
74
     * and still need to be validated.
75
     */
76
77
    /**
78
     * @var mixed[][]
79
     */
80
    private $deferredIdentificationVariables = [];
81
82
    /**
83
     * @var mixed[][]
84
     */
85
    private $deferredPartialObjectExpressions = [];
86
87
    /**
88
     * @var mixed[][]
89
     */
90
    private $deferredPathExpressions = [];
91
92
    /**
93
     * @var mixed[][]
94
     */
95
    private $deferredResultVariables = [];
96
97
    /**
98
     * @var mixed[][]
99
     */
100
    private $deferredNewObjectExpressions = [];
101
102
    /**
103
     * The lexer.
104
     *
105
     * @var \Doctrine\ORM\Query\Lexer
106
     */
107
    private $lexer;
108
109
    /**
110
     * The parser result.
111
     *
112
     * @var \Doctrine\ORM\Query\ParserResult
113
     */
114
    private $parserResult;
115
116
    /**
117
     * The EntityManager.
118
     *
119
     * @var \Doctrine\ORM\EntityManagerInterface
120
     */
121
    private $em;
122
123
    /**
124
     * The Query to parse.
125
     *
126
     * @var Query
127
     */
128
    private $query;
129
130
    /**
131
     * Map of declared query components in the parsed query.
132
     *
133
     * @var mixed[][]
134
     */
135
    private $queryComponents = [];
136
137
    /**
138
     * Keeps the nesting level of defined ResultVariables.
139
     *
140
     * @var int
141
     */
142
    private $nestingLevel = 0;
143
144
    /**
145
     * Any additional custom tree walkers that modify the AST.
146
     *
147
     * @var string[]
148
     */
149
    private $customTreeWalkers = [];
150
151
    /**
152
     * The custom last tree walker, if any, that is responsible for producing the output.
153
     *
154
     * @var TreeWalker
155
     */
156
    private $customOutputWalker;
157
158
    /**
159
     * @var Node[]
160
     */
161
    private $identVariableExpressions = [];
162
163
    /**
164
     * Creates a new query parser object.
165
     *
166
     * @param Query $query The Query to parse.
167
     */
168 753
    public function __construct(Query $query)
169
    {
170 753
        $this->query        = $query;
171 753
        $this->em           = $query->getEntityManager();
172 753
        $this->lexer        = new Lexer($query->getDQL());
173 753
        $this->parserResult = new ParserResult();
174 753
    }
175
176
    /**
177
     * Sets a custom tree walker that produces output.
178
     * This tree walker will be run last over the AST, after any other walkers.
179
     *
180
     * @param string $className
181
     */
182 86
    public function setCustomOutputTreeWalker($className)
183
    {
184 86
        $this->customOutputWalker = $className;
185 86
    }
186
187
    /**
188
     * Adds a custom tree walker for modifying the AST.
189
     *
190
     * @param string $className
191
     */
192
    public function addCustomTreeWalker($className)
193
    {
194
        $this->customTreeWalkers[] = $className;
195
    }
196
197
    /**
198
     * Gets the lexer used by the parser.
199
     *
200
     * @return \Doctrine\ORM\Query\Lexer
201
     */
202 30
    public function getLexer()
203
    {
204 30
        return $this->lexer;
205
    }
206
207
    /**
208
     * Gets the ParserResult that is being filled with information during parsing.
209
     *
210
     * @return \Doctrine\ORM\Query\ParserResult
211
     */
212
    public function getParserResult()
213
    {
214
        return $this->parserResult;
215
    }
216
217
    /**
218
     * Gets the EntityManager used by the parser.
219
     *
220
     * @return \Doctrine\ORM\EntityManagerInterface
221
     */
222
    public function getEntityManager()
223
    {
224
        return $this->em;
225
    }
226
227
    /**
228
     * Parses and builds AST for the given Query.
229
     *
230
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
231
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
232
     *         \Doctrine\ORM\Query\AST\DeleteStatement
233
     */
234 753
    public function getAST()
235
    {
236
        // Parse & build AST
237 753
        $AST = $this->QueryLanguage();
238
239
        // Process any deferred validations of some nodes in the AST.
240
        // This also allows post-processing of the AST for modification purposes.
241 753
        $this->processDeferredIdentificationVariables();
242
243 753
        if ($this->deferredPartialObjectExpressions) {
244 7
            $this->processDeferredPartialObjectExpressions();
245
        }
246
247 753
        if ($this->deferredPathExpressions) {
248 558
            $this->processDeferredPathExpressions();
249
        }
250
251 753
        if ($this->deferredResultVariables) {
252 32
            $this->processDeferredResultVariables();
253
        }
254
255 753
        if ($this->deferredNewObjectExpressions) {
256 22
            $this->processDeferredNewObjectExpressions($AST);
257
        }
258
259 753
        $this->processRootEntityAliasSelected();
260
261
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
262 753
        $this->fixIdentificationVariableOrder($AST);
263
264 753
        return $AST;
265
    }
266
267
    /**
268
     * Attempts to match the given token with the current lookahead token.
269
     *
270
     * If they match, updates the lookahead token; otherwise raises a syntax
271
     * error.
272
     *
273
     * @param int $token The token type.
274
     *
275
     * @throws QueryException If the tokens don't match.
276
     */
277 758
    public function match($token)
278
    {
279 758
        $lookaheadType = $this->lexer->lookahead['type'];
280
281
        // Short-circuit on first condition, usually types match
282 758
        if ($lookaheadType !== $token) {
283
            // If parameter is not identifier (1-99) must be exact match
284 3
            if ($token < Lexer::T_IDENTIFIER) {
285
                $this->syntaxError($this->lexer->getLiteral($token));
286
            }
287
288
            // If parameter is keyword (200+) must be exact match
289 3
            if ($token > Lexer::T_IDENTIFIER) {
290
                $this->syntaxError($this->lexer->getLiteral($token));
291
            }
292
293
            // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
294 3
            if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
295
                $this->syntaxError($this->lexer->getLiteral($token));
296
            }
297
        }
298
299 758
        $this->lexer->moveNext();
300 758
    }
301
302
    /**
303
     * Frees this parser, enabling it to be reused.
304
     *
305
     * @param bool $deep     Whether to clean peek and reset errors.
306
     * @param int  $position Position to reset.
307
     */
308
    public function free($deep = false, $position = 0)
309
    {
310
        // WARNING! Use this method with care. It resets the scanner!
311
        $this->lexer->resetPosition($position);
312
313
        // Deep = true cleans peek and also any previously defined errors
314
        if ($deep) {
315
            $this->lexer->resetPeek();
316
        }
317
318
        $this->lexer->token     = null;
319
        $this->lexer->lookahead = null;
320
    }
321
322
    /**
323
     * Parses a query string.
324
     *
325
     * @return ParserResult
326
     */
327 753
    public function parse()
328
    {
329 753
        $AST = $this->getAST();
330
331 753
        $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
332 753
        if ($customWalkers !== false) {
333 95
            $this->customTreeWalkers = $customWalkers;
334
        }
335
336 753
        $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
337
338 753
        if ($customOutputWalker !== false) {
339 78
            $this->customOutputWalker = $customOutputWalker;
340
        }
341
342
        // Run any custom tree walkers over the AST
343 753
        if ($this->customTreeWalkers) {
344 95
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
345
346 95
            foreach ($this->customTreeWalkers as $walker) {
347 95
                $treeWalkerChain->addTreeWalker($walker);
348
            }
349
350
            switch (true) {
351 95
                case ($AST instanceof AST\UpdateStatement):
352
                    $treeWalkerChain->walkUpdateStatement($AST);
353
                    break;
354
355 95
                case ($AST instanceof AST\DeleteStatement):
356
                    $treeWalkerChain->walkDeleteStatement($AST);
357
                    break;
358
359 95
                case ($AST instanceof AST\SelectStatement):
360
                default:
361 95
                    $treeWalkerChain->walkSelectStatement($AST);
362
            }
363
364 89
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
365
        }
366
367 747
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
368 747
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
369
370
        // Assign an SQL executor to the parser result
371 747
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
372
373 747
        return $this->parserResult;
374
    }
375
376
    /**
377
     * Fixes order of identification variables.
378
     *
379
     * They have to appear in the select clause in the same order as the
380
     * declarations (from ... x join ... y join ... z ...) appear in the query
381
     * as the hydration process relies on that order for proper operation.
382
     *
383
     * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
384
     */
385 753
    private function fixIdentificationVariableOrder($AST)
386
    {
387 753
        if (count($this->identVariableExpressions) <= 1) {
388 587
            return;
389
        }
390
391 171
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
392 171
            if (! isset($this->identVariableExpressions[$dqlAlias])) {
393 8
                continue;
394
            }
395
396 171
            $expr = $this->identVariableExpressions[$dqlAlias];
397 171
            $key  = array_search($expr, $AST->selectClause->selectExpressions, true);
398
399 171
            unset($AST->selectClause->selectExpressions[$key]);
400
401 171
            $AST->selectClause->selectExpressions[] = $expr;
402
        }
403 171
    }
404
405
    /**
406
     * Generates a new syntax error.
407
     *
408
     * @param string       $expected Expected string.
409
     * @param mixed[]|null $token    Got token.
410
     *
411
     * @throws \Doctrine\ORM\Query\QueryException
412
     */
413
    public function syntaxError($expected = '', $token = null)
414
    {
415
        if ($token === null) {
416
            $token = $this->lexer->lookahead;
417
        }
418
419
        $tokenPos = $token['position'] ?? '-1';
420
421
        $message  = sprintf('line 0, col %d: Error: ', $tokenPos);
422
        $message .= ($expected !== '') ? sprintf('Expected %s, got ', $expected) : 'Unexpected ';
423
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : sprintf("'%s'", $token['value']);
424
425
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
426
    }
427
428
    /**
429
     * Generates a new semantical error.
430
     *
431
     * @param string       $message Optional message.
432
     * @param mixed[]|null $token   Optional token.
433
     *
434
     * @throws \Doctrine\ORM\Query\QueryException
435
     */
436
    public function semanticalError($message = '', $token = null, ?\Throwable $previousFailure = null)
437
    {
438
        if ($token === null) {
439
            $token = $this->lexer->lookahead;
440
        }
441
442
        // Minimum exposed chars ahead of token
443
        $distance = 12;
444
445
        // Find a position of a final word to display in error string
446
        $dql    = $this->query->getDQL();
447
        $length = strlen($dql);
448
        $pos    = $token['position'] + $distance;
449
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
450
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
451
452
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
453
        $tokenStr = substr($dql, (int) $token['position'], $length);
454
455
        // Building informative message
456
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
457
458
        throw QueryException::semanticalError(
459
            $message,
460
            QueryException::dqlError($this->query->getDQL(), $previousFailure)
461
        );
462
    }
463
464
    /**
465
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
466
     *
467
     * @param bool $resetPeek Reset peek after finding the closing parenthesis.
468
     *
469
     * @return mixed[]
470
     */
471 164
    private function peekBeyondClosingParenthesis($resetPeek = true)
472
    {
473 164
        $token        = $this->lexer->peek();
474 164
        $numUnmatched = 1;
475
476 164
        while ($numUnmatched > 0 && $token !== null) {
477 163
            switch ($token['type']) {
478
                case Lexer::T_OPEN_PARENTHESIS:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
479 42
                    ++$numUnmatched;
480 42
                    break;
481
482
                case Lexer::T_CLOSE_PARENTHESIS:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
483 163
                    --$numUnmatched;
484 163
                    break;
485
486
                default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
487
                    // Do nothing
488
            }
489
490 163
            $token = $this->lexer->peek();
491
        }
492
493 164
        if ($resetPeek) {
494 143
            $this->lexer->resetPeek();
495
        }
496
497 164
        return $token;
498
    }
499
500
    /**
501
     * Checks if the given token indicates a mathematical operator.
502
     *
503
     * @param mixed[] $token
504
     *
505
     * @return bool TRUE if the token is a mathematical operator, FALSE otherwise.
506
     */
507 338
    private function isMathOperator($token)
508
    {
509 338
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY], true);
510
    }
511
512
    /**
513
     * Checks if the next-next (after lookahead) token starts a function.
514
     *
515
     * @return bool TRUE if the next-next tokens start a function, FALSE otherwise.
516
     */
517 369
    private function isFunction()
518
    {
519 369
        $lookaheadType = $this->lexer->lookahead['type'];
520 369
        $peek          = $this->lexer->peek();
521
522 369
        $this->lexer->resetPeek();
523
524 369
        return $lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
525
    }
526
527
    /**
528
     * Checks whether the given token type indicates an aggregate function.
529
     *
530
     * @param int $tokenType
531
     *
532
     * @return bool TRUE if the token type is an aggregate function, FALSE otherwise.
533
     */
534 1
    private function isAggregateFunction($tokenType)
535
    {
536 1
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT], true);
537
    }
538
539
    /**
540
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
541
     *
542
     * @return bool
543
     */
544 278
    private function isNextAllAnySome()
545
    {
546 278
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME], true);
547
    }
548
549
    /**
550
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
551
     * It must exist in query components list.
552
     */
553 753
    private function processDeferredIdentificationVariables()
554
    {
555 753
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
556 733
            $identVariable = $deferredItem['expression'];
557
558
            // Check if IdentificationVariable exists in queryComponents
559 733
            if (! isset($this->queryComponents[$identVariable])) {
560
                $this->semanticalError(
561
                    sprintf("'%s' is not defined.", $identVariable),
562
                    $deferredItem['token']
563
                );
564
            }
565
566 733
            $qComp = $this->queryComponents[$identVariable];
567
568
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
569 733
            if (! isset($qComp['metadata'])) {
570
                $this->semanticalError(
571
                    sprintf("'%s' does not point to a Class.", $identVariable),
572
                    $deferredItem['token']
573
                );
574
            }
575
576
            // Validate if identification variable nesting level is lower or equal than the current one
577 733
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
578
                $this->semanticalError(
579
                    sprintf("'%s' is used outside the scope of its declaration.", $identVariable),
580 733
                    $deferredItem['token']
581
                );
582
            }
583
        }
584 753
    }
585
586
    /**
587
     * Validates that the given <tt>NewObjectExpression</tt>.
588
     *
589
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
590
     */
591 22
    private function processDeferredNewObjectExpressions($AST)
592
    {
593 22
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
594 22
            $expression    = $deferredItem['expression'];
595 22
            $token         = $deferredItem['token'];
596 22
            $className     = $expression->className;
597 22
            $args          = $expression->args;
598 22
            $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
599
600
            // If the namespace is not given then assumes the first FROM entity namespace
601 22
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
602 10
                $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
603 10
                $fqcn      = $namespace . '\\' . $className;
604
605 10
                if (class_exists($fqcn)) {
606 10
                    $expression->className = $fqcn;
607 10
                    $className             = $fqcn;
608
                }
609
            }
610
611 22
            if (! class_exists($className)) {
612
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
613
            }
614
615 22
            $class = new \ReflectionClass($className);
616
617 22
            if (! $class->isInstantiable()) {
618
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
619
            }
620
621 22
            if ($class->getConstructor() === null) {
622
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
623
            }
624
625 22
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
626 22
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
627
            }
628
        }
629 22
    }
630
631
    /**
632
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
633
     * It must exist in query components list.
634
     */
635 7
    private function processDeferredPartialObjectExpressions()
636
    {
637 7
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
638 7
            $expr  = $deferredItem['expression'];
639 7
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
640
641 7
            foreach ($expr->partialFieldSet as $field) {
642 7
                $property = $class->getProperty($field);
643
644 7
                if ($property instanceof FieldMetadata ||
645 7
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
646 7
                    continue;
647
                }
648
649
                $this->semanticalError(
650
                    sprintf("There is no mapped field named '%s' on class %s.", $field, $class->getClassName()),
651
                    $deferredItem['token']
652
                );
653
            }
654
655 7
            if (array_intersect($class->identifier, $expr->partialFieldSet) !== $class->identifier) {
656
                $this->semanticalError(
657
                    sprintf('The partial field selection of class %s must contain the identifier.', $class->getClassName()),
658 7
                    $deferredItem['token']
659
                );
660
            }
661
        }
662 7
    }
663
664
    /**
665
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
666
     * It must exist in query components list.
667
     */
668 32
    private function processDeferredResultVariables()
669
    {
670 32
        foreach ($this->deferredResultVariables as $deferredItem) {
671 32
            $resultVariable = $deferredItem['expression'];
672
673
            // Check if ResultVariable exists in queryComponents
674 32
            if (! isset($this->queryComponents[$resultVariable])) {
675
                $this->semanticalError(
676
                    sprintf("'%s' is not defined.", $resultVariable),
677
                    $deferredItem['token']
678
                );
679
            }
680
681 32
            $qComp = $this->queryComponents[$resultVariable];
682
683
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
684 32
            if (! isset($qComp['resultVariable'])) {
685
                $this->semanticalError(
686
                    sprintf("'%s' does not point to a ResultVariable.", $resultVariable),
687
                    $deferredItem['token']
688
                );
689
            }
690
691
            // Validate if identification variable nesting level is lower or equal than the current one
692 32
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
693
                $this->semanticalError(
694
                    sprintf("'%s' is used outside the scope of its declaration.", $resultVariable),
695 32
                    $deferredItem['token']
696
                );
697
            }
698
        }
699 32
    }
700
701
    /**
702
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
703
     *
704
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
705
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
706
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
707
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
708
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
709
     */
710 558
    private function processDeferredPathExpressions()
711
    {
712 558
        foreach ($this->deferredPathExpressions as $deferredItem) {
713 558
            $pathExpression = $deferredItem['expression'];
714
715 558
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
716 558
            $class = $qComp['metadata'];
717 558
            $field = $pathExpression->field;
718
719 558
            if ($field === null) {
720 38
                $field = $pathExpression->field = $class->identifier[0];
721
            }
722
723 558
            $property = $class->getProperty($field);
724
725
            // Check if field or association exists
726 558
            if (! $property) {
727
                $this->semanticalError(
728
                    'Class ' . $class->getClassName() . ' has no field or association named ' . $field,
729
                    $deferredItem['token']
730
                );
731
            }
732
733 558
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
734
735 558
            if ($property instanceof AssociationMetadata) {
736 85
                $fieldType = $property instanceof ToOneAssociationMetadata
737 63
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
738 85
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
739
                ;
740
            }
741
742
            // Validate if PathExpression is one of the expected types
743 558
            $expectedType = $pathExpression->expectedType;
744
745 558
            if (! ($expectedType & $fieldType)) {
746
                // We need to recognize which was expected type(s)
747
                $expectedStringTypes = [];
748
749
                // Validate state field type
750
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
751
                    $expectedStringTypes[] = 'StateFieldPathExpression';
752
                }
753
754
                // Validate single valued association (*-to-one)
755
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
756
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
757
                }
758
759
                // Validate single valued association (*-to-many)
760
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
761
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
762
                }
763
764
                // Build the error message
765
                $semanticalError  = 'Invalid PathExpression. ';
766
                $semanticalError .= \count($expectedStringTypes) === 1
767
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
768
                    : implode(' or ', $expectedStringTypes) . ' expected.';
769
770
                $this->semanticalError($semanticalError, $deferredItem['token']);
771
            }
772
773
            // We need to force the type in PathExpression
774 558
            $pathExpression->type = $fieldType;
775
        }
776 558
    }
777
778 753
    private function processRootEntityAliasSelected()
779
    {
780 753
        if (! $this->identVariableExpressions) {
781 229
            return;
782
        }
783
784 534
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
785 534
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
786 534
                return;
787
            }
788
        }
789
790
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
791
    }
792
793
    /**
794
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
795
     *
796
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
797
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
798
     *         \Doctrine\ORM\Query\AST\DeleteStatement
799
     */
800 753
    public function QueryLanguage()
801
    {
802 753
        $statement = null;
803
804 753
        $this->lexer->moveNext();
805
806 753
        switch ($this->lexer->lookahead['type']) {
807
            case Lexer::T_SELECT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
808 693
                $statement = $this->SelectStatement();
809 693
                break;
810
811
            case Lexer::T_UPDATE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
812 30
                $statement = $this->UpdateStatement();
813 30
                break;
814
815
            case Lexer::T_DELETE:
816 38
                $statement = $this->DeleteStatement();
817 38
                break;
818
819
            default:
820
                $this->syntaxError('SELECT, UPDATE or DELETE');
821
                break;
822
        }
823
824
        // Check for end of string
825 753
        if ($this->lexer->lookahead !== null) {
826
            $this->syntaxError('end of string');
827
        }
828
829 753
        return $statement;
830
    }
831
832
    /**
833
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
834
     *
835
     * @return \Doctrine\ORM\Query\AST\SelectStatement
836
     */
837 693
    public function SelectStatement()
838
    {
839 693
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
840
841 693
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
842 693
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
843 693
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
844 693
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
845
846 693
        return $selectStatement;
847
    }
848
849
    /**
850
     * UpdateStatement ::= UpdateClause [WhereClause]
851
     *
852
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
853
     */
854 30
    public function UpdateStatement()
855
    {
856 30
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
857
858 30
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
859
860 30
        return $updateStatement;
861
    }
862
863
    /**
864
     * DeleteStatement ::= DeleteClause [WhereClause]
865
     *
866
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
867
     */
868 38
    public function DeleteStatement()
869
    {
870 38
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
871
872 38
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
873
874 38
        return $deleteStatement;
875
    }
876
877
    /**
878
     * IdentificationVariable ::= identifier
879
     *
880
     * @return string
881
     */
882 733
    public function IdentificationVariable()
883
    {
884 733
        $this->match(Lexer::T_IDENTIFIER);
885
886 733
        $identVariable = $this->lexer->token['value'];
887
888 733
        $this->deferredIdentificationVariables[] = [
889 733
            'expression'   => $identVariable,
890 733
            'nestingLevel' => $this->nestingLevel,
891 733
            'token'        => $this->lexer->token,
892
        ];
893
894 733
        return $identVariable;
895
    }
896
897
    /**
898
     * AliasIdentificationVariable = identifier
899
     *
900
     * @return string
901
     */
902 753
    public function AliasIdentificationVariable()
903
    {
904 753
        $this->match(Lexer::T_IDENTIFIER);
905
906 753
        $aliasIdentVariable = $this->lexer->token['value'];
907 753
        $exists             = isset($this->queryComponents[$aliasIdentVariable]);
908
909 753
        if ($exists) {
910
            $this->semanticalError(sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token);
911
        }
912
913 753
        return $aliasIdentVariable;
914
    }
915
916
    /**
917
     * AbstractSchemaName ::= fully_qualified_name | identifier
918
     *
919
     * @return string
920
     */
921 756
    public function AbstractSchemaName()
922
    {
923 756
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
924 755
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
925
926 755
            return $this->lexer->token['value'];
927
        }
928
929 11
        $this->match(Lexer::T_IDENTIFIER);
930
931 11
        return $this->lexer->token['value'];
932
    }
933
934
    /**
935
     * Validates an AbstractSchemaName, making sure the class exists.
936
     *
937
     * @param string $schemaName The name to validate.
938
     *
939
     * @throws QueryException If the name does not exist.
940
     */
941 753
    private function validateAbstractSchemaName($schemaName) : void
942
    {
943 753
        if (class_exists($schemaName, true) || interface_exists($schemaName, true)) {
944 753
            return;
945
        }
946
947
        try {
948
            $this->getEntityManager()->getClassMetadata($schemaName);
949
950
            return;
951
        } catch (MappingException $mappingException) {
952
            $this->semanticalError(
953
                \sprintf('Class %s could not be mapped', $schemaName),
954
                $this->lexer->token
955
            );
956
        }
957
958
        $this->semanticalError(sprintf("Class '%s' is not defined.", $schemaName), $this->lexer->token);
959
    }
960
961
    /**
962
     * AliasResultVariable ::= identifier
963
     *
964
     * @return string
965
     */
966 117
    public function AliasResultVariable()
967
    {
968 117
        $this->match(Lexer::T_IDENTIFIER);
969
970 117
        $resultVariable = $this->lexer->token['value'];
971 117
        $exists         = isset($this->queryComponents[$resultVariable]);
972
973 117
        if ($exists) {
974
            $this->semanticalError(sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token);
975
        }
976
977 117
        return $resultVariable;
978
    }
979
980
    /**
981
     * ResultVariable ::= identifier
982
     *
983
     * @return string
984
     */
985 32
    public function ResultVariable()
986
    {
987 32
        $this->match(Lexer::T_IDENTIFIER);
988
989 32
        $resultVariable = $this->lexer->token['value'];
990
991
        // Defer ResultVariable validation
992 32
        $this->deferredResultVariables[] = [
993 32
            'expression'   => $resultVariable,
994 32
            'nestingLevel' => $this->nestingLevel,
995 32
            'token'        => $this->lexer->token,
996
        ];
997
998 32
        return $resultVariable;
999
    }
1000
1001
    /**
1002
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1003
     *
1004
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1005
     */
1006 237
    public function JoinAssociationPathExpression()
1007
    {
1008 237
        $identVariable = $this->IdentificationVariable();
1009
1010 237
        if (! isset($this->queryComponents[$identVariable])) {
1011
            $this->semanticalError(
1012
                'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.'
1013
            );
1014
        }
1015
1016 237
        $this->match(Lexer::T_DOT);
1017 237
        $this->match(Lexer::T_IDENTIFIER);
1018
1019 237
        $field = $this->lexer->token['value'];
1020
1021
        // Validate association field
1022 237
        $qComp    = $this->queryComponents[$identVariable];
1023 237
        $class    = $qComp['metadata'];
1024 237
        $property = $class->getProperty($field);
1025
1026 237
        if (! ($property !== null && $property instanceof AssociationMetadata)) {
1027
            $this->semanticalError('Class ' . $class->getClassName() . ' has no association named ' . $field);
1028
        }
1029
1030 237
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1031
    }
1032
1033
    /**
1034
     * Parses an arbitrary path expression and defers semantical validation
1035
     * based on expected types.
1036
     *
1037
     * PathExpression ::= IdentificationVariable {"." identifier}*
1038
     *
1039
     * @param int $expectedTypes
1040
     *
1041
     * @return \Doctrine\ORM\Query\AST\PathExpression
1042
     */
1043 558
    public function PathExpression($expectedTypes)
1044
    {
1045 558
        $identVariable = $this->IdentificationVariable();
1046 558
        $field         = null;
1047
1048 558
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1049 555
            $this->match(Lexer::T_DOT);
1050 555
            $this->match(Lexer::T_IDENTIFIER);
1051
1052 555
            $field = $this->lexer->token['value'];
1053
1054 555
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1055
                $this->match(Lexer::T_DOT);
1056
                $this->match(Lexer::T_IDENTIFIER);
1057
                $field .= '.' . $this->lexer->token['value'];
1058
            }
1059
        }
1060
1061
        // Creating AST node
1062 558
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1063
1064
        // Defer PathExpression validation if requested to be deferred
1065 558
        $this->deferredPathExpressions[] = [
1066 558
            'expression'   => $pathExpr,
1067 558
            'nestingLevel' => $this->nestingLevel,
1068 558
            'token'        => $this->lexer->token,
1069
        ];
1070
1071 558
        return $pathExpr;
1072
    }
1073
1074
    /**
1075
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1076
     *
1077
     * @return \Doctrine\ORM\Query\AST\PathExpression
1078
     */
1079
    public function AssociationPathExpression()
1080
    {
1081
        return $this->PathExpression(
1082
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1083
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1084
        );
1085
    }
1086
1087
    /**
1088
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1089
     *
1090
     * @return \Doctrine\ORM\Query\AST\PathExpression
1091
     */
1092 477
    public function SingleValuedPathExpression()
1093
    {
1094 477
        return $this->PathExpression(
1095 477
            AST\PathExpression::TYPE_STATE_FIELD |
1096 477
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1097
        );
1098
    }
1099
1100
    /**
1101
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1102
     *
1103
     * @return \Doctrine\ORM\Query\AST\PathExpression
1104
     */
1105 190
    public function StateFieldPathExpression()
1106
    {
1107 190
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1108
    }
1109
1110
    /**
1111
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1112
     *
1113
     * @return \Doctrine\ORM\Query\AST\PathExpression
1114
     */
1115 8
    public function SingleValuedAssociationPathExpression()
1116
    {
1117 8
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1118
    }
1119
1120
    /**
1121
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1122
     *
1123
     * @return \Doctrine\ORM\Query\AST\PathExpression
1124
     */
1125 23
    public function CollectionValuedPathExpression()
1126
    {
1127 23
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1128
    }
1129
1130
    /**
1131
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1132
     *
1133
     * @return \Doctrine\ORM\Query\AST\SelectClause
1134
     */
1135 693
    public function SelectClause()
1136
    {
1137 693
        $isDistinct = false;
1138 693
        $this->match(Lexer::T_SELECT);
1139
1140
        // Check for DISTINCT
1141 693
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1142 6
            $this->match(Lexer::T_DISTINCT);
1143
1144 6
            $isDistinct = true;
1145
        }
1146
1147
        // Process SelectExpressions (1..N)
1148 693
        $selectExpressions   = [];
1149 693
        $selectExpressions[] = $this->SelectExpression();
1150
1151 693
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1152 279
            $this->match(Lexer::T_COMMA);
1153
1154 279
            $selectExpressions[] = $this->SelectExpression();
1155
        }
1156
1157 693
        return new AST\SelectClause($selectExpressions, $isDistinct);
1158
    }
1159
1160
    /**
1161
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1162
     *
1163
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1164
     */
1165 46
    public function SimpleSelectClause()
1166
    {
1167 46
        $isDistinct = false;
1168 46
        $this->match(Lexer::T_SELECT);
1169
1170 46
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1171
            $this->match(Lexer::T_DISTINCT);
1172
1173
            $isDistinct = true;
1174
        }
1175
1176 46
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1177
    }
1178
1179
    /**
1180
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1181
     *
1182
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1183
     */
1184 30
    public function UpdateClause()
1185
    {
1186 30
        $this->match(Lexer::T_UPDATE);
1187
1188 30
        $token              = $this->lexer->lookahead;
1189 30
        $abstractSchemaName = $this->AbstractSchemaName();
1190
1191 30
        $this->validateAbstractSchemaName($abstractSchemaName);
1192
1193 30
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1194 2
            $this->match(Lexer::T_AS);
1195
        }
1196
1197 30
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1198
1199 30
        $class = $this->em->getClassMetadata($abstractSchemaName);
1200
1201
        // Building queryComponent
1202
        $queryComponent = [
1203 30
            'metadata'     => $class,
1204
            'parent'       => null,
1205
            'relation'     => null,
1206
            'map'          => null,
1207 30
            'nestingLevel' => $this->nestingLevel,
1208 30
            'token'        => $token,
1209
        ];
1210
1211 30
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1212
1213 30
        $this->match(Lexer::T_SET);
1214
1215 30
        $updateItems   = [];
1216 30
        $updateItems[] = $this->UpdateItem();
1217
1218 30
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1219 4
            $this->match(Lexer::T_COMMA);
1220
1221 4
            $updateItems[] = $this->UpdateItem();
1222
        }
1223
1224 30
        $updateClause                              = new AST\UpdateClause($abstractSchemaName, $updateItems);
1225 30
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1226
1227 30
        return $updateClause;
1228
    }
1229
1230
    /**
1231
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1232
     *
1233
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1234
     */
1235 38
    public function DeleteClause()
1236
    {
1237 38
        $this->match(Lexer::T_DELETE);
1238
1239 38
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1240 6
            $this->match(Lexer::T_FROM);
1241
        }
1242
1243 38
        $token              = $this->lexer->lookahead;
1244 38
        $abstractSchemaName = $this->AbstractSchemaName();
1245
1246 38
        $this->validateAbstractSchemaName($abstractSchemaName);
1247
1248 38
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1249
1250 38
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1251 1
            $this->match(Lexer::T_AS);
1252
        }
1253
1254 38
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1255
1256 38
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1257 38
        $class                                     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1258
1259
        // Building queryComponent
1260
        $queryComponent = [
1261 38
            'metadata'     => $class,
1262
            'parent'       => null,
1263
            'relation'     => null,
1264
            'map'          => null,
1265 38
            'nestingLevel' => $this->nestingLevel,
1266 38
            'token'        => $token,
1267
        ];
1268
1269 38
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1270
1271 38
        return $deleteClause;
1272
    }
1273
1274
    /**
1275
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1276
     *
1277
     * @return \Doctrine\ORM\Query\AST\FromClause
1278
     */
1279 693
    public function FromClause()
1280
    {
1281 693
        $this->match(Lexer::T_FROM);
1282
1283 693
        $identificationVariableDeclarations   = [];
1284 693
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1285
1286 693
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1287 7
            $this->match(Lexer::T_COMMA);
1288
1289 7
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1290
        }
1291
1292 693
        return new AST\FromClause($identificationVariableDeclarations);
1293
    }
1294
1295
    /**
1296
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1297
     *
1298
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1299
     */
1300 46
    public function SubselectFromClause()
1301
    {
1302 46
        $this->match(Lexer::T_FROM);
1303
1304 46
        $identificationVariables   = [];
1305 46
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1306
1307 46
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1308
            $this->match(Lexer::T_COMMA);
1309
1310
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1311
        }
1312
1313 46
        return new AST\SubselectFromClause($identificationVariables);
1314
    }
1315
1316
    /**
1317
     * WhereClause ::= "WHERE" ConditionalExpression
1318
     *
1319
     * @return \Doctrine\ORM\Query\AST\WhereClause
1320
     */
1321 312
    public function WhereClause()
1322
    {
1323 312
        $this->match(Lexer::T_WHERE);
1324
1325 312
        return new AST\WhereClause($this->ConditionalExpression());
1326
    }
1327
1328
    /**
1329
     * HavingClause ::= "HAVING" ConditionalExpression
1330
     *
1331
     * @return \Doctrine\ORM\Query\AST\HavingClause
1332
     */
1333 21
    public function HavingClause()
1334
    {
1335 21
        $this->match(Lexer::T_HAVING);
1336
1337 21
        return new AST\HavingClause($this->ConditionalExpression());
1338
    }
1339
1340
    /**
1341
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1342
     *
1343
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1344
     */
1345 30
    public function GroupByClause()
1346
    {
1347 30
        $this->match(Lexer::T_GROUP);
1348 30
        $this->match(Lexer::T_BY);
1349
1350 30
        $groupByItems = [$this->GroupByItem()];
1351
1352 30
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1353 7
            $this->match(Lexer::T_COMMA);
1354
1355 7
            $groupByItems[] = $this->GroupByItem();
1356
        }
1357
1358 30
        return new AST\GroupByClause($groupByItems);
1359
    }
1360
1361
    /**
1362
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1363
     *
1364
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1365
     */
1366 173
    public function OrderByClause()
1367
    {
1368 173
        $this->match(Lexer::T_ORDER);
1369 173
        $this->match(Lexer::T_BY);
1370
1371 173
        $orderByItems   = [];
1372 173
        $orderByItems[] = $this->OrderByItem();
1373
1374 173
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1375 15
            $this->match(Lexer::T_COMMA);
1376
1377 15
            $orderByItems[] = $this->OrderByItem();
1378
        }
1379
1380 173
        return new AST\OrderByClause($orderByItems);
1381
    }
1382
1383
    /**
1384
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1385
     *
1386
     * @return \Doctrine\ORM\Query\AST\Subselect
1387
     */
1388 46
    public function Subselect()
1389
    {
1390
        // Increase query nesting level
1391 46
        $this->nestingLevel++;
1392
1393 46
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1394
1395 46
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1396 46
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1397 46
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1398 46
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1399
1400
        // Decrease query nesting level
1401 46
        $this->nestingLevel--;
1402
1403 46
        return $subselect;
1404
    }
1405
1406
    /**
1407
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1408
     *
1409
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1410
     */
1411 30
    public function UpdateItem()
1412
    {
1413 30
        $pathExpr = $this->SingleValuedPathExpression();
1414
1415 30
        $this->match(Lexer::T_EQUALS);
1416
1417 30
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1418
1419 30
        return $updateItem;
1420
    }
1421
1422
    /**
1423
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1424
     *
1425
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1426
     */
1427 30
    public function GroupByItem()
1428
    {
1429
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1430 30
        $glimpse = $this->lexer->glimpse();
1431
1432 30
        if ($glimpse['type'] === Lexer::T_DOT) {
1433 13
            return $this->SingleValuedPathExpression();
1434
        }
1435
1436
        // Still need to decide between IdentificationVariable or ResultVariable
1437 17
        $lookaheadValue = $this->lexer->lookahead['value'];
1438
1439 17
        if (! isset($this->queryComponents[$lookaheadValue])) {
1440
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1441
        }
1442
1443 17
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1444 15
            ? $this->IdentificationVariable()
1445 17
            : $this->ResultVariable();
1446
    }
1447
1448
    /**
1449
     * OrderByItem ::= (
1450
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1451
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1452
     * ) ["ASC" | "DESC"]
1453
     *
1454
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1455
     */
1456 173
    public function OrderByItem()
1457
    {
1458 173
        $this->lexer->peek(); // lookahead => '.'
1459 173
        $this->lexer->peek(); // lookahead => token after '.'
1460
1461 173
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1462
1463 173
        $this->lexer->resetPeek();
1464
1465 173
        $glimpse = $this->lexer->glimpse();
1466
1467
        switch (true) {
1468 173
            case ($this->isFunction()):
1469 2
                $expr = $this->FunctionDeclaration();
1470 2
                break;
1471
1472 171
            case ($this->isMathOperator($peek)):
1473 25
                $expr = $this->SimpleArithmeticExpression();
1474 25
                break;
1475
1476 147
            case ($glimpse['type'] === Lexer::T_DOT):
1477 132
                $expr = $this->SingleValuedPathExpression();
1478 132
                break;
1479
1480 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1481 2
                $expr = $this->ScalarExpression();
1482 2
                break;
1483
1484
            default:
1485 17
                $expr = $this->ResultVariable();
1486 17
                break;
1487
        }
1488
1489 173
        $type = 'ASC';
1490 173
        $item = new AST\OrderByItem($expr);
1491
1492
        switch (true) {
1493 173
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1494 95
                $this->match(Lexer::T_DESC);
1495 95
                $type = 'DESC';
1496 95
                break;
1497
1498 145
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1499 91
                $this->match(Lexer::T_ASC);
1500 91
                break;
1501
1502
            default:
1503
                // Do nothing
1504
        }
1505
1506 173
        $item->type = $type;
1507
1508 173
        return $item;
1509
    }
1510
1511
    /**
1512
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1513
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1514
     *
1515
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1516
     * grammar that needs to be supported:
1517
     *
1518
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1519
     *
1520
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1521
     *
1522
     * @return AST\ArithmeticExpression
1523
     */
1524 30
    public function NewValue()
1525
    {
1526 30
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1527 1
            $this->match(Lexer::T_NULL);
1528
1529 1
            return null;
1530
        }
1531
1532 29
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1533 17
            $this->match(Lexer::T_INPUT_PARAMETER);
1534
1535 17
            return new AST\InputParameter($this->lexer->token['value']);
1536
        }
1537
1538 12
        return $this->ArithmeticExpression();
1539
    }
1540
1541
    /**
1542
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1543
     *
1544
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1545
     */
1546 694
    public function IdentificationVariableDeclaration()
1547
    {
1548 694
        $joins                    = [];
1549 694
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1550 694
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1551 4
            ? $this->IndexBy()
1552 694
            : null;
1553
1554 694
        $rangeVariableDeclaration->isRoot = true;
1555
1556 694
        while ($this->lexer->isNextToken(Lexer::T_LEFT) ||
1557 694
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1558 694
            $this->lexer->isNextToken(Lexer::T_JOIN)
1559
        ) {
1560 257
            $joins[] = $this->Join();
1561
        }
1562
1563 694
        return new AST\IdentificationVariableDeclaration(
1564 694
            $rangeVariableDeclaration,
1565 694
            $indexBy,
1566 694
            $joins
1567
        );
1568
    }
1569
1570
    /**
1571
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1572
     *
1573
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1574
     * Desired EBNF support:
1575
     *
1576
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1577
     *
1578
     * It demands that entire SQL generation to become programmatical. This is
1579
     * needed because association based subselect requires "WHERE" conditional
1580
     * expressions to be injected, but there is no scope to do that. Only scope
1581
     * accessible is "FROM", prohibiting an easy implementation without larger
1582
     * changes.}
1583
     *
1584
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1585
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1586
     */
1587 46
    public function SubselectIdentificationVariableDeclaration()
1588
    {
1589
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1590
        NOT YET IMPLEMENTED!
1591
1592
        $glimpse = $this->lexer->glimpse();
1593
1594
        if ($glimpse['type'] == Lexer::T_DOT) {
1595
            $associationPathExpression = $this->AssociationPathExpression();
1596
1597
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1598
                $this->match(Lexer::T_AS);
1599
            }
1600
1601
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1602
            $identificationVariable      = $associationPathExpression->identificationVariable;
1603
            $field                       = $associationPathExpression->associationField;
1604
1605
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1606
            $association = $class->getProperty($field);
1607
            $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1608
1609
            // Building queryComponent
1610
            $joinQueryComponent = array(
1611
                'metadata'     => $targetClass,
1612
                'parent'       => $identificationVariable,
1613
                'relation'     => $association,
1614
                'map'          => null,
1615
                'nestingLevel' => $this->nestingLevel,
1616
                'token'        => $this->lexer->lookahead
1617
            );
1618
1619
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1620
1621
            return new AST\SubselectIdentificationVariableDeclaration(
1622
                $associationPathExpression, $aliasIdentificationVariable
1623
            );
1624
        }
1625
        */
1626
1627 46
        return $this->IdentificationVariableDeclaration();
1628
    }
1629
1630
    /**
1631
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1632
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1633
     *          ["WITH" ConditionalExpression]
1634
     *
1635
     * @return \Doctrine\ORM\Query\AST\Join
1636
     */
1637 257
    public function Join()
1638
    {
1639
        // Check Join type
1640 257
        $joinType = AST\Join::JOIN_TYPE_INNER;
1641
1642
        switch (true) {
1643 257
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1644 58
                $this->match(Lexer::T_LEFT);
1645
1646 58
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1647
1648
                // Possible LEFT OUTER join
1649 58
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1650
                    $this->match(Lexer::T_OUTER);
1651
1652
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1653
                }
1654 58
                break;
1655
1656 202
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1657 16
                $this->match(Lexer::T_INNER);
1658 16
                break;
1659
1660
            default:
1661
                // Do nothing
1662
        }
1663
1664 257
        $this->match(Lexer::T_JOIN);
1665
1666 257
        $next            = $this->lexer->glimpse();
1667 257
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1668 257
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1669 257
        $join            = new AST\Join($joinType, $joinDeclaration);
1670
1671
        // Describe non-root join declaration
1672 257
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1673 21
            $joinDeclaration->isRoot = false;
1674
        }
1675
1676
        // Check for ad-hoc Join conditions
1677 257
        if ($adhocConditions) {
1678 25
            $this->match(Lexer::T_WITH);
1679
1680 25
            $join->conditionalExpression = $this->ConditionalExpression();
1681
        }
1682
1683 257
        return $join;
1684
    }
1685
1686
    /**
1687
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1688
     *
1689
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1690
     *
1691
     * @throws QueryException
1692
     */
1693 694
    public function RangeVariableDeclaration()
1694
    {
1695 694
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1696
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1697
        }
1698
1699 694
        $abstractSchemaName = $this->AbstractSchemaName();
1700
1701 694
        $this->validateAbstractSchemaName($abstractSchemaName);
1702
1703 694
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1704 2
            $this->match(Lexer::T_AS);
1705
        }
1706
1707 694
        $token                       = $this->lexer->lookahead;
1708 694
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1709 694
        $classMetadata               = $this->em->getClassMetadata($abstractSchemaName);
1710
1711
        // Building queryComponent
1712
        $queryComponent = [
1713 694
            'metadata'     => $classMetadata,
1714
            'parent'       => null,
1715
            'relation'     => null,
1716
            'map'          => null,
1717 694
            'nestingLevel' => $this->nestingLevel,
1718 694
            'token'        => $token,
1719
        ];
1720
1721 694
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1722
1723 694
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1724
    }
1725
1726
    /**
1727
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1728
     *
1729
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1730
     */
1731 237
    public function JoinAssociationDeclaration()
1732
    {
1733 237
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1734
1735 237
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1736 2
            $this->match(Lexer::T_AS);
1737
        }
1738
1739 237
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1740 237
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1741
1742 237
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1743 237
        $field                  = $joinAssociationPathExpression->associationField;
1744
1745 237
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1746 237
        $association = $class->getProperty($field);
1747 237
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1748
1749
        // Building queryComponent
1750
        $joinQueryComponent = [
1751 237
            'metadata'     => $targetClass,
1752 237
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1753 237
            'relation'     => $association,
1754
            'map'          => null,
1755 237
            'nestingLevel' => $this->nestingLevel,
1756 237
            'token'        => $this->lexer->lookahead,
1757
        ];
1758
1759 237
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1760
1761 237
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1762
    }
1763
1764
    /**
1765
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1766
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1767
     *
1768
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1769
     */
1770 7
    public function PartialObjectExpression()
1771
    {
1772 7
        $this->match(Lexer::T_PARTIAL);
1773
1774 7
        $partialFieldSet = [];
1775
1776 7
        $identificationVariable = $this->IdentificationVariable();
1777
1778 7
        $this->match(Lexer::T_DOT);
1779 7
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1780 7
        $this->match(Lexer::T_IDENTIFIER);
1781
1782 7
        $field = $this->lexer->token['value'];
1783
1784
        // First field in partial expression might be embeddable property
1785 7
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1786
            $this->match(Lexer::T_DOT);
1787
            $this->match(Lexer::T_IDENTIFIER);
1788
            $field .= '.' . $this->lexer->token['value'];
1789
        }
1790
1791 7
        $partialFieldSet[] = $field;
1792
1793 7
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1794 7
            $this->match(Lexer::T_COMMA);
1795 7
            $this->match(Lexer::T_IDENTIFIER);
1796
1797 7
            $field = $this->lexer->token['value'];
1798
1799 7
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1800
                $this->match(Lexer::T_DOT);
1801
                $this->match(Lexer::T_IDENTIFIER);
1802
                $field .= '.' . $this->lexer->token['value'];
1803
            }
1804
1805 7
            $partialFieldSet[] = $field;
1806
        }
1807
1808 7
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1809
1810 7
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1811
1812
        // Defer PartialObjectExpression validation
1813 7
        $this->deferredPartialObjectExpressions[] = [
1814 7
            'expression'   => $partialObjectExpression,
1815 7
            'nestingLevel' => $this->nestingLevel,
1816 7
            'token'        => $this->lexer->token,
1817
        ];
1818
1819 7
        return $partialObjectExpression;
1820
    }
1821
1822
    /**
1823
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1824
     *
1825
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1826
     */
1827 22
    public function NewObjectExpression()
1828
    {
1829 22
        $this->match(Lexer::T_NEW);
1830
1831 22
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1832 22
        $token     = $this->lexer->token;
1833
1834 22
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1835
1836 22
        $args[] = $this->NewObjectArg();
1837
1838 22
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1839 22
            $this->match(Lexer::T_COMMA);
1840
1841 22
            $args[] = $this->NewObjectArg();
1842
        }
1843
1844 22
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1845
1846 22
        $expression = new AST\NewObjectExpression($className, $args);
1847
1848
        // Defer NewObjectExpression validation
1849 22
        $this->deferredNewObjectExpressions[] = [
1850 22
            'token'        => $token,
1851 22
            'expression'   => $expression,
1852 22
            'nestingLevel' => $this->nestingLevel,
1853
        ];
1854
1855 22
        return $expression;
1856
    }
1857
1858
    /**
1859
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1860
     *
1861
     * @return mixed
1862
     */
1863 22
    public function NewObjectArg()
1864
    {
1865 22
        $token = $this->lexer->lookahead;
1866 22
        $peek  = $this->lexer->glimpse();
1867
1868 22
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1869 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1870 2
            $expression = $this->Subselect();
1871 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1872
1873 2
            return $expression;
1874
        }
1875
1876 22
        return $this->ScalarExpression();
1877
    }
1878
1879
    /**
1880
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1881
     *
1882
     * @return \Doctrine\ORM\Query\AST\IndexBy
1883
     */
1884 6
    public function IndexBy()
1885
    {
1886 6
        $this->match(Lexer::T_INDEX);
1887 6
        $this->match(Lexer::T_BY);
1888 6
        $pathExpr = $this->StateFieldPathExpression();
1889
1890
        // Add the INDEX BY info to the query component
1891 6
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1892
1893 6
        return new AST\IndexBy($pathExpr);
1894
    }
1895
1896
    /**
1897
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1898
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1899
     *                      InstanceOfExpression
1900
     *
1901
     * @return mixed One of the possible expressions or subexpressions.
1902
     */
1903 154
    public function ScalarExpression()
1904
    {
1905 154
        $lookahead = $this->lexer->lookahead['type'];
1906 154
        $peek      = $this->lexer->glimpse();
1907
1908 154
        switch (true) {
1909
            case ($lookahead === Lexer::T_INTEGER):
1910 151
            case ($lookahead === Lexer::T_FLOAT):
1911
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1912 151
            case ($lookahead === Lexer::T_MINUS):
1913 151
            case ($lookahead === Lexer::T_PLUS):
1914 17
                return $this->SimpleArithmeticExpression();
1915
1916 151
            case ($lookahead === Lexer::T_STRING):
1917 13
                return $this->StringPrimary();
1918
1919 149
            case ($lookahead === Lexer::T_TRUE):
1920 149
            case ($lookahead === Lexer::T_FALSE):
1921 3
                $this->match($lookahead);
1922
1923 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1924
1925 149
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1926
                switch (true) {
1927 1
                    case $this->isMathOperator($peek):
1928
                        // :param + u.value
1929 1
                        return $this->SimpleArithmeticExpression();
1930
                    default:
1931
                        return $this->InputParameter();
1932
                }
1933
                // cannot get here
1934
1935 149
            case ($lookahead === Lexer::T_CASE):
1936 145
            case ($lookahead === Lexer::T_COALESCE):
1937 145
            case ($lookahead === Lexer::T_NULLIF):
1938
                // Since NULLIF and COALESCE can be identified as a function,
1939
                // we need to check these before checking for FunctionDeclaration
1940 8
                return $this->CaseExpression();
1941
1942 145
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1943 4
                return $this->SimpleArithmeticExpression();
1944
1945
            // this check must be done before checking for a filed path expression
1946 142
            case ($this->isFunction()):
1947 25
                $this->lexer->peek(); // "("
1948
1949
                switch (true) {
1950 25
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1951
                        // SUM(u.id) + COUNT(u.id)
1952 6
                        return $this->SimpleArithmeticExpression();
1953
1954
                    default:
1955
                        // IDENTITY(u)
1956 21
                        return $this->FunctionDeclaration();
1957
                }
1958
1959
                break;
1960
            // it is no function, so it must be a field path
1961 125
            case ($lookahead === Lexer::T_IDENTIFIER):
1962 125
                $this->lexer->peek(); // lookahead => '.'
1963 125
                $this->lexer->peek(); // lookahead => token after '.'
1964 125
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1965 125
                $this->lexer->resetPeek();
1966
1967 125
                if ($this->isMathOperator($peek)) {
1968 7
                    return $this->SimpleArithmeticExpression();
1969
                }
1970
1971 120
                return $this->StateFieldPathExpression();
1972
1973
            default:
1974
                $this->syntaxError();
1975
        }
1976
    }
1977
1978
    /**
1979
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1980
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1981
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1982
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1983
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1984
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1985
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1986
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1987
     *
1988
     * @return mixed One of the possible expressions or subexpressions.
1989
     */
1990 19
    public function CaseExpression()
1991
    {
1992 19
        $lookahead = $this->lexer->lookahead['type'];
1993
1994 19
        switch ($lookahead) {
1995
            case Lexer::T_NULLIF:
1996 5
                return $this->NullIfExpression();
1997
1998
            case Lexer::T_COALESCE:
1999 2
                return $this->CoalesceExpression();
2000
2001
            case Lexer::T_CASE:
2002 14
                $this->lexer->resetPeek();
2003 14
                $peek = $this->lexer->peek();
2004
2005 14
                if ($peek['type'] === Lexer::T_WHEN) {
2006 9
                    return $this->GeneralCaseExpression();
2007
                }
2008
2009 5
                return $this->SimpleCaseExpression();
2010
2011
            default:
2012
                // Do nothing
2013
                break;
2014
        }
2015
2016
        $this->syntaxError();
2017
    }
2018
2019
    /**
2020
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2021
     *
2022
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2023
     */
2024 3
    public function CoalesceExpression()
2025
    {
2026 3
        $this->match(Lexer::T_COALESCE);
2027 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2028
2029
        // Process ScalarExpressions (1..N)
2030 3
        $scalarExpressions   = [];
2031 3
        $scalarExpressions[] = $this->ScalarExpression();
2032
2033 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2034 3
            $this->match(Lexer::T_COMMA);
2035
2036 3
            $scalarExpressions[] = $this->ScalarExpression();
2037
        }
2038
2039 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2040
2041 3
        return new AST\CoalesceExpression($scalarExpressions);
2042
    }
2043
2044
    /**
2045
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2046
     *
2047
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2048
     */
2049 5
    public function NullIfExpression()
2050
    {
2051 5
        $this->match(Lexer::T_NULLIF);
2052 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2053
2054 5
        $firstExpression = $this->ScalarExpression();
2055 5
        $this->match(Lexer::T_COMMA);
2056 5
        $secondExpression = $this->ScalarExpression();
2057
2058 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2059
2060 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2061
    }
2062
2063
    /**
2064
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2065
     *
2066
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2067
     */
2068 9
    public function GeneralCaseExpression()
2069
    {
2070 9
        $this->match(Lexer::T_CASE);
2071
2072
        // Process WhenClause (1..N)
2073 9
        $whenClauses = [];
2074
2075
        do {
2076 9
            $whenClauses[] = $this->WhenClause();
2077 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2078
2079 9
        $this->match(Lexer::T_ELSE);
2080 9
        $scalarExpression = $this->ScalarExpression();
2081 9
        $this->match(Lexer::T_END);
2082
2083 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2084
    }
2085
2086
    /**
2087
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2088
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2089
     *
2090
     * @return AST\SimpleCaseExpression
2091
     */
2092 5
    public function SimpleCaseExpression()
2093
    {
2094 5
        $this->match(Lexer::T_CASE);
2095 5
        $caseOperand = $this->StateFieldPathExpression();
2096
2097
        // Process SimpleWhenClause (1..N)
2098 5
        $simpleWhenClauses = [];
2099
2100
        do {
2101 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2102 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2103
2104 5
        $this->match(Lexer::T_ELSE);
2105 5
        $scalarExpression = $this->ScalarExpression();
2106 5
        $this->match(Lexer::T_END);
2107
2108 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2109
    }
2110
2111
    /**
2112
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2113
     *
2114
     * @return \Doctrine\ORM\Query\AST\WhenClause
2115
     */
2116 9
    public function WhenClause()
2117
    {
2118 9
        $this->match(Lexer::T_WHEN);
2119 9
        $conditionalExpression = $this->ConditionalExpression();
2120 9
        $this->match(Lexer::T_THEN);
2121
2122 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2123
    }
2124
2125
    /**
2126
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2127
     *
2128
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2129
     */
2130 5
    public function SimpleWhenClause()
2131
    {
2132 5
        $this->match(Lexer::T_WHEN);
2133 5
        $conditionalExpression = $this->ScalarExpression();
2134 5
        $this->match(Lexer::T_THEN);
2135
2136 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2137
    }
2138
2139
    /**
2140
     * SelectExpression ::= (
2141
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2142
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2143
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2144
     *
2145
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2146
     */
2147 693
    public function SelectExpression()
2148
    {
2149 693
        $expression    = null;
2150 693
        $identVariable = null;
2151 693
        $peek          = $this->lexer->glimpse();
2152 693
        $lookaheadType = $this->lexer->lookahead['type'];
2153
2154 693
        switch (true) {
2155
            // ScalarExpression (u.name)
2156 635
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2157 103
                $expression = $this->ScalarExpression();
2158 103
                break;
2159
2160
            // IdentificationVariable (u)
2161 635
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2162 529
                $expression = $identVariable = $this->IdentificationVariable();
2163 529
                break;
2164
2165
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2166 164
            case ($lookaheadType === Lexer::T_CASE):
2167 159
            case ($lookaheadType === Lexer::T_COALESCE):
2168 157
            case ($lookaheadType === Lexer::T_NULLIF):
2169 9
                $expression = $this->CaseExpression();
2170 9
                break;
2171
2172
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2173 155
            case ($this->isFunction()):
2174 97
                $this->lexer->peek(); // "("
2175
2176
                switch (true) {
2177 97
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2178
                        // SUM(u.id) + COUNT(u.id)
2179 2
                        $expression = $this->ScalarExpression();
2180 2
                        break;
2181
2182
                    default:
2183
                        // IDENTITY(u)
2184 95
                        $expression = $this->FunctionDeclaration();
2185 95
                        break;
2186
                }
2187
2188 97
                break;
2189
2190
            // PartialObjectExpression (PARTIAL u.{id, name})
2191 59
            case ($lookaheadType === Lexer::T_PARTIAL):
2192 7
                $expression    = $this->PartialObjectExpression();
2193 7
                $identVariable = $expression->identificationVariable;
2194 7
                break;
2195
2196
            // Subselect
2197 52
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2198 21
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2199 21
                $expression = $this->Subselect();
2200 21
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2201 21
                break;
2202
2203
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2204 31
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2205 27
            case ($lookaheadType === Lexer::T_INTEGER):
2206 25
            case ($lookaheadType === Lexer::T_STRING):
2207 23
            case ($lookaheadType === Lexer::T_FLOAT):
2208
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2209 23
            case ($lookaheadType === Lexer::T_MINUS):
2210 23
            case ($lookaheadType === Lexer::T_PLUS):
2211 9
                $expression = $this->SimpleArithmeticExpression();
2212 9
                break;
2213
2214
            // NewObjectExpression (New ClassName(id, name))
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2215 22
            case ($lookaheadType === Lexer::T_NEW):
2216 22
                $expression = $this->NewObjectExpression();
2217 22
                break;
2218
2219
            default:
2220
                $this->syntaxError(
2221
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2222
                    $this->lexer->lookahead
2223
                );
2224
        }
2225
2226
        // [["AS"] ["HIDDEN"] AliasResultVariable]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2227 693
        $mustHaveAliasResultVariable = false;
2228
2229 693
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2230 110
            $this->match(Lexer::T_AS);
2231
2232 110
            $mustHaveAliasResultVariable = true;
2233
        }
2234
2235 693
        $hiddenAliasResultVariable = false;
2236
2237 693
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2238 10
            $this->match(Lexer::T_HIDDEN);
2239
2240 10
            $hiddenAliasResultVariable = true;
2241
        }
2242
2243 693
        $aliasResultVariable = null;
2244
2245 693
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2246 117
            $token               = $this->lexer->lookahead;
2247 117
            $aliasResultVariable = $this->AliasResultVariable();
2248
2249
            // Include AliasResultVariable in query components.
2250 117
            $this->queryComponents[$aliasResultVariable] = [
2251 117
                'resultVariable' => $expression,
2252 117
                'nestingLevel'   => $this->nestingLevel,
2253 117
                'token'          => $token,
2254
            ];
2255
        }
2256
2257
        // AST
2258
2259 693
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2260
2261 693
        if ($identVariable) {
2262 534
            $this->identVariableExpressions[$identVariable] = $expr;
2263
        }
2264
2265 693
        return $expr;
2266
    }
2267
2268
    /**
2269
     * SimpleSelectExpression ::= (
2270
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2271
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2272
     * ) [["AS"] AliasResultVariable]
2273
     *
2274
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2275
     */
2276 46
    public function SimpleSelectExpression()
2277
    {
2278 46
        $peek = $this->lexer->glimpse();
2279
2280 46
        switch ($this->lexer->lookahead['type']) {
2281
            case Lexer::T_IDENTIFIER:
2282 18
                switch (true) {
2283 18
                    case ($peek['type'] === Lexer::T_DOT):
2284 15
                        $expression = $this->StateFieldPathExpression();
2285
2286 15
                        return new AST\SimpleSelectExpression($expression);
2287
2288 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2289 2
                        $expression = $this->IdentificationVariable();
2290
2291 2
                        return new AST\SimpleSelectExpression($expression);
2292
2293 1
                    case ($this->isFunction()):
2294
                        // SUM(u.id) + COUNT(u.id)
2295 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2296
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2297
                        }
2298
                        // COUNT(u.id)
2299 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2300
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2301
                        }
2302
                        // IDENTITY(u)
2303 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2304
2305
                    default:
2306
                        // Do nothing
2307
                }
2308
                break;
2309
2310
            case Lexer::T_OPEN_PARENTHESIS:
2311 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2312
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2313 3
                    $expression = $this->SimpleArithmeticExpression();
2314
2315 3
                    return new AST\SimpleSelectExpression($expression);
2316
                }
2317
2318
                // Subselect
2319
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2320
                $expression = $this->Subselect();
2321
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2322
2323
                return new AST\SimpleSelectExpression($expression);
2324
2325
            default:
2326
                // Do nothing
2327
        }
2328
2329 26
        $this->lexer->peek();
2330
2331 26
        $expression = $this->ScalarExpression();
2332 26
        $expr       = new AST\SimpleSelectExpression($expression);
2333
2334 26
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2335
            $this->match(Lexer::T_AS);
2336
        }
2337
2338 26
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2339 1
            $token                             = $this->lexer->lookahead;
2340 1
            $resultVariable                    = $this->AliasResultVariable();
2341 1
            $expr->fieldIdentificationVariable = $resultVariable;
2342
2343
            // Include AliasResultVariable in query components.
2344 1
            $this->queryComponents[$resultVariable] = [
2345 1
                'resultvariable' => $expr,
2346 1
                'nestingLevel'   => $this->nestingLevel,
2347 1
                'token'          => $token,
2348
            ];
2349
        }
2350
2351 26
        return $expr;
2352
    }
2353
2354
    /**
2355
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2356
     *
2357
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2358
     */
2359 356
    public function ConditionalExpression()
2360
    {
2361 356
        $conditionalTerms   = [];
2362 356
        $conditionalTerms[] = $this->ConditionalTerm();
2363
2364 356
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2365 16
            $this->match(Lexer::T_OR);
2366
2367 16
            $conditionalTerms[] = $this->ConditionalTerm();
2368
        }
2369
2370
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2371
        // if only one AST\ConditionalTerm is defined
2372 356
        if (\count($conditionalTerms) === 1) {
2373 348
            return $conditionalTerms[0];
2374
        }
2375
2376 16
        return new AST\ConditionalExpression($conditionalTerms);
2377
    }
2378
2379
    /**
2380
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2381
     *
2382
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2383
     */
2384 356
    public function ConditionalTerm()
2385
    {
2386 356
        $conditionalFactors   = [];
2387 356
        $conditionalFactors[] = $this->ConditionalFactor();
2388
2389 356
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2390 32
            $this->match(Lexer::T_AND);
2391
2392 32
            $conditionalFactors[] = $this->ConditionalFactor();
2393
        }
2394
2395
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2396
        // if only one AST\ConditionalFactor is defined
2397 356
        if (\count($conditionalFactors) === 1) {
2398 337
            return $conditionalFactors[0];
2399
        }
2400
2401 32
        return new AST\ConditionalTerm($conditionalFactors);
2402
    }
2403
2404
    /**
2405
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2406
     *
2407
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2408
     */
2409 356
    public function ConditionalFactor()
2410
    {
2411 356
        $not = false;
2412
2413 356
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2414 6
            $this->match(Lexer::T_NOT);
2415
2416 6
            $not = true;
2417
        }
2418
2419 356
        $conditionalPrimary = $this->ConditionalPrimary();
2420
2421
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2422
        // if only one AST\ConditionalPrimary is defined
2423 356
        if (! $not) {
2424 354
            return $conditionalPrimary;
2425
        }
2426
2427 6
        $conditionalFactor      = new AST\ConditionalFactor($conditionalPrimary);
2428 6
        $conditionalFactor->not = $not;
2429
2430 6
        return $conditionalFactor;
2431
    }
2432
2433
    /**
2434
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2435
     *
2436
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2437
     */
2438 356
    public function ConditionalPrimary()
2439
    {
2440 356
        $condPrimary = new AST\ConditionalPrimary;
2441
2442 356
        if (! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2443 347
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2444
2445 347
            return $condPrimary;
2446
        }
2447
2448
        // Peek beyond the matching closing parenthesis ')'
2449 25
        $peek = $this->peekBeyondClosingParenthesis();
2450
2451 25
        if (in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!='], true) ||
2452 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS], true) ||
2453 25
            $this->isMathOperator($peek)) {
2454 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2455
2456 15
            return $condPrimary;
2457
        }
2458
2459 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2460 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2461 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2462
2463 21
        return $condPrimary;
2464
    }
2465
2466
    /**
2467
     * SimpleConditionalExpression ::=
2468
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2469
     *      InExpression | NullComparisonExpression | ExistsExpression |
2470
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2471
     *      InstanceOfExpression
2472
     */
2473 356
    public function SimpleConditionalExpression()
2474
    {
2475 356
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2476 7
            return $this->ExistsExpression();
2477
        }
2478
2479 356
        $token     = $this->lexer->lookahead;
2480 356
        $peek      = $this->lexer->glimpse();
2481 356
        $lookahead = $token;
2482
2483 356
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2484
            $token = $this->lexer->glimpse();
2485
        }
2486
2487 356
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2488
            // Peek beyond the matching closing parenthesis.
2489 332
            $beyond = $this->lexer->peek();
2490
2491 332
            switch ($peek['value']) {
2492
                case '(':
2493
                    // Peeks beyond the matched closing parenthesis.
2494 37
                    $token = $this->peekBeyondClosingParenthesis(false);
2495
2496 37
                    if ($token['type'] === Lexer::T_NOT) {
2497 3
                        $token = $this->lexer->peek();
2498
                    }
2499
2500 37
                    if ($token['type'] === Lexer::T_IS) {
2501 2
                        $lookahead = $this->lexer->peek();
2502
                    }
2503 37
                    break;
2504
2505
                default:
2506
                    // Peek beyond the PathExpression or InputParameter.
2507 304
                    $token = $beyond;
2508
2509 304
                    while ($token['value'] === '.') {
2510 263
                        $this->lexer->peek();
2511
2512 263
                        $token = $this->lexer->peek();
2513
                    }
2514
2515
                    // Also peek beyond a NOT if there is one.
2516 304
                    if ($token['type'] === Lexer::T_NOT) {
2517 11
                        $token = $this->lexer->peek();
2518
                    }
2519
2520
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2521 304
                    $lookahead = $this->lexer->peek();
2522
            }
2523
2524
            // Also peek beyond a NOT if there is one.
2525 332
            if ($lookahead['type'] === Lexer::T_NOT) {
2526 7
                $lookahead = $this->lexer->peek();
2527
            }
2528
2529 332
            $this->lexer->resetPeek();
2530
        }
2531
2532 356
        if ($token['type'] === Lexer::T_BETWEEN) {
2533 8
            return $this->BetweenExpression();
2534
        }
2535
2536 350
        if ($token['type'] === Lexer::T_LIKE) {
2537 14
            return $this->LikeExpression();
2538
        }
2539
2540 337
        if ($token['type'] === Lexer::T_IN) {
2541 30
            return $this->InExpression();
2542
        }
2543
2544 316
        if ($token['type'] === Lexer::T_INSTANCE) {
2545 16
            return $this->InstanceOfExpression();
2546
        }
2547
2548 300
        if ($token['type'] === Lexer::T_MEMBER) {
2549 8
            return $this->CollectionMemberExpression();
2550
        }
2551
2552 292
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2553 13
            return $this->NullComparisonExpression();
2554
        }
2555
2556 282
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) {
2557 4
            return $this->EmptyCollectionComparisonExpression();
2558
        }
2559
2560 278
        return $this->ComparisonExpression();
2561
    }
2562
2563
    /**
2564
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2565
     *
2566
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2567
     */
2568 4
    public function EmptyCollectionComparisonExpression()
2569
    {
2570 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2571 4
            $this->CollectionValuedPathExpression()
2572
        );
2573 4
        $this->match(Lexer::T_IS);
2574
2575 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2576 2
            $this->match(Lexer::T_NOT);
2577 2
            $emptyCollectionCompExpr->not = true;
2578
        }
2579
2580 4
        $this->match(Lexer::T_EMPTY);
2581
2582 4
        return $emptyCollectionCompExpr;
2583
    }
2584
2585
    /**
2586
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2587
     *
2588
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2589
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2590
     *
2591
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2592
     */
2593 8
    public function CollectionMemberExpression()
2594
    {
2595 8
        $not        = false;
2596 8
        $entityExpr = $this->EntityExpression();
2597
2598 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2599
            $this->match(Lexer::T_NOT);
2600
2601
            $not = true;
2602
        }
2603
2604 8
        $this->match(Lexer::T_MEMBER);
2605
2606 8
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2607 8
            $this->match(Lexer::T_OF);
2608
        }
2609
2610 8
        $collMemberExpr      = new AST\CollectionMemberExpression(
2611 8
            $entityExpr,
2612 8
            $this->CollectionValuedPathExpression()
2613
        );
2614 8
        $collMemberExpr->not = $not;
2615
2616 8
        return $collMemberExpr;
2617
    }
2618
2619
    /**
2620
     * Literal ::= string | char | integer | float | boolean
2621
     *
2622
     * @return \Doctrine\ORM\Query\AST\Literal
2623
     */
2624 171
    public function Literal()
2625
    {
2626 171
        switch ($this->lexer->lookahead['type']) {
2627
            case Lexer::T_STRING:
2628 37
                $this->match(Lexer::T_STRING);
2629
2630 37
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2631
            case Lexer::T_INTEGER:
2632
            case Lexer::T_FLOAT:
2633 134
                $this->match(
2634 134
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2635
                );
2636
2637 134
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2638
            case Lexer::T_TRUE:
2639
            case Lexer::T_FALSE:
2640 8
                $this->match(
2641 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2642
                );
2643
2644 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2645
            default:
2646
                $this->syntaxError('Literal');
2647
        }
2648
    }
2649
2650
    /**
2651
     * InParameter ::= Literal | InputParameter
2652
     *
2653
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2654
     */
2655 22
    public function InParameter()
2656
    {
2657 22
        if ($this->lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
2658 12
            return $this->InputParameter();
2659
        }
2660
2661 10
        return $this->Literal();
2662
    }
2663
2664
    /**
2665
     * InputParameter ::= PositionalParameter | NamedParameter
2666
     *
2667
     * @return \Doctrine\ORM\Query\AST\InputParameter
2668
     */
2669 150
    public function InputParameter()
2670
    {
2671 150
        $this->match(Lexer::T_INPUT_PARAMETER);
2672
2673 150
        return new AST\InputParameter($this->lexer->token['value']);
2674
    }
2675
2676
    /**
2677
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2678
     *
2679
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2680
     */
2681 307
    public function ArithmeticExpression()
2682
    {
2683 307
        $expr = new AST\ArithmeticExpression;
2684
2685 307
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2686 19
            $peek = $this->lexer->glimpse();
2687
2688 19
            if ($peek['type'] === Lexer::T_SELECT) {
2689 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2690 7
                $expr->subselect = $this->Subselect();
2691 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2692
2693 7
                return $expr;
2694
            }
2695
        }
2696
2697 307
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2698
2699 307
        return $expr;
2700
    }
2701
2702
    /**
2703
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2704
     *
2705
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2706
     */
2707 400
    public function SimpleArithmeticExpression()
2708
    {
2709 400
        $terms   = [];
2710 400
        $terms[] = $this->ArithmeticTerm();
2711
2712 400
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2713 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2714
2715 21
            $terms[] = $this->lexer->token['value'];
2716 21
            $terms[] = $this->ArithmeticTerm();
2717
        }
2718
2719
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2720
        // if only one AST\ArithmeticTerm is defined
2721 400
        if (\count($terms) === 1) {
2722 395
            return $terms[0];
2723
        }
2724
2725 21
        return new AST\SimpleArithmeticExpression($terms);
2726
    }
2727
2728
    /**
2729
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2730
     *
2731
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2732
     */
2733 400
    public function ArithmeticTerm()
2734
    {
2735 400
        $factors   = [];
2736 400
        $factors[] = $this->ArithmeticFactor();
2737
2738 400
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2739 52
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2740
2741 52
            $factors[] = $this->lexer->token['value'];
2742 52
            $factors[] = $this->ArithmeticFactor();
2743
        }
2744
2745
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2746
        // if only one AST\ArithmeticFactor is defined
2747 400
        if (\count($factors) === 1) {
2748 372
            return $factors[0];
2749
        }
2750
2751 52
        return new AST\ArithmeticTerm($factors);
2752
    }
2753
2754
    /**
2755
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2756
     *
2757
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2758
     */
2759 400
    public function ArithmeticFactor()
2760
    {
2761 400
        $sign   = null;
2762 400
        $isPlus = $this->lexer->isNextToken(Lexer::T_PLUS);
2763
2764 400
        if ($isPlus || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2765 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2766 3
            $sign = $isPlus;
2767
        }
2768
2769 400
        $primary = $this->ArithmeticPrimary();
2770
2771
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2772
        // if only one AST\ArithmeticPrimary is defined
2773 400
        if ($sign === null) {
2774 399
            return $primary;
2775
        }
2776
2777 3
        return new AST\ArithmeticFactor($primary, $sign);
2778
    }
2779
2780
    /**
2781
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2782
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2783
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2784
     *          | InputParameter | CaseExpression
2785
     */
2786 413
    public function ArithmeticPrimary()
2787
    {
2788 413
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2789 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2790
2791 25
            $expr = $this->SimpleArithmeticExpression();
2792
2793 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2794
2795 25
            return new AST\ParenthesisExpression($expr);
2796
        }
2797
2798 413
        switch ($this->lexer->lookahead['type']) {
2799
            case Lexer::T_COALESCE:
2800
            case Lexer::T_NULLIF:
2801
            case Lexer::T_CASE:
2802 4
                return $this->CaseExpression();
2803
2804
            case Lexer::T_IDENTIFIER:
2805 390
                $peek = $this->lexer->glimpse();
2806
2807 390
                if ($peek['value'] === '(') {
2808 41
                    return $this->FunctionDeclaration();
2809
                }
2810
2811 361
                if ($peek['value'] === '.') {
2812 353
                    return $this->SingleValuedPathExpression();
2813
                }
2814
2815 43
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2816 10
                    return $this->ResultVariable();
2817
                }
2818
2819 35
                return $this->StateFieldPathExpression();
2820
2821
            case Lexer::T_INPUT_PARAMETER:
2822 133
                return $this->InputParameter();
2823
2824
            default:
2825 166
                $peek = $this->lexer->glimpse();
2826
2827 166
                if ($peek['value'] === '(') {
2828 17
                    return $this->FunctionDeclaration();
2829
                }
2830
2831 163
                return $this->Literal();
2832
        }
2833
    }
2834
2835
    /**
2836
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2837
     *
2838
     * @return \Doctrine\ORM\Query\AST\Subselect |
2839
     *         string
2840
     */
2841 14
    public function StringExpression()
2842
    {
2843 14
        $peek = $this->lexer->glimpse();
2844
2845
        // Subselect
2846 14
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2847
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2848
            $expr = $this->Subselect();
2849
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2850
2851
            return $expr;
2852
        }
2853
2854
        // ResultVariable (string)
2855 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2856 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2857 2
            return $this->ResultVariable();
2858
        }
2859
2860 12
        return $this->StringPrimary();
2861
    }
2862
2863
    /**
2864
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2865
     */
2866 63
    public function StringPrimary()
2867
    {
2868 63
        $lookaheadType = $this->lexer->lookahead['type'];
2869
2870 63
        switch ($lookaheadType) {
2871
            case Lexer::T_IDENTIFIER:
2872 35
                $peek = $this->lexer->glimpse();
2873
2874 35
                if ($peek['value'] === '.') {
2875 35
                    return $this->StateFieldPathExpression();
2876
                }
2877
2878 8
                if ($peek['value'] === '(') {
2879
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2880 8
                    return $this->FunctionDeclaration();
2881
                }
2882
2883
                $this->syntaxError("'.' or '('");
2884
                break;
2885
2886
            case Lexer::T_STRING:
2887 45
                $this->match(Lexer::T_STRING);
2888
2889 45
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2890
2891
            case Lexer::T_INPUT_PARAMETER:
2892 2
                return $this->InputParameter();
2893
2894
            case Lexer::T_CASE:
2895
            case Lexer::T_COALESCE:
2896
            case Lexer::T_NULLIF:
2897
                return $this->CaseExpression();
2898
        }
2899
2900
        $this->syntaxError(
2901
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2902
        );
2903
    }
2904
2905
    /**
2906
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2907
     *
2908
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2909
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2910
     */
2911 8
    public function EntityExpression()
2912
    {
2913 8
        $glimpse = $this->lexer->glimpse();
2914
2915 8
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2916 1
            return $this->SingleValuedAssociationPathExpression();
2917
        }
2918
2919 7
        return $this->SimpleEntityExpression();
2920
    }
2921
2922
    /**
2923
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2924
     *
2925
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2926
     */
2927 7
    public function SimpleEntityExpression()
2928
    {
2929 7
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2930 5
            return $this->InputParameter();
2931
        }
2932
2933 2
        return $this->StateFieldPathExpression();
2934
    }
2935
2936
    /**
2937
     * AggregateExpression ::=
2938
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2939
     *
2940
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2941
     */
2942 82
    public function AggregateExpression()
2943
    {
2944 82
        $lookaheadType = $this->lexer->lookahead['type'];
2945 82
        $isDistinct    = false;
2946
2947 82
        if (! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM], true)) {
2948
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2949
        }
2950
2951 82
        $this->match($lookaheadType);
2952 82
        $functionName = $this->lexer->token['value'];
2953 82
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2954
2955 82
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2956 3
            $this->match(Lexer::T_DISTINCT);
2957 3
            $isDistinct = true;
2958
        }
2959
2960 82
        $pathExp = $this->SimpleArithmeticExpression();
2961
2962 82
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2963
2964 82
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2965
    }
2966
2967
    /**
2968
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2969
     *
2970
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2971
     */
2972 3
    public function QuantifiedExpression()
2973
    {
2974 3
        $lookaheadType = $this->lexer->lookahead['type'];
2975 3
        $value         = $this->lexer->lookahead['value'];
2976
2977 3
        if (! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME], true)) {
2978
            $this->syntaxError('ALL, ANY or SOME');
2979
        }
2980
2981 3
        $this->match($lookaheadType);
2982 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2983
2984 3
        $qExpr       = new AST\QuantifiedExpression($this->Subselect());
2985 3
        $qExpr->type = $value;
2986
2987 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2988
2989 3
        return $qExpr;
2990
    }
2991
2992
    /**
2993
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
2994
     *
2995
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
2996
     */
2997 8
    public function BetweenExpression()
2998
    {
2999 8
        $not        = false;
3000 8
        $arithExpr1 = $this->ArithmeticExpression();
3001
3002 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3003 3
            $this->match(Lexer::T_NOT);
3004 3
            $not = true;
3005
        }
3006
3007 8
        $this->match(Lexer::T_BETWEEN);
3008 8
        $arithExpr2 = $this->ArithmeticExpression();
3009 8
        $this->match(Lexer::T_AND);
3010 8
        $arithExpr3 = $this->ArithmeticExpression();
3011
3012 8
        $betweenExpr      = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3013 8
        $betweenExpr->not = $not;
3014
3015 8
        return $betweenExpr;
3016
    }
3017
3018
    /**
3019
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3020
     *
3021
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3022
     */
3023 278
    public function ComparisonExpression()
3024
    {
3025 278
        $this->lexer->glimpse();
3026
3027 278
        $leftExpr  = $this->ArithmeticExpression();
3028 278
        $operator  = $this->ComparisonOperator();
3029 278
        $rightExpr = ($this->isNextAllAnySome())
3030 3
            ? $this->QuantifiedExpression()
3031 278
            : $this->ArithmeticExpression();
3032
3033 278
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3034
    }
3035
3036
    /**
3037
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3038
     *
3039
     * @return \Doctrine\ORM\Query\AST\InExpression
3040
     */
3041 30
    public function InExpression()
3042
    {
3043 30
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3044
3045 30
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3046 6
            $this->match(Lexer::T_NOT);
3047 6
            $inExpression->not = true;
3048
        }
3049
3050 30
        $this->match(Lexer::T_IN);
3051 30
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3052
3053 30
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3054 8
            $inExpression->subselect = $this->Subselect();
3055
        } else {
3056 22
            $literals   = [];
3057 22
            $literals[] = $this->InParameter();
3058
3059 22
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3060 12
                $this->match(Lexer::T_COMMA);
3061 12
                $literals[] = $this->InParameter();
3062
            }
3063
3064 22
            $inExpression->literals = $literals;
3065
        }
3066
3067 30
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3068
3069 30
        return $inExpression;
3070
    }
3071
3072
    /**
3073
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3074
     *
3075
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3076
     */
3077 16
    public function InstanceOfExpression()
3078
    {
3079 16
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3080
3081 16
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3082 1
            $this->match(Lexer::T_NOT);
3083 1
            $instanceOfExpression->not = true;
3084
        }
3085
3086 16
        $this->match(Lexer::T_INSTANCE);
3087 16
        $this->match(Lexer::T_OF);
3088
3089 16
        $exprValues = [];
3090
3091 16
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3092 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3093
3094 2
            $exprValues[] = $this->InstanceOfParameter();
3095
3096 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3097 2
                $this->match(Lexer::T_COMMA);
3098
3099 2
                $exprValues[] = $this->InstanceOfParameter();
3100
            }
3101
3102 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3103
3104 2
            $instanceOfExpression->value = $exprValues;
3105
3106 2
            return $instanceOfExpression;
3107
        }
3108
3109 14
        $exprValues[] = $this->InstanceOfParameter();
3110
3111 14
        $instanceOfExpression->value = $exprValues;
3112
3113 14
        return $instanceOfExpression;
3114
    }
3115
3116
    /**
3117
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3118
     *
3119
     * @return mixed
3120
     */
3121 16
    public function InstanceOfParameter()
3122
    {
3123 16
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3124 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3125
3126 6
            return new AST\InputParameter($this->lexer->token['value']);
3127
        }
3128
3129 10
        $abstractSchemaName = $this->AbstractSchemaName();
3130
3131 10
        $this->validateAbstractSchemaName($abstractSchemaName);
3132
3133 10
        return $abstractSchemaName;
3134
    }
3135
3136
    /**
3137
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3138
     *
3139
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3140
     */
3141 14
    public function LikeExpression()
3142
    {
3143 14
        $stringExpr = $this->StringExpression();
3144 14
        $not        = false;
3145
3146 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3147 3
            $this->match(Lexer::T_NOT);
3148 3
            $not = true;
3149
        }
3150
3151 14
        $this->match(Lexer::T_LIKE);
3152
3153 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3154 3
            $this->match(Lexer::T_INPUT_PARAMETER);
3155 3
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3156
        } else {
3157 12
            $stringPattern = $this->StringPrimary();
3158
        }
3159
3160 14
        $escapeChar = null;
3161
3162 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3163 2
            $this->match(Lexer::T_ESCAPE);
3164 2
            $this->match(Lexer::T_STRING);
3165
3166 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3167
        }
3168
3169 14
        $likeExpr      = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3170 14
        $likeExpr->not = $not;
3171
3172 14
        return $likeExpr;
3173
    }
3174
3175
    /**
3176
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3177
     *
3178
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3179
     */
3180 13
    public function NullComparisonExpression()
3181
    {
3182
        switch (true) {
3183 13
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3184
                $this->match(Lexer::T_INPUT_PARAMETER);
3185
3186
                $expr = new AST\InputParameter($this->lexer->token['value']);
3187
                break;
3188
3189 13
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3190 1
                $expr = $this->NullIfExpression();
3191 1
                break;
3192
3193 13
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3194 1
                $expr = $this->CoalesceExpression();
3195 1
                break;
3196
3197 13
            case $this->isFunction():
3198 2
                $expr = $this->FunctionDeclaration();
3199 2
                break;
3200
3201
            default:
3202
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3203 12
                $glimpse = $this->lexer->glimpse();
3204
3205 12
                if ($glimpse['type'] === Lexer::T_DOT) {
3206 8
                    $expr = $this->SingleValuedPathExpression();
3207
3208
                    // Leave switch statement
3209 8
                    break;
3210
                }
3211
3212 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3213
3214
                // Validate existing component
3215 4
                if (! isset($this->queryComponents[$lookaheadValue])) {
3216
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3217
                }
3218
3219
                // Validate SingleValuedPathExpression (ie.: "product")
3220 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3221 1
                    $expr = $this->SingleValuedPathExpression();
3222 1
                    break;
3223
                }
3224
3225
                // Validating ResultVariable
3226 3
                if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3227
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3228
                }
3229
3230 3
                $expr = $this->ResultVariable();
3231 3
                break;
3232
        }
3233
3234 13
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3235
3236 13
        $this->match(Lexer::T_IS);
3237
3238 13
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3239 5
            $this->match(Lexer::T_NOT);
3240
3241 5
            $nullCompExpr->not = true;
3242
        }
3243
3244 13
        $this->match(Lexer::T_NULL);
3245
3246 13
        return $nullCompExpr;
3247
    }
3248
3249
    /**
3250
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3251
     *
3252
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3253
     */
3254 7
    public function ExistsExpression()
3255
    {
3256 7
        $not = false;
3257
3258 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3259
            $this->match(Lexer::T_NOT);
3260
            $not = true;
3261
        }
3262
3263 7
        $this->match(Lexer::T_EXISTS);
3264 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3265
3266 7
        $existsExpression      = new AST\ExistsExpression($this->Subselect());
3267 7
        $existsExpression->not = $not;
3268
3269 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3270
3271 7
        return $existsExpression;
3272
    }
3273
3274
    /**
3275
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3276
     *
3277
     * @return string
3278
     */
3279 278
    public function ComparisonOperator()
3280
    {
3281 278
        switch ($this->lexer->lookahead['value']) {
3282
            case '=':
3283 227
                $this->match(Lexer::T_EQUALS);
3284
3285 227
                return '=';
3286
3287
            case '<':
3288 17
                $this->match(Lexer::T_LOWER_THAN);
3289 17
                $operator = '<';
3290
3291 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3292 5
                    $this->match(Lexer::T_EQUALS);
3293 5
                    $operator .= '=';
3294 12
                } elseif ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3295 3
                    $this->match(Lexer::T_GREATER_THAN);
3296 3
                    $operator .= '>';
3297
                }
3298
3299 17
                return $operator;
3300
3301
            case '>':
3302 47
                $this->match(Lexer::T_GREATER_THAN);
3303 47
                $operator = '>';
3304
3305 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3306 6
                    $this->match(Lexer::T_EQUALS);
3307 6
                    $operator .= '=';
3308
                }
3309
3310 47
                return $operator;
3311
3312
            case '!':
3313 6
                $this->match(Lexer::T_NEGATE);
3314 6
                $this->match(Lexer::T_EQUALS);
3315
3316 6
                return '<>';
3317
3318
            default:
3319
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3320
        }
3321
    }
3322
3323
    /**
3324
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3325
     *
3326
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3327
     */
3328 149
    public function FunctionDeclaration()
3329
    {
3330 149
        $token    = $this->lexer->lookahead;
3331 149
        $funcName = strtolower($token['value']);
3332
3333 149
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3334
3335
        // Check for custom functions functions first!
3336 149
        switch (true) {
3337
            case $customFunctionDeclaration !== null:
3338 4
                return $customFunctionDeclaration;
3339
3340 145
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3341 32
                return $this->FunctionsReturningStrings();
3342
3343 118
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3344 103
                return $this->FunctionsReturningNumerics();
3345
3346 16
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3347 16
                return $this->FunctionsReturningDatetime();
3348
3349
            default:
3350
                $this->syntaxError('known function', $token);
3351
        }
3352
    }
3353
3354
    /**
3355
     * Helper function for FunctionDeclaration grammar rule.
3356
     *
3357
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3358
     */
3359 149
    private function CustomFunctionDeclaration()
3360
    {
3361 149
        $token    = $this->lexer->lookahead;
3362 149
        $funcName = strtolower($token['value']);
3363
3364
        // Check for custom functions afterwards
3365 149
        $config = $this->em->getConfiguration();
3366
3367 149
        switch (true) {
3368 149
            case ($config->getCustomStringFunction($funcName) !== null):
3369 3
                return $this->CustomFunctionsReturningStrings();
3370
3371 147
            case ($config->getCustomNumericFunction($funcName) !== null):
3372 2
                return $this->CustomFunctionsReturningNumerics();
3373
3374 145
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3375
                return $this->CustomFunctionsReturningDatetime();
3376
3377
            default:
3378 145
                return null;
3379
        }
3380
    }
3381
3382
    /**
3383
     * FunctionsReturningNumerics ::=
3384
     *      "LENGTH" "(" StringPrimary ")" |
3385
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3386
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3387
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3388
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3389
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3390
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3391
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3392
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3393
     *
3394
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3395
     */
3396 103
    public function FunctionsReturningNumerics()
3397
    {
3398 103
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3399 103
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3400
3401 103
        $function = new $funcClass($funcNameLower);
3402 103
        $function->parse($this);
3403
3404 103
        return $function;
3405
    }
3406
3407
    /**
3408
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3409
     */
3410 2
    public function CustomFunctionsReturningNumerics()
3411
    {
3412
        // getCustomNumericFunction is case-insensitive
3413 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3414 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3415
3416 2
        $function = is_string($functionClass)
3417 1
            ? new $functionClass($functionName)
3418 2
            : call_user_func($functionClass, $functionName);
3419
3420 2
        $function->parse($this);
3421
3422 2
        return $function;
3423
    }
3424
3425
    /**
3426
     * FunctionsReturningDateTime ::=
3427
     *     "CURRENT_DATE" |
3428
     *     "CURRENT_TIME" |
3429
     *     "CURRENT_TIMESTAMP" |
3430
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3431
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3432
     *
3433
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3434
     */
3435 16
    public function FunctionsReturningDatetime()
3436
    {
3437 16
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3438 16
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3439
3440 16
        $function = new $funcClass($funcNameLower);
3441 16
        $function->parse($this);
3442
3443 16
        return $function;
3444
    }
3445
3446
    /**
3447
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3448
     */
3449
    public function CustomFunctionsReturningDatetime()
3450
    {
3451
        // getCustomDatetimeFunction is case-insensitive
3452
        $functionName  = $this->lexer->lookahead['value'];
3453
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3454
3455
        $function = is_string($functionClass)
3456
            ? new $functionClass($functionName)
3457
            : call_user_func($functionClass, $functionName);
3458
3459
        $function->parse($this);
3460
3461
        return $function;
3462
    }
3463
3464
    /**
3465
     * FunctionsReturningStrings ::=
3466
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3467
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3468
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3469
     *   "LOWER" "(" StringPrimary ")" |
3470
     *   "UPPER" "(" StringPrimary ")" |
3471
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3472
     *
3473
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3474
     */
3475 32
    public function FunctionsReturningStrings()
3476
    {
3477 32
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3478 32
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3479
3480 32
        $function = new $funcClass($funcNameLower);
3481 32
        $function->parse($this);
3482
3483 32
        return $function;
3484
    }
3485
3486
    /**
3487
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3488
     */
3489 3
    public function CustomFunctionsReturningStrings()
3490
    {
3491
        // getCustomStringFunction is case-insensitive
3492 3
        $functionName  = $this->lexer->lookahead['value'];
3493 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3494
3495 3
        $function = is_string($functionClass)
3496 2
            ? new $functionClass($functionName)
3497 3
            : call_user_func($functionClass, $functionName);
3498
3499 3
        $function->parse($this);
3500
3501 3
        return $function;
3502
    }
3503
}
3504