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