Parser.php 88.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\ORM\Query;

24
use Doctrine\ORM\Query;
25 26

/**
27
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
28 29
 * Parses a DQL query, reports any errors in it, and generates an AST.
 *
30 31 32 33 34 35 36 37
 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link    www.doctrine-project.org
 * @since   2.0
 * @version $Revision: 3938 $
 * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author  Jonathan Wage <jonwage@gmail.com>
 * @author  Roman Borschel <roman@code-factory.org>
 * @author  Janne Vanhala <jpvanhal@cc.hut.fi>
38 39 40
 */
class Parser
{
41
    /** READ-ONLY: Maps BUILT-IN string function names to AST class names. */
42
    private static $_STRING_FUNCTIONS = array(
43
        'concat'    => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction',
44
        'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
45 46 47
        'trim'      => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
        'lower'     => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
        'upper'     => 'Doctrine\ORM\Query\AST\Functions\UpperFunction'
48
    );
49

50
    /** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
51 52 53
    private static $_NUMERIC_FUNCTIONS = array(
        'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
        'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
54 55 56 57
        'abs'    => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
        'sqrt'   => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
        'mod'    => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
        'size'   => 'Doctrine\ORM\Query\AST\Functions\SizeFunction'
58 59
    );

60
    /** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */
61
    private static $_DATETIME_FUNCTIONS = array(
62 63
        'current_date'      => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
        'current_time'      => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
64 65 66 67
        'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction'
    );

    /**
68
     * Expressions that were encountered during parsing of identifiers and expressions
69 70
     * and still need to be validated.
     */
71 72 73 74
    private $_deferredIdentificationVariables = array();
    private $_deferredPartialObjectExpressions = array();
    private $_deferredPathExpressions = array();
    private $_deferredResultVariables = array();
75 76

    /**
romanb's avatar
romanb committed
77
     * The lexer.
78 79 80 81 82 83
     *
     * @var Doctrine\ORM\Query\Lexer
     */
    private $_lexer;

    /**
romanb's avatar
romanb committed
84
     * The parser result.
85 86 87 88
     *
     * @var Doctrine\ORM\Query\ParserResult
     */
    private $_parserResult;
romanb's avatar
romanb committed
89

90 91 92 93 94 95
    /**
     * The EntityManager.
     *
     * @var EnityManager
     */
    private $_em;
96

97 98 99 100 101 102 103 104
    /**
     * The Query to parse.
     *
     * @var Query
     */
    private $_query;

    /**
romanb's avatar
romanb committed
105
     * Map of declared query components in the parsed query.
106 107 108 109
     *
     * @var array
     */
    private $_queryComponents = array();
110

111 112 113 114 115 116
    /**
     * Keeps the nesting level of defined ResultVariables
     *
     * @var integer
     */
    private $_nestingLevel = 0;
117

118
    /**
119
     * Any additional custom tree walkers that modify the AST.
120
     *
121 122 123
     * @var array
     */
    private $_customTreeWalkers = array();
124

125 126
    /**
     * The custom last tree walker, if any, that is responsible for producing the output.
127
     *
128
     * @var TreeWalker
129
     */
130
    private $_customOutputWalker;
131 132 133 134 135 136 137 138 139 140 141

    /**
     * Creates a new query parser object.
     *
     * @param Query $query The Query to parse.
     */
    public function __construct(Query $query)
    {
        $this->_query = $query;
        $this->_em = $query->getEntityManager();
        $this->_lexer = new Lexer($query->getDql());
142
        $this->_parserResult = new ParserResult();
143 144
    }

145
    /**
146 147
     * Sets a custom tree walker that produces output.
     * This tree walker will be run last over the AST, after any other walkers.
148
     *
149
     * @param string $className
150
     */
151
    public function setCustomOutputTreeWalker($className)
152
    {
153 154
        $this->_customOutputWalker = $className;
    }
155

156 157
    /**
     * Adds a custom tree walker for modifying the AST.
158
     *
159 160 161 162 163
     * @param string $className
     */
    public function addCustomTreeWalker($className)
    {
        $this->_customTreeWalkers[] = $className;
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
    }

    /**
     * Gets the lexer used by the parser.
     *
     * @return Doctrine\ORM\Query\Lexer
     */
    public function getLexer()
    {
        return $this->_lexer;
    }

    /**
     * Gets the ParserResult that is being filled with information during parsing.
     *
     * @return Doctrine\ORM\Query\ParserResult
     */
    public function getParserResult()
    {
        return $this->_parserResult;
    }
185

186 187 188 189 190 191 192 193 194
    /**
     * Gets the EntityManager used by the parser.
     *
     * @return EntityManager
     */
    public function getEntityManager()
    {
        return $this->_em;
    }
195

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    /**
     * Parse and build AST for the given Query.
     *
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
     *         \Doctrine\ORM\Query\AST\DeleteStatement
     */
    public function getAST()
    {
        // Parse & build AST
        $AST = $this->QueryLanguage();

        // Process any deferred validations of some nodes in the AST.
        // This also allows post-processing of the AST for modification purposes.
        $this->_processDeferredIdentificationVariables();
        if ($this->_deferredPartialObjectExpressions) {
            $this->_processDeferredPartialObjectExpressions();
        }
        if ($this->_deferredPathExpressions) {
            $this->_processDeferredPathExpressions($AST);
        }
        if ($this->_deferredResultVariables) {
            $this->_processDeferredResultVariables();
        }

        return $AST;
    }

224 225 226 227 228 229 230
    /**
     * Attempts to match the given token with the current lookahead token.
     *
     * If they match, updates the lookahead token; otherwise raises a syntax
     * error.
     *
     * @param int|string token type or value
231 232
     * @return void
     * @throws QueryException If the tokens dont match.
233 234 235
     */
    public function match($token)
    {
236
        if ( ! ($this->_lexer->lookahead['type'] === $token)) {
237
            $this->syntaxError($this->_lexer->getLiteral($token));
238
        }
239 240 241 242 243

        $this->_lexer->moveNext();
    }

    /**
romanb's avatar
romanb committed
244 245 246 247
     * Free this parser enabling it to be reused
     *
     * @param boolean $deep     Whether to clean peek and reset errors
     * @param integer $position Position to reset
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
     */
    public function free($deep = false, $position = 0)
    {
        // WARNING! Use this method with care. It resets the scanner!
        $this->_lexer->resetPosition($position);

        // Deep = true cleans peek and also any previously defined errors
        if ($deep) {
            $this->_lexer->resetPeek();
        }

        $this->_lexer->token = null;
        $this->_lexer->lookahead = null;
    }

    /**
     * Parses a query string.
romanb's avatar
romanb committed
265
     *
266 267 268 269
     * @return ParserResult
     */
    public function parse()
    {
270
        $AST = $this->getAST();
271

272 273 274
        if ($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) {
            $this->_customTreeWalkers = $customWalkers;
        }
275

276 277 278 279
        if ($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) {
            $this->_customOutputWalker = $customOutputWalker;
        }

280 281 282
        // Run any custom tree walkers over the AST
        if ($this->_customTreeWalkers) {
            $treeWalkerChain = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents);
283

284 285
            foreach ($this->_customTreeWalkers as $walker) {
                $treeWalkerChain->addTreeWalker($walker);
286
            }
287

288 289 290 291 292 293 294 295
            if ($AST instanceof AST\SelectStatement) {
                $treeWalkerChain->walkSelectStatement($AST);
            } else if ($AST instanceof AST\UpdateStatement) {
                $treeWalkerChain->walkUpdateStatement($AST);
            } else {
                $treeWalkerChain->walkDeleteStatement($AST);
            }
        }
296

297 298 299 300
        if ($this->_customOutputWalker) {
            $outputWalker = new $this->_customOutputWalker(
                $this->_query, $this->_parserResult, $this->_queryComponents
            );
301
        } else {
302
            $outputWalker = new SqlWalker(
303 304
                $this->_query, $this->_parserResult, $this->_queryComponents
            );
305
        }
306 307

        // Assign an SQL executor to the parser result
308
        $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
309 310 311

        return $this->_parserResult;
    }
312

313 314 315
    /**
     * Generates a new syntax error.
     *
316 317
     * @param string $expected Expected string.
     * @param array $token Got token.
318 319
     *
     * @throws \Doctrine\ORM\Query\QueryException
320 321 322 323 324 325 326
     */
    public function syntaxError($expected = '', $token = null)
    {
        if ($token === null) {
            $token = $this->_lexer->lookahead;
        }

327 328
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
        $message  = "line 0, col {$tokenPos}: Error: ";
329 330

        if ($expected !== '') {
331
            $message .= "Expected {$expected}, got ";
332 333 334 335 336 337 338
        } else {
            $message .= 'Unexpected ';
        }

        if ($this->_lexer->lookahead === null) {
            $message .= 'end of string.';
        } else {
339
            $message .= "'{$token['value']}'";
340 341
        }

342
        throw QueryException::syntaxError($message);
343 344 345 346 347 348 349
    }

    /**
     * Generates a new semantical error.
     *
     * @param string $message Optional message.
     * @param array $token Optional token.
350 351
     *
     * @throws \Doctrine\ORM\Query\QueryException
352 353 354 355
     */
    public function semanticalError($message = '', $token = null)
    {
        if ($token === null) {
356
            $token = $this->_lexer->lookahead;
357
        }
358

359 360
        // Minimum exposed chars ahead of token
        $distance = 12;
361

362 363
        // Find a position of a final word to display in error string
        $dql = $this->_query->getDql();
364 365 366 367
        $length = strlen($dql);
        $pos = $token['position'] + $distance;
        $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
368

369
        // Building informative message
370 371 372
        $message = 'line 0, col ' . (
            (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'
        ) . " near '" . substr($dql, $token['position'], $length) . "': Error: " . $message;
guilhermeblanco's avatar
guilhermeblanco committed
373

374
        throw \Doctrine\ORM\Query\QueryException::semanticalError($message);
375
    }
376

377
    /**
378
     * Peeks beyond the specified token and returns the first token after that one.
379 380 381
     *
     * @param array $token
     * @return array
382
     */
383 384 385 386 387 388 389 390 391 392
    private function _peekBeyond($token)
    {
        $peek = $this->_lexer->peek();

        while ($peek['value'] != $token) {
            $peek = $this->_lexer->peek();
        }

        $peek = $this->_lexer->peek();
        $this->_lexer->resetPeek();
romanb's avatar
romanb committed
393

394 395
        return $peek;
    }
396 397

    /**
398
     * Checks if the next-next (after lookahead) token starts a function.
399
     *
400
     * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
401
     */
402
    private function _isFunction()
403
    {
romanb's avatar
romanb committed
404
        $peek = $this->_lexer->peek();
405 406
        $nextpeek = $this->_lexer->peek();
        $this->_lexer->resetPeek();
romanb's avatar
romanb committed
407

408 409
        // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function
        return ($peek['value'] === '(' && $nextpeek['type'] !== Lexer::T_SELECT);
410
    }
411

412
    /**
413
     * Checks whether the given token type indicates an aggregate function.
414
     *
415
     * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
416
     */
417
    private function _isAggregateFunction($tokenType)
418
    {
419 420 421
        return $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN ||
               $tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM ||
               $tokenType == Lexer::T_COUNT;
422 423
    }

424 425 426 427 428 429 430 431 432 433 434 435 436
    /**
     * Checks whether the current lookahead token of the lexer has the type
     * T_ALL, T_ANY or T_SOME.
     *
     * @return boolean
     */
    private function _isNextAllAnySome()
    {
        return $this->_lexer->lookahead['type'] === Lexer::T_ALL ||
               $this->_lexer->lookahead['type'] === Lexer::T_ANY ||
               $this->_lexer->lookahead['type'] === Lexer::T_SOME;
    }

437 438 439 440 441 442 443 444 445
    /**
     * Checks whether the next 2 tokens start a subselect.
     *
     * @return boolean TRUE if the next 2 tokens start a subselect, FALSE otherwise.
     */
    private function _isSubselect()
    {
        $la = $this->_lexer->lookahead;
        $next = $this->_lexer->glimpse();
guilhermeblanco's avatar
guilhermeblanco committed
446

447 448
        return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
    }
449

450
    /**
451
     * Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
452 453
     * It must exist in query components list.
     *
454
     * @return void
455
     */
456
    private function _processDeferredIdentificationVariables()
457
    {
458 459 460 461 462 463
        foreach ($this->_deferredIdentificationVariables as $deferredItem) {
            $identVariable = $deferredItem['expression'];

            // Check if IdentificationVariable exists in queryComponents
            if ( ! isset($this->_queryComponents[$identVariable])) {
                $this->semanticalError(
464
                "'$identVariable' is not defined.", $deferredItem['token']
465 466 467 468 469 470 471 472
                );
            }

            $qComp = $this->_queryComponents[$identVariable];

            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
            if ( ! isset($qComp['metadata'])) {
                $this->semanticalError(
473
                "'$identVariable' does not point to a Class.", $deferredItem['token']
474 475 476 477 478 479
                );
            }

            // Validate if identification variable nesting level is lower or equal than the current one
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
                $this->semanticalError(
480
                "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
                );
            }
        }
    }

    private function _processDeferredPartialObjectExpressions()
    {
        foreach ($this->_deferredPartialObjectExpressions as $deferredItem) {
            $expr = $deferredItem['expression'];
            $class = $this->_queryComponents[$expr->identificationVariable]['metadata'];
            foreach ($expr->partialFieldSet as $field) {
                if ( ! isset($class->fieldMappings[$field])) {
                    $this->semanticalError(
                    "There is no mapped field named '$field' on class " . $class->name . ".",
                    $deferredItem['token']
                    );
                }
            }
            if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
                $this->semanticalError(
                "The partial field selection of class " . $class->name . " must contain the identifier.",
                $deferredItem['token']
                );
            }
505
        }
506
    }
507

508
    /**
509
     * Validates that the given <tt>ResultVariable</tt> is a semantically correct.
510 511
     * It must exist in query components list.
     *
512
     * @return void
513
     */
514
    private function _processDeferredResultVariables()
515
    {
516 517 518 519 520 521
        foreach ($this->_deferredResultVariables as $deferredItem) {
            $resultVariable = $deferredItem['expression'];

            // Check if ResultVariable exists in queryComponents
            if ( ! isset($this->_queryComponents[$resultVariable])) {
                $this->semanticalError(
522
                "'$resultVariable' is not defined.", $deferredItem['token']
523 524 525 526 527 528 529 530
                );
            }

            $qComp = $this->_queryComponents[$resultVariable];

            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
            if ( ! isset($qComp['resultVariable'])) {
                $this->semanticalError(
531
                "'$identVariable' does not point to a ResultVariable.", $deferredItem['token']
532 533 534 535 536 537
                );
            }

            // Validate if identification variable nesting level is lower or equal than the current one
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
                $this->semanticalError(
538
                "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
539 540
                );
            }
541
        }
542
    }
543

544 545 546 547 548 549 550 551 552
    /**
     * Validates that the given <tt>PathExpression</tt> is a semantically correct for grammar rules:
     *
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
     * CollectionValuedPathExpression        ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
     *
553 554
     * @param array $deferredItem
     * @param mixed $AST
555
     */
556
    private function _processDeferredPathExpressions($AST)
557
    {
558 559 560 561 562 563 564 565 566 567
        foreach ($this->_deferredPathExpressions as $deferredItem) {
            $pathExpression = $deferredItem['expression'];
            $parts = $pathExpression->parts;
            $numParts = count($parts);

            $qComp = $this->_queryComponents[$pathExpression->identificationVariable];

            $aliasIdentificationVariable = $pathExpression->identificationVariable;
            $parentField = $pathExpression->identificationVariable;
            $class = $qComp['metadata'];
568 569
            $fieldType = ($pathExpression->expectedType == AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE)
                ? AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE : null;
570 571 572 573 574 575
            $curIndex = 0;

            foreach ($parts as $field) {
                // Check if it is not in a state field
                if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
                    $this->semanticalError(
576
                    'Cannot navigate through state field named ' . $field . ' on ' . $parentField,
577
                    $deferredItem['token']
578 579 580 581 582 583
                    );
                }

                // Check if it is not a collection field
                if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
                    $this->semanticalError(
584
                    'Cannot navigate through collection field named ' . $field . ' on ' . $parentField,
585
                    $deferredItem['token']
586 587 588 589 590 591
                    );
                }

                // Check if field or association exists
                if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
                    $this->semanticalError(
592
                    'Class ' . $class->name . ' has no field or association named ' . $field,
593
                    $deferredItem['token']
594 595 596 597
                    );
                }

                $parentField = $field;
598

599 600 601 602 603 604 605
                if (isset($class->fieldMappings[$field])) {
                    $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
                } else {
                    $assoc = $class->associationMappings[$field];
                    $class = $this->_em->getClassMetadata($assoc->targetEntityName);

                    if (
606 607
                        ($curIndex != $numParts - 1) &&
                        ! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field])
608 609 610
                    ) {
                        // Building queryComponent
                        $joinQueryComponent = array(
611 612 613 614 615 616
                        'metadata'     => $class,
                        'parent'       => $aliasIdentificationVariable,
                        'relation'     => $assoc,
                        'map'          => null,
                        'nestingLevel' => $this->_nestingLevel,
                        'token'        => $deferredItem['token'],
617
                        );
618

619 620
                        // Create AST node
                        $joinVariableDeclaration = new AST\JoinVariableDeclaration(
621 622 623 624 625 626 627
                            new AST\Join(
                                AST\Join::JOIN_TYPE_INNER,
                                new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
                                $aliasIdentificationVariable . '.' . $field,
                                false
                            ),
                            null
628 629 630 631 632 633 634 635 636 637 638 639 640
                        );
                        $AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;

                        $this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;
                    }

                    $aliasIdentificationVariable .= '.' . $field;

                    if ($assoc->isOneToOne()) {
                        $fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
                    } else {
                        $fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
                    }
641
                }
642 643 644 645 646 647 648 649 650 651 652

                ++$curIndex;
            }

            // Validate if PathExpression is one of the expected types
            $expectedType = $pathExpression->expectedType;

            if ( ! ($expectedType & $fieldType)) {
                // We need to recognize which was expected type(s)
                $expectedStringTypes = array();

653 654 655 656 657
                // Validate state field type
                if ($expectedType & AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE) {
                    $expectedStringTypes[] = 'IdentificationVariable';
                }

658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
                // Validate state field type
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
                    $expectedStringTypes[] = 'StateFieldPathExpression';
                }

                // Validate single valued association (*-to-one)
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
                }

                // Validate single valued association (*-to-many)
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
                }

                // Build the error message
                $semanticalError = 'Invalid PathExpression. ';

                if (count($expectedStringTypes) == 1) {
                    $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
678
                } else {
679
                    $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
680
                }
681 682

                $this->semanticalError($semanticalError, $deferredItem['token']);
683
            }
684 685 686

            // We need to force the type in PathExpression
            $pathExpression->type = $fieldType;
687
        }
688
    }
689

690 691
    /**
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
692
     *
693 694
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
695
     *         \Doctrine\ORM\Query\AST\DeleteStatement
696
     */
romanb's avatar
romanb committed
697
    public function QueryLanguage()
698 699
    {
        $this->_lexer->moveNext();
700

701 702
        switch ($this->_lexer->lookahead['type']) {
            case Lexer::T_SELECT:
703 704
                $statement = $this->SelectStatement();
                break;
705
            case Lexer::T_UPDATE:
706 707
                $statement = $this->UpdateStatement();
                break;
708
            case Lexer::T_DELETE:
709 710
                $statement = $this->DeleteStatement();
                break;
711 712
            default:
                $this->syntaxError('SELECT, UPDATE or DELETE');
713
                break;
714
        }
715

716 717 718 719
        // Check for end of string
        if ($this->_lexer->lookahead !== null) {
            $this->syntaxError('end of string');
        }
720

721
        return $statement;
722 723 724 725
    }

    /**
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
726 727
     *
     * @return \Doctrine\ORM\Query\AST\SelectStatement
728
     */
romanb's avatar
romanb committed
729
    public function SelectStatement()
730
    {
731
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
732

733
        $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
guilhermeblanco's avatar
guilhermeblanco committed
734
            ? $this->WhereClause() : null;
735

736
        $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP)
guilhermeblanco's avatar
guilhermeblanco committed
737
            ? $this->GroupByClause() : null;
738

739
        $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING)
guilhermeblanco's avatar
guilhermeblanco committed
740
            ? $this->HavingClause() : null;
741

742
        $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
guilhermeblanco's avatar
guilhermeblanco committed
743
            ? $this->OrderByClause() : null;
744

745
        return $selectStatement;
746 747 748
    }

    /**
749
     * UpdateStatement ::= UpdateClause [WhereClause]
750 751
     *
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
752
     */
753
    public function UpdateStatement()
754
    {
755
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
756
        $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
757
                ? $this->WhereClause() : null;
758 759

        return $updateStatement;
760
    }
761

762 763
    /**
     * DeleteStatement ::= DeleteClause [WhereClause]
764 765
     *
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
766 767 768 769
     */
    public function DeleteStatement()
    {
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
770
        $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
771
                ? $this->WhereClause() : null;
772

773 774
        return $deleteStatement;
    }
775

776
    /**
777
     * IdentificationVariable ::= identifier
778 779
     *
     * @return string
780
     */
781
    public function IdentificationVariable()
782
    {
783
        $this->match(Lexer::T_IDENTIFIER);
guilhermeblanco's avatar
guilhermeblanco committed
784

785
        $identVariable = $this->_lexer->token['value'];
786 787

        $this->_deferredIdentificationVariables[] = array(
788 789 790 791 792 793
            'expression'   => $identVariable,
            'nestingLevel' => $this->_nestingLevel,
            'token'        => $this->_lexer->token,
        );

        return $identVariable;
794
    }
795

796 797
    /**
     * AliasIdentificationVariable = identifier
798 799
     *
     * @return string
800 801 802 803
     */
    public function AliasIdentificationVariable()
    {
        $this->match(Lexer::T_IDENTIFIER);
804

805
        $aliasIdentVariable = $this->_lexer->token['value'];
806
        $exists = isset($this->_queryComponents[$aliasIdentVariable]);
807

808 809 810 811 812
        if ($exists) {
            $this->semanticalError(
                "'$aliasIdentVariable' is already defined.", $this->_lexer->token
            );
        }
guilhermeblanco's avatar
guilhermeblanco committed
813

814
        return $aliasIdentVariable;
815
    }
816

817 818
    /**
     * AbstractSchemaName ::= identifier
819 820
     *
     * @return string
821 822 823 824
     */
    public function AbstractSchemaName()
    {
        $this->match(Lexer::T_IDENTIFIER);
guilhermeblanco's avatar
guilhermeblanco committed
825

826
        $schemaName = $this->_lexer->token['value'];
827 828 829 830

        if (strrpos($schemaName, ':') !== false) {
            list($namespaceAlias, $simpleClassName) = explode(':', $schemaName);
            $schemaName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
831
        }
832

833
        $exists = class_exists($schemaName, true);
834

835 836 837
        if ( ! $exists) {
            $this->semanticalError("Class '$schemaName' is not defined.", $this->_lexer->token);
        }
838 839

        return $schemaName;
840
    }
841

842 843 844 845 846 847 848 849
    /**
     * AliasResultVariable ::= identifier
     *
     * @return string
     */
    public function AliasResultVariable()
    {
        $this->match(Lexer::T_IDENTIFIER);
850

851 852
        $resultVariable = $this->_lexer->token['value'];
        $exists = isset($this->_queryComponents[$resultVariable]);
853

854 855 856 857 858
        if ($exists) {
            $this->semanticalError(
                "'$resultVariable' is already defined.", $this->_lexer->token
            );
        }
859

860 861
        return $resultVariable;
    }
862

863 864
    /**
     * ResultVariable ::= identifier
865 866
     *
     * @return string
867 868 869 870
     */
    public function ResultVariable()
    {
        $this->match(Lexer::T_IDENTIFIER);
871

872
        $resultVariable = $this->_lexer->token['value'];
873

874
        // Defer ResultVariable validation
875 876 877 878
        $this->_deferredResultVariables[] = array(
            'expression'   => $resultVariable,
            'nestingLevel' => $this->_nestingLevel,
            'token'        => $this->_lexer->token,
879
        );
880

881
        return $resultVariable;
882
    }
guilhermeblanco's avatar
guilhermeblanco committed
883

884
    /**
885
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
886 887
     *
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
888
     */
889
    public function JoinAssociationPathExpression()
890
    {
891
        $token = $this->_lexer->lookahead;
892

893
        $identVariable = $this->IdentificationVariable();
894
        $this->match(Lexer::T_DOT);
romanb's avatar
romanb committed
895
        $this->match($this->_lexer->lookahead['type']);
896
        $field = $this->_lexer->token['value'];
romanb's avatar
romanb committed
897

898 899 900
        // Validate association field
        $qComp = $this->_queryComponents[$identVariable];
        $class = $qComp['metadata'];
901

902 903 904 905 906
        if ( ! isset($class->associationMappings[$field])) {
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
        }

        return new AST\JoinAssociationPathExpression($identVariable, $field);
907
    }
908 909

    /**
910
     * Parses an arbitrary path expression and defers semantical validation
911
     * based on expected types.
912
     *
913
     * PathExpression ::= IdentificationVariable {"." identifier}* "." identifier
914
     *
915
     * @param integer $expectedTypes
916
     * @return \Doctrine\ORM\Query\AST\PathExpression
917
     */
918
    public function PathExpression($expectedTypes)
919
    {
920
        $token = $this->_lexer->lookahead;
921
        $identVariable = $this->IdentificationVariable();
922 923
        $parts = array();

924
        while ($this->_lexer->isNextToken(Lexer::T_DOT)) {
925
            $this->match(Lexer::T_DOT);
926
            $this->match(Lexer::T_IDENTIFIER);
927

928
            $parts[] = $this->_lexer->token['value'];
929
        }
930

931
        // Creating AST node
932
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
933

934
        // Defer PathExpression validation if requested to be defered
935 936 937 938
        $this->_deferredPathExpressions[] = array(
            'expression'   => $pathExpr,
            'nestingLevel' => $this->_nestingLevel,
            'token'        => $this->_lexer->token,
939 940
        );

941
        return $pathExpr;
942
    }
943

944 945
    /**
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
946 947
     *
     * @return \Doctrine\ORM\Query\AST\PathExpression
948 949 950
     */
    public function AssociationPathExpression()
    {
951 952 953 954
        return $this->PathExpression(
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
        );
955
    }
956

957 958
    /**
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
959 960
     *
     * @return \Doctrine\ORM\Query\AST\PathExpression
961 962 963
     */
    public function SingleValuedPathExpression()
    {
964 965 966 967
        return $this->PathExpression(
            AST\PathExpression::TYPE_STATE_FIELD |
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
        );
968
    }
969

970 971
    /**
     * StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
972 973
     *
     * @return \Doctrine\ORM\Query\AST\PathExpression
974 975 976
     */
    public function StateFieldPathExpression()
    {
977
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
978
    }
979

980
    /**
981
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
982 983
     *
     * @return \Doctrine\ORM\Query\AST\PathExpression
984
     */
985
    public function SingleValuedAssociationPathExpression()
986
    {
987
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
988
    }
989

990 991
    /**
     * CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
992 993
     *
     * @return \Doctrine\ORM\Query\AST\PathExpression
994 995 996
     */
    public function CollectionValuedPathExpression()
    {
997
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
998
    }
999

1000 1001
    /**
     * SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
1002 1003
     *
     * @return \Doctrine\ORM\Query\AST\PathExpression
1004 1005 1006
     */
    public function SimpleStateFieldPathExpression()
    {
1007
        $pathExpression = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1008
        $parts = $pathExpression->parts;
1009

1010 1011
        if (count($parts) > 1) {
            $this->semanticalError(
1012
                "Invalid SimpleStateFieldPathExpression. " .
1013 1014 1015
                "Expected state field, got association '{$parts[0]}'."
            );
        }
1016

1017
        return $pathExpression;
1018 1019
    }

1020 1021
    /**
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1022 1023
     *
     * @return \Doctrine\ORM\Query\AST\SelectClause
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
     */
    public function SelectClause()
    {
        $isDistinct = false;
        $this->match(Lexer::T_SELECT);

        // Check for DISTINCT
        if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
            $this->match(Lexer::T_DISTINCT);
            $isDistinct = true;
        }

        // Process SelectExpressions (1..N)
        $selectExpressions = array();
        $selectExpressions[] = $this->SelectExpression();

1040 1041
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
1042 1043 1044 1045 1046 1047 1048 1049
            $selectExpressions[] = $this->SelectExpression();
        }

        return new AST\SelectClause($selectExpressions, $isDistinct);
    }

    /**
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1050 1051
     *
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1052 1053 1054
     */
    public function SimpleSelectClause()
    {
1055
        $isDistinct = false;
1056 1057 1058 1059
        $this->match(Lexer::T_SELECT);

        if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
            $this->match(Lexer::T_DISTINCT);
1060
            $isDistinct = true;
1061 1062
        }

1063
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1064 1065
    }

1066
    /**
1067
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1068 1069
     *
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1070
     */
romanb's avatar
romanb committed
1071
    public function UpdateClause()
1072 1073
    {
        $this->match(Lexer::T_UPDATE);
1074
        $token = $this->_lexer->lookahead;
romanb's avatar
romanb committed
1075
        $abstractSchemaName = $this->AbstractSchemaName();
1076

1077 1078 1079
        if ($this->_lexer->isNextToken(Lexer::T_AS)) {
            $this->match(Lexer::T_AS);
        }
guilhermeblanco's avatar
guilhermeblanco committed
1080

1081
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1082

1083
        $class = $this->_em->getClassMetadata($abstractSchemaName);
guilhermeblanco's avatar
guilhermeblanco committed
1084

1085 1086
        // Building queryComponent
        $queryComponent = array(
1087
            'metadata'     => $class,
1088 1089 1090 1091
            'parent'       => null,
            'relation'     => null,
            'map'          => null,
            'nestingLevel' => $this->_nestingLevel,
1092
            'token'        => $token,
1093 1094 1095
        );
        $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;

1096
        $this->match(Lexer::T_SET);
1097

1098 1099 1100
        $updateItems = array();
        $updateItems[] = $this->UpdateItem();

1101 1102
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
1103 1104 1105
            $updateItems[] = $this->UpdateItem();
        }

1106
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1107
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1108 1109 1110 1111 1112

        return $updateClause;
    }

    /**
1113
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1114 1115
     *
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1116
     */
romanb's avatar
romanb committed
1117
    public function DeleteClause()
1118 1119
    {
        $this->match(Lexer::T_DELETE);
guilhermeblanco's avatar
guilhermeblanco committed
1120

1121 1122 1123
        if ($this->_lexer->isNextToken(Lexer::T_FROM)) {
            $this->match(Lexer::T_FROM);
        }
guilhermeblanco's avatar
guilhermeblanco committed
1124

1125
        $token = $this->_lexer->lookahead;
romanb's avatar
romanb committed
1126
        $deleteClause = new AST\DeleteClause($this->AbstractSchemaName());
1127

1128 1129 1130
        if ($this->_lexer->isNextToken(Lexer::T_AS)) {
            $this->match(Lexer::T_AS);
        }
guilhermeblanco's avatar
guilhermeblanco committed
1131

1132
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1133

1134 1135
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
        $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
1136

1137
        // Building queryComponent
1138
        $queryComponent = array(
1139
            'metadata'     => $class,
1140 1141 1142 1143
            'parent'       => null,
            'relation'     => null,
            'map'          => null,
            'nestingLevel' => $this->_nestingLevel,
1144
            'token'        => $token,
1145
        );
1146
        $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
1147 1148 1149 1150 1151

        return $deleteClause;
    }

    /**
1152
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1153 1154
     *
     * @return \Doctrine\ORM\Query\AST\FromClause
1155
     */
1156
    public function FromClause()
1157
    {
1158 1159 1160 1161
        $this->match(Lexer::T_FROM);
        $identificationVariableDeclarations = array();
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();

1162 1163
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
1164
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1165 1166
        }

1167 1168 1169 1170 1171
        return new AST\FromClause($identificationVariableDeclarations);
    }

    /**
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1172 1173
     *
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1174 1175 1176 1177 1178 1179
     */
    public function SubselectFromClause()
    {
        $this->match(Lexer::T_FROM);
        $identificationVariables = array();
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
guilhermeblanco's avatar
guilhermeblanco committed
1180

1181 1182
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
1183
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1184 1185
        }

1186
        return new AST\SubselectFromClause($identificationVariables);
1187 1188 1189
    }

    /**
1190
     * WhereClause ::= "WHERE" ConditionalExpression
1191 1192
     *
     * @return \Doctrine\ORM\Query\AST\WhereClause
1193
     */
1194
    public function WhereClause()
1195
    {
1196 1197 1198 1199 1200 1201 1202
        $this->match(Lexer::T_WHERE);

        return new AST\WhereClause($this->ConditionalExpression());
    }

    /**
     * HavingClause ::= "HAVING" ConditionalExpression
1203 1204
     *
     * @return \Doctrine\ORM\Query\AST\HavingClause
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
     */
    public function HavingClause()
    {
        $this->match(Lexer::T_HAVING);

        return new AST\HavingClause($this->ConditionalExpression());
    }

    /**
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1215 1216
     *
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1217 1218 1219 1220 1221 1222 1223
     */
    public function GroupByClause()
    {
        $this->match(Lexer::T_GROUP);
        $this->match(Lexer::T_BY);

        $groupByItems = array($this->GroupByItem());
1224

1225 1226
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
1227
            $groupByItems[] = $this->GroupByItem();
1228 1229
        }

1230 1231
        return new AST\GroupByClause($groupByItems);
    }
1232

1233 1234
    /**
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1235 1236
     *
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1237 1238 1239 1240 1241 1242 1243 1244 1245
     */
    public function OrderByClause()
    {
        $this->match(Lexer::T_ORDER);
        $this->match(Lexer::T_BY);

        $orderByItems = array();
        $orderByItems[] = $this->OrderByItem();

1246 1247
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
1248 1249 1250 1251
            $orderByItems[] = $this->OrderByItem();
        }

        return new AST\OrderByClause($orderByItems);
1252 1253 1254
    }

    /**
1255
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1256 1257
     *
     * @return \Doctrine\ORM\Query\AST\Subselect
1258
     */
1259 1260
    public function Subselect()
    {
1261 1262
        // Increase query nesting level
        $this->_nestingLevel++;
1263

1264
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1265 1266

        $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
1267
            ? $this->WhereClause() : null;
1268 1269

        $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP)
1270
            ? $this->GroupByClause() : null;
1271 1272

        $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING)
1273
            ? $this->HavingClause() : null;
1274 1275

        $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
1276
            ? $this->OrderByClause() : null;
1277

1278 1279
        // Decrease query nesting level
        $this->_nestingLevel--;
1280 1281 1282 1283 1284

        return $subselect;
    }

    /**
1285
     * UpdateItem ::= IdentificationVariable "." {StateField | SingleValuedAssociationField} "=" NewValue
1286 1287
     *
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1288 1289
     */
    public function UpdateItem()
1290
    {
1291
        $token = $this->_lexer->lookahead;
1292

1293
        $identVariable = $this->IdentificationVariable();
1294
        $this->match(Lexer::T_DOT);
1295 1296
        $this->match(Lexer::T_IDENTIFIER);
        $field = $this->_lexer->token['value'];
1297

1298
        // Check if field exists
1299
        $class = $this->_queryComponents[$identVariable]['metadata'];
1300

1301 1302 1303 1304 1305
        if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
            $this->semanticalError(
                'Class ' . $class->name . ' has no field named ' . $field, $token
            );
        }
1306

1307
        $this->match(Lexer::T_EQUALS);
1308

1309
        $newValue = $this->NewValue();
guilhermeblanco's avatar
guilhermeblanco committed
1310

1311
        $updateItem = new AST\UpdateItem($field, $newValue);
1312
        $updateItem->identificationVariable = $identVariable;
1313 1314 1315 1316 1317 1318

        return $updateItem;
    }

    /**
     * GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
1319 1320
     *
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1321 1322 1323
     */
    public function GroupByItem()
    {
1324 1325
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
        $glimpse = $this->_lexer->glimpse();
1326

1327
        if ($glimpse['value'] != '.') {
1328
            $token = $this->_lexer->lookahead;
1329
            $identVariable = $this->IdentificationVariable();
1330

1331
            return $identVariable;
1332
        }
1333

1334 1335 1336 1337 1338 1339
        return $this->SingleValuedPathExpression();
    }

    /**
     * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
     *
1340
     * @todo Post 2.0 release. Support general SingleValuedPathExpression instead
1341
     * of only StateFieldPathExpression.
1342 1343
     *
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1344 1345 1346
     */
    public function OrderByItem()
    {
1347
        $type = 'ASC';
1348

1349 1350
        // We need to check if we are in a ResultVariable or StateFieldPathExpression
        $glimpse = $this->_lexer->glimpse();
1351

1352
        if ($glimpse['value'] != '.') {
1353
            $token = $this->_lexer->lookahead;
1354
            $expr = $this->ResultVariable();
1355
        } else {
1356 1357
            $expr = $this->StateFieldPathExpression();
        }
1358

1359
        $item = new AST\OrderByItem($expr);
1360

1361 1362
        if ($this->_lexer->isNextToken(Lexer::T_ASC)) {
            $this->match(Lexer::T_ASC);
1363
        } else if ($this->_lexer->isNextToken(Lexer::T_DESC)) {
1364
            $this->match(Lexer::T_DESC);
1365
            $type = 'DESC';
1366
        }
1367

1368
        $item->type = $type;
1369 1370
        return $item;
    }
guilhermeblanco's avatar
guilhermeblanco committed
1371

1372 1373 1374 1375 1376 1377
    /**
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
     *      EnumPrimary | SimpleEntityExpression | "NULL"
     *
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
     * grammar that needs to be supported:
1378
     *
1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391
     * NewValue ::= SimpleArithmeticExpression | "NULL"
     *
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimplEntityExpression
     */
    public function NewValue()
    {
        if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
            $this->match(Lexer::T_NULL);
            return null;
        } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
            $this->match(Lexer::T_INPUT_PARAMETER);
            return new AST\InputParameter($this->_lexer->token['value']);
        }
1392

1393
        return $this->SimpleArithmeticExpression();
1394 1395 1396 1397
    }

    /**
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
1398 1399
     *
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1400
     */
romanb's avatar
romanb committed
1401
    public function IdentificationVariableDeclaration()
1402
    {
romanb's avatar
romanb committed
1403 1404
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
        $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1405
        $joinVariableDeclarations = array();
guilhermeblanco's avatar
guilhermeblanco committed
1406

1407
        while (
guilhermeblanco's avatar
guilhermeblanco committed
1408 1409 1410
            $this->_lexer->isNextToken(Lexer::T_LEFT) ||
            $this->_lexer->isNextToken(Lexer::T_INNER) ||
            $this->_lexer->isNextToken(Lexer::T_JOIN)
1411
        ) {
romanb's avatar
romanb committed
1412
            $joinVariableDeclarations[] = $this->JoinVariableDeclaration();
1413 1414 1415
        }

        return new AST\IdentificationVariableDeclaration(
guilhermeblanco's avatar
guilhermeblanco committed
1416
            $rangeVariableDeclaration, $indexBy, $joinVariableDeclarations
1417 1418 1419
        );
    }

1420 1421
    /**
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1422 1423 1424
     *
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1425 1426 1427 1428 1429 1430
     */
    public function SubselectIdentificationVariableDeclaration()
    {
        $peek = $this->_lexer->glimpse();

        if ($peek['value'] == '.') {
1431 1432
            $subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration();
            $subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression();
1433
            $this->match(Lexer::T_AS);
1434
            $subselectIdVarDecl->aliasIdentificationVariable = $this->AliasIdentificationVariable();
1435

1436
            return $subselectIdVarDecl;
1437 1438 1439 1440 1441 1442 1443
        }

        return $this->IdentificationVariableDeclaration();
    }

    /**
     * JoinVariableDeclaration ::= Join [IndexBy]
1444 1445
     *
     * @return \Doctrine\ORM\Query\AST\JoinVariableDeclaration
1446 1447 1448 1449 1450
     */
    public function JoinVariableDeclaration()
    {
        $join = $this->Join();
        $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
1451
                ? $this->IndexBy() : null;
1452 1453 1454 1455

        return new AST\JoinVariableDeclaration($join, $indexBy);
    }

1456 1457
    /**
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1458
     *
1459
     * @return Doctrine\ORM\Query\AST\RangeVariableDeclaration
1460
     */
romanb's avatar
romanb committed
1461
    public function RangeVariableDeclaration()
1462
    {
romanb's avatar
romanb committed
1463
        $abstractSchemaName = $this->AbstractSchemaName();
1464

1465 1466 1467
        if ($this->_lexer->isNextToken(Lexer::T_AS)) {
            $this->match(Lexer::T_AS);
        }
guilhermeblanco's avatar
guilhermeblanco committed
1468

1469
        $token = $this->_lexer->lookahead;
romanb's avatar
romanb committed
1470
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1471 1472 1473 1474
        $classMetadata = $this->_em->getClassMetadata($abstractSchemaName);

        // Building queryComponent
        $queryComponent = array(
1475 1476 1477 1478 1479
            'metadata'     => $classMetadata,
            'parent'       => null,
            'relation'     => null,
            'map'          => null,
            'nestingLevel' => $this->_nestingLevel,
1480
            'token'        => $token
1481 1482
        );
        $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
guilhermeblanco's avatar
guilhermeblanco committed
1483

1484
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1485 1486
    }

1487 1488 1489
    /**
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1490
     *
1491 1492 1493 1494
     * @return array
     */
    public function PartialObjectExpression()
    {
1495
        $this->match(Lexer::T_PARTIAL);
1496 1497 1498 1499 1500

        $partialFieldSet = array();

        $identificationVariable = $this->IdentificationVariable();
        $this->match(Lexer::T_DOT);
1501

1502 1503 1504 1505 1506 1507 1508 1509 1510
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
        $this->match(Lexer::T_IDENTIFIER);
        $partialFieldSet[] = $this->_lexer->token['value'];
        while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
            $this->match(Lexer::T_COMMA);
            $this->match(Lexer::T_IDENTIFIER);
            $partialFieldSet[] = $this->_lexer->token['value'];
        }
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1511

1512
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1513

1514 1515 1516 1517 1518 1519
        // Defer PartialObjectExpression validation
        $this->_deferredPartialObjectExpressions[] = array(
            'expression'   => $partialObjectExpression,
            'nestingLevel' => $this->_nestingLevel,
            'token'        => $this->_lexer->token,
        );
1520

1521 1522 1523
        return $partialObjectExpression;
    }

1524
    /**
1525
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
1526
     *          ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
1527
     *
1528
     * @return Doctrine\ORM\Query\AST\Join
1529
     */
romanb's avatar
romanb committed
1530
    public function Join()
1531 1532 1533
    {
        // Check Join type
        $joinType = AST\Join::JOIN_TYPE_INNER;
guilhermeblanco's avatar
guilhermeblanco committed
1534

1535 1536
        if ($this->_lexer->isNextToken(Lexer::T_LEFT)) {
            $this->match(Lexer::T_LEFT);
guilhermeblanco's avatar
guilhermeblanco committed
1537

1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549
            // Possible LEFT OUTER join
            if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
                $this->match(Lexer::T_OUTER);
                $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
            } else {
                $joinType = AST\Join::JOIN_TYPE_LEFT;
            }
        } else if ($this->_lexer->isNextToken(Lexer::T_INNER)) {
            $this->match(Lexer::T_INNER);
        }

        $this->match(Lexer::T_JOIN);
1550

1551
        $joinPathExpression = $this->JoinAssociationPathExpression();
guilhermeblanco's avatar
guilhermeblanco committed
1552

1553 1554 1555 1556
        if ($this->_lexer->isNextToken(Lexer::T_AS)) {
            $this->match(Lexer::T_AS);
        }

1557
        $token = $this->_lexer->lookahead;
romanb's avatar
romanb committed
1558
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1559 1560

        // Verify that the association exists.
1561 1562
        $parentClass = $this->_queryComponents[$joinPathExpression->identificationVariable]['metadata'];
        $assocField = $joinPathExpression->associationField;
guilhermeblanco's avatar
guilhermeblanco committed
1563

1564
        if ( ! $parentClass->hasAssociation($assocField)) {
guilhermeblanco's avatar
guilhermeblanco committed
1565 1566 1567
            $this->semanticalError(
                "Class " . $parentClass->name . " has no association named '$assocField'."
            );
1568
        }
guilhermeblanco's avatar
guilhermeblanco committed
1569

1570
        $targetClassName = $parentClass->getAssociationMapping($assocField)->targetEntityName;
1571 1572 1573

        // Building queryComponent
        $joinQueryComponent = array(
1574
            'metadata'     => $this->_em->getClassMetadata($targetClassName),
1575
            'parent'       => $joinPathExpression->identificationVariable,
1576 1577 1578
            'relation'     => $parentClass->getAssociationMapping($assocField),
            'map'          => null,
            'nestingLevel' => $this->_nestingLevel,
1579
            'token'        => $token
1580 1581 1582 1583 1584 1585 1586
        );
        $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;

        // Create AST node
        $join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);

        // Check for ad-hoc Join conditions
1587 1588
        if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
            $this->match(Lexer::T_WITH);
1589
            $join->conditionalExpression = $this->ConditionalExpression();
1590 1591 1592 1593 1594 1595 1596
        }

        return $join;
    }

    /**
     * IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
1597
     *
1598
     * @return Doctrine\ORM\Query\AST\IndexBy
1599
     */
romanb's avatar
romanb committed
1600
    public function IndexBy()
1601 1602 1603
    {
        $this->match(Lexer::T_INDEX);
        $this->match(Lexer::T_BY);
romanb's avatar
romanb committed
1604
        $pathExp = $this->SimpleStateFieldPathExpression();
guilhermeblanco's avatar
guilhermeblanco committed
1605

1606
        // Add the INDEX BY info to the query component
1607 1608
        $parts = $pathExp->parts;
        $this->_queryComponents[$pathExp->identificationVariable]['map'] = $parts[0];
guilhermeblanco's avatar
guilhermeblanco committed
1609

1610 1611 1612
        return $pathExp;
    }

1613 1614 1615 1616
    /**
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
     *                      EntityTypeExpression
1617
     *
1618 1619 1620 1621 1622 1623 1624 1625 1626 1627
     * @return mixed One of the possible expressions or subexpressions.
     */
    public function ScalarExpression()
    {
        $lookahead = $this->_lexer->lookahead['type'];
        if ($lookahead === Lexer::T_IDENTIFIER) {
            $this->_lexer->peek(); // lookahead => '.'
            $this->_lexer->peek(); // lookahead => token after '.'
            $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
            $this->_lexer->resetPeek();
1628

1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660
            if ($peek['value'] == '+' || $peek['value'] == '-' || $peek['value'] == '/' || $peek['value'] == '*') {
                return $this->SimpleArithmeticExpression();
            }

            return $this->StateFieldPathExpression();
        } else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
            return $this->SimpleArithmeticExpression();
        } else if ($this->_isFunction()) {
            return $this->FunctionDeclaration();
        } else if ($lookahead == Lexer::T_STRING) {
            return $this->StringPrimary();
        } else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
            return $this->InputParameter();
        } else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
            $this->match($lookahead);
            return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
        } else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
            return $this->CaseExpression();
        } else {
            $this->syntaxError();
        }
    }

    public function CaseExpression()
    {
        // if "CASE" "WHEN" => GeneralCaseExpression
        // else if "CASE" => SimpleCaseExpression
        // else if "COALESCE" => CoalesceExpression
        // else if "NULLIF" => NullifExpression
        $this->semanticalError('CaseExpression not yet supported.');
    }

1661
    /**
1662 1663
     * SelectExpression ::=
     *      IdentificationVariable | StateFieldPathExpression |
1664
     *      (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
1665
     *
1666
     * @return Doctrine\ORM\Query\AST\SelectExpression
1667
     */
1668
    public function SelectExpression()
1669
    {
1670 1671 1672
        $expression = null;
        $fieldAliasIdentificationVariable = null;
        $peek = $this->_lexer->glimpse();
guilhermeblanco's avatar
guilhermeblanco committed
1673

1674 1675 1676 1677 1678
        $supportsAlias = true;
        if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
            if ($peek['value'] == '.') {
                // ScalarExpression
                $expression = $this->ScalarExpression();
1679
            } else {
1680 1681 1682 1683 1684 1685
                $supportsAlias = false;
                $expression = $this->IdentificationVariable();
            }
        } else if ($this->_lexer->lookahead['value'] == '(') {
            if ($peek['type'] == Lexer::T_SELECT) {
                // Subselect
1686
                $this->match(Lexer::T_OPEN_PARENTHESIS);
1687
                $expression = $this->Subselect();
1688
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
1689 1690 1691
            } else {
                // Shortcut: ScalarExpression => SimpleArithmeticExpression
                $expression = $this->SimpleArithmeticExpression();
1692
            }
1693 1694 1695 1696
        } else if ($this->_isFunction()) {
            if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
                $expression = $this->AggregateExpression();
            } else {
1697
                // Shortcut: ScalarExpression => Function
1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711
                $expression = $this->FunctionDeclaration();
            }
        } else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
            $supportsAlias = false;
            $expression = $this->PartialObjectExpression();
        } else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
                $this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
            $expression = $this->SimpleArithmeticExpression();
        } else {
            $this->syntaxError('IdentificationVariable | StateFieldPathExpression'
                    . ' | AggregateExpression | "(" Subselect ")" | ScalarExpression',
                    $this->_lexer->lookahead);
        }
1712

1713
        if ($supportsAlias) {
1714 1715 1716
            if ($this->_lexer->isNextToken(Lexer::T_AS)) {
                $this->match(Lexer::T_AS);
            }
guilhermeblanco's avatar
guilhermeblanco committed
1717

1718
            if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
1719
                $token = $this->_lexer->lookahead;
1720
                $fieldAliasIdentificationVariable = $this->AliasResultVariable();
1721

1722
                // Include AliasResultVariable in query components.
1723
                $this->_queryComponents[$fieldAliasIdentificationVariable] = array(
1724
                    'resultVariable' => $expression,
1725
                    'nestingLevel'   => $this->_nestingLevel,
1726
                    'token'          => $token,
1727
                );
1728
            }
1729
        }
guilhermeblanco's avatar
guilhermeblanco committed
1730

1731
        return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
1732 1733 1734
    }

    /**
1735
     * SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] AliasResultVariable])
1736 1737
     *
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
1738
     */
1739
    public function SimpleSelectExpression()
1740
    {
1741 1742 1743 1744 1745 1746 1747 1748
        if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
            // SingleValuedPathExpression | IdentificationVariable
            $peek = $this->_lexer->glimpse();

            if ($peek['value'] == '.') {
                return new AST\SimpleSelectExpression($this->StateFieldPathExpression());
            }

1749
            $this->match(Lexer::T_IDENTIFIER);
1750 1751

            return new AST\SimpleSelectExpression($this->_lexer->token['value']);
1752
        }
1753

1754
        $expr = new AST\SimpleSelectExpression($this->AggregateExpression());
guilhermeblanco's avatar
guilhermeblanco committed
1755

1756 1757 1758
        if ($this->_lexer->isNextToken(Lexer::T_AS)) {
            $this->match(Lexer::T_AS);
        }
1759

1760
        if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
1761
            $token = $this->_lexer->lookahead;
1762
            $resultVariable = $this->AliasResultVariable();
1763
            $expr->fieldIdentificationVariable = $resultVariable;
1764

1765
            // Include AliasResultVariable in query components.
1766
            $this->_queryComponents[$resultVariable] = array(
1767 1768
                'resultvariable' => $expr,
                'nestingLevel'   => $this->_nestingLevel,
1769
                'token'          => $token,
1770
            );
1771
        }
1772 1773

        return $expr;
1774 1775 1776 1777
    }

    /**
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
1778 1779
     *
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
1780
     */
romanb's avatar
romanb committed
1781
    public function ConditionalExpression()
1782 1783
    {
        $conditionalTerms = array();
romanb's avatar
romanb committed
1784
        $conditionalTerms[] = $this->ConditionalTerm();
guilhermeblanco's avatar
guilhermeblanco committed
1785

1786 1787
        while ($this->_lexer->isNextToken(Lexer::T_OR)) {
            $this->match(Lexer::T_OR);
romanb's avatar
romanb committed
1788
            $conditionalTerms[] = $this->ConditionalTerm();
1789
        }
guilhermeblanco's avatar
guilhermeblanco committed
1790

1791 1792 1793 1794 1795
        return new AST\ConditionalExpression($conditionalTerms);
    }

    /**
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
1796 1797
     *
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
1798
     */
romanb's avatar
romanb committed
1799
    public function ConditionalTerm()
1800 1801
    {
        $conditionalFactors = array();
romanb's avatar
romanb committed
1802
        $conditionalFactors[] = $this->ConditionalFactor();
guilhermeblanco's avatar
guilhermeblanco committed
1803

1804 1805
        while ($this->_lexer->isNextToken(Lexer::T_AND)) {
            $this->match(Lexer::T_AND);
romanb's avatar
romanb committed
1806
            $conditionalFactors[] = $this->ConditionalFactor();
1807
        }
guilhermeblanco's avatar
guilhermeblanco committed
1808

1809 1810 1811 1812 1813
        return new AST\ConditionalTerm($conditionalFactors);
    }

    /**
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
1814 1815
     *
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
1816
     */
romanb's avatar
romanb committed
1817
    public function ConditionalFactor()
1818 1819
    {
        $not = false;
guilhermeblanco's avatar
guilhermeblanco committed
1820

1821 1822 1823 1824
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $this->match(Lexer::T_NOT);
            $not = true;
        }
guilhermeblanco's avatar
guilhermeblanco committed
1825

1826 1827
        $condFactor = new AST\ConditionalFactor($this->ConditionalPrimary());
        $condFactor->not = $not;
1828

1829
        return $condFactor;
1830 1831 1832 1833
    }

    /**
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
1834
     *
1835
     * @return Doctrine\ORM\Query\AST\ConditionalPrimary
1836
     */
romanb's avatar
romanb committed
1837
    public function ConditionalPrimary()
1838 1839
    {
        $condPrimary = new AST\ConditionalPrimary;
1840

1841
        if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
1842
            // Peek beyond the matching closing paranthesis ')'
romanb's avatar
romanb committed
1843
            $peek = $this->_peekBeyondClosingParenthesis();
1844

1845 1846 1847 1848 1849 1850 1851
            if (in_array($peek['value'], array("=",  "<", "<=", "<>", ">", ">=", "!=")) ||
                    $peek['type'] === Lexer::T_NOT ||
                    $peek['type'] === Lexer::T_BETWEEN ||
                    $peek['type'] === Lexer::T_LIKE ||
                    $peek['type'] === Lexer::T_IN ||
                    $peek['type'] === Lexer::T_IS ||
                    $peek['type'] === Lexer::T_EXISTS) {
1852
                $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
1853
            } else {
1854
                $this->match(Lexer::T_OPEN_PARENTHESIS);
romanb's avatar
romanb committed
1855
                $condPrimary->conditionalExpression = $this->ConditionalExpression();
1856
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
1857 1858
            }
        } else {
1859
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
1860
        }
1861

1862 1863 1864 1865 1866 1867 1868 1869 1870
        return $condPrimary;
    }

    /**
     * SimpleConditionalExpression ::=
     *      ComparisonExpression | BetweenExpression | LikeExpression |
     *      InExpression | NullComparisonExpression | ExistsExpression |
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression
     */
romanb's avatar
romanb committed
1871
    public function SimpleConditionalExpression()
1872 1873 1874 1875 1876 1877
    {
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $token = $this->_lexer->glimpse();
        } else {
            $token = $this->_lexer->lookahead;
        }
guilhermeblanco's avatar
guilhermeblanco committed
1878

1879
        if ($token['type'] === Lexer::T_EXISTS) {
romanb's avatar
romanb committed
1880
            return $this->ExistsExpression();
1881 1882
        }

romanb's avatar
romanb committed
1883
        $peek = $this->_lexer->glimpse();
1884

romanb's avatar
romanb committed
1885
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) {
romanb's avatar
romanb committed
1886 1887
            if ($peek['value'] == '(') {
                // Peek beyond the matching closing paranthesis ')'
1888
                $this->_lexer->peek();
romanb's avatar
romanb committed
1889 1890 1891
                $token = $this->_peekBeyondClosingParenthesis();
            } else {
                // Peek beyond the PathExpression (or InputParameter)
1892 1893
                $peek = $this->_lexer->peek();

romanb's avatar
romanb committed
1894 1895 1896 1897
                while ($peek['value'] === '.') {
                    $this->_lexer->peek();
                    $peek = $this->_lexer->peek();
                }
guilhermeblanco's avatar
guilhermeblanco committed
1898

romanb's avatar
romanb committed
1899 1900 1901 1902
                // Also peek beyond a NOT if there is one
                if ($peek['type'] === Lexer::T_NOT) {
                    $peek = $this->_lexer->peek();
                }
guilhermeblanco's avatar
guilhermeblanco committed
1903

romanb's avatar
romanb committed
1904
                $token = $peek;
guilhermeblanco's avatar
guilhermeblanco committed
1905

romanb's avatar
romanb committed
1906 1907
                // We need to go even further in case of IS (differenciate between NULL and EMPTY)
                $lookahead = $this->_lexer->peek();
guilhermeblanco's avatar
guilhermeblanco committed
1908

romanb's avatar
romanb committed
1909 1910 1911 1912
                // Also peek beyond a NOT if there is one
                if ($lookahead['type'] === Lexer::T_NOT) {
                    $lookahead = $this->_lexer->peek();
                }
guilhermeblanco's avatar
guilhermeblanco committed
1913

romanb's avatar
romanb committed
1914 1915 1916
                $this->_lexer->resetPeek();
            }
        }
guilhermeblanco's avatar
guilhermeblanco committed
1917

romanb's avatar
romanb committed
1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935
        switch ($token['type']) {
            case Lexer::T_BETWEEN:
                return $this->BetweenExpression();
            case Lexer::T_LIKE:
                return $this->LikeExpression();
            case Lexer::T_IN:
                return $this->InExpression();
            case Lexer::T_IS:
                if ($lookahead['type'] == Lexer::T_NULL) {
                    return $this->NullComparisonExpression();
                }
                return $this->EmptyCollectionComparisonExpression();
            case Lexer::T_MEMBER:
                return $this->CollectionMemberExpression();
            default:
                return $this->ComparisonExpression();
        }
    }
1936

romanb's avatar
romanb committed
1937 1938 1939 1940 1941 1942 1943 1944 1945
    private function _peekBeyondClosingParenthesis()
    {
        $numUnmatched = 1;
        $token = $this->_lexer->peek();
        while ($numUnmatched > 0 && $token !== null) {
            if ($token['value'] == ')') {
                --$numUnmatched;
            } else if ($token['value'] == '(') {
                ++$numUnmatched;
1946
            }
romanb's avatar
romanb committed
1947
            $token = $this->_lexer->peek();
1948
        }
romanb's avatar
romanb committed
1949
        $this->_lexer->resetPeek();
1950

romanb's avatar
romanb committed
1951
        return $token;
1952
    }
1953

1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967
    /**
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
     *
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
     */
    public function EmptyCollectionComparisonExpression()
    {
        $emptyColletionCompExpr = new AST\EmptyCollectionComparisonExpression(
            $this->CollectionValuedPathExpression()
        );
        $this->match(Lexer::T_IS);

        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $this->match(Lexer::T_NOT);
1968
            $emptyColletionCompExpr->not = true;
1969 1970 1971 1972 1973 1974
        }

        $this->match(Lexer::T_EMPTY);

        return $emptyColletionCompExpr;
    }
1975

1976
    /**
romanb's avatar
romanb committed
1977
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
1978
     *
romanb's avatar
romanb committed
1979 1980
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
1981
     *
1982
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
1983
     */
romanb's avatar
romanb committed
1984
    public function CollectionMemberExpression()
1985
    {
1986
        $not = false;
guilhermeblanco's avatar
guilhermeblanco committed
1987

1988
        $entityExpr = $this->EntityExpression();
guilhermeblanco's avatar
guilhermeblanco committed
1989

1990
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
1991
            $not = true;
1992 1993
            $this->match(Lexer::T_NOT);
        }
guilhermeblanco's avatar
guilhermeblanco committed
1994

1995
        $this->match(Lexer::T_MEMBER);
guilhermeblanco's avatar
guilhermeblanco committed
1996

1997 1998
        if ($this->_lexer->isNextToken(Lexer::T_OF)) {
            $this->match(Lexer::T_OF);
romanb's avatar
romanb committed
1999
        }
2000

2001 2002
        $collMemberExpr = new AST\CollectionMemberExpression(
            $entityExpr, $this->CollectionValuedPathExpression()
2003
        );
2004
        $collMemberExpr->not = $not;
2005

2006
        return $collMemberExpr;
romanb's avatar
romanb committed
2007 2008 2009
    }

    /**
2010
     * Literal ::= string | char | integer | float | boolean
romanb's avatar
romanb committed
2011
     *
2012
     * @return string
romanb's avatar
romanb committed
2013
     */
2014
    public function Literal()
romanb's avatar
romanb committed
2015
    {
2016 2017
        switch ($this->_lexer->lookahead['type']) {
            case Lexer::T_STRING:
2018
                $this->match(Lexer::T_STRING);
romanb's avatar
romanb committed
2019
                return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']);
2020

2021 2022
            case Lexer::T_INTEGER:
            case Lexer::T_FLOAT:
2023 2024 2025
                $this->match(
                    $this->_lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
                );
romanb's avatar
romanb committed
2026
                return new AST\Literal(AST\Literal::NUMERIC, $this->_lexer->token['value']);
2027

romanb's avatar
romanb committed
2028 2029
            case Lexer::T_TRUE:
            case Lexer::T_FALSE:
2030 2031 2032
                $this->match(
                    $this->_lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
                );
romanb's avatar
romanb committed
2033
                return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
2034

2035 2036
            default:
                $this->syntaxError('Literal');
2037 2038
        }
    }
2039

2040 2041 2042 2043 2044 2045 2046 2047 2048 2049
    /**
     * InParameter ::= Literal | InputParameter
     *
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
     */
    public function InParameter()
    {
        if ($this->_lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
            return $this->InputParameter();
        }
2050

2051 2052
        return $this->Literal();
    }
2053

2054 2055 2056 2057 2058 2059 2060
    /**
     * InputParameter ::= PositionalParameter | NamedParameter
     *
     * @return \Doctrine\ORM\Query\AST\InputParameter
     */
    public function InputParameter()
    {
2061
        $this->match(Lexer::T_INPUT_PARAMETER);
2062

2063 2064
        return new AST\InputParameter($this->_lexer->token['value']);
    }
2065

2066 2067
    /**
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2068 2069
     *
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2070
     */
romanb's avatar
romanb committed
2071
    public function ArithmeticExpression()
2072 2073
    {
        $expr = new AST\ArithmeticExpression;
guilhermeblanco's avatar
guilhermeblanco committed
2074

2075
        if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2076
            $peek = $this->_lexer->glimpse();
guilhermeblanco's avatar
guilhermeblanco committed
2077

2078
            if ($peek['type'] === Lexer::T_SELECT) {
2079
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2080
                $expr->subselect = $this->Subselect();
2081
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
guilhermeblanco's avatar
guilhermeblanco committed
2082

2083 2084 2085
                return $expr;
            }
        }
guilhermeblanco's avatar
guilhermeblanco committed
2086

2087
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
guilhermeblanco's avatar
guilhermeblanco committed
2088

2089 2090 2091 2092 2093
        return $expr;
    }

    /**
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2094 2095
     *
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2096
     */
romanb's avatar
romanb committed
2097
    public function SimpleArithmeticExpression()
2098 2099
    {
        $terms = array();
romanb's avatar
romanb committed
2100
        $terms[] = $this->ArithmeticTerm();
guilhermeblanco's avatar
guilhermeblanco committed
2101

2102 2103
        while (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) {
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
guilhermeblanco's avatar
guilhermeblanco committed
2104

2105
            $terms[] = $this->_lexer->token['value'];
romanb's avatar
romanb committed
2106
            $terms[] = $this->ArithmeticTerm();
2107
        }
2108

2109 2110 2111 2112 2113
        return new AST\SimpleArithmeticExpression($terms);
    }

    /**
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2114 2115
     *
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2116
     */
romanb's avatar
romanb committed
2117
    public function ArithmeticTerm()
2118 2119
    {
        $factors = array();
romanb's avatar
romanb committed
2120
        $factors[] = $this->ArithmeticFactor();
guilhermeblanco's avatar
guilhermeblanco committed
2121

2122 2123
        while (($isMult = $this->_lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->_lexer->isNextToken(Lexer::T_DIVIDE)) {
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2124

2125
            $factors[] = $this->_lexer->token['value'];
romanb's avatar
romanb committed
2126
            $factors[] = $this->ArithmeticFactor();
2127
        }
guilhermeblanco's avatar
guilhermeblanco committed
2128

2129 2130 2131 2132 2133
        return new AST\ArithmeticTerm($factors);
    }

    /**
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2134 2135
     *
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2136
     */
romanb's avatar
romanb committed
2137
    public function ArithmeticFactor()
2138
    {
2139
       $sign = null;
guilhermeblanco's avatar
guilhermeblanco committed
2140

2141 2142 2143 2144
       if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) {
           $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
           $sign = $isPlus;
       }
2145

2146
        return new AST\ArithmeticFactor($this->ArithmeticPrimary(), $sign);
2147 2148 2149
    }

    /**
2150 2151
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2152
     *          | FunctionsReturningDatetime | IdentificationVariable
2153
     */
2154
    public function ArithmeticPrimary()
2155
    {
2156 2157
        if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2158
            $expr = $this->SimpleArithmeticExpression();
2159

2160
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
guilhermeblanco's avatar
guilhermeblanco committed
2161

2162
            return $expr;
2163
        }
guilhermeblanco's avatar
guilhermeblanco committed
2164

2165 2166 2167
        switch ($this->_lexer->lookahead['type']) {
            case Lexer::T_IDENTIFIER:
                $peek = $this->_lexer->glimpse();
2168

2169 2170 2171
                if ($peek['value'] == '(') {
                    return $this->FunctionDeclaration();
                }
2172

2173 2174 2175
                if ($peek['value'] == '.') {
                    return $this->SingleValuedPathExpression();
                }
guilhermeblanco's avatar
guilhermeblanco committed
2176

2177
                return $this->PathExpression(AST\PathExpression::TYPE_IDENTIFICATION_VARIABLE);
guilhermeblanco's avatar
guilhermeblanco committed
2178

2179
            case Lexer::T_INPUT_PARAMETER:
2180
                return $this->InputParameter();
2181

2182 2183
            default:
                $peek = $this->_lexer->glimpse();
guilhermeblanco's avatar
guilhermeblanco committed
2184

2185 2186 2187 2188
                if ($peek['value'] == '(') {
                    if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
                        return $this->AggregateExpression();
                    }
romanb's avatar
romanb committed
2189

2190
                    return $this->FunctionDeclaration();
romanb's avatar
romanb committed
2191 2192
                } else {
                    return $this->Literal();
2193 2194
                }
        }
2195
    }
2196

2197
    /**
2198
     * StringExpression ::= StringPrimary | "(" Subselect ")"
2199 2200 2201
     *
     * @return \Doctrine\ORM\Query\AST\StringPrimary |
     *         \Doctrine]ORM\Query\AST\Subselect
2202
     */
2203
    public function StringExpression()
2204
    {
2205
        if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2206
            $peek = $this->_lexer->glimpse();
2207

2208
            if ($peek['type'] === Lexer::T_SELECT) {
2209
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2210
                $expr = $this->Subselect();
2211
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2212

2213 2214 2215
                return $expr;
            }
        }
2216

2217
        return $this->StringPrimary();
2218 2219 2220
    }

    /**
2221
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
2222
     */
2223
    public function StringPrimary()
2224
    {
2225
        if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2226
            $peek = $this->_lexer->glimpse();
guilhermeblanco's avatar
guilhermeblanco committed
2227

2228 2229 2230 2231 2232 2233 2234
            if ($peek['value'] == '.') {
                return $this->StateFieldPathExpression();
            } else if ($peek['value'] == '(') {
                return $this->FunctionsReturningStrings();
            } else {
                $this->syntaxError("'.' or '('");
            }
2235
        } else if ($this->_lexer->isNextToken(Lexer::T_STRING)) {
2236
            $this->match(Lexer::T_STRING);
guilhermeblanco's avatar
guilhermeblanco committed
2237

2238
            return $this->_lexer->token['value'];
2239
        } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2240
            return $this->InputParameter();
2241 2242 2243 2244 2245
        } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
            return $this->AggregateExpression();
        }

        $this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
2246 2247 2248
    }

    /**
2249
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2250 2251 2252
     *
     * @return \Doctrine\ORM\Query\AST\SingleValuedAssociationPathExpression |
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2253
     */
2254
    public function EntityExpression()
2255
    {
2256
        $glimpse = $this->_lexer->glimpse();
2257

2258 2259
        if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
            return $this->SingleValuedAssociationPathExpression();
2260
        }
2261

2262
        return $this->SimpleEntityExpression();
2263
    }
2264

2265
    /**
2266
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2267 2268
     *
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2269
     */
2270
    public function SimpleEntityExpression()
2271
    {
2272
        if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2273
            return $this->InputParameter();
2274
        }
2275

2276
        return $this->IdentificationVariable();
2277 2278 2279
    }

    /**
2280 2281 2282
     * AggregateExpression ::=
     *  ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
     *  "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
2283 2284
     *
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2285
     */
2286
    public function AggregateExpression()
2287
    {
2288 2289
        $isDistinct = false;
        $functionName = '';
guilhermeblanco's avatar
guilhermeblanco committed
2290

2291 2292 2293
        if ($this->_lexer->isNextToken(Lexer::T_COUNT)) {
            $this->match(Lexer::T_COUNT);
            $functionName = $this->_lexer->token['value'];
2294
            $this->match(Lexer::T_OPEN_PARENTHESIS);
guilhermeblanco's avatar
guilhermeblanco committed
2295

2296 2297 2298
            if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
                $this->match(Lexer::T_DISTINCT);
                $isDistinct = true;
2299
            }
guilhermeblanco's avatar
guilhermeblanco committed
2300

2301
            $pathExp = $this->SingleValuedPathExpression();
2302
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313
        } else {
            if ($this->_lexer->isNextToken(Lexer::T_AVG)) {
                $this->match(Lexer::T_AVG);
            } else if ($this->_lexer->isNextToken(Lexer::T_MAX)) {
                $this->match(Lexer::T_MAX);
            } else if ($this->_lexer->isNextToken(Lexer::T_MIN)) {
                $this->match(Lexer::T_MIN);
            } else if ($this->_lexer->isNextToken(Lexer::T_SUM)) {
                $this->match(Lexer::T_SUM);
            } else {
                $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2314
            }
guilhermeblanco's avatar
guilhermeblanco committed
2315

2316
            $functionName = $this->_lexer->token['value'];
2317
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2318
            $pathExp = $this->StateFieldPathExpression();
2319
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2320
        }
2321 2322

        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2323 2324 2325
    }

    /**
2326
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2327 2328
     *
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2329
     */
2330
    public function QuantifiedExpression()
2331
    {
2332
        $type = '';
guilhermeblanco's avatar
guilhermeblanco committed
2333

2334 2335
        if ($this->_lexer->isNextToken(Lexer::T_ALL)) {
            $this->match(Lexer::T_ALL);
2336
            $type = 'ALL';
2337 2338
        } else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
            $this->match(Lexer::T_ANY);
2339
             $type = 'ANY';
2340 2341
        } else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
            $this->match(Lexer::T_SOME);
2342
             $type = 'SOME';
2343 2344 2345
        } else {
            $this->syntaxError('ALL, ANY or SOME');
        }
guilhermeblanco's avatar
guilhermeblanco committed
2346

2347
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2348
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
2349
        $qExpr->type = $type;
2350
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
guilhermeblanco's avatar
guilhermeblanco committed
2351

2352
        return $qExpr;
2353 2354 2355 2356
    }

    /**
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
2357 2358
     *
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
2359
     */
romanb's avatar
romanb committed
2360
    public function BetweenExpression()
2361 2362
    {
        $not = false;
romanb's avatar
romanb committed
2363
        $arithExpr1 = $this->ArithmeticExpression();
guilhermeblanco's avatar
guilhermeblanco committed
2364

2365 2366 2367 2368
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $this->match(Lexer::T_NOT);
            $not = true;
        }
guilhermeblanco's avatar
guilhermeblanco committed
2369

2370
        $this->match(Lexer::T_BETWEEN);
romanb's avatar
romanb committed
2371
        $arithExpr2 = $this->ArithmeticExpression();
2372
        $this->match(Lexer::T_AND);
romanb's avatar
romanb committed
2373
        $arithExpr3 = $this->ArithmeticExpression();
2374 2375

        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
2376
        $betweenExpr->not = $not;
2377 2378 2379 2380 2381

        return $betweenExpr;
    }

    /**
2382 2383
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
     *
2384
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
2385
     */
2386
    public function ComparisonExpression()
2387
    {
2388
        $peek = $this->_lexer->glimpse();
guilhermeblanco's avatar
guilhermeblanco committed
2389

2390 2391 2392 2393 2394 2395 2396
        $leftExpr = $this->ArithmeticExpression();
        $operator = $this->ComparisonOperator();

        if ($this->_isNextAllAnySome()) {
            $rightExpr = $this->QuantifiedExpression();
        } else {
            $rightExpr = $this->ArithmeticExpression();
2397 2398
        }

2399 2400
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
    }
guilhermeblanco's avatar
guilhermeblanco committed
2401

2402
    /**
2403
     * InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
2404 2405
     *
     * @return \Doctrine\ORM\Query\AST\InExpression
2406 2407 2408 2409 2410 2411 2412
     */
    public function InExpression()
    {
        $inExpression = new AST\InExpression($this->StateFieldPathExpression());

        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $this->match(Lexer::T_NOT);
2413
            $inExpression->not = true;
2414 2415 2416
        }

        $this->match(Lexer::T_IN);
2417
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2418 2419

        if ($this->_lexer->isNextToken(Lexer::T_SELECT)) {
2420
            $inExpression->subselect = $this->Subselect();
2421 2422
        } else {
            $literals = array();
2423
            $literals[] = $this->InParameter();
2424

2425 2426
            while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
                $this->match(Lexer::T_COMMA);
2427
                $literals[] = $this->InParameter();
2428
            }
guilhermeblanco's avatar
guilhermeblanco committed
2429

2430
            $inExpression->literals = $literals;
2431
        }
guilhermeblanco's avatar
guilhermeblanco committed
2432

2433
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
guilhermeblanco's avatar
guilhermeblanco committed
2434

2435 2436
        return $inExpression;
    }
guilhermeblanco's avatar
guilhermeblanco committed
2437

2438 2439
    /**
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
2440 2441
     *
     * @return \Doctrine\ORM\Query\AST\LikeExpression
2442 2443 2444 2445
     */
    public function LikeExpression()
    {
        $stringExpr = $this->StringExpression();
2446
        $not = false;
guilhermeblanco's avatar
guilhermeblanco committed
2447

2448
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
2449
            $this->match(Lexer::T_NOT);
2450
            $not = true;
2451
        }
guilhermeblanco's avatar
guilhermeblanco committed
2452

2453
        $this->match(Lexer::T_LIKE);
guilhermeblanco's avatar
guilhermeblanco committed
2454

2455 2456 2457 2458 2459 2460 2461
        if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
            $this->match(Lexer::T_INPUT_PARAMETER);
            $stringPattern = new AST\InputParameter($this->_lexer->token['value']);
        } else {
            $this->match(Lexer::T_STRING);
            $stringPattern = $this->_lexer->token['value'];
        }
guilhermeblanco's avatar
guilhermeblanco committed
2462

2463
        $escapeChar = null;
guilhermeblanco's avatar
guilhermeblanco committed
2464

2465 2466 2467 2468
        if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) {
            $this->match(Lexer::T_ESCAPE);
            $this->match(Lexer::T_STRING);
            $escapeChar = $this->_lexer->token['value'];
2469
        }
2470

2471 2472
        $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
        $likeExpr->not = $not;
2473

2474
        return $likeExpr;
2475 2476 2477
    }

    /**
2478
     * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
2479 2480
     *
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
2481
     */
2482
    public function NullComparisonExpression()
2483
    {
2484 2485 2486 2487 2488 2489
        if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
            $this->match(Lexer::T_INPUT_PARAMETER);
            $expr = new AST\InputParameter($this->_lexer->token['value']);
        } else {
            $expr = $this->SingleValuedPathExpression();
        }
guilhermeblanco's avatar
guilhermeblanco committed
2490

2491 2492
        $nullCompExpr = new AST\NullComparisonExpression($expr);
        $this->match(Lexer::T_IS);
2493

2494 2495
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $this->match(Lexer::T_NOT);
2496
            $nullCompExpr->not = true;
2497
        }
guilhermeblanco's avatar
guilhermeblanco committed
2498

2499 2500 2501
        $this->match(Lexer::T_NULL);

        return $nullCompExpr;
2502 2503 2504
    }

    /**
2505
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
2506 2507
     *
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
2508
     */
2509
    public function ExistsExpression()
2510
    {
2511
        $not = false;
guilhermeblanco's avatar
guilhermeblanco committed
2512

2513 2514 2515 2516 2517 2518
        if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
            $this->match(Lexer::T_NOT);
            $not = true;
        }

        $this->match(Lexer::T_EXISTS);
2519
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2520
        $existsExpression = new AST\ExistsExpression($this->Subselect());
2521
        $existsExpression->not = $not;
2522
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2523 2524

        return $existsExpression;
2525 2526 2527 2528
    }

    /**
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
2529 2530
     *
     * @return string
2531
     */
romanb's avatar
romanb committed
2532
    public function ComparisonOperator()
2533 2534 2535
    {
        switch ($this->_lexer->lookahead['value']) {
            case '=':
2536
                $this->match(Lexer::T_EQUALS);
guilhermeblanco's avatar
guilhermeblanco committed
2537

2538
                return '=';
guilhermeblanco's avatar
guilhermeblanco committed
2539

2540
            case '<':
2541
                $this->match(Lexer::T_LOWER_THAN);
2542
                $operator = '<';
guilhermeblanco's avatar
guilhermeblanco committed
2543

2544 2545
                if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) {
                    $this->match(Lexer::T_EQUALS);
2546
                    $operator .= '=';
2547 2548
                } else if ($this->_lexer->isNextToken(Lexer::T_GREATER_THAN)) {
                    $this->match(Lexer::T_GREATER_THAN);
2549 2550
                    $operator .= '>';
                }
guilhermeblanco's avatar
guilhermeblanco committed
2551

2552
                return $operator;
guilhermeblanco's avatar
guilhermeblanco committed
2553

2554
            case '>':
2555
                $this->match(Lexer::T_GREATER_THAN);
2556
                $operator = '>';
guilhermeblanco's avatar
guilhermeblanco committed
2557

2558 2559
                if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) {
                    $this->match(Lexer::T_EQUALS);
2560 2561
                    $operator .= '=';
                }
guilhermeblanco's avatar
guilhermeblanco committed
2562

2563
                return $operator;
guilhermeblanco's avatar
guilhermeblanco committed
2564

2565
            case '!':
2566 2567
                $this->match(Lexer::T_NEGATE);
                $this->match(Lexer::T_EQUALS);
guilhermeblanco's avatar
guilhermeblanco committed
2568

2569
                return '<>';
guilhermeblanco's avatar
guilhermeblanco committed
2570

2571 2572 2573 2574 2575 2576
            default:
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
        }
    }

    /**
2577
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
2578
     */
2579
    public function FunctionDeclaration()
2580
    {
2581 2582
        $token = $this->_lexer->lookahead;
        $funcName = strtolower($token['value']);
guilhermeblanco's avatar
guilhermeblanco committed
2583

2584 2585 2586 2587 2588 2589 2590 2591
        // Check for built-in functions first!
        if (isset(self::$_STRING_FUNCTIONS[$funcName])) {
            return $this->FunctionsReturningStrings();
        } else if (isset(self::$_NUMERIC_FUNCTIONS[$funcName])) {
            return $this->FunctionsReturningNumerics();
        } else if (isset(self::$_DATETIME_FUNCTIONS[$funcName])) {
            return $this->FunctionsReturningDatetime();
        }
2592

2593 2594
        // Check for custom functions afterwards
        $config = $this->_em->getConfiguration();
2595

2596 2597 2598 2599 2600 2601
        if ($config->getCustomStringFunction($funcName) !== null) {
            return $this->CustomFunctionsReturningStrings();
        } else if ($config->getCustomNumericFunction($funcName) !== null) {
            return $this->CustomFunctionsReturningNumerics();
        } else if ($config->getCustomDatetimeFunction($funcName) !== null) {
            return $this->CustomFunctionsReturningDatetime();
2602
        }
2603

2604
        $this->syntaxError('known function', $token);
2605 2606 2607
    }

    /**
2608 2609 2610 2611 2612 2613 2614
     * FunctionsReturningNumerics ::=
     *      "LENGTH" "(" StringPrimary ")" |
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
     *      "ABS" "(" SimpleArithmeticExpression ")" |
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
     *      "SIZE" "(" CollectionValuedPathExpression ")"
2615
     */
2616
    public function FunctionsReturningNumerics()
2617
    {
2618 2619 2620 2621
        $funcNameLower = strtolower($this->_lexer->lookahead['value']);
        $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
        $function = new $funcClass($funcNameLower);
        $function->parse($this);
guilhermeblanco's avatar
guilhermeblanco committed
2622

2623
        return $function;
2624 2625
    }

2626 2627 2628 2629 2630 2631 2632 2633 2634 2635
    public function CustomFunctionsReturningNumerics()
    {
        $funcNameLower = strtolower($this->_lexer->lookahead['value']);
        $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcNameLower);
        $function = new $funcClass($funcNameLower);
        $function->parse($this);

        return $function;
    }

2636
    /**
2637
     * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP"
2638
     */
2639
    public function FunctionsReturningDatetime()
2640
    {
2641 2642 2643 2644
        $funcNameLower = strtolower($this->_lexer->lookahead['value']);
        $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
        $function = new $funcClass($funcNameLower);
        $function->parse($this);
2645

2646
        return $function;
2647
    }
2648

2649 2650 2651 2652 2653 2654 2655 2656 2657 2658
    public function CustomFunctionsReturningDatetime()
    {
        $funcNameLower = strtolower($this->_lexer->lookahead['value']);
        $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcNameLower);
        $function = new $funcClass($funcNameLower);
        $function->parse($this);

        return $function;
    }

2659
    /**
2660 2661 2662 2663 2664 2665
     * FunctionsReturningStrings ::=
     *   "CONCAT" "(" StringPrimary "," StringPrimary ")" |
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
     *   "LOWER" "(" StringPrimary ")" |
     *   "UPPER" "(" StringPrimary ")"
2666
     */
2667
    public function FunctionsReturningStrings()
2668
    {
2669 2670
        $funcNameLower = strtolower($this->_lexer->lookahead['value']);
        $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower];
2671 2672 2673 2674 2675 2676 2677 2678 2679 2680
        $function = new $funcClass($funcNameLower);
        $function->parse($this);

        return $function;
    }

    public function CustomFunctionsReturningStrings()
    {
        $funcNameLower = strtolower($this->_lexer->lookahead['value']);
        $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcNameLower);
2681 2682
        $function = new $funcClass($funcNameLower);
        $function->parse($this);
2683

2684
        return $function;
2685
    }
2686
}