vendor/doctrine/lib/Doctrine/ORM/Query/Parser.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 /*
       
     3  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
     4  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
     5  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
       
     6  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
       
     7  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
       
     8  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       
     9  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    10  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    11  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    12  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    13  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    14  *
       
    15  * This software consists of voluntary contributions made by many individuals
       
    16  * and is licensed under the LGPL. For more information, see
       
    17  * <http://www.doctrine-project.org>.
       
    18  */
       
    19 
       
    20 namespace Doctrine\ORM\Query;
       
    21 
       
    22 use Doctrine\ORM\Query;
       
    23 use Doctrine\ORM\Mapping\ClassMetadata;
       
    24 
       
    25 /**
       
    26  * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
       
    27  * Parses a DQL query, reports any errors in it, and generates an AST.
       
    28  *
       
    29  * @since   2.0
       
    30  * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
       
    31  * @author  Jonathan Wage <jonwage@gmail.com>
       
    32  * @author  Roman Borschel <roman@code-factory.org>
       
    33  * @author  Janne Vanhala <jpvanhal@cc.hut.fi>
       
    34  */
       
    35 class Parser
       
    36 {
       
    37     /** READ-ONLY: Maps BUILT-IN string function names to AST class names. */
       
    38     private static $_STRING_FUNCTIONS = array(
       
    39         'concat'    => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction',
       
    40         'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
       
    41         'trim'      => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
       
    42         'lower'     => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
       
    43         'upper'     => 'Doctrine\ORM\Query\AST\Functions\UpperFunction'
       
    44     );
       
    45 
       
    46     /** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
       
    47     private static $_NUMERIC_FUNCTIONS = array(
       
    48         'length'    => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
       
    49         'locate'    => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
       
    50         'abs'       => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
       
    51         'sqrt'      => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
       
    52         'mod'       => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
       
    53         'size'      => 'Doctrine\ORM\Query\AST\Functions\SizeFunction',
       
    54         'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction',
       
    55     );
       
    56 
       
    57     /** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */
       
    58     private static $_DATETIME_FUNCTIONS = array(
       
    59         'current_date'      => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
       
    60         'current_time'      => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
       
    61         'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction',
       
    62         'date_add'          => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction',
       
    63         'date_sub'          => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction',
       
    64     );
       
    65 
       
    66     /**
       
    67      * Expressions that were encountered during parsing of identifiers and expressions
       
    68      * and still need to be validated.
       
    69      */
       
    70     private $_deferredIdentificationVariables = array();
       
    71     private $_deferredPartialObjectExpressions = array();
       
    72     private $_deferredPathExpressions = array();
       
    73     private $_deferredResultVariables = array();
       
    74 
       
    75     /**
       
    76      * The lexer.
       
    77      *
       
    78      * @var Doctrine\ORM\Query\Lexer
       
    79      */
       
    80     private $_lexer;
       
    81 
       
    82     /**
       
    83      * The parser result.
       
    84      *
       
    85      * @var Doctrine\ORM\Query\ParserResult
       
    86      */
       
    87     private $_parserResult;
       
    88 
       
    89     /**
       
    90      * The EntityManager.
       
    91      *
       
    92      * @var EnityManager
       
    93      */
       
    94     private $_em;
       
    95 
       
    96     /**
       
    97      * The Query to parse.
       
    98      *
       
    99      * @var Query
       
   100      */
       
   101     private $_query;
       
   102 
       
   103     /**
       
   104      * Map of declared query components in the parsed query.
       
   105      *
       
   106      * @var array
       
   107      */
       
   108     private $_queryComponents = array();
       
   109 
       
   110     /**
       
   111      * Keeps the nesting level of defined ResultVariables
       
   112      *
       
   113      * @var integer
       
   114      */
       
   115     private $_nestingLevel = 0;
       
   116 
       
   117     /**
       
   118      * Any additional custom tree walkers that modify the AST.
       
   119      *
       
   120      * @var array
       
   121      */
       
   122     private $_customTreeWalkers = array();
       
   123 
       
   124     /**
       
   125      * The custom last tree walker, if any, that is responsible for producing the output.
       
   126      *
       
   127      * @var TreeWalker
       
   128      */
       
   129     private $_customOutputWalker;
       
   130 
       
   131     /**
       
   132      * @var array
       
   133      */
       
   134     private $_identVariableExpressions = array();
       
   135 
       
   136     /**
       
   137      * Creates a new query parser object.
       
   138      *
       
   139      * @param Query $query The Query to parse.
       
   140      */
       
   141     public function __construct(Query $query)
       
   142     {
       
   143         $this->_query = $query;
       
   144         $this->_em = $query->getEntityManager();
       
   145         $this->_lexer = new Lexer($query->getDql());
       
   146         $this->_parserResult = new ParserResult();
       
   147     }
       
   148 
       
   149     /**
       
   150      * Sets a custom tree walker that produces output.
       
   151      * This tree walker will be run last over the AST, after any other walkers.
       
   152      *
       
   153      * @param string $className
       
   154      */
       
   155     public function setCustomOutputTreeWalker($className)
       
   156     {
       
   157         $this->_customOutputWalker = $className;
       
   158     }
       
   159 
       
   160     /**
       
   161      * Adds a custom tree walker for modifying the AST.
       
   162      *
       
   163      * @param string $className
       
   164      */
       
   165     public function addCustomTreeWalker($className)
       
   166     {
       
   167         $this->_customTreeWalkers[] = $className;
       
   168     }
       
   169 
       
   170     /**
       
   171      * Gets the lexer used by the parser.
       
   172      *
       
   173      * @return Doctrine\ORM\Query\Lexer
       
   174      */
       
   175     public function getLexer()
       
   176     {
       
   177         return $this->_lexer;
       
   178     }
       
   179 
       
   180     /**
       
   181      * Gets the ParserResult that is being filled with information during parsing.
       
   182      *
       
   183      * @return Doctrine\ORM\Query\ParserResult
       
   184      */
       
   185     public function getParserResult()
       
   186     {
       
   187         return $this->_parserResult;
       
   188     }
       
   189 
       
   190     /**
       
   191      * Gets the EntityManager used by the parser.
       
   192      *
       
   193      * @return EntityManager
       
   194      */
       
   195     public function getEntityManager()
       
   196     {
       
   197         return $this->_em;
       
   198     }
       
   199 
       
   200     /**
       
   201      * Parse and build AST for the given Query.
       
   202      *
       
   203      * @return \Doctrine\ORM\Query\AST\SelectStatement |
       
   204      *         \Doctrine\ORM\Query\AST\UpdateStatement |
       
   205      *         \Doctrine\ORM\Query\AST\DeleteStatement
       
   206      */
       
   207     public function getAST()
       
   208     {
       
   209         // Parse & build AST
       
   210         $AST = $this->QueryLanguage();
       
   211 
       
   212         // Process any deferred validations of some nodes in the AST.
       
   213         // This also allows post-processing of the AST for modification purposes.
       
   214         $this->_processDeferredIdentificationVariables();
       
   215 
       
   216         if ($this->_deferredPartialObjectExpressions) {
       
   217             $this->_processDeferredPartialObjectExpressions();
       
   218         }
       
   219 
       
   220         if ($this->_deferredPathExpressions) {
       
   221             $this->_processDeferredPathExpressions($AST);
       
   222         }
       
   223 
       
   224         if ($this->_deferredResultVariables) {
       
   225             $this->_processDeferredResultVariables();
       
   226         }
       
   227 
       
   228         return $AST;
       
   229     }
       
   230 
       
   231     /**
       
   232      * Attempts to match the given token with the current lookahead token.
       
   233      *
       
   234      * If they match, updates the lookahead token; otherwise raises a syntax
       
   235      * error.
       
   236      *
       
   237      * @param int token type
       
   238      * @return void
       
   239      * @throws QueryException If the tokens dont match.
       
   240      */
       
   241     public function match($token)
       
   242     {
       
   243         // short-circuit on first condition, usually types match
       
   244         if ($this->_lexer->lookahead['type'] !== $token &&
       
   245                 $token !== Lexer::T_IDENTIFIER &&
       
   246                 $this->_lexer->lookahead['type'] <= Lexer::T_IDENTIFIER
       
   247          ) {
       
   248             $this->syntaxError($this->_lexer->getLiteral($token));
       
   249         }
       
   250 
       
   251         $this->_lexer->moveNext();
       
   252     }
       
   253 
       
   254     /**
       
   255      * Free this parser enabling it to be reused
       
   256      *
       
   257      * @param boolean $deep     Whether to clean peek and reset errors
       
   258      * @param integer $position Position to reset
       
   259      */
       
   260     public function free($deep = false, $position = 0)
       
   261     {
       
   262         // WARNING! Use this method with care. It resets the scanner!
       
   263         $this->_lexer->resetPosition($position);
       
   264 
       
   265         // Deep = true cleans peek and also any previously defined errors
       
   266         if ($deep) {
       
   267             $this->_lexer->resetPeek();
       
   268         }
       
   269 
       
   270         $this->_lexer->token = null;
       
   271         $this->_lexer->lookahead = null;
       
   272     }
       
   273 
       
   274     /**
       
   275      * Parses a query string.
       
   276      *
       
   277      * @return ParserResult
       
   278      */
       
   279     public function parse()
       
   280     {
       
   281         $AST = $this->getAST();
       
   282 
       
   283         $this->fixIdentificationVariableOrder($AST);
       
   284         $this->assertSelectEntityRootAliasRequirement();
       
   285 
       
   286         if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
       
   287             $this->_customTreeWalkers = $customWalkers;
       
   288         }
       
   289 
       
   290         if (($customOutputWalker = $this->_query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
       
   291             $this->_customOutputWalker = $customOutputWalker;
       
   292         }
       
   293 
       
   294         // Run any custom tree walkers over the AST
       
   295         if ($this->_customTreeWalkers) {
       
   296             $treeWalkerChain = new TreeWalkerChain($this->_query, $this->_parserResult, $this->_queryComponents);
       
   297 
       
   298             foreach ($this->_customTreeWalkers as $walker) {
       
   299                 $treeWalkerChain->addTreeWalker($walker);
       
   300             }
       
   301 
       
   302             if ($AST instanceof AST\SelectStatement) {
       
   303                 $treeWalkerChain->walkSelectStatement($AST);
       
   304             } else if ($AST instanceof AST\UpdateStatement) {
       
   305                 $treeWalkerChain->walkUpdateStatement($AST);
       
   306             } else {
       
   307                 $treeWalkerChain->walkDeleteStatement($AST);
       
   308             }
       
   309         }
       
   310 
       
   311         if ($this->_customOutputWalker) {
       
   312             $outputWalker = new $this->_customOutputWalker(
       
   313                 $this->_query, $this->_parserResult, $this->_queryComponents
       
   314             );
       
   315         } else {
       
   316             $outputWalker = new SqlWalker(
       
   317                 $this->_query, $this->_parserResult, $this->_queryComponents
       
   318             );
       
   319         }
       
   320 
       
   321         // Assign an SQL executor to the parser result
       
   322         $this->_parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
       
   323 
       
   324         return $this->_parserResult;
       
   325     }
       
   326     
       
   327     private function assertSelectEntityRootAliasRequirement()
       
   328     {
       
   329         if ( count($this->_identVariableExpressions) > 0) {
       
   330             $foundRootEntity = false;
       
   331             foreach ($this->_identVariableExpressions AS $dqlAlias => $expr) {
       
   332                 if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) {
       
   333                     $foundRootEntity = true;
       
   334                 }
       
   335             }
       
   336             
       
   337             if (!$foundRootEntity) {
       
   338                 $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
       
   339             }
       
   340         }
       
   341     }
       
   342     
       
   343     /**
       
   344      * Fix order of identification variables.
       
   345      * 
       
   346      * They have to appear in the select clause in the same order as the
       
   347      * declarations (from ... x join ... y join ... z ...) appear in the query
       
   348      * as the hydration process relies on that order for proper operation.
       
   349      * 
       
   350      * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
       
   351      * @return void
       
   352      */
       
   353     private function fixIdentificationVariableOrder($AST)
       
   354     {
       
   355         if ( count($this->_identVariableExpressions) > 1) {
       
   356             foreach ($this->_queryComponents as $dqlAlias => $qComp) {
       
   357                 if (isset($this->_identVariableExpressions[$dqlAlias])) {
       
   358                     $expr = $this->_identVariableExpressions[$dqlAlias];
       
   359                     $key = array_search($expr, $AST->selectClause->selectExpressions);
       
   360                     unset($AST->selectClause->selectExpressions[$key]);
       
   361                     $AST->selectClause->selectExpressions[] = $expr;
       
   362                 }
       
   363             }
       
   364         }
       
   365     }
       
   366 
       
   367     /**
       
   368      * Generates a new syntax error.
       
   369      *
       
   370      * @param string $expected Expected string.
       
   371      * @param array $token Got token.
       
   372      *
       
   373      * @throws \Doctrine\ORM\Query\QueryException
       
   374      */
       
   375     public function syntaxError($expected = '', $token = null)
       
   376     {
       
   377         if ($token === null) {
       
   378             $token = $this->_lexer->lookahead;
       
   379         }
       
   380 
       
   381         $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
       
   382         $message  = "line 0, col {$tokenPos}: Error: ";
       
   383 
       
   384         if ($expected !== '') {
       
   385             $message .= "Expected {$expected}, got ";
       
   386         } else {
       
   387             $message .= 'Unexpected ';
       
   388         }
       
   389 
       
   390         if ($this->_lexer->lookahead === null) {
       
   391             $message .= 'end of string.';
       
   392         } else {
       
   393             $message .= "'{$token['value']}'";
       
   394         }
       
   395 
       
   396         throw QueryException::syntaxError($message);
       
   397     }
       
   398 
       
   399     /**
       
   400      * Generates a new semantical error.
       
   401      *
       
   402      * @param string $message Optional message.
       
   403      * @param array $token Optional token.
       
   404      *
       
   405      * @throws \Doctrine\ORM\Query\QueryException
       
   406      */
       
   407     public function semanticalError($message = '', $token = null)
       
   408     {
       
   409         if ($token === null) {
       
   410             $token = $this->_lexer->lookahead;
       
   411         }
       
   412 
       
   413         // Minimum exposed chars ahead of token
       
   414         $distance = 12;
       
   415 
       
   416         // Find a position of a final word to display in error string
       
   417         $dql = $this->_query->getDql();
       
   418         $length = strlen($dql);
       
   419         $pos = $token['position'] + $distance;
       
   420         $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
       
   421         $length = ($pos !== false) ? $pos - $token['position'] : $distance;
       
   422 
       
   423         // Building informative message
       
   424         $message = 'line 0, col ' . (
       
   425             (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'
       
   426         ) . " near '" . substr($dql, $token['position'], $length) . "': Error: " . $message;
       
   427 
       
   428         throw \Doctrine\ORM\Query\QueryException::semanticalError($message);
       
   429     }
       
   430 
       
   431     /**
       
   432      * Peeks beyond the specified token and returns the first token after that one.
       
   433      *
       
   434      * @param array $token
       
   435      * @return array
       
   436      */
       
   437     private function _peekBeyond($token)
       
   438     {
       
   439         $peek = $this->_lexer->peek();
       
   440 
       
   441         while ($peek['value'] != $token) {
       
   442             $peek = $this->_lexer->peek();
       
   443         }
       
   444 
       
   445         $peek = $this->_lexer->peek();
       
   446         $this->_lexer->resetPeek();
       
   447 
       
   448         return $peek;
       
   449     }
       
   450 
       
   451     /**
       
   452      * Peek beyond the matched closing parenthesis and return the first token after that one.
       
   453      *
       
   454      * @return array
       
   455      */
       
   456     private function _peekBeyondClosingParenthesis()
       
   457     {
       
   458         $token = $this->_lexer->peek();
       
   459         $numUnmatched = 1;
       
   460 
       
   461         while ($numUnmatched > 0 && $token !== null) {
       
   462             if ($token['value'] == ')') {
       
   463                 --$numUnmatched;
       
   464             } else if ($token['value'] == '(') {
       
   465                 ++$numUnmatched;
       
   466             }
       
   467 
       
   468             $token = $this->_lexer->peek();
       
   469         }
       
   470         
       
   471         $this->_lexer->resetPeek();
       
   472 
       
   473         return $token;
       
   474     }
       
   475 
       
   476     /**
       
   477      * Checks if the given token indicates a mathematical operator.
       
   478      *
       
   479      * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
       
   480      */
       
   481     private function _isMathOperator($token)
       
   482     {
       
   483         return in_array($token['value'], array("+", "-", "/", "*"));
       
   484     }
       
   485 
       
   486     /**
       
   487      * Checks if the next-next (after lookahead) token starts a function.
       
   488      *
       
   489      * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
       
   490      */
       
   491     private function _isFunction()
       
   492     {
       
   493         $peek = $this->_lexer->peek();
       
   494         $nextpeek = $this->_lexer->peek();
       
   495         $this->_lexer->resetPeek();
       
   496 
       
   497         // We deny the COUNT(SELECT * FROM User u) here. COUNT won't be considered a function
       
   498         return ($peek['value'] === '(' && $nextpeek['type'] !== Lexer::T_SELECT);
       
   499     }
       
   500 
       
   501     /**
       
   502      * Checks whether the given token type indicates an aggregate function.
       
   503      *
       
   504      * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
       
   505      */
       
   506     private function _isAggregateFunction($tokenType)
       
   507     {
       
   508         return $tokenType == Lexer::T_AVG || $tokenType == Lexer::T_MIN ||
       
   509                $tokenType == Lexer::T_MAX || $tokenType == Lexer::T_SUM ||
       
   510                $tokenType == Lexer::T_COUNT;
       
   511     }
       
   512 
       
   513     /**
       
   514      * Checks whether the current lookahead token of the lexer has the type
       
   515      * T_ALL, T_ANY or T_SOME.
       
   516      *
       
   517      * @return boolean
       
   518      */
       
   519     private function _isNextAllAnySome()
       
   520     {
       
   521         return $this->_lexer->lookahead['type'] === Lexer::T_ALL ||
       
   522                $this->_lexer->lookahead['type'] === Lexer::T_ANY ||
       
   523                $this->_lexer->lookahead['type'] === Lexer::T_SOME;
       
   524     }
       
   525 
       
   526     /**
       
   527      * Checks whether the next 2 tokens start a subselect.
       
   528      *
       
   529      * @return boolean TRUE if the next 2 tokens start a subselect, FALSE otherwise.
       
   530      */
       
   531     private function _isSubselect()
       
   532     {
       
   533         $la = $this->_lexer->lookahead;
       
   534         $next = $this->_lexer->glimpse();
       
   535 
       
   536         return ($la['value'] === '(' && $next['type'] === Lexer::T_SELECT);
       
   537     }
       
   538 
       
   539     /**
       
   540      * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
       
   541      * It must exist in query components list.
       
   542      *
       
   543      * @return void
       
   544      */
       
   545     private function _processDeferredIdentificationVariables()
       
   546     {
       
   547         foreach ($this->_deferredIdentificationVariables as $deferredItem) {
       
   548             $identVariable = $deferredItem['expression'];
       
   549 
       
   550             // Check if IdentificationVariable exists in queryComponents
       
   551             if ( ! isset($this->_queryComponents[$identVariable])) {
       
   552                 $this->semanticalError(
       
   553                     "'$identVariable' is not defined.", $deferredItem['token']
       
   554                 );
       
   555             }
       
   556 
       
   557             $qComp = $this->_queryComponents[$identVariable];
       
   558 
       
   559             // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
       
   560             if ( ! isset($qComp['metadata'])) {
       
   561                 $this->semanticalError(
       
   562                     "'$identVariable' does not point to a Class.", $deferredItem['token']
       
   563                 );
       
   564             }
       
   565 
       
   566             // Validate if identification variable nesting level is lower or equal than the current one
       
   567             if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
       
   568                 $this->semanticalError(
       
   569                     "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
       
   570                 );
       
   571             }
       
   572         }
       
   573     }
       
   574 
       
   575     /**
       
   576      * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
       
   577      * It must exist in query components list.
       
   578      *
       
   579      * @return void
       
   580      */
       
   581     private function _processDeferredPartialObjectExpressions()
       
   582     {
       
   583         foreach ($this->_deferredPartialObjectExpressions as $deferredItem) {
       
   584             $expr = $deferredItem['expression'];
       
   585             $class = $this->_queryComponents[$expr->identificationVariable]['metadata'];
       
   586 
       
   587             foreach ($expr->partialFieldSet as $field) {
       
   588                 if ( ! isset($class->fieldMappings[$field])) {
       
   589                     $this->semanticalError(
       
   590                         "There is no mapped field named '$field' on class " . $class->name . ".",
       
   591                         $deferredItem['token']
       
   592                     );
       
   593                 }
       
   594             }
       
   595 
       
   596             if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
       
   597                 $this->semanticalError(
       
   598                     "The partial field selection of class " . $class->name . " must contain the identifier.",
       
   599                     $deferredItem['token']
       
   600                 );
       
   601             }
       
   602         }
       
   603     }
       
   604 
       
   605     /**
       
   606      * Validates that the given <tt>ResultVariable</tt> is semantically correct.
       
   607      * It must exist in query components list.
       
   608      *
       
   609      * @return void
       
   610      */
       
   611     private function _processDeferredResultVariables()
       
   612     {
       
   613         foreach ($this->_deferredResultVariables as $deferredItem) {
       
   614             $resultVariable = $deferredItem['expression'];
       
   615 
       
   616             // Check if ResultVariable exists in queryComponents
       
   617             if ( ! isset($this->_queryComponents[$resultVariable])) {
       
   618                 $this->semanticalError(
       
   619                     "'$resultVariable' is not defined.", $deferredItem['token']
       
   620                 );
       
   621             }
       
   622 
       
   623             $qComp = $this->_queryComponents[$resultVariable];
       
   624 
       
   625             // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
       
   626             if ( ! isset($qComp['resultVariable'])) {
       
   627                 $this->semanticalError(
       
   628                     "'$identVariable' does not point to a ResultVariable.", $deferredItem['token']
       
   629                 );
       
   630             }
       
   631 
       
   632             // Validate if identification variable nesting level is lower or equal than the current one
       
   633             if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
       
   634                 $this->semanticalError(
       
   635                     "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
       
   636                 );
       
   637             }
       
   638         }
       
   639     }
       
   640 
       
   641     /**
       
   642      * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
       
   643      *
       
   644      * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
       
   645      * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
       
   646      * StateFieldPathExpression              ::= IdentificationVariable "." StateField
       
   647      * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
       
   648      * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
       
   649      *
       
   650      * @param array $deferredItem
       
   651      * @param mixed $AST
       
   652      */
       
   653     private function _processDeferredPathExpressions($AST)
       
   654     {
       
   655         foreach ($this->_deferredPathExpressions as $deferredItem) {
       
   656             $pathExpression = $deferredItem['expression'];
       
   657 
       
   658             $qComp = $this->_queryComponents[$pathExpression->identificationVariable];
       
   659             $class = $qComp['metadata'];
       
   660 
       
   661             if (($field = $pathExpression->field) === null) {
       
   662                 $field = $pathExpression->field = $class->identifier[0];
       
   663             }
       
   664             
       
   665             // Check if field or association exists
       
   666             if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
       
   667                 $this->semanticalError(
       
   668                     'Class ' . $class->name . ' has no field or association named ' . $field,
       
   669                     $deferredItem['token']
       
   670                 );
       
   671             }
       
   672 
       
   673             if (isset($class->fieldMappings[$field])) {
       
   674                 $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
       
   675             } else {
       
   676                 $assoc = $class->associationMappings[$field];
       
   677                 $class = $this->_em->getClassMetadata($assoc['targetEntity']);
       
   678 
       
   679                 if ($assoc['type'] & ClassMetadata::TO_ONE) {
       
   680                     $fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
       
   681                 } else {
       
   682                     $fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
       
   683                 }
       
   684             }
       
   685 
       
   686             // Validate if PathExpression is one of the expected types
       
   687             $expectedType = $pathExpression->expectedType;
       
   688 
       
   689             if ( ! ($expectedType & $fieldType)) {
       
   690                 // We need to recognize which was expected type(s)
       
   691                 $expectedStringTypes = array();
       
   692 
       
   693                 // Validate state field type
       
   694                 if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
       
   695                     $expectedStringTypes[] = 'StateFieldPathExpression';
       
   696                 }
       
   697 
       
   698                 // Validate single valued association (*-to-one)
       
   699                 if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
       
   700                     $expectedStringTypes[] = 'SingleValuedAssociationField';
       
   701                 }
       
   702 
       
   703                 // Validate single valued association (*-to-many)
       
   704                 if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
       
   705                     $expectedStringTypes[] = 'CollectionValuedAssociationField';
       
   706                 }
       
   707 
       
   708                 // Build the error message
       
   709                 $semanticalError = 'Invalid PathExpression. ';
       
   710 
       
   711                 if (count($expectedStringTypes) == 1) {
       
   712                     $semanticalError .= 'Must be a ' . $expectedStringTypes[0] . '.';
       
   713                 } else {
       
   714                     $semanticalError .= implode(' or ', $expectedStringTypes) . ' expected.';
       
   715                 }
       
   716 
       
   717                 $this->semanticalError($semanticalError, $deferredItem['token']);
       
   718             }
       
   719             
       
   720             // We need to force the type in PathExpression
       
   721             $pathExpression->type = $fieldType;
       
   722         }
       
   723     }
       
   724 
       
   725     /**
       
   726      * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
       
   727      *
       
   728      * @return \Doctrine\ORM\Query\AST\SelectStatement |
       
   729      *         \Doctrine\ORM\Query\AST\UpdateStatement |
       
   730      *         \Doctrine\ORM\Query\AST\DeleteStatement
       
   731      */
       
   732     public function QueryLanguage()
       
   733     {
       
   734         $this->_lexer->moveNext();
       
   735 
       
   736         switch ($this->_lexer->lookahead['type']) {
       
   737             case Lexer::T_SELECT:
       
   738                 $statement = $this->SelectStatement();
       
   739                 break;
       
   740             case Lexer::T_UPDATE:
       
   741                 $statement = $this->UpdateStatement();
       
   742                 break;
       
   743             case Lexer::T_DELETE:
       
   744                 $statement = $this->DeleteStatement();
       
   745                 break;
       
   746             default:
       
   747                 $this->syntaxError('SELECT, UPDATE or DELETE');
       
   748                 break;
       
   749         }
       
   750 
       
   751         // Check for end of string
       
   752         if ($this->_lexer->lookahead !== null) {
       
   753             $this->syntaxError('end of string');
       
   754         }
       
   755 
       
   756         return $statement;
       
   757     }
       
   758 
       
   759     /**
       
   760      * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
       
   761      *
       
   762      * @return \Doctrine\ORM\Query\AST\SelectStatement
       
   763      */
       
   764     public function SelectStatement()
       
   765     {
       
   766         $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
       
   767 
       
   768         $selectStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
       
   769             ? $this->WhereClause() : null;
       
   770 
       
   771         $selectStatement->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP)
       
   772             ? $this->GroupByClause() : null;
       
   773 
       
   774         $selectStatement->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING)
       
   775             ? $this->HavingClause() : null;
       
   776 
       
   777         $selectStatement->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
       
   778             ? $this->OrderByClause() : null;
       
   779 
       
   780         return $selectStatement;
       
   781     }
       
   782 
       
   783     /**
       
   784      * UpdateStatement ::= UpdateClause [WhereClause]
       
   785      *
       
   786      * @return \Doctrine\ORM\Query\AST\UpdateStatement
       
   787      */
       
   788     public function UpdateStatement()
       
   789     {
       
   790         $updateStatement = new AST\UpdateStatement($this->UpdateClause());
       
   791         $updateStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
       
   792                 ? $this->WhereClause() : null;
       
   793 
       
   794         return $updateStatement;
       
   795     }
       
   796 
       
   797     /**
       
   798      * DeleteStatement ::= DeleteClause [WhereClause]
       
   799      *
       
   800      * @return \Doctrine\ORM\Query\AST\DeleteStatement
       
   801      */
       
   802     public function DeleteStatement()
       
   803     {
       
   804         $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
       
   805         $deleteStatement->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
       
   806                 ? $this->WhereClause() : null;
       
   807 
       
   808         return $deleteStatement;
       
   809     }
       
   810 
       
   811     /**
       
   812      * IdentificationVariable ::= identifier
       
   813      *
       
   814      * @return string
       
   815      */
       
   816     public function IdentificationVariable()
       
   817     {
       
   818         $this->match(Lexer::T_IDENTIFIER);
       
   819 
       
   820         $identVariable = $this->_lexer->token['value'];
       
   821 
       
   822         $this->_deferredIdentificationVariables[] = array(
       
   823             'expression'   => $identVariable,
       
   824             'nestingLevel' => $this->_nestingLevel,
       
   825             'token'        => $this->_lexer->token,
       
   826         );
       
   827 
       
   828         return $identVariable;
       
   829     }
       
   830 
       
   831     /**
       
   832      * AliasIdentificationVariable = identifier
       
   833      *
       
   834      * @return string
       
   835      */
       
   836     public function AliasIdentificationVariable()
       
   837     {
       
   838         $this->match(Lexer::T_IDENTIFIER);
       
   839 
       
   840         $aliasIdentVariable = $this->_lexer->token['value'];
       
   841         $exists = isset($this->_queryComponents[$aliasIdentVariable]);
       
   842 
       
   843         if ($exists) {
       
   844             $this->semanticalError(
       
   845                 "'$aliasIdentVariable' is already defined.", $this->_lexer->token
       
   846             );
       
   847         }
       
   848 
       
   849         return $aliasIdentVariable;
       
   850     }
       
   851 
       
   852     /**
       
   853      * AbstractSchemaName ::= identifier
       
   854      *
       
   855      * @return string
       
   856      */
       
   857     public function AbstractSchemaName()
       
   858     {
       
   859         $this->match(Lexer::T_IDENTIFIER);
       
   860 
       
   861         $schemaName = ltrim($this->_lexer->token['value'], '\\');
       
   862 
       
   863         if (strrpos($schemaName, ':') !== false) {
       
   864             list($namespaceAlias, $simpleClassName) = explode(':', $schemaName);
       
   865             $schemaName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
       
   866         }
       
   867 
       
   868         $exists = class_exists($schemaName, true);
       
   869 
       
   870         if ( ! $exists) {
       
   871             $this->semanticalError("Class '$schemaName' is not defined.", $this->_lexer->token);
       
   872         }
       
   873 
       
   874         return $schemaName;
       
   875     }
       
   876 
       
   877     /**
       
   878      * AliasResultVariable ::= identifier
       
   879      *
       
   880      * @return string
       
   881      */
       
   882     public function AliasResultVariable()
       
   883     {
       
   884         $this->match(Lexer::T_IDENTIFIER);
       
   885 
       
   886         $resultVariable = $this->_lexer->token['value'];
       
   887         $exists = isset($this->_queryComponents[$resultVariable]);
       
   888 
       
   889         if ($exists) {
       
   890             $this->semanticalError(
       
   891                 "'$resultVariable' is already defined.", $this->_lexer->token
       
   892             );
       
   893         }
       
   894 
       
   895         return $resultVariable;
       
   896     }
       
   897 
       
   898     /**
       
   899      * ResultVariable ::= identifier
       
   900      *
       
   901      * @return string
       
   902      */
       
   903     public function ResultVariable()
       
   904     {
       
   905         $this->match(Lexer::T_IDENTIFIER);
       
   906 
       
   907         $resultVariable = $this->_lexer->token['value'];
       
   908 
       
   909         // Defer ResultVariable validation
       
   910         $this->_deferredResultVariables[] = array(
       
   911             'expression'   => $resultVariable,
       
   912             'nestingLevel' => $this->_nestingLevel,
       
   913             'token'        => $this->_lexer->token,
       
   914         );
       
   915 
       
   916         return $resultVariable;
       
   917     }
       
   918 
       
   919     /**
       
   920      * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
       
   921      *
       
   922      * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
       
   923      */
       
   924     public function JoinAssociationPathExpression()
       
   925     {
       
   926         $token = $this->_lexer->lookahead;
       
   927         $identVariable = $this->IdentificationVariable();
       
   928 
       
   929         if (!isset($this->_queryComponents[$identVariable])) {
       
   930             $this->semanticalError('Identification Variable ' . $identVariable .' used in join path expression but was not defined before.');
       
   931         }
       
   932 
       
   933         $this->match(Lexer::T_DOT);
       
   934         $this->match(Lexer::T_IDENTIFIER);
       
   935 
       
   936         $field = $this->_lexer->token['value'];
       
   937 
       
   938         // Validate association field
       
   939         $qComp = $this->_queryComponents[$identVariable];
       
   940         $class = $qComp['metadata'];
       
   941 
       
   942         if ( ! isset($class->associationMappings[$field])) {
       
   943             $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
       
   944         }
       
   945 
       
   946         return new AST\JoinAssociationPathExpression($identVariable, $field);
       
   947     }
       
   948 
       
   949     /**
       
   950      * Parses an arbitrary path expression and defers semantical validation
       
   951      * based on expected types.
       
   952      *
       
   953      * PathExpression ::= IdentificationVariable "." identifier
       
   954      *
       
   955      * @param integer $expectedTypes
       
   956      * @return \Doctrine\ORM\Query\AST\PathExpression
       
   957      */
       
   958     public function PathExpression($expectedTypes)
       
   959     {
       
   960         $token = $this->_lexer->lookahead;
       
   961         $identVariable = $this->IdentificationVariable();
       
   962         $field = null;
       
   963 
       
   964         if ($this->_lexer->isNextToken(Lexer::T_DOT)) {
       
   965             $this->match(Lexer::T_DOT);
       
   966             $this->match(Lexer::T_IDENTIFIER);
       
   967 
       
   968             $field = $this->_lexer->token['value'];
       
   969         }
       
   970         
       
   971         // Creating AST node
       
   972         $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
       
   973 
       
   974         // Defer PathExpression validation if requested to be defered
       
   975         $this->_deferredPathExpressions[] = array(
       
   976             'expression'   => $pathExpr,
       
   977             'nestingLevel' => $this->_nestingLevel,
       
   978             'token'        => $this->_lexer->token,
       
   979         );
       
   980 
       
   981         return $pathExpr;
       
   982     }
       
   983 
       
   984     /**
       
   985      * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
       
   986      *
       
   987      * @return \Doctrine\ORM\Query\AST\PathExpression
       
   988      */
       
   989     public function AssociationPathExpression()
       
   990     {
       
   991         return $this->PathExpression(
       
   992             AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
       
   993             AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
       
   994         );
       
   995     }
       
   996 
       
   997     /**
       
   998      * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
       
   999      *
       
  1000      * @return \Doctrine\ORM\Query\AST\PathExpression
       
  1001      */
       
  1002     public function SingleValuedPathExpression()
       
  1003     {
       
  1004         return $this->PathExpression(
       
  1005             AST\PathExpression::TYPE_STATE_FIELD |
       
  1006             AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
       
  1007         );
       
  1008     }
       
  1009 
       
  1010     /**
       
  1011      * StateFieldPathExpression ::= IdentificationVariable "." StateField
       
  1012      *
       
  1013      * @return \Doctrine\ORM\Query\AST\PathExpression
       
  1014      */
       
  1015     public function StateFieldPathExpression()
       
  1016     {
       
  1017         return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
       
  1018     }
       
  1019 
       
  1020     /**
       
  1021      * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
       
  1022      *
       
  1023      * @return \Doctrine\ORM\Query\AST\PathExpression
       
  1024      */
       
  1025     public function SingleValuedAssociationPathExpression()
       
  1026     {
       
  1027         return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
       
  1028     }
       
  1029 
       
  1030     /**
       
  1031      * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
       
  1032      *
       
  1033      * @return \Doctrine\ORM\Query\AST\PathExpression
       
  1034      */
       
  1035     public function CollectionValuedPathExpression()
       
  1036     {
       
  1037         return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
       
  1038     }
       
  1039 
       
  1040     /**
       
  1041      * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
       
  1042      *
       
  1043      * @return \Doctrine\ORM\Query\AST\SelectClause
       
  1044      */
       
  1045     public function SelectClause()
       
  1046     {
       
  1047         $isDistinct = false;
       
  1048         $this->match(Lexer::T_SELECT);
       
  1049 
       
  1050         // Check for DISTINCT
       
  1051         if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
       
  1052             $this->match(Lexer::T_DISTINCT);
       
  1053             $isDistinct = true;
       
  1054         }
       
  1055 
       
  1056         // Process SelectExpressions (1..N)
       
  1057         $selectExpressions = array();
       
  1058         $selectExpressions[] = $this->SelectExpression();
       
  1059 
       
  1060         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1061             $this->match(Lexer::T_COMMA);
       
  1062             $selectExpressions[] = $this->SelectExpression();
       
  1063         }
       
  1064 
       
  1065         return new AST\SelectClause($selectExpressions, $isDistinct);
       
  1066     }
       
  1067 
       
  1068     /**
       
  1069      * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
       
  1070      *
       
  1071      * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
       
  1072      */
       
  1073     public function SimpleSelectClause()
       
  1074     {
       
  1075         $isDistinct = false;
       
  1076         $this->match(Lexer::T_SELECT);
       
  1077 
       
  1078         if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
       
  1079             $this->match(Lexer::T_DISTINCT);
       
  1080             $isDistinct = true;
       
  1081         }
       
  1082 
       
  1083         return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
       
  1084     }
       
  1085 
       
  1086     /**
       
  1087      * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
       
  1088      *
       
  1089      * @return \Doctrine\ORM\Query\AST\UpdateClause
       
  1090      */
       
  1091     public function UpdateClause()
       
  1092     {
       
  1093         $this->match(Lexer::T_UPDATE);
       
  1094         $token = $this->_lexer->lookahead;
       
  1095         $abstractSchemaName = $this->AbstractSchemaName();
       
  1096 
       
  1097         if ($this->_lexer->isNextToken(Lexer::T_AS)) {
       
  1098             $this->match(Lexer::T_AS);
       
  1099         }
       
  1100 
       
  1101         $aliasIdentificationVariable = $this->AliasIdentificationVariable();
       
  1102 
       
  1103         $class = $this->_em->getClassMetadata($abstractSchemaName);
       
  1104 
       
  1105         // Building queryComponent
       
  1106         $queryComponent = array(
       
  1107             'metadata'     => $class,
       
  1108             'parent'       => null,
       
  1109             'relation'     => null,
       
  1110             'map'          => null,
       
  1111             'nestingLevel' => $this->_nestingLevel,
       
  1112             'token'        => $token,
       
  1113         );
       
  1114         $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
       
  1115 
       
  1116         $this->match(Lexer::T_SET);
       
  1117 
       
  1118         $updateItems = array();
       
  1119         $updateItems[] = $this->UpdateItem();
       
  1120 
       
  1121         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1122             $this->match(Lexer::T_COMMA);
       
  1123             $updateItems[] = $this->UpdateItem();
       
  1124         }
       
  1125 
       
  1126         $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
       
  1127         $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
       
  1128 
       
  1129         return $updateClause;
       
  1130     }
       
  1131 
       
  1132     /**
       
  1133      * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
       
  1134      *
       
  1135      * @return \Doctrine\ORM\Query\AST\DeleteClause
       
  1136      */
       
  1137     public function DeleteClause()
       
  1138     {
       
  1139         $this->match(Lexer::T_DELETE);
       
  1140 
       
  1141         if ($this->_lexer->isNextToken(Lexer::T_FROM)) {
       
  1142             $this->match(Lexer::T_FROM);
       
  1143         }
       
  1144 
       
  1145         $token = $this->_lexer->lookahead;
       
  1146         $deleteClause = new AST\DeleteClause($this->AbstractSchemaName());
       
  1147 
       
  1148         if ($this->_lexer->isNextToken(Lexer::T_AS)) {
       
  1149             $this->match(Lexer::T_AS);
       
  1150         }
       
  1151 
       
  1152         $aliasIdentificationVariable = $this->AliasIdentificationVariable();
       
  1153 
       
  1154         $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
       
  1155         $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName);
       
  1156 
       
  1157         // Building queryComponent
       
  1158         $queryComponent = array(
       
  1159             'metadata'     => $class,
       
  1160             'parent'       => null,
       
  1161             'relation'     => null,
       
  1162             'map'          => null,
       
  1163             'nestingLevel' => $this->_nestingLevel,
       
  1164             'token'        => $token,
       
  1165         );
       
  1166         $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
       
  1167 
       
  1168         return $deleteClause;
       
  1169     }
       
  1170 
       
  1171     /**
       
  1172      * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
       
  1173      *
       
  1174      * @return \Doctrine\ORM\Query\AST\FromClause
       
  1175      */
       
  1176     public function FromClause()
       
  1177     {
       
  1178         $this->match(Lexer::T_FROM);
       
  1179         $identificationVariableDeclarations = array();
       
  1180         $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
       
  1181 
       
  1182         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1183             $this->match(Lexer::T_COMMA);
       
  1184             $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
       
  1185         }
       
  1186 
       
  1187         return new AST\FromClause($identificationVariableDeclarations);
       
  1188     }
       
  1189 
       
  1190     /**
       
  1191      * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
       
  1192      *
       
  1193      * @return \Doctrine\ORM\Query\AST\SubselectFromClause
       
  1194      */
       
  1195     public function SubselectFromClause()
       
  1196     {
       
  1197         $this->match(Lexer::T_FROM);
       
  1198         $identificationVariables = array();
       
  1199         $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
       
  1200 
       
  1201         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1202             $this->match(Lexer::T_COMMA);
       
  1203             $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
       
  1204         }
       
  1205 
       
  1206         return new AST\SubselectFromClause($identificationVariables);
       
  1207     }
       
  1208 
       
  1209     /**
       
  1210      * WhereClause ::= "WHERE" ConditionalExpression
       
  1211      *
       
  1212      * @return \Doctrine\ORM\Query\AST\WhereClause
       
  1213      */
       
  1214     public function WhereClause()
       
  1215     {
       
  1216         $this->match(Lexer::T_WHERE);
       
  1217 
       
  1218         return new AST\WhereClause($this->ConditionalExpression());
       
  1219     }
       
  1220 
       
  1221     /**
       
  1222      * HavingClause ::= "HAVING" ConditionalExpression
       
  1223      *
       
  1224      * @return \Doctrine\ORM\Query\AST\HavingClause
       
  1225      */
       
  1226     public function HavingClause()
       
  1227     {
       
  1228         $this->match(Lexer::T_HAVING);
       
  1229 
       
  1230         return new AST\HavingClause($this->ConditionalExpression());
       
  1231     }
       
  1232 
       
  1233     /**
       
  1234      * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
       
  1235      *
       
  1236      * @return \Doctrine\ORM\Query\AST\GroupByClause
       
  1237      */
       
  1238     public function GroupByClause()
       
  1239     {
       
  1240         $this->match(Lexer::T_GROUP);
       
  1241         $this->match(Lexer::T_BY);
       
  1242 
       
  1243         $groupByItems = array($this->GroupByItem());
       
  1244 
       
  1245         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1246             $this->match(Lexer::T_COMMA);
       
  1247             $groupByItems[] = $this->GroupByItem();
       
  1248         }
       
  1249 
       
  1250         return new AST\GroupByClause($groupByItems);
       
  1251     }
       
  1252 
       
  1253     /**
       
  1254      * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
       
  1255      *
       
  1256      * @return \Doctrine\ORM\Query\AST\OrderByClause
       
  1257      */
       
  1258     public function OrderByClause()
       
  1259     {
       
  1260         $this->match(Lexer::T_ORDER);
       
  1261         $this->match(Lexer::T_BY);
       
  1262 
       
  1263         $orderByItems = array();
       
  1264         $orderByItems[] = $this->OrderByItem();
       
  1265 
       
  1266         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1267             $this->match(Lexer::T_COMMA);
       
  1268             $orderByItems[] = $this->OrderByItem();
       
  1269         }
       
  1270 
       
  1271         return new AST\OrderByClause($orderByItems);
       
  1272     }
       
  1273 
       
  1274     /**
       
  1275      * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
       
  1276      *
       
  1277      * @return \Doctrine\ORM\Query\AST\Subselect
       
  1278      */
       
  1279     public function Subselect()
       
  1280     {
       
  1281         // Increase query nesting level
       
  1282         $this->_nestingLevel++;
       
  1283 
       
  1284         $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
       
  1285 
       
  1286         $subselect->whereClause = $this->_lexer->isNextToken(Lexer::T_WHERE)
       
  1287             ? $this->WhereClause() : null;
       
  1288 
       
  1289         $subselect->groupByClause = $this->_lexer->isNextToken(Lexer::T_GROUP)
       
  1290             ? $this->GroupByClause() : null;
       
  1291 
       
  1292         $subselect->havingClause = $this->_lexer->isNextToken(Lexer::T_HAVING)
       
  1293             ? $this->HavingClause() : null;
       
  1294 
       
  1295         $subselect->orderByClause = $this->_lexer->isNextToken(Lexer::T_ORDER)
       
  1296             ? $this->OrderByClause() : null;
       
  1297 
       
  1298         // Decrease query nesting level
       
  1299         $this->_nestingLevel--;
       
  1300 
       
  1301         return $subselect;
       
  1302     }
       
  1303 
       
  1304     /**
       
  1305      * UpdateItem ::= SingleValuedPathExpression "=" NewValue
       
  1306      *
       
  1307      * @return \Doctrine\ORM\Query\AST\UpdateItem
       
  1308      */
       
  1309     public function UpdateItem()
       
  1310     {
       
  1311         $pathExpr = $this->SingleValuedPathExpression();
       
  1312 
       
  1313         $this->match(Lexer::T_EQUALS);
       
  1314 
       
  1315         $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
       
  1316 
       
  1317         return $updateItem;
       
  1318     }
       
  1319 
       
  1320     /**
       
  1321      * GroupByItem ::= IdentificationVariable | SingleValuedPathExpression
       
  1322      *
       
  1323      * @return string | \Doctrine\ORM\Query\AST\PathExpression
       
  1324      */
       
  1325     public function GroupByItem()
       
  1326     {
       
  1327         // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
       
  1328         $glimpse = $this->_lexer->glimpse();
       
  1329 
       
  1330         if ($glimpse['type'] != Lexer::T_DOT) {
       
  1331             $token = $this->_lexer->lookahead;
       
  1332             $identVariable = $this->IdentificationVariable();
       
  1333 
       
  1334             if (!isset($this->_queryComponents[$identVariable])) {
       
  1335                 $this->semanticalError('Cannot group by undefined identification variable.');
       
  1336             }
       
  1337 
       
  1338             return $identVariable;
       
  1339         }
       
  1340 
       
  1341         return $this->SingleValuedPathExpression();
       
  1342     }
       
  1343 
       
  1344     /**
       
  1345      * OrderByItem ::= (ResultVariable | StateFieldPathExpression) ["ASC" | "DESC"]
       
  1346      *
       
  1347      * @todo Post 2.0 release. Support general SingleValuedPathExpression instead
       
  1348      * of only StateFieldPathExpression.
       
  1349      *
       
  1350      * @return \Doctrine\ORM\Query\AST\OrderByItem
       
  1351      */
       
  1352     public function OrderByItem()
       
  1353     {
       
  1354         $type = 'ASC';
       
  1355 
       
  1356         // We need to check if we are in a ResultVariable or StateFieldPathExpression
       
  1357         $glimpse = $this->_lexer->glimpse();
       
  1358 
       
  1359         if ($glimpse['type'] != Lexer::T_DOT) {
       
  1360             $token = $this->_lexer->lookahead;
       
  1361             $expr = $this->ResultVariable();
       
  1362         } else {
       
  1363             $expr = $this->StateFieldPathExpression();
       
  1364         }
       
  1365 
       
  1366         $item = new AST\OrderByItem($expr);
       
  1367 
       
  1368         if ($this->_lexer->isNextToken(Lexer::T_ASC)) {
       
  1369             $this->match(Lexer::T_ASC);
       
  1370         } else if ($this->_lexer->isNextToken(Lexer::T_DESC)) {
       
  1371             $this->match(Lexer::T_DESC);
       
  1372             $type = 'DESC';
       
  1373         }
       
  1374 
       
  1375         $item->type = $type;
       
  1376         return $item;
       
  1377     }
       
  1378 
       
  1379     /**
       
  1380      * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
       
  1381      *      EnumPrimary | SimpleEntityExpression | "NULL"
       
  1382      *
       
  1383      * NOTE: Since it is not possible to correctly recognize individual types, here is the full
       
  1384      * grammar that needs to be supported:
       
  1385      *
       
  1386      * NewValue ::= SimpleArithmeticExpression | "NULL"
       
  1387      *
       
  1388      * SimpleArithmeticExpression covers all *Primary grammar rules and also SimplEntityExpression
       
  1389      */
       
  1390     public function NewValue()
       
  1391     {
       
  1392         if ($this->_lexer->isNextToken(Lexer::T_NULL)) {
       
  1393             $this->match(Lexer::T_NULL);
       
  1394             return null;
       
  1395         } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
       
  1396             $this->match(Lexer::T_INPUT_PARAMETER);
       
  1397             return new AST\InputParameter($this->_lexer->token['value']);
       
  1398         }
       
  1399 
       
  1400         return $this->SimpleArithmeticExpression();
       
  1401     }
       
  1402 
       
  1403     /**
       
  1404      * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
       
  1405      *
       
  1406      * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
       
  1407      */
       
  1408     public function IdentificationVariableDeclaration()
       
  1409     {
       
  1410         $rangeVariableDeclaration = $this->RangeVariableDeclaration();
       
  1411         $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
       
  1412         $joinVariableDeclarations = array();
       
  1413 
       
  1414         while (
       
  1415             $this->_lexer->isNextToken(Lexer::T_LEFT) ||
       
  1416             $this->_lexer->isNextToken(Lexer::T_INNER) ||
       
  1417             $this->_lexer->isNextToken(Lexer::T_JOIN)
       
  1418         ) {
       
  1419             $joinVariableDeclarations[] = $this->JoinVariableDeclaration();
       
  1420         }
       
  1421 
       
  1422         return new AST\IdentificationVariableDeclaration(
       
  1423             $rangeVariableDeclaration, $indexBy, $joinVariableDeclarations
       
  1424         );
       
  1425     }
       
  1426 
       
  1427     /**
       
  1428      * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
       
  1429      *
       
  1430      * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
       
  1431      *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
       
  1432      */
       
  1433     public function SubselectIdentificationVariableDeclaration()
       
  1434     {
       
  1435         $glimpse = $this->_lexer->glimpse();
       
  1436 
       
  1437         /* NOT YET IMPLEMENTED!
       
  1438 
       
  1439         if ($glimpse['type'] == Lexer::T_DOT) {
       
  1440             $subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration();
       
  1441             $subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression();
       
  1442             $this->match(Lexer::T_AS);
       
  1443             $subselectIdVarDecl->aliasIdentificationVariable = $this->AliasIdentificationVariable();
       
  1444 
       
  1445             return $subselectIdVarDecl;
       
  1446         }
       
  1447         */
       
  1448 
       
  1449         return $this->IdentificationVariableDeclaration();
       
  1450     }
       
  1451 
       
  1452     /**
       
  1453      * JoinVariableDeclaration ::= Join [IndexBy]
       
  1454      *
       
  1455      * @return \Doctrine\ORM\Query\AST\JoinVariableDeclaration
       
  1456      */
       
  1457     public function JoinVariableDeclaration()
       
  1458     {
       
  1459         $join = $this->Join();
       
  1460         $indexBy = $this->_lexer->isNextToken(Lexer::T_INDEX)
       
  1461                 ? $this->IndexBy() : null;
       
  1462 
       
  1463         return new AST\JoinVariableDeclaration($join, $indexBy);
       
  1464     }
       
  1465 
       
  1466     /**
       
  1467      * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
       
  1468      *
       
  1469      * @return Doctrine\ORM\Query\AST\RangeVariableDeclaration
       
  1470      */
       
  1471     public function RangeVariableDeclaration()
       
  1472     {
       
  1473         $abstractSchemaName = $this->AbstractSchemaName();
       
  1474 
       
  1475         if ($this->_lexer->isNextToken(Lexer::T_AS)) {
       
  1476             $this->match(Lexer::T_AS);
       
  1477         }
       
  1478 
       
  1479         $token = $this->_lexer->lookahead;
       
  1480         $aliasIdentificationVariable = $this->AliasIdentificationVariable();
       
  1481         $classMetadata = $this->_em->getClassMetadata($abstractSchemaName);
       
  1482 
       
  1483         // Building queryComponent
       
  1484         $queryComponent = array(
       
  1485             'metadata'     => $classMetadata,
       
  1486             'parent'       => null,
       
  1487             'relation'     => null,
       
  1488             'map'          => null,
       
  1489             'nestingLevel' => $this->_nestingLevel,
       
  1490             'token'        => $token
       
  1491         );
       
  1492         $this->_queryComponents[$aliasIdentificationVariable] = $queryComponent;
       
  1493 
       
  1494         return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
       
  1495     }
       
  1496 
       
  1497     /**
       
  1498      * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
       
  1499      * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
       
  1500      *
       
  1501      * @return array
       
  1502      */
       
  1503     public function PartialObjectExpression()
       
  1504     {
       
  1505         $this->match(Lexer::T_PARTIAL);
       
  1506 
       
  1507         $partialFieldSet = array();
       
  1508 
       
  1509         $identificationVariable = $this->IdentificationVariable();
       
  1510         $this->match(Lexer::T_DOT);
       
  1511 
       
  1512         $this->match(Lexer::T_OPEN_CURLY_BRACE);
       
  1513         $this->match(Lexer::T_IDENTIFIER);
       
  1514         $partialFieldSet[] = $this->_lexer->token['value'];
       
  1515 
       
  1516         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1517             $this->match(Lexer::T_COMMA);
       
  1518             $this->match(Lexer::T_IDENTIFIER);
       
  1519             $partialFieldSet[] = $this->_lexer->token['value'];
       
  1520         }
       
  1521         
       
  1522         $this->match(Lexer::T_CLOSE_CURLY_BRACE);
       
  1523 
       
  1524         $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
       
  1525 
       
  1526         // Defer PartialObjectExpression validation
       
  1527         $this->_deferredPartialObjectExpressions[] = array(
       
  1528             'expression'   => $partialObjectExpression,
       
  1529             'nestingLevel' => $this->_nestingLevel,
       
  1530             'token'        => $this->_lexer->token,
       
  1531         );
       
  1532 
       
  1533         return $partialObjectExpression;
       
  1534     }
       
  1535 
       
  1536     /**
       
  1537      * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
       
  1538      *          ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
       
  1539      *
       
  1540      * @return Doctrine\ORM\Query\AST\Join
       
  1541      */
       
  1542     public function Join()
       
  1543     {
       
  1544         // Check Join type
       
  1545         $joinType = AST\Join::JOIN_TYPE_INNER;
       
  1546 
       
  1547         if ($this->_lexer->isNextToken(Lexer::T_LEFT)) {
       
  1548             $this->match(Lexer::T_LEFT);
       
  1549 
       
  1550             // Possible LEFT OUTER join
       
  1551             if ($this->_lexer->isNextToken(Lexer::T_OUTER)) {
       
  1552                 $this->match(Lexer::T_OUTER);
       
  1553                 $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
       
  1554             } else {
       
  1555                 $joinType = AST\Join::JOIN_TYPE_LEFT;
       
  1556             }
       
  1557         } else if ($this->_lexer->isNextToken(Lexer::T_INNER)) {
       
  1558             $this->match(Lexer::T_INNER);
       
  1559         }
       
  1560 
       
  1561         $this->match(Lexer::T_JOIN);
       
  1562 
       
  1563         $joinPathExpression = $this->JoinAssociationPathExpression();
       
  1564 
       
  1565         if ($this->_lexer->isNextToken(Lexer::T_AS)) {
       
  1566             $this->match(Lexer::T_AS);
       
  1567         }
       
  1568 
       
  1569         $token = $this->_lexer->lookahead;
       
  1570         $aliasIdentificationVariable = $this->AliasIdentificationVariable();
       
  1571 
       
  1572         // Verify that the association exists.
       
  1573         $parentClass = $this->_queryComponents[$joinPathExpression->identificationVariable]['metadata'];
       
  1574         $assocField = $joinPathExpression->associationField;
       
  1575 
       
  1576         if ( ! $parentClass->hasAssociation($assocField)) {
       
  1577             $this->semanticalError(
       
  1578                 "Class " . $parentClass->name . " has no association named '$assocField'."
       
  1579             );
       
  1580         }
       
  1581 
       
  1582         $targetClassName = $parentClass->associationMappings[$assocField]['targetEntity'];
       
  1583 
       
  1584         // Building queryComponent
       
  1585         $joinQueryComponent = array(
       
  1586             'metadata'     => $this->_em->getClassMetadata($targetClassName),
       
  1587             'parent'       => $joinPathExpression->identificationVariable,
       
  1588             'relation'     => $parentClass->getAssociationMapping($assocField),
       
  1589             'map'          => null,
       
  1590             'nestingLevel' => $this->_nestingLevel,
       
  1591             'token'        => $token
       
  1592         );
       
  1593         $this->_queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
       
  1594 
       
  1595         // Create AST node
       
  1596         $join = new AST\Join($joinType, $joinPathExpression, $aliasIdentificationVariable);
       
  1597 
       
  1598         // Check for ad-hoc Join conditions
       
  1599         if ($this->_lexer->isNextToken(Lexer::T_WITH)) {
       
  1600             $this->match(Lexer::T_WITH);
       
  1601             $join->conditionalExpression = $this->ConditionalExpression();
       
  1602         }
       
  1603 
       
  1604         return $join;
       
  1605     }
       
  1606 
       
  1607     /**
       
  1608      * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
       
  1609      *
       
  1610      * @return Doctrine\ORM\Query\AST\IndexBy
       
  1611      */
       
  1612     public function IndexBy()
       
  1613     {
       
  1614         $this->match(Lexer::T_INDEX);
       
  1615         $this->match(Lexer::T_BY);
       
  1616         $pathExpr = $this->StateFieldPathExpression();
       
  1617 
       
  1618         // Add the INDEX BY info to the query component
       
  1619         $this->_queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
       
  1620 
       
  1621         return new AST\IndexBy($pathExpr);
       
  1622     }
       
  1623 
       
  1624     /**
       
  1625      * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
       
  1626      *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
       
  1627      *                      EntityTypeExpression
       
  1628      *
       
  1629      * @return mixed One of the possible expressions or subexpressions.
       
  1630      */
       
  1631     public function ScalarExpression()
       
  1632     {
       
  1633         $lookahead = $this->_lexer->lookahead['type'];
       
  1634         if ($lookahead === Lexer::T_IDENTIFIER) {
       
  1635             $this->_lexer->peek(); // lookahead => '.'
       
  1636             $this->_lexer->peek(); // lookahead => token after '.'
       
  1637             $peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
       
  1638             $this->_lexer->resetPeek();
       
  1639 
       
  1640             if ($this->_isMathOperator($peek)) {
       
  1641                 return $this->SimpleArithmeticExpression();
       
  1642             }
       
  1643 
       
  1644             return $this->StateFieldPathExpression();
       
  1645         } else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
       
  1646             return $this->SimpleArithmeticExpression();
       
  1647         } else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
       
  1648             // Since NULLIF and COALESCE can be identified as a function, 
       
  1649             // we need to check if before check for FunctionDeclaration
       
  1650             return $this->CaseExpression();
       
  1651         } else if ($this->_isFunction() || $this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
       
  1652             // We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator)
       
  1653             $this->_lexer->peek(); // "("
       
  1654             $peek = $this->_peekBeyondClosingParenthesis();
       
  1655 
       
  1656             if ($this->_isMathOperator($peek)) {
       
  1657                 return $this->SimpleArithmeticExpression();
       
  1658             }
       
  1659 
       
  1660             if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
       
  1661                 return $this->AggregateExpression();
       
  1662             } else {
       
  1663                 return $this->FunctionDeclaration();
       
  1664             }
       
  1665         } else if ($lookahead == Lexer::T_STRING) {
       
  1666             return $this->StringPrimary();
       
  1667         } else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
       
  1668             return $this->InputParameter();
       
  1669         } else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
       
  1670             $this->match($lookahead);
       
  1671             return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
       
  1672         } else {
       
  1673             $this->syntaxError();
       
  1674         }
       
  1675     }
       
  1676 
       
  1677     public function CaseExpression()
       
  1678     {
       
  1679         $lookahead = $this->_lexer->lookahead['type'];
       
  1680         
       
  1681         // if "CASE" "WHEN" => GeneralCaseExpression
       
  1682         // else if "CASE" => SimpleCaseExpression
       
  1683         // [DONE] else if "COALESCE" => CoalesceExpression
       
  1684         // [DONE] else if "NULLIF" => NullifExpression
       
  1685         switch ($lookahead) {
       
  1686             case Lexer::T_NULLIF:
       
  1687                 return $this->NullIfExpression();
       
  1688                 
       
  1689             case Lexer::T_COALESCE:
       
  1690                 return $this->CoalesceExpression();
       
  1691                 
       
  1692             default:
       
  1693                 $this->semanticalError('CaseExpression not yet supported.');
       
  1694                 return null;
       
  1695         }
       
  1696     }
       
  1697     
       
  1698     /**
       
  1699      * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
       
  1700      * 
       
  1701      * @return Doctrine\ORM\Query\AST\CoalesceExpression 
       
  1702      */
       
  1703     public function CoalesceExpression()
       
  1704     {
       
  1705         $this->match(Lexer::T_COALESCE);
       
  1706         $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  1707         
       
  1708         // Process ScalarExpressions (1..N)
       
  1709         $scalarExpressions = array();
       
  1710         $scalarExpressions[] = $this->ScalarExpression();
       
  1711 
       
  1712         while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  1713             $this->match(Lexer::T_COMMA);
       
  1714             $scalarExpressions[] = $this->ScalarExpression();
       
  1715         }
       
  1716         
       
  1717         $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  1718         
       
  1719         return new AST\CoalesceExpression($scalarExpressions);
       
  1720     }
       
  1721     
       
  1722     /**
       
  1723      * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
       
  1724      * 
       
  1725      * @return Doctrine\ORM\Query\AST\ExistsExpression 
       
  1726      */
       
  1727     public function NullIfExpression()
       
  1728     {
       
  1729         $this->match(Lexer::T_NULLIF);
       
  1730         $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  1731         
       
  1732         $firstExpression = $this->ScalarExpression();
       
  1733         $this->match(Lexer::T_COMMA);
       
  1734         $secondExpression = $this->ScalarExpression();
       
  1735         
       
  1736         $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  1737 
       
  1738         return new AST\NullIfExpression($firstExpression, $secondExpression);
       
  1739     }
       
  1740 
       
  1741     /**
       
  1742      * SelectExpression ::=
       
  1743      *      IdentificationVariable | StateFieldPathExpression |
       
  1744      *      (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
       
  1745      *
       
  1746      * @return Doctrine\ORM\Query\AST\SelectExpression
       
  1747      */
       
  1748     public function SelectExpression()
       
  1749     {
       
  1750         $expression = null;
       
  1751         $identVariable = null;
       
  1752         $fieldAliasIdentificationVariable = null;
       
  1753         $peek = $this->_lexer->glimpse();
       
  1754 
       
  1755         $supportsAlias = true;
       
  1756 
       
  1757         if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
       
  1758             if ($peek['value'] == '.') {
       
  1759                 // ScalarExpression
       
  1760                 $expression = $this->ScalarExpression();
       
  1761             } else {
       
  1762                 $supportsAlias = false;
       
  1763                 $expression = $identVariable = $this->IdentificationVariable();
       
  1764             }
       
  1765         } else if ($this->_lexer->lookahead['value'] == '(') {
       
  1766             if ($peek['type'] == Lexer::T_SELECT) {
       
  1767                 // Subselect
       
  1768                 $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  1769                 $expression = $this->Subselect();
       
  1770                 $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  1771             } else {
       
  1772                 // Shortcut: ScalarExpression => SimpleArithmeticExpression
       
  1773                 $expression = $this->SimpleArithmeticExpression();
       
  1774             }
       
  1775         } else if ($this->_isFunction()) {
       
  1776             $this->_lexer->peek(); // "("
       
  1777             
       
  1778             $lookaheadType = $this->_lexer->lookahead['type'];
       
  1779             $beyond        = $this->_peekBeyondClosingParenthesis();
       
  1780             
       
  1781             if ($this->_isMathOperator($beyond)) {
       
  1782                 $expression = $this->ScalarExpression();
       
  1783             } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
       
  1784                 $expression = $this->AggregateExpression();
       
  1785             } else if (in_array ($lookaheadType, array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) {
       
  1786                 $expression = $this->CaseExpression();
       
  1787             } else {
       
  1788                 // Shortcut: ScalarExpression => Function
       
  1789                 $expression = $this->FunctionDeclaration();
       
  1790             }
       
  1791         } else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
       
  1792             $supportsAlias = false;
       
  1793             $expression = $this->PartialObjectExpression();
       
  1794             $identVariable = $expression->identificationVariable;
       
  1795         } else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
       
  1796                 $this->_lexer->lookahead['type'] == Lexer::T_FLOAT ||
       
  1797                 $this->_lexer->lookahead['type'] == Lexer::T_STRING) {
       
  1798             // Shortcut: ScalarExpression => SimpleArithmeticExpression
       
  1799             $expression = $this->SimpleArithmeticExpression();
       
  1800         } else {
       
  1801             $this->syntaxError('IdentificationVariable | StateFieldPathExpression'
       
  1802                     . ' | AggregateExpression | "(" Subselect ")" | ScalarExpression',
       
  1803                     $this->_lexer->lookahead);
       
  1804         }
       
  1805 
       
  1806         if ($supportsAlias) {
       
  1807             if ($this->_lexer->isNextToken(Lexer::T_AS)) {
       
  1808                 $this->match(Lexer::T_AS);
       
  1809             }
       
  1810 
       
  1811             if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
       
  1812                 $token = $this->_lexer->lookahead;
       
  1813                 $fieldAliasIdentificationVariable = $this->AliasResultVariable();
       
  1814 
       
  1815                 // Include AliasResultVariable in query components.
       
  1816                 $this->_queryComponents[$fieldAliasIdentificationVariable] = array(
       
  1817                     'resultVariable' => $expression,
       
  1818                     'nestingLevel'   => $this->_nestingLevel,
       
  1819                     'token'          => $token,
       
  1820                 );
       
  1821             }
       
  1822         }
       
  1823 
       
  1824         $expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
       
  1825         if (!$supportsAlias) {
       
  1826             $this->_identVariableExpressions[$identVariable] = $expr;
       
  1827         }
       
  1828         return $expr;
       
  1829     }
       
  1830 
       
  1831     /**
       
  1832      * SimpleSelectExpression ::=
       
  1833      *      StateFieldPathExpression | IdentificationVariable |
       
  1834      *      ((AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable])
       
  1835      *
       
  1836      * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
       
  1837      */
       
  1838     public function SimpleSelectExpression()
       
  1839     {
       
  1840         $peek = $this->_lexer->glimpse();
       
  1841 
       
  1842         if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
       
  1843             // SingleValuedPathExpression | IdentificationVariable
       
  1844             if ($peek['value'] == '.') {
       
  1845                 $expression = $this->StateFieldPathExpression();
       
  1846             } else {
       
  1847                 $expression = $this->IdentificationVariable();
       
  1848             }
       
  1849 
       
  1850             return new AST\SimpleSelectExpression($expression);
       
  1851         } else if ($this->_lexer->lookahead['value'] == '(') {
       
  1852             if ($peek['type'] == Lexer::T_SELECT) {
       
  1853                 // Subselect
       
  1854                 $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  1855                 $expression = $this->Subselect();
       
  1856                 $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  1857             } else {
       
  1858                 // Shortcut: ScalarExpression => SimpleArithmeticExpression
       
  1859                 $expression = $this->SimpleArithmeticExpression();
       
  1860             }
       
  1861 
       
  1862             return new AST\SimpleSelectExpression($expression);
       
  1863         }
       
  1864 
       
  1865         $this->_lexer->peek();
       
  1866 
       
  1867         $expression = $this->ScalarExpression();
       
  1868 
       
  1869         $expr = new AST\SimpleSelectExpression($expression);
       
  1870 
       
  1871         if ($this->_lexer->isNextToken(Lexer::T_AS)) {
       
  1872             $this->match(Lexer::T_AS);
       
  1873         }
       
  1874 
       
  1875         if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
       
  1876             $token = $this->_lexer->lookahead;
       
  1877             $resultVariable = $this->AliasResultVariable();
       
  1878             $expr->fieldIdentificationVariable = $resultVariable;
       
  1879 
       
  1880             // Include AliasResultVariable in query components.
       
  1881             $this->_queryComponents[$resultVariable] = array(
       
  1882                 'resultvariable' => $expr,
       
  1883                 'nestingLevel'   => $this->_nestingLevel,
       
  1884                 'token'          => $token,
       
  1885             );
       
  1886         }
       
  1887 
       
  1888         return $expr;
       
  1889     }
       
  1890 
       
  1891     /**
       
  1892      * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
       
  1893      *
       
  1894      * @return \Doctrine\ORM\Query\AST\ConditionalExpression
       
  1895      */
       
  1896     public function ConditionalExpression()
       
  1897     {
       
  1898         $conditionalTerms = array();
       
  1899         $conditionalTerms[] = $this->ConditionalTerm();
       
  1900 
       
  1901         while ($this->_lexer->isNextToken(Lexer::T_OR)) {
       
  1902             $this->match(Lexer::T_OR);
       
  1903             $conditionalTerms[] = $this->ConditionalTerm();
       
  1904         }
       
  1905 
       
  1906         // Phase 1 AST optimization: Prevent AST\ConditionalExpression
       
  1907         // if only one AST\ConditionalTerm is defined
       
  1908         if (count($conditionalTerms) == 1) {
       
  1909             return $conditionalTerms[0];
       
  1910         }
       
  1911 
       
  1912         return new AST\ConditionalExpression($conditionalTerms);
       
  1913     }
       
  1914 
       
  1915     /**
       
  1916      * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
       
  1917      *
       
  1918      * @return \Doctrine\ORM\Query\AST\ConditionalTerm
       
  1919      */
       
  1920     public function ConditionalTerm()
       
  1921     {
       
  1922         $conditionalFactors = array();
       
  1923         $conditionalFactors[] = $this->ConditionalFactor();
       
  1924 
       
  1925         while ($this->_lexer->isNextToken(Lexer::T_AND)) {
       
  1926             $this->match(Lexer::T_AND);
       
  1927             $conditionalFactors[] = $this->ConditionalFactor();
       
  1928         }
       
  1929 
       
  1930         // Phase 1 AST optimization: Prevent AST\ConditionalTerm
       
  1931         // if only one AST\ConditionalFactor is defined
       
  1932         if (count($conditionalFactors) == 1) {
       
  1933             return $conditionalFactors[0];
       
  1934         }
       
  1935 
       
  1936         return new AST\ConditionalTerm($conditionalFactors);
       
  1937     }
       
  1938 
       
  1939     /**
       
  1940      * ConditionalFactor ::= ["NOT"] ConditionalPrimary
       
  1941      *
       
  1942      * @return \Doctrine\ORM\Query\AST\ConditionalFactor
       
  1943      */
       
  1944     public function ConditionalFactor()
       
  1945     {
       
  1946         $not = false;
       
  1947 
       
  1948         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  1949             $this->match(Lexer::T_NOT);
       
  1950             $not = true;
       
  1951         }
       
  1952         
       
  1953         $conditionalPrimary = $this->ConditionalPrimary();
       
  1954 
       
  1955         // Phase 1 AST optimization: Prevent AST\ConditionalFactor
       
  1956         // if only one AST\ConditionalPrimary is defined
       
  1957         if ( ! $not) {
       
  1958             return $conditionalPrimary;
       
  1959         }
       
  1960 
       
  1961         $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary);
       
  1962         $conditionalFactor->not = $not;
       
  1963 
       
  1964         return $conditionalFactor;
       
  1965     }
       
  1966 
       
  1967     /**
       
  1968      * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
       
  1969      *
       
  1970      * @return Doctrine\ORM\Query\AST\ConditionalPrimary
       
  1971      */
       
  1972     public function ConditionalPrimary()
       
  1973     {
       
  1974         $condPrimary = new AST\ConditionalPrimary;
       
  1975 
       
  1976         if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
       
  1977             // Peek beyond the matching closing paranthesis ')'
       
  1978             $peek = $this->_peekBeyondClosingParenthesis();
       
  1979 
       
  1980             if (in_array($peek['value'], array("=",  "<", "<=", "<>", ">", ">=", "!=")) ||
       
  1981                     $peek['type'] === Lexer::T_NOT ||
       
  1982                     $peek['type'] === Lexer::T_BETWEEN ||
       
  1983                     $peek['type'] === Lexer::T_LIKE ||
       
  1984                     $peek['type'] === Lexer::T_IN ||
       
  1985                     $peek['type'] === Lexer::T_IS ||
       
  1986                     $peek['type'] === Lexer::T_EXISTS) {
       
  1987                 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
       
  1988             } else {
       
  1989                 $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  1990                 $condPrimary->conditionalExpression = $this->ConditionalExpression();
       
  1991                 $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  1992             }
       
  1993         } else {
       
  1994             $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
       
  1995         }
       
  1996 
       
  1997         return $condPrimary;
       
  1998     }
       
  1999 
       
  2000     /**
       
  2001      * SimpleConditionalExpression ::=
       
  2002      *      ComparisonExpression | BetweenExpression | LikeExpression |
       
  2003      *      InExpression | NullComparisonExpression | ExistsExpression |
       
  2004      *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
       
  2005      *      InstanceOfExpression
       
  2006      */
       
  2007     public function SimpleConditionalExpression()
       
  2008     {
       
  2009         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2010             $token = $this->_lexer->glimpse();
       
  2011         } else {
       
  2012             $token = $this->_lexer->lookahead;
       
  2013         }
       
  2014 
       
  2015         if ($token['type'] === Lexer::T_EXISTS) {
       
  2016             return $this->ExistsExpression();
       
  2017         }
       
  2018 
       
  2019         $peek = $this->_lexer->glimpse();
       
  2020 
       
  2021         if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER) {
       
  2022             if ($peek['value'] == '(') {
       
  2023                 // Peek beyond the matching closing paranthesis ')'
       
  2024                 $this->_lexer->peek();
       
  2025                 $token = $this->_peekBeyondClosingParenthesis();
       
  2026             } else {
       
  2027                 // Peek beyond the PathExpression (or InputParameter)
       
  2028                 $peek = $this->_lexer->peek();
       
  2029 
       
  2030                 while ($peek['value'] === '.') {
       
  2031                     $this->_lexer->peek();
       
  2032                     $peek = $this->_lexer->peek();
       
  2033                 }
       
  2034 
       
  2035                 // Also peek beyond a NOT if there is one
       
  2036                 if ($peek['type'] === Lexer::T_NOT) {
       
  2037                     $peek = $this->_lexer->peek();
       
  2038                 }
       
  2039 
       
  2040                 $token = $peek;
       
  2041 
       
  2042                 // We need to go even further in case of IS (differenciate between NULL and EMPTY)
       
  2043                 $lookahead = $this->_lexer->peek();
       
  2044 
       
  2045                 // Also peek beyond a NOT if there is one
       
  2046                 if ($lookahead['type'] === Lexer::T_NOT) {
       
  2047                     $lookahead = $this->_lexer->peek();
       
  2048                 }
       
  2049 
       
  2050                 $this->_lexer->resetPeek();
       
  2051             }
       
  2052         }
       
  2053 
       
  2054         switch ($token['type']) {
       
  2055             case Lexer::T_BETWEEN:
       
  2056                 return $this->BetweenExpression();
       
  2057             case Lexer::T_LIKE:
       
  2058                 return $this->LikeExpression();
       
  2059             case Lexer::T_IN:
       
  2060                 return $this->InExpression();
       
  2061             case Lexer::T_INSTANCE:
       
  2062                 return $this->InstanceOfExpression();
       
  2063             case Lexer::T_IS:
       
  2064                 if ($lookahead['type'] == Lexer::T_NULL) {
       
  2065                     return $this->NullComparisonExpression();
       
  2066                 }
       
  2067                 return $this->EmptyCollectionComparisonExpression();
       
  2068             case Lexer::T_MEMBER:
       
  2069                 return $this->CollectionMemberExpression();
       
  2070             default:
       
  2071                 return $this->ComparisonExpression();
       
  2072         }
       
  2073     }
       
  2074 
       
  2075     /**
       
  2076      * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
       
  2077      *
       
  2078      * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
       
  2079      */
       
  2080     public function EmptyCollectionComparisonExpression()
       
  2081     {
       
  2082         $emptyColletionCompExpr = new AST\EmptyCollectionComparisonExpression(
       
  2083             $this->CollectionValuedPathExpression()
       
  2084         );
       
  2085         $this->match(Lexer::T_IS);
       
  2086 
       
  2087         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2088             $this->match(Lexer::T_NOT);
       
  2089             $emptyColletionCompExpr->not = true;
       
  2090         }
       
  2091 
       
  2092         $this->match(Lexer::T_EMPTY);
       
  2093 
       
  2094         return $emptyColletionCompExpr;
       
  2095     }
       
  2096 
       
  2097     /**
       
  2098      * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
       
  2099      *
       
  2100      * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
       
  2101      * SimpleEntityExpression ::= IdentificationVariable | InputParameter
       
  2102      *
       
  2103      * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
       
  2104      */
       
  2105     public function CollectionMemberExpression()
       
  2106     {
       
  2107         $not = false;
       
  2108 
       
  2109         $entityExpr = $this->EntityExpression();
       
  2110 
       
  2111         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2112             $not = true;
       
  2113             $this->match(Lexer::T_NOT);
       
  2114         }
       
  2115 
       
  2116         $this->match(Lexer::T_MEMBER);
       
  2117 
       
  2118         if ($this->_lexer->isNextToken(Lexer::T_OF)) {
       
  2119             $this->match(Lexer::T_OF);
       
  2120         }
       
  2121 
       
  2122         $collMemberExpr = new AST\CollectionMemberExpression(
       
  2123             $entityExpr, $this->CollectionValuedPathExpression()
       
  2124         );
       
  2125         $collMemberExpr->not = $not;
       
  2126 
       
  2127         return $collMemberExpr;
       
  2128     }
       
  2129 
       
  2130     /**
       
  2131      * Literal ::= string | char | integer | float | boolean
       
  2132      *
       
  2133      * @return string
       
  2134      */
       
  2135     public function Literal()
       
  2136     {
       
  2137         switch ($this->_lexer->lookahead['type']) {
       
  2138             case Lexer::T_STRING:
       
  2139                 $this->match(Lexer::T_STRING);
       
  2140                 return new AST\Literal(AST\Literal::STRING, $this->_lexer->token['value']);
       
  2141 
       
  2142             case Lexer::T_INTEGER:
       
  2143             case Lexer::T_FLOAT:
       
  2144                 $this->match(
       
  2145                     $this->_lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
       
  2146                 );
       
  2147                 return new AST\Literal(AST\Literal::NUMERIC, $this->_lexer->token['value']);
       
  2148 
       
  2149             case Lexer::T_TRUE:
       
  2150             case Lexer::T_FALSE:
       
  2151                 $this->match(
       
  2152                     $this->_lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
       
  2153                 );
       
  2154                 return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
       
  2155 
       
  2156             default:
       
  2157                 $this->syntaxError('Literal');
       
  2158         }
       
  2159     }
       
  2160 
       
  2161     /**
       
  2162      * InParameter ::= Literal | InputParameter
       
  2163      *
       
  2164      * @return string | \Doctrine\ORM\Query\AST\InputParameter
       
  2165      */
       
  2166     public function InParameter()
       
  2167     {
       
  2168         if ($this->_lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
       
  2169             return $this->InputParameter();
       
  2170         }
       
  2171 
       
  2172         return $this->Literal();
       
  2173     }
       
  2174 
       
  2175     /**
       
  2176      * InputParameter ::= PositionalParameter | NamedParameter
       
  2177      *
       
  2178      * @return \Doctrine\ORM\Query\AST\InputParameter
       
  2179      */
       
  2180     public function InputParameter()
       
  2181     {
       
  2182         $this->match(Lexer::T_INPUT_PARAMETER);
       
  2183 
       
  2184         return new AST\InputParameter($this->_lexer->token['value']);
       
  2185     }
       
  2186 
       
  2187     /**
       
  2188      * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
       
  2189      *
       
  2190      * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
       
  2191      */
       
  2192     public function ArithmeticExpression()
       
  2193     {
       
  2194         $expr = new AST\ArithmeticExpression;
       
  2195 
       
  2196         if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
       
  2197             $peek = $this->_lexer->glimpse();
       
  2198 
       
  2199             if ($peek['type'] === Lexer::T_SELECT) {
       
  2200                 $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2201                 $expr->subselect = $this->Subselect();
       
  2202                 $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2203 
       
  2204                 return $expr;
       
  2205             }
       
  2206         }
       
  2207 
       
  2208         $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
       
  2209 
       
  2210         return $expr;
       
  2211     }
       
  2212 
       
  2213     /**
       
  2214      * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
       
  2215      *
       
  2216      * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
       
  2217      */
       
  2218     public function SimpleArithmeticExpression()
       
  2219     {
       
  2220         $terms = array();
       
  2221         $terms[] = $this->ArithmeticTerm();
       
  2222 
       
  2223         while (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) {
       
  2224             $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
       
  2225 
       
  2226             $terms[] = $this->_lexer->token['value'];
       
  2227             $terms[] = $this->ArithmeticTerm();
       
  2228         }
       
  2229 
       
  2230         // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
       
  2231         // if only one AST\ArithmeticTerm is defined
       
  2232         if (count($terms) == 1) {
       
  2233             return $terms[0];
       
  2234         }
       
  2235 
       
  2236         return new AST\SimpleArithmeticExpression($terms);
       
  2237     }
       
  2238 
       
  2239     /**
       
  2240      * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
       
  2241      *
       
  2242      * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
       
  2243      */
       
  2244     public function ArithmeticTerm()
       
  2245     {
       
  2246         $factors = array();
       
  2247         $factors[] = $this->ArithmeticFactor();
       
  2248 
       
  2249         while (($isMult = $this->_lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->_lexer->isNextToken(Lexer::T_DIVIDE)) {
       
  2250             $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
       
  2251 
       
  2252             $factors[] = $this->_lexer->token['value'];
       
  2253             $factors[] = $this->ArithmeticFactor();
       
  2254         }
       
  2255 
       
  2256         // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
       
  2257         // if only one AST\ArithmeticFactor is defined
       
  2258         if (count($factors) == 1) {
       
  2259             return $factors[0];
       
  2260         }
       
  2261 
       
  2262         return new AST\ArithmeticTerm($factors);
       
  2263     }
       
  2264 
       
  2265     /**
       
  2266      * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
       
  2267      *
       
  2268      * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
       
  2269      */
       
  2270     public function ArithmeticFactor()
       
  2271     {
       
  2272         $sign = null;
       
  2273 
       
  2274         if (($isPlus = $this->_lexer->isNextToken(Lexer::T_PLUS)) || $this->_lexer->isNextToken(Lexer::T_MINUS)) {
       
  2275             $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
       
  2276             $sign = $isPlus;
       
  2277         }
       
  2278         
       
  2279         $primary = $this->ArithmeticPrimary();
       
  2280 
       
  2281         // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
       
  2282         // if only one AST\ArithmeticPrimary is defined
       
  2283         if ($sign === null) {
       
  2284             return $primary;
       
  2285         }
       
  2286 
       
  2287         return new AST\ArithmeticFactor($primary, $sign);
       
  2288     }
       
  2289 
       
  2290     /**
       
  2291      * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
       
  2292      *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
       
  2293      *          | FunctionsReturningDatetime | IdentificationVariable
       
  2294      */
       
  2295     public function ArithmeticPrimary()
       
  2296     {
       
  2297         if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
       
  2298             $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2299             $expr = $this->SimpleArithmeticExpression();
       
  2300 
       
  2301             $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2302 
       
  2303             return $expr;
       
  2304         }
       
  2305 
       
  2306         switch ($this->_lexer->lookahead['type']) {
       
  2307             case Lexer::T_IDENTIFIER:
       
  2308                 $peek = $this->_lexer->glimpse();
       
  2309 
       
  2310                 if ($peek['value'] == '(') {
       
  2311                     return $this->FunctionDeclaration();
       
  2312                 }
       
  2313 
       
  2314                 if ($peek['value'] == '.') {
       
  2315                     return $this->SingleValuedPathExpression();
       
  2316                 }
       
  2317 
       
  2318                 return $this->StateFieldPathExpression();
       
  2319 
       
  2320             case Lexer::T_INPUT_PARAMETER:
       
  2321                 return $this->InputParameter();
       
  2322 
       
  2323             default:
       
  2324                 $peek = $this->_lexer->glimpse();
       
  2325 
       
  2326                 if ($peek['value'] == '(') {
       
  2327                     if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
       
  2328                         return $this->AggregateExpression();
       
  2329                     }
       
  2330 
       
  2331                     return $this->FunctionDeclaration();
       
  2332                 } else {
       
  2333                     return $this->Literal();
       
  2334                 }
       
  2335         }
       
  2336     }
       
  2337 
       
  2338     /**
       
  2339      * StringExpression ::= StringPrimary | "(" Subselect ")"
       
  2340      *
       
  2341      * @return \Doctrine\ORM\Query\AST\StringPrimary |
       
  2342      *         \Doctrine]ORM\Query\AST\Subselect
       
  2343      */
       
  2344     public function StringExpression()
       
  2345     {
       
  2346         if ($this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
       
  2347             $peek = $this->_lexer->glimpse();
       
  2348 
       
  2349             if ($peek['type'] === Lexer::T_SELECT) {
       
  2350                 $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2351                 $expr = $this->Subselect();
       
  2352                 $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2353 
       
  2354                 return $expr;
       
  2355             }
       
  2356         }
       
  2357 
       
  2358         return $this->StringPrimary();
       
  2359     }
       
  2360 
       
  2361     /**
       
  2362      * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression
       
  2363      */
       
  2364     public function StringPrimary()
       
  2365     {
       
  2366         if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
       
  2367             $peek = $this->_lexer->glimpse();
       
  2368 
       
  2369             if ($peek['value'] == '.') {
       
  2370                 return $this->StateFieldPathExpression();
       
  2371             } else if ($peek['value'] == '(') {
       
  2372                 // do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
       
  2373                 return $this->FunctionDeclaration();
       
  2374             } else {
       
  2375                 $this->syntaxError("'.' or '('");
       
  2376             }
       
  2377         } else if ($this->_lexer->isNextToken(Lexer::T_STRING)) {
       
  2378             $this->match(Lexer::T_STRING);
       
  2379 
       
  2380             return $this->_lexer->token['value'];
       
  2381         } else if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
       
  2382             return $this->InputParameter();
       
  2383         } else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
       
  2384             return $this->AggregateExpression();
       
  2385         }
       
  2386 
       
  2387         $this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
       
  2388     }
       
  2389 
       
  2390     /**
       
  2391      * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
       
  2392      *
       
  2393      * @return \Doctrine\ORM\Query\AST\SingleValuedAssociationPathExpression |
       
  2394      *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
       
  2395      */
       
  2396     public function EntityExpression()
       
  2397     {
       
  2398         $glimpse = $this->_lexer->glimpse();
       
  2399 
       
  2400         if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
       
  2401             return $this->SingleValuedAssociationPathExpression();
       
  2402         }
       
  2403 
       
  2404         return $this->SimpleEntityExpression();
       
  2405     }
       
  2406 
       
  2407     /**
       
  2408      * SimpleEntityExpression ::= IdentificationVariable | InputParameter
       
  2409      *
       
  2410      * @return string | \Doctrine\ORM\Query\AST\InputParameter
       
  2411      */
       
  2412     public function SimpleEntityExpression()
       
  2413     {
       
  2414         if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
       
  2415             return $this->InputParameter();
       
  2416         }
       
  2417 
       
  2418         return $this->IdentificationVariable();
       
  2419     }
       
  2420 
       
  2421     /**
       
  2422      * AggregateExpression ::=
       
  2423      *  ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
       
  2424      *  "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
       
  2425      *
       
  2426      * @return \Doctrine\ORM\Query\AST\AggregateExpression
       
  2427      */
       
  2428     public function AggregateExpression()
       
  2429     {
       
  2430         $isDistinct = false;
       
  2431         $functionName = '';
       
  2432 
       
  2433         if ($this->_lexer->isNextToken(Lexer::T_COUNT)) {
       
  2434             $this->match(Lexer::T_COUNT);
       
  2435             $functionName = $this->_lexer->token['value'];
       
  2436             $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2437 
       
  2438             if ($this->_lexer->isNextToken(Lexer::T_DISTINCT)) {
       
  2439                 $this->match(Lexer::T_DISTINCT);
       
  2440                 $isDistinct = true;
       
  2441             }
       
  2442 
       
  2443             $pathExp = $this->SingleValuedPathExpression();
       
  2444             $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2445         } else {
       
  2446             if ($this->_lexer->isNextToken(Lexer::T_AVG)) {
       
  2447                 $this->match(Lexer::T_AVG);
       
  2448             } else if ($this->_lexer->isNextToken(Lexer::T_MAX)) {
       
  2449                 $this->match(Lexer::T_MAX);
       
  2450             } else if ($this->_lexer->isNextToken(Lexer::T_MIN)) {
       
  2451                 $this->match(Lexer::T_MIN);
       
  2452             } else if ($this->_lexer->isNextToken(Lexer::T_SUM)) {
       
  2453                 $this->match(Lexer::T_SUM);
       
  2454             } else {
       
  2455                 $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
       
  2456             }
       
  2457 
       
  2458             $functionName = $this->_lexer->token['value'];
       
  2459             $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2460             $pathExp = $this->SimpleArithmeticExpression();
       
  2461             $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2462         }
       
  2463 
       
  2464         return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
       
  2465     }
       
  2466 
       
  2467     /**
       
  2468      * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
       
  2469      *
       
  2470      * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
       
  2471      */
       
  2472     public function QuantifiedExpression()
       
  2473     {
       
  2474         $type = '';
       
  2475 
       
  2476         if ($this->_lexer->isNextToken(Lexer::T_ALL)) {
       
  2477             $this->match(Lexer::T_ALL);
       
  2478             $type = 'ALL';
       
  2479         } else if ($this->_lexer->isNextToken(Lexer::T_ANY)) {
       
  2480             $this->match(Lexer::T_ANY);
       
  2481              $type = 'ANY';
       
  2482         } else if ($this->_lexer->isNextToken(Lexer::T_SOME)) {
       
  2483             $this->match(Lexer::T_SOME);
       
  2484              $type = 'SOME';
       
  2485         } else {
       
  2486             $this->syntaxError('ALL, ANY or SOME');
       
  2487         }
       
  2488 
       
  2489         $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2490         $qExpr = new AST\QuantifiedExpression($this->Subselect());
       
  2491         $qExpr->type = $type;
       
  2492         $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2493 
       
  2494         return $qExpr;
       
  2495     }
       
  2496 
       
  2497     /**
       
  2498      * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
       
  2499      *
       
  2500      * @return \Doctrine\ORM\Query\AST\BetweenExpression
       
  2501      */
       
  2502     public function BetweenExpression()
       
  2503     {
       
  2504         $not = false;
       
  2505         $arithExpr1 = $this->ArithmeticExpression();
       
  2506 
       
  2507         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2508             $this->match(Lexer::T_NOT);
       
  2509             $not = true;
       
  2510         }
       
  2511 
       
  2512         $this->match(Lexer::T_BETWEEN);
       
  2513         $arithExpr2 = $this->ArithmeticExpression();
       
  2514         $this->match(Lexer::T_AND);
       
  2515         $arithExpr3 = $this->ArithmeticExpression();
       
  2516 
       
  2517         $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
       
  2518         $betweenExpr->not = $not;
       
  2519 
       
  2520         return $betweenExpr;
       
  2521     }
       
  2522 
       
  2523     /**
       
  2524      * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
       
  2525      *
       
  2526      * @return \Doctrine\ORM\Query\AST\ComparisonExpression
       
  2527      */
       
  2528     public function ComparisonExpression()
       
  2529     {
       
  2530         $peek = $this->_lexer->glimpse();
       
  2531 
       
  2532         $leftExpr = $this->ArithmeticExpression();
       
  2533         $operator = $this->ComparisonOperator();
       
  2534 
       
  2535         if ($this->_isNextAllAnySome()) {
       
  2536             $rightExpr = $this->QuantifiedExpression();
       
  2537         } else {
       
  2538             $rightExpr = $this->ArithmeticExpression();
       
  2539         }
       
  2540 
       
  2541         return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
       
  2542     }
       
  2543 
       
  2544     /**
       
  2545      * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
       
  2546      *
       
  2547      * @return \Doctrine\ORM\Query\AST\InExpression
       
  2548      */
       
  2549     public function InExpression()
       
  2550     {
       
  2551         $inExpression = new AST\InExpression($this->SingleValuedPathExpression());
       
  2552 
       
  2553         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2554             $this->match(Lexer::T_NOT);
       
  2555             $inExpression->not = true;
       
  2556         }
       
  2557 
       
  2558         $this->match(Lexer::T_IN);
       
  2559         $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2560 
       
  2561         if ($this->_lexer->isNextToken(Lexer::T_SELECT)) {
       
  2562             $inExpression->subselect = $this->Subselect();
       
  2563         } else {
       
  2564             $literals = array();
       
  2565             $literals[] = $this->InParameter();
       
  2566 
       
  2567             while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
       
  2568                 $this->match(Lexer::T_COMMA);
       
  2569                 $literals[] = $this->InParameter();
       
  2570             }
       
  2571 
       
  2572             $inExpression->literals = $literals;
       
  2573         }
       
  2574 
       
  2575         $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2576 
       
  2577         return $inExpression;
       
  2578     }
       
  2579 
       
  2580     /**
       
  2581      * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
       
  2582      *
       
  2583      * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
       
  2584      */
       
  2585     public function InstanceOfExpression()
       
  2586     {
       
  2587         $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
       
  2588 
       
  2589         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2590             $this->match(Lexer::T_NOT);
       
  2591             $instanceOfExpression->not = true;
       
  2592         }
       
  2593 
       
  2594         $this->match(Lexer::T_INSTANCE);
       
  2595 
       
  2596         if ($this->_lexer->isNextToken(Lexer::T_OF)) {
       
  2597             $this->match(Lexer::T_OF);
       
  2598         }
       
  2599 
       
  2600         if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
       
  2601             $this->match(Lexer::T_INPUT_PARAMETER);
       
  2602             $exprValue = new AST\InputParameter($this->_lexer->token['value']);
       
  2603         } else {
       
  2604             $exprValue = $this->AliasIdentificationVariable();
       
  2605         }
       
  2606 
       
  2607         $instanceOfExpression->value = $exprValue;
       
  2608         
       
  2609         return $instanceOfExpression;
       
  2610     }
       
  2611 
       
  2612     /**
       
  2613      * LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
       
  2614      *
       
  2615      * @return \Doctrine\ORM\Query\AST\LikeExpression
       
  2616      */
       
  2617     public function LikeExpression()
       
  2618     {
       
  2619         $stringExpr = $this->StringExpression();
       
  2620         $not = false;
       
  2621 
       
  2622         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2623             $this->match(Lexer::T_NOT);
       
  2624             $not = true;
       
  2625         }
       
  2626 
       
  2627         $this->match(Lexer::T_LIKE);
       
  2628 
       
  2629         if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
       
  2630             $this->match(Lexer::T_INPUT_PARAMETER);
       
  2631             $stringPattern = new AST\InputParameter($this->_lexer->token['value']);
       
  2632         } else {
       
  2633             $this->match(Lexer::T_STRING);
       
  2634             $stringPattern = $this->_lexer->token['value'];
       
  2635         }
       
  2636 
       
  2637         $escapeChar = null;
       
  2638 
       
  2639         if ($this->_lexer->lookahead['type'] === Lexer::T_ESCAPE) {
       
  2640             $this->match(Lexer::T_ESCAPE);
       
  2641             $this->match(Lexer::T_STRING);
       
  2642             $escapeChar = $this->_lexer->token['value'];
       
  2643         }
       
  2644 
       
  2645         $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
       
  2646         $likeExpr->not = $not;
       
  2647 
       
  2648         return $likeExpr;
       
  2649     }
       
  2650 
       
  2651     /**
       
  2652      * NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
       
  2653      *
       
  2654      * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
       
  2655      */
       
  2656     public function NullComparisonExpression()
       
  2657     {
       
  2658         if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
       
  2659             $this->match(Lexer::T_INPUT_PARAMETER);
       
  2660             $expr = new AST\InputParameter($this->_lexer->token['value']);
       
  2661         } else {
       
  2662             $expr = $this->SingleValuedPathExpression();
       
  2663         }
       
  2664 
       
  2665         $nullCompExpr = new AST\NullComparisonExpression($expr);
       
  2666         $this->match(Lexer::T_IS);
       
  2667 
       
  2668         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2669             $this->match(Lexer::T_NOT);
       
  2670             $nullCompExpr->not = true;
       
  2671         }
       
  2672 
       
  2673         $this->match(Lexer::T_NULL);
       
  2674 
       
  2675         return $nullCompExpr;
       
  2676     }
       
  2677 
       
  2678     /**
       
  2679      * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
       
  2680      *
       
  2681      * @return \Doctrine\ORM\Query\AST\ExistsExpression
       
  2682      */
       
  2683     public function ExistsExpression()
       
  2684     {
       
  2685         $not = false;
       
  2686 
       
  2687         if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
       
  2688             $this->match(Lexer::T_NOT);
       
  2689             $not = true;
       
  2690         }
       
  2691 
       
  2692         $this->match(Lexer::T_EXISTS);
       
  2693         $this->match(Lexer::T_OPEN_PARENTHESIS);
       
  2694         $existsExpression = new AST\ExistsExpression($this->Subselect());
       
  2695         $existsExpression->not = $not;
       
  2696         $this->match(Lexer::T_CLOSE_PARENTHESIS);
       
  2697 
       
  2698         return $existsExpression;
       
  2699     }
       
  2700 
       
  2701     /**
       
  2702      * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
       
  2703      *
       
  2704      * @return string
       
  2705      */
       
  2706     public function ComparisonOperator()
       
  2707     {
       
  2708         switch ($this->_lexer->lookahead['value']) {
       
  2709             case '=':
       
  2710                 $this->match(Lexer::T_EQUALS);
       
  2711 
       
  2712                 return '=';
       
  2713 
       
  2714             case '<':
       
  2715                 $this->match(Lexer::T_LOWER_THAN);
       
  2716                 $operator = '<';
       
  2717 
       
  2718                 if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) {
       
  2719                     $this->match(Lexer::T_EQUALS);
       
  2720                     $operator .= '=';
       
  2721                 } else if ($this->_lexer->isNextToken(Lexer::T_GREATER_THAN)) {
       
  2722                     $this->match(Lexer::T_GREATER_THAN);
       
  2723                     $operator .= '>';
       
  2724                 }
       
  2725 
       
  2726                 return $operator;
       
  2727 
       
  2728             case '>':
       
  2729                 $this->match(Lexer::T_GREATER_THAN);
       
  2730                 $operator = '>';
       
  2731 
       
  2732                 if ($this->_lexer->isNextToken(Lexer::T_EQUALS)) {
       
  2733                     $this->match(Lexer::T_EQUALS);
       
  2734                     $operator .= '=';
       
  2735                 }
       
  2736 
       
  2737                 return $operator;
       
  2738 
       
  2739             case '!':
       
  2740                 $this->match(Lexer::T_NEGATE);
       
  2741                 $this->match(Lexer::T_EQUALS);
       
  2742 
       
  2743                 return '<>';
       
  2744 
       
  2745             default:
       
  2746                 $this->syntaxError('=, <, <=, <>, >, >=, !=');
       
  2747         }
       
  2748     }
       
  2749 
       
  2750     /**
       
  2751      * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
       
  2752      */
       
  2753     public function FunctionDeclaration()
       
  2754     {
       
  2755         $token = $this->_lexer->lookahead;
       
  2756         $funcName = strtolower($token['value']);
       
  2757 
       
  2758         // Check for built-in functions first!
       
  2759         if (isset(self::$_STRING_FUNCTIONS[$funcName])) {
       
  2760             return $this->FunctionsReturningStrings();
       
  2761         } else if (isset(self::$_NUMERIC_FUNCTIONS[$funcName])) {
       
  2762             return $this->FunctionsReturningNumerics();
       
  2763         } else if (isset(self::$_DATETIME_FUNCTIONS[$funcName])) {
       
  2764             return $this->FunctionsReturningDatetime();
       
  2765         }
       
  2766 
       
  2767         // Check for custom functions afterwards
       
  2768         $config = $this->_em->getConfiguration();
       
  2769 
       
  2770         if ($config->getCustomStringFunction($funcName) !== null) {
       
  2771             return $this->CustomFunctionsReturningStrings();
       
  2772         } else if ($config->getCustomNumericFunction($funcName) !== null) {
       
  2773             return $this->CustomFunctionsReturningNumerics();
       
  2774         } else if ($config->getCustomDatetimeFunction($funcName) !== null) {
       
  2775             return $this->CustomFunctionsReturningDatetime();
       
  2776         }
       
  2777 
       
  2778         $this->syntaxError('known function', $token);
       
  2779     }
       
  2780 
       
  2781     /**
       
  2782      * FunctionsReturningNumerics ::=
       
  2783      *      "LENGTH" "(" StringPrimary ")" |
       
  2784      *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
       
  2785      *      "ABS" "(" SimpleArithmeticExpression ")" |
       
  2786      *      "SQRT" "(" SimpleArithmeticExpression ")" |
       
  2787      *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
       
  2788      *      "SIZE" "(" CollectionValuedPathExpression ")"
       
  2789      */
       
  2790     public function FunctionsReturningNumerics()
       
  2791     {
       
  2792         $funcNameLower = strtolower($this->_lexer->lookahead['value']);
       
  2793         $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
       
  2794         $function = new $funcClass($funcNameLower);
       
  2795         $function->parse($this);
       
  2796 
       
  2797         return $function;
       
  2798     }
       
  2799 
       
  2800     public function CustomFunctionsReturningNumerics()
       
  2801     {
       
  2802         $funcName = strtolower($this->_lexer->lookahead['value']);
       
  2803         // getCustomNumericFunction is case-insensitive
       
  2804         $funcClass = $this->_em->getConfiguration()->getCustomNumericFunction($funcName);
       
  2805         $function = new $funcClass($funcName);
       
  2806         $function->parse($this);
       
  2807 
       
  2808         return $function;
       
  2809     }
       
  2810 
       
  2811     /**
       
  2812      * FunctionsReturningDateTime ::= "CURRENT_DATE" | "CURRENT_TIME" | "CURRENT_TIMESTAMP"
       
  2813      */
       
  2814     public function FunctionsReturningDatetime()
       
  2815     {
       
  2816         $funcNameLower = strtolower($this->_lexer->lookahead['value']);
       
  2817         $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
       
  2818         $function = new $funcClass($funcNameLower);
       
  2819         $function->parse($this);
       
  2820 
       
  2821         return $function;
       
  2822     }
       
  2823 
       
  2824     public function CustomFunctionsReturningDatetime()
       
  2825     {
       
  2826         $funcName = $this->_lexer->lookahead['value'];
       
  2827         // getCustomDatetimeFunction is case-insensitive
       
  2828         $funcClass = $this->_em->getConfiguration()->getCustomDatetimeFunction($funcName);
       
  2829         $function = new $funcClass($funcName);
       
  2830         $function->parse($this);
       
  2831 
       
  2832         return $function;
       
  2833     }
       
  2834 
       
  2835     /**
       
  2836      * FunctionsReturningStrings ::=
       
  2837      *   "CONCAT" "(" StringPrimary "," StringPrimary ")" |
       
  2838      *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
       
  2839      *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
       
  2840      *   "LOWER" "(" StringPrimary ")" |
       
  2841      *   "UPPER" "(" StringPrimary ")"
       
  2842      */
       
  2843     public function FunctionsReturningStrings()
       
  2844     {
       
  2845         $funcNameLower = strtolower($this->_lexer->lookahead['value']);
       
  2846         $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower];
       
  2847         $function = new $funcClass($funcNameLower);
       
  2848         $function->parse($this);
       
  2849 
       
  2850         return $function;
       
  2851     }
       
  2852 
       
  2853     public function CustomFunctionsReturningStrings()
       
  2854     {
       
  2855         $funcName = $this->_lexer->lookahead['value'];
       
  2856         // getCustomStringFunction is case-insensitive
       
  2857         $funcClass = $this->_em->getConfiguration()->getCustomStringFunction($funcName);
       
  2858         $function = new $funcClass($funcName);
       
  2859         $function->parse($this);
       
  2860 
       
  2861         return $function;
       
  2862     }
       
  2863 }