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