|
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\DBAL\LockMode, |
|
23 Doctrine\ORM\Mapping\ClassMetadata, |
|
24 Doctrine\ORM\Query, |
|
25 Doctrine\ORM\Query\QueryException; |
|
26 |
|
27 /** |
|
28 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs |
|
29 * the corresponding SQL. |
|
30 * |
|
31 * @author Roman Borschel <roman@code-factory.org> |
|
32 * @author Benjamin Eberlei <kontakt@beberlei.de> |
|
33 * @since 2.0 |
|
34 * @todo Rename: SQLWalker |
|
35 */ |
|
36 class SqlWalker implements TreeWalker |
|
37 { |
|
38 /** |
|
39 * @var ResultSetMapping |
|
40 */ |
|
41 private $_rsm; |
|
42 |
|
43 /** Counters for generating unique column aliases, table aliases and parameter indexes. */ |
|
44 private $_aliasCounter = 0; |
|
45 private $_tableAliasCounter = 0; |
|
46 private $_scalarResultCounter = 1; |
|
47 private $_sqlParamIndex = 0; |
|
48 |
|
49 /** |
|
50 * @var ParserResult |
|
51 */ |
|
52 private $_parserResult; |
|
53 |
|
54 /** |
|
55 * @var EntityManager |
|
56 */ |
|
57 private $_em; |
|
58 |
|
59 /** |
|
60 * @var Doctrine\DBAL\Connection |
|
61 */ |
|
62 private $_conn; |
|
63 |
|
64 /** |
|
65 * @var AbstractQuery |
|
66 */ |
|
67 private $_query; |
|
68 |
|
69 private $_tableAliasMap = array(); |
|
70 |
|
71 /** Map from result variable names to their SQL column alias names. */ |
|
72 private $_scalarResultAliasMap = array(); |
|
73 |
|
74 /** Map of all components/classes that appear in the DQL query. */ |
|
75 private $_queryComponents; |
|
76 |
|
77 /** A list of classes that appear in non-scalar SelectExpressions. */ |
|
78 private $_selectedClasses = array(); |
|
79 |
|
80 /** |
|
81 * The DQL alias of the root class of the currently traversed query. |
|
82 */ |
|
83 private $_rootAliases = array(); |
|
84 |
|
85 /** |
|
86 * Flag that indicates whether to generate SQL table aliases in the SQL. |
|
87 * These should only be generated for SELECT queries, not for UPDATE/DELETE. |
|
88 */ |
|
89 private $_useSqlTableAliases = true; |
|
90 |
|
91 /** |
|
92 * The database platform abstraction. |
|
93 * |
|
94 * @var AbstractPlatform |
|
95 */ |
|
96 private $_platform; |
|
97 |
|
98 /** |
|
99 * {@inheritDoc} |
|
100 */ |
|
101 public function __construct($query, $parserResult, array $queryComponents) |
|
102 { |
|
103 $this->_query = $query; |
|
104 $this->_parserResult = $parserResult; |
|
105 $this->_queryComponents = $queryComponents; |
|
106 $this->_rsm = $parserResult->getResultSetMapping(); |
|
107 $this->_em = $query->getEntityManager(); |
|
108 $this->_conn = $this->_em->getConnection(); |
|
109 $this->_platform = $this->_conn->getDatabasePlatform(); |
|
110 } |
|
111 |
|
112 /** |
|
113 * Gets the Query instance used by the walker. |
|
114 * |
|
115 * @return Query. |
|
116 */ |
|
117 public function getQuery() |
|
118 { |
|
119 return $this->_query; |
|
120 } |
|
121 |
|
122 /** |
|
123 * Gets the Connection used by the walker. |
|
124 * |
|
125 * @return Connection |
|
126 */ |
|
127 public function getConnection() |
|
128 { |
|
129 return $this->_conn; |
|
130 } |
|
131 |
|
132 /** |
|
133 * Gets the EntityManager used by the walker. |
|
134 * |
|
135 * @return EntityManager |
|
136 */ |
|
137 public function getEntityManager() |
|
138 { |
|
139 return $this->_em; |
|
140 } |
|
141 |
|
142 /** |
|
143 * Gets the information about a single query component. |
|
144 * |
|
145 * @param string $dqlAlias The DQL alias. |
|
146 * @return array |
|
147 */ |
|
148 public function getQueryComponent($dqlAlias) |
|
149 { |
|
150 return $this->_queryComponents[$dqlAlias]; |
|
151 } |
|
152 |
|
153 /** |
|
154 * Gets an executor that can be used to execute the result of this walker. |
|
155 * |
|
156 * @return AbstractExecutor |
|
157 */ |
|
158 public function getExecutor($AST) |
|
159 { |
|
160 $isDeleteStatement = $AST instanceof AST\DeleteStatement; |
|
161 $isUpdateStatement = $AST instanceof AST\UpdateStatement; |
|
162 |
|
163 if ($isDeleteStatement) { |
|
164 $primaryClass = $this->_em->getClassMetadata( |
|
165 $AST->deleteClause->abstractSchemaName |
|
166 ); |
|
167 |
|
168 if ($primaryClass->isInheritanceTypeJoined()) { |
|
169 return new Exec\MultiTableDeleteExecutor($AST, $this); |
|
170 } else { |
|
171 return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); |
|
172 } |
|
173 } else if ($isUpdateStatement) { |
|
174 $primaryClass = $this->_em->getClassMetadata( |
|
175 $AST->updateClause->abstractSchemaName |
|
176 ); |
|
177 |
|
178 if ($primaryClass->isInheritanceTypeJoined()) { |
|
179 return new Exec\MultiTableUpdateExecutor($AST, $this); |
|
180 } else { |
|
181 return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); |
|
182 } |
|
183 } |
|
184 |
|
185 return new Exec\SingleSelectExecutor($AST, $this); |
|
186 } |
|
187 |
|
188 /** |
|
189 * Generates a unique, short SQL table alias. |
|
190 * |
|
191 * @param string $tableName Table name |
|
192 * @param string $dqlAlias The DQL alias. |
|
193 * @return string Generated table alias. |
|
194 */ |
|
195 public function getSQLTableAlias($tableName, $dqlAlias = '') |
|
196 { |
|
197 $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; |
|
198 |
|
199 if ( ! isset($this->_tableAliasMap[$tableName])) { |
|
200 $this->_tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_'; |
|
201 } |
|
202 |
|
203 return $this->_tableAliasMap[$tableName]; |
|
204 } |
|
205 |
|
206 /** |
|
207 * Forces the SqlWalker to use a specific alias for a table name, rather than |
|
208 * generating an alias on its own. |
|
209 * |
|
210 * @param string $tableName |
|
211 * @param string $alias |
|
212 * @param string $dqlAlias |
|
213 * @return string |
|
214 */ |
|
215 public function setSQLTableAlias($tableName, $alias, $dqlAlias = '') |
|
216 { |
|
217 $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; |
|
218 |
|
219 $this->_tableAliasMap[$tableName] = $alias; |
|
220 |
|
221 return $alias; |
|
222 } |
|
223 |
|
224 /** |
|
225 * Gets an SQL column alias for a column name. |
|
226 * |
|
227 * @param string $columnName |
|
228 * @return string |
|
229 */ |
|
230 public function getSQLColumnAlias($columnName) |
|
231 { |
|
232 return $columnName . $this->_aliasCounter++; |
|
233 } |
|
234 |
|
235 /** |
|
236 * Generates the SQL JOINs that are necessary for Class Table Inheritance |
|
237 * for the given class. |
|
238 * |
|
239 * @param ClassMetadata $class The class for which to generate the joins. |
|
240 * @param string $dqlAlias The DQL alias of the class. |
|
241 * @return string The SQL. |
|
242 */ |
|
243 private function _generateClassTableInheritanceJoins($class, $dqlAlias) |
|
244 { |
|
245 $sql = ''; |
|
246 |
|
247 $baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
248 |
|
249 // INNER JOIN parent class tables |
|
250 foreach ($class->parentClasses as $parentClassName) { |
|
251 $parentClass = $this->_em->getClassMetadata($parentClassName); |
|
252 $tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias); |
|
253 // If this is a joined association we must use left joins to preserve the correct result. |
|
254 $sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; |
|
255 $sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) |
|
256 . ' ' . $tableAlias . ' ON '; |
|
257 $first = true; |
|
258 foreach ($class->identifier as $idField) { |
|
259 if ($first) $first = false; else $sql .= ' AND '; |
|
260 |
|
261 $columnName = $class->getQuotedColumnName($idField, $this->_platform); |
|
262 $sql .= $baseTableAlias . '.' . $columnName |
|
263 . ' = ' |
|
264 . $tableAlias . '.' . $columnName; |
|
265 } |
|
266 } |
|
267 |
|
268 // LEFT JOIN subclass tables, if partial objects disallowed. |
|
269 if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { |
|
270 foreach ($class->subClasses as $subClassName) { |
|
271 $subClass = $this->_em->getClassMetadata($subClassName); |
|
272 $tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); |
|
273 $sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) |
|
274 . ' ' . $tableAlias . ' ON '; |
|
275 $first = true; |
|
276 foreach ($class->identifier as $idField) { |
|
277 if ($first) $first = false; else $sql .= ' AND '; |
|
278 |
|
279 $columnName = $class->getQuotedColumnName($idField, $this->_platform); |
|
280 $sql .= $baseTableAlias . '.' . $columnName |
|
281 . ' = ' |
|
282 . $tableAlias . '.' . $columnName; |
|
283 } |
|
284 } |
|
285 } |
|
286 |
|
287 return $sql; |
|
288 } |
|
289 |
|
290 private function _generateOrderedCollectionOrderByItems() |
|
291 { |
|
292 $sql = ''; |
|
293 foreach ($this->_selectedClasses AS $dqlAlias => $class) { |
|
294 $qComp = $this->_queryComponents[$dqlAlias]; |
|
295 if (isset($qComp['relation']['orderBy'])) { |
|
296 foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { |
|
297 if ($qComp['metadata']->isInheritanceTypeJoined()) { |
|
298 $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); |
|
299 } else { |
|
300 $tableName = $qComp['metadata']->table['name']; |
|
301 } |
|
302 |
|
303 if ($sql != '') { |
|
304 $sql .= ', '; |
|
305 } |
|
306 $sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . |
|
307 $qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation"; |
|
308 } |
|
309 } |
|
310 } |
|
311 return $sql; |
|
312 } |
|
313 |
|
314 /** |
|
315 * Generates a discriminator column SQL condition for the class with the given DQL alias. |
|
316 * |
|
317 * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions. |
|
318 * @return string |
|
319 */ |
|
320 private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases) |
|
321 { |
|
322 $encapsulate = false; |
|
323 $sql = ''; |
|
324 |
|
325 foreach ($dqlAliases as $dqlAlias) { |
|
326 $class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
327 |
|
328 if ($class->isInheritanceTypeSingleTable()) { |
|
329 $conn = $this->_em->getConnection(); |
|
330 $values = array(); |
|
331 if ($class->discriminatorValue !== null) { // discrimnators can be 0 |
|
332 $values[] = $conn->quote($class->discriminatorValue); |
|
333 } |
|
334 |
|
335 foreach ($class->subClasses as $subclassName) { |
|
336 $values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue); |
|
337 } |
|
338 |
|
339 if ($sql != '') { |
|
340 $sql .= ' AND '; |
|
341 $encapsulate = true; |
|
342 } |
|
343 |
|
344 $sql .= ($sql != '' ? ' AND ' : '') |
|
345 . (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.' : '') |
|
346 . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; |
|
347 } |
|
348 } |
|
349 |
|
350 return ($encapsulate) ? '(' . $sql . ')' : $sql; |
|
351 } |
|
352 |
|
353 /** |
|
354 * Walks down a SelectStatement AST node, thereby generating the appropriate SQL. |
|
355 * |
|
356 * @return string The SQL. |
|
357 */ |
|
358 public function walkSelectStatement(AST\SelectStatement $AST) |
|
359 { |
|
360 $sql = $this->walkSelectClause($AST->selectClause); |
|
361 $sql .= $this->walkFromClause($AST->fromClause); |
|
362 |
|
363 if (($whereClause = $AST->whereClause) !== null) { |
|
364 $sql .= $this->walkWhereClause($whereClause); |
|
365 } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { |
|
366 $sql .= ' WHERE ' . $discSql; |
|
367 } |
|
368 |
|
369 $sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : ''; |
|
370 $sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : ''; |
|
371 |
|
372 if (($orderByClause = $AST->orderByClause) !== null) { |
|
373 $sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : ''; |
|
374 } else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') { |
|
375 $sql .= ' ORDER BY '.$orderBySql; |
|
376 } |
|
377 |
|
378 |
|
379 $sql = $this->_platform->modifyLimitQuery( |
|
380 $sql, $this->_query->getMaxResults(), $this->_query->getFirstResult() |
|
381 ); |
|
382 |
|
383 if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) { |
|
384 if ($lockMode == LockMode::PESSIMISTIC_READ) { |
|
385 $sql .= " " . $this->_platform->getReadLockSQL(); |
|
386 } else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { |
|
387 $sql .= " " . $this->_platform->getWriteLockSQL(); |
|
388 } else if ($lockMode == LockMode::OPTIMISTIC) { |
|
389 foreach ($this->_selectedClasses AS $class) { |
|
390 if ( ! $class->isVersioned) { |
|
391 throw \Doctrine\ORM\OptimisticLockException::lockFailed(); |
|
392 } |
|
393 } |
|
394 } |
|
395 } |
|
396 |
|
397 return $sql; |
|
398 } |
|
399 |
|
400 /** |
|
401 * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. |
|
402 * |
|
403 * @param UpdateStatement |
|
404 * @return string The SQL. |
|
405 */ |
|
406 public function walkUpdateStatement(AST\UpdateStatement $AST) |
|
407 { |
|
408 $this->_useSqlTableAliases = false; |
|
409 $sql = $this->walkUpdateClause($AST->updateClause); |
|
410 |
|
411 if (($whereClause = $AST->whereClause) !== null) { |
|
412 $sql .= $this->walkWhereClause($whereClause); |
|
413 } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { |
|
414 $sql .= ' WHERE ' . $discSql; |
|
415 } |
|
416 |
|
417 return $sql; |
|
418 } |
|
419 |
|
420 /** |
|
421 * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. |
|
422 * |
|
423 * @param DeleteStatement |
|
424 * @return string The SQL. |
|
425 */ |
|
426 public function walkDeleteStatement(AST\DeleteStatement $AST) |
|
427 { |
|
428 $this->_useSqlTableAliases = false; |
|
429 $sql = $this->walkDeleteClause($AST->deleteClause); |
|
430 |
|
431 if (($whereClause = $AST->whereClause) !== null) { |
|
432 $sql .= $this->walkWhereClause($whereClause); |
|
433 } else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { |
|
434 $sql .= ' WHERE ' . $discSql; |
|
435 } |
|
436 |
|
437 return $sql; |
|
438 } |
|
439 |
|
440 |
|
441 /** |
|
442 * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. |
|
443 * |
|
444 * @param string $identificationVariable |
|
445 * @param string $fieldName |
|
446 * @return string The SQL. |
|
447 */ |
|
448 public function walkIdentificationVariable($identificationVariable, $fieldName = null) |
|
449 { |
|
450 $class = $this->_queryComponents[$identificationVariable]['metadata']; |
|
451 |
|
452 if ( |
|
453 $fieldName !== null && $class->isInheritanceTypeJoined() && |
|
454 isset($class->fieldMappings[$fieldName]['inherited']) |
|
455 ) { |
|
456 $class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); |
|
457 } |
|
458 |
|
459 return $this->getSQLTableAlias($class->table['name'], $identificationVariable); |
|
460 } |
|
461 |
|
462 /** |
|
463 * Walks down a PathExpression AST node, thereby generating the appropriate SQL. |
|
464 * |
|
465 * @param mixed |
|
466 * @return string The SQL. |
|
467 */ |
|
468 public function walkPathExpression($pathExpr) |
|
469 { |
|
470 $sql = ''; |
|
471 |
|
472 switch ($pathExpr->type) { |
|
473 case AST\PathExpression::TYPE_STATE_FIELD: |
|
474 $fieldName = $pathExpr->field; |
|
475 $dqlAlias = $pathExpr->identificationVariable; |
|
476 $class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
477 |
|
478 if ($this->_useSqlTableAliases) { |
|
479 $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.'; |
|
480 } |
|
481 |
|
482 $sql .= $class->getQuotedColumnName($fieldName, $this->_platform); |
|
483 break; |
|
484 |
|
485 case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: |
|
486 // 1- the owning side: |
|
487 // Just use the foreign key, i.e. u.group_id |
|
488 $fieldName = $pathExpr->field; |
|
489 $dqlAlias = $pathExpr->identificationVariable; |
|
490 $class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
491 |
|
492 if (isset($class->associationMappings[$fieldName]['inherited'])) { |
|
493 $class = $this->_em->getClassMetadata($class->associationMappings[$fieldName]['inherited']); |
|
494 } |
|
495 |
|
496 $assoc = $class->associationMappings[$fieldName]; |
|
497 |
|
498 if ($assoc['isOwningSide']) { |
|
499 // COMPOSITE KEYS NOT (YET?) SUPPORTED |
|
500 if (count($assoc['sourceToTargetKeyColumns']) > 1) { |
|
501 throw QueryException::associationPathCompositeKeyNotSupported(); |
|
502 } |
|
503 |
|
504 if ($this->_useSqlTableAliases) { |
|
505 $sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.'; |
|
506 } |
|
507 |
|
508 $sql .= reset($assoc['targetToSourceKeyColumns']); |
|
509 } else { |
|
510 throw QueryException::associationPathInverseSideNotSupported(); |
|
511 } |
|
512 break; |
|
513 |
|
514 default: |
|
515 throw QueryException::invalidPathExpression($pathExpr); |
|
516 } |
|
517 |
|
518 return $sql; |
|
519 } |
|
520 |
|
521 /** |
|
522 * Walks down a SelectClause AST node, thereby generating the appropriate SQL. |
|
523 * |
|
524 * @param $selectClause |
|
525 * @return string The SQL. |
|
526 */ |
|
527 public function walkSelectClause($selectClause) |
|
528 { |
|
529 $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '') . implode( |
|
530 ', ', array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)) |
|
531 ); |
|
532 |
|
533 $addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && |
|
534 $this->_query->getHydrationMode() == Query::HYDRATE_OBJECT |
|
535 || |
|
536 $this->_query->getHydrationMode() != Query::HYDRATE_OBJECT && |
|
537 $this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS); |
|
538 |
|
539 foreach ($this->_selectedClasses as $dqlAlias => $class) { |
|
540 // Register as entity or joined entity result |
|
541 if ($this->_queryComponents[$dqlAlias]['relation'] === null) { |
|
542 $this->_rsm->addEntityResult($class->name, $dqlAlias); |
|
543 } else { |
|
544 $this->_rsm->addJoinedEntityResult( |
|
545 $class->name, $dqlAlias, |
|
546 $this->_queryComponents[$dqlAlias]['parent'], |
|
547 $this->_queryComponents[$dqlAlias]['relation']['fieldName'] |
|
548 ); |
|
549 } |
|
550 |
|
551 if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { |
|
552 // Add discriminator columns to SQL |
|
553 $rootClass = $this->_em->getClassMetadata($class->rootEntityName); |
|
554 $tblAlias = $this->getSQLTableAlias($rootClass->table['name'], $dqlAlias); |
|
555 $discrColumn = $rootClass->discriminatorColumn; |
|
556 $columnAlias = $this->getSQLColumnAlias($discrColumn['name']); |
|
557 $sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias; |
|
558 |
|
559 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
560 $this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); |
|
561 $this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); |
|
562 |
|
563 // Add foreign key columns to SQL, if necessary |
|
564 if ($addMetaColumns) { |
|
565 //FIXME: Include foreign key columns of child classes also!!?? |
|
566 foreach ($class->associationMappings as $assoc) { |
|
567 if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { |
|
568 if (isset($assoc['inherited'])) { |
|
569 $owningClass = $this->_em->getClassMetadata($assoc['inherited']); |
|
570 $sqlTableAlias = $this->getSQLTableAlias($owningClass->table['name'], $dqlAlias); |
|
571 } else { |
|
572 $sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
573 } |
|
574 |
|
575 foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { |
|
576 $columnAlias = $this->getSQLColumnAlias($srcColumn); |
|
577 $sql .= ", $sqlTableAlias." . $srcColumn . ' AS ' . $columnAlias; |
|
578 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
579 $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); |
|
580 } |
|
581 } |
|
582 } |
|
583 } |
|
584 } else { |
|
585 // Add foreign key columns to SQL, if necessary |
|
586 if ($addMetaColumns) { |
|
587 $sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
588 foreach ($class->associationMappings as $assoc) { |
|
589 if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { |
|
590 foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { |
|
591 $columnAlias = $this->getSQLColumnAlias($srcColumn); |
|
592 $sql .= ', ' . $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; |
|
593 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
594 $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); |
|
595 } |
|
596 } |
|
597 } |
|
598 } |
|
599 } |
|
600 } |
|
601 |
|
602 return $sql; |
|
603 } |
|
604 |
|
605 /** |
|
606 * Walks down a FromClause AST node, thereby generating the appropriate SQL. |
|
607 * |
|
608 * @return string The SQL. |
|
609 */ |
|
610 public function walkFromClause($fromClause) |
|
611 { |
|
612 $identificationVarDecls = $fromClause->identificationVariableDeclarations; |
|
613 $sqlParts = array(); |
|
614 |
|
615 foreach ($identificationVarDecls as $identificationVariableDecl) { |
|
616 $sql = ''; |
|
617 |
|
618 $rangeDecl = $identificationVariableDecl->rangeVariableDeclaration; |
|
619 $dqlAlias = $rangeDecl->aliasIdentificationVariable; |
|
620 |
|
621 $this->_rootAliases[] = $dqlAlias; |
|
622 |
|
623 $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); |
|
624 $sql .= $class->getQuotedTableName($this->_platform) . ' ' |
|
625 . $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
626 |
|
627 if ($class->isInheritanceTypeJoined()) { |
|
628 $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); |
|
629 } |
|
630 |
|
631 foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) { |
|
632 $sql .= $this->walkJoinVariableDeclaration($joinVarDecl); |
|
633 } |
|
634 |
|
635 if ($identificationVariableDecl->indexBy) { |
|
636 $this->_rsm->addIndexBy( |
|
637 $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, |
|
638 $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field |
|
639 ); |
|
640 } |
|
641 |
|
642 $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); |
|
643 } |
|
644 |
|
645 return ' FROM ' . implode(', ', $sqlParts); |
|
646 } |
|
647 |
|
648 /** |
|
649 * Walks down a FunctionNode AST node, thereby generating the appropriate SQL. |
|
650 * |
|
651 * @return string The SQL. |
|
652 */ |
|
653 public function walkFunction($function) |
|
654 { |
|
655 return $function->getSql($this); |
|
656 } |
|
657 |
|
658 /** |
|
659 * Walks down an OrderByClause AST node, thereby generating the appropriate SQL. |
|
660 * |
|
661 * @param OrderByClause |
|
662 * @return string The SQL. |
|
663 */ |
|
664 public function walkOrderByClause($orderByClause) |
|
665 { |
|
666 $colSql = $this->_generateOrderedCollectionOrderByItems(); |
|
667 if ($colSql != '') { |
|
668 $colSql = ", ".$colSql; |
|
669 } |
|
670 |
|
671 // OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* |
|
672 return ' ORDER BY ' . implode( |
|
673 ', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems) |
|
674 ) . $colSql; |
|
675 } |
|
676 |
|
677 /** |
|
678 * Walks down an OrderByItem AST node, thereby generating the appropriate SQL. |
|
679 * |
|
680 * @param OrderByItem |
|
681 * @return string The SQL. |
|
682 */ |
|
683 public function walkOrderByItem($orderByItem) |
|
684 { |
|
685 $sql = ''; |
|
686 $expr = $orderByItem->expression; |
|
687 |
|
688 if ($expr instanceof AST\PathExpression) { |
|
689 $sql = $this->walkPathExpression($expr); |
|
690 } else { |
|
691 $columnName = $this->_queryComponents[$expr]['token']['value']; |
|
692 |
|
693 $sql = $this->_scalarResultAliasMap[$columnName]; |
|
694 } |
|
695 |
|
696 return $sql . ' ' . strtoupper($orderByItem->type); |
|
697 } |
|
698 |
|
699 /** |
|
700 * Walks down a HavingClause AST node, thereby generating the appropriate SQL. |
|
701 * |
|
702 * @param HavingClause |
|
703 * @return string The SQL. |
|
704 */ |
|
705 public function walkHavingClause($havingClause) |
|
706 { |
|
707 return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); |
|
708 } |
|
709 |
|
710 /** |
|
711 * Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. |
|
712 * |
|
713 * @param JoinVariableDeclaration $joinVarDecl |
|
714 * @return string The SQL. |
|
715 */ |
|
716 public function walkJoinVariableDeclaration($joinVarDecl) |
|
717 { |
|
718 $join = $joinVarDecl->join; |
|
719 $joinType = $join->joinType; |
|
720 |
|
721 if ($joinVarDecl->indexBy) { |
|
722 // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. |
|
723 $this->_rsm->addIndexBy( |
|
724 $joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, |
|
725 $joinVarDecl->indexBy->simpleStateFieldPathExpression->field |
|
726 ); |
|
727 } |
|
728 |
|
729 if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { |
|
730 $sql = ' LEFT JOIN '; |
|
731 } else { |
|
732 $sql = ' INNER JOIN '; |
|
733 } |
|
734 |
|
735 $joinAssocPathExpr = $join->joinAssociationPathExpression; |
|
736 $joinedDqlAlias = $join->aliasIdentificationVariable; |
|
737 $relation = $this->_queryComponents[$joinedDqlAlias]['relation']; |
|
738 $targetClass = $this->_em->getClassMetadata($relation['targetEntity']); |
|
739 $sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']); |
|
740 $targetTableName = $targetClass->getQuotedTableName($this->_platform); |
|
741 $targetTableAlias = $this->getSQLTableAlias($targetClass->table['name'], $joinedDqlAlias); |
|
742 $sourceTableAlias = $this->getSQLTableAlias($sourceClass->table['name'], $joinAssocPathExpr->identificationVariable); |
|
743 |
|
744 // Ensure we got the owning side, since it has all mapping info |
|
745 $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; |
|
746 |
|
747 if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true) { |
|
748 if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { |
|
749 throw QueryException::iterateWithFetchJoinNotAllowed($assoc); |
|
750 } |
|
751 } |
|
752 |
|
753 if ($joinVarDecl->indexBy) { |
|
754 // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. |
|
755 $this->_rsm->addIndexBy( |
|
756 $joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, |
|
757 $joinVarDecl->indexBy->simpleStateFieldPathExpression->field |
|
758 ); |
|
759 } else if (isset($relation['indexBy'])) { |
|
760 $this->_rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']); |
|
761 } |
|
762 |
|
763 // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot |
|
764 // be the owning side and previously we ensured that $assoc is always the owning side of the associations. |
|
765 // The owning side is necessary at this point because only it contains the JoinColumn information. |
|
766 if ($assoc['type'] & ClassMetadata::TO_ONE) { |
|
767 |
|
768 $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; |
|
769 $first = true; |
|
770 |
|
771 foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) { |
|
772 if ( ! $first) $sql .= ' AND '; else $first = false; |
|
773 |
|
774 if ($relation['isOwningSide']) { |
|
775 if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) { |
|
776 $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
777 } else { |
|
778 $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); |
|
779 } |
|
780 $sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; |
|
781 } else { |
|
782 if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) { |
|
783 $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
784 } else { |
|
785 $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); |
|
786 } |
|
787 $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; |
|
788 } |
|
789 } |
|
790 } else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) { |
|
791 // Join relation table |
|
792 $joinTable = $assoc['joinTable']; |
|
793 $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias); |
|
794 $sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON '; |
|
795 |
|
796 $first = true; |
|
797 if ($relation['isOwningSide']) { |
|
798 foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { |
|
799 if ( ! $first) $sql .= ' AND '; else $first = false; |
|
800 |
|
801 if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) { |
|
802 $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted. |
|
803 } else { |
|
804 $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform); |
|
805 } |
|
806 |
|
807 $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
808 } |
|
809 } else { |
|
810 foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { |
|
811 if ( ! $first) $sql .= ' AND '; else $first = false; |
|
812 |
|
813 if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) { |
|
814 $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
815 } else { |
|
816 $quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); |
|
817 } |
|
818 |
|
819 $sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
820 } |
|
821 } |
|
822 |
|
823 // Join target table |
|
824 $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) |
|
825 ? ' LEFT JOIN ' : ' INNER JOIN '; |
|
826 $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; |
|
827 |
|
828 $first = true; |
|
829 if ($relation['isOwningSide']) { |
|
830 foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { |
|
831 if ( ! $first) $sql .= ' AND '; else $first = false; |
|
832 |
|
833 if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) { |
|
834 $quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
835 } else { |
|
836 $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); |
|
837 } |
|
838 |
|
839 $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
840 } |
|
841 } else { |
|
842 foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { |
|
843 if ( ! $first) $sql .= ' AND '; else $first = false; |
|
844 |
|
845 if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) { |
|
846 $quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted. |
|
847 } else { |
|
848 $quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform); |
|
849 } |
|
850 |
|
851 $sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
852 } |
|
853 } |
|
854 } |
|
855 |
|
856 // Handle WITH clause |
|
857 if (($condExpr = $join->conditionalExpression) !== null) { |
|
858 // Phase 2 AST optimization: Skip processment of ConditionalExpression |
|
859 // if only one ConditionalTerm is defined |
|
860 $sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')'; |
|
861 } |
|
862 |
|
863 $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); |
|
864 |
|
865 if ($discrSql) { |
|
866 $sql .= ' AND ' . $discrSql; |
|
867 } |
|
868 |
|
869 // FIXME: these should either be nested or all forced to be left joins (DDC-XXX) |
|
870 if ($targetClass->isInheritanceTypeJoined()) { |
|
871 $sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); |
|
872 } |
|
873 |
|
874 return $sql; |
|
875 } |
|
876 |
|
877 /** |
|
878 * Walks down a CoalesceExpression AST node and generates the corresponding SQL. |
|
879 * |
|
880 * @param CoalesceExpression $coalesceExpression |
|
881 * @return string The SQL. |
|
882 */ |
|
883 public function walkCoalesceExpression($coalesceExpression) |
|
884 { |
|
885 $sql = 'COALESCE('; |
|
886 |
|
887 $scalarExpressions = array(); |
|
888 |
|
889 foreach ($coalesceExpression->scalarExpressions as $scalarExpression) { |
|
890 $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression); |
|
891 } |
|
892 |
|
893 $sql .= implode(', ', $scalarExpressions) . ')'; |
|
894 |
|
895 return $sql; |
|
896 } |
|
897 |
|
898 public function walkCaseExpression($expression) |
|
899 { |
|
900 switch (true) { |
|
901 case ($expression instanceof AST\CoalesceExpression): |
|
902 return $this->walkCoalesceExpression($expression); |
|
903 |
|
904 case ($expression instanceof AST\NullIfExpression): |
|
905 return $this->walkNullIfExpression($expression); |
|
906 |
|
907 default: |
|
908 return ''; |
|
909 } |
|
910 } |
|
911 |
|
912 /** |
|
913 * Walks down a NullIfExpression AST node and generates the corresponding SQL. |
|
914 * |
|
915 * @param NullIfExpression $nullIfExpression |
|
916 * @return string The SQL. |
|
917 */ |
|
918 public function walkNullIfExpression($nullIfExpression) |
|
919 { |
|
920 $firstExpression = is_string($nullIfExpression->firstExpression) |
|
921 ? $this->_conn->quote($nullIfExpression->firstExpression) |
|
922 : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression); |
|
923 |
|
924 $secondExpression = is_string($nullIfExpression->secondExpression) |
|
925 ? $this->_conn->quote($nullIfExpression->secondExpression) |
|
926 : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression); |
|
927 |
|
928 return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; |
|
929 } |
|
930 |
|
931 /** |
|
932 * Walks down a SelectExpression AST node and generates the corresponding SQL. |
|
933 * |
|
934 * @param SelectExpression $selectExpression |
|
935 * @return string The SQL. |
|
936 */ |
|
937 public function walkSelectExpression($selectExpression) |
|
938 { |
|
939 $sql = ''; |
|
940 $expr = $selectExpression->expression; |
|
941 |
|
942 if ($expr instanceof AST\PathExpression) { |
|
943 if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) { |
|
944 $fieldName = $expr->field; |
|
945 $dqlAlias = $expr->identificationVariable; |
|
946 $qComp = $this->_queryComponents[$dqlAlias]; |
|
947 $class = $qComp['metadata']; |
|
948 |
|
949 if ( ! $selectExpression->fieldIdentificationVariable) { |
|
950 $resultAlias = $fieldName; |
|
951 } else { |
|
952 $resultAlias = $selectExpression->fieldIdentificationVariable; |
|
953 } |
|
954 |
|
955 if ($class->isInheritanceTypeJoined()) { |
|
956 $tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); |
|
957 } else { |
|
958 $tableName = $class->getTableName(); |
|
959 } |
|
960 |
|
961 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); |
|
962 $columnName = $class->getQuotedColumnName($fieldName, $this->_platform); |
|
963 |
|
964 $columnAlias = $this->getSQLColumnAlias($columnName); |
|
965 $sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; |
|
966 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
967 $this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
968 } else { |
|
969 throw QueryException::invalidPathExpression($expr->type); |
|
970 } |
|
971 } |
|
972 else if ($expr instanceof AST\AggregateExpression) { |
|
973 if ( ! $selectExpression->fieldIdentificationVariable) { |
|
974 $resultAlias = $this->_scalarResultCounter++; |
|
975 } else { |
|
976 $resultAlias = $selectExpression->fieldIdentificationVariable; |
|
977 } |
|
978 |
|
979 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
980 $sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; |
|
981 $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
982 |
|
983 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
984 $this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
985 } |
|
986 else if ($expr instanceof AST\Subselect) { |
|
987 if ( ! $selectExpression->fieldIdentificationVariable) { |
|
988 $resultAlias = $this->_scalarResultCounter++; |
|
989 } else { |
|
990 $resultAlias = $selectExpression->fieldIdentificationVariable; |
|
991 } |
|
992 |
|
993 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
994 $sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; |
|
995 $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
996 |
|
997 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
998 $this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
999 } |
|
1000 else if ($expr instanceof AST\Functions\FunctionNode) { |
|
1001 if ( ! $selectExpression->fieldIdentificationVariable) { |
|
1002 $resultAlias = $this->_scalarResultCounter++; |
|
1003 } else { |
|
1004 $resultAlias = $selectExpression->fieldIdentificationVariable; |
|
1005 } |
|
1006 |
|
1007 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
1008 $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; |
|
1009 $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
1010 |
|
1011 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
1012 $this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
1013 } else if ( |
|
1014 $expr instanceof AST\SimpleArithmeticExpression || |
|
1015 $expr instanceof AST\ArithmeticTerm || |
|
1016 $expr instanceof AST\ArithmeticFactor || |
|
1017 $expr instanceof AST\ArithmeticPrimary || |
|
1018 $expr instanceof AST\Literal |
|
1019 ) { |
|
1020 if ( ! $selectExpression->fieldIdentificationVariable) { |
|
1021 $resultAlias = $this->_scalarResultCounter++; |
|
1022 } else { |
|
1023 $resultAlias = $selectExpression->fieldIdentificationVariable; |
|
1024 } |
|
1025 |
|
1026 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
1027 |
|
1028 if ($expr instanceof AST\Literal) { |
|
1029 $sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias; |
|
1030 } else { |
|
1031 $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; |
|
1032 } |
|
1033 |
|
1034 $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
1035 |
|
1036 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
1037 $this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
1038 } else if ( |
|
1039 $expr instanceof AST\NullIfExpression || |
|
1040 $expr instanceof AST\CoalesceExpression || |
|
1041 $expr instanceof AST\CaseExpression |
|
1042 ) { |
|
1043 if ( ! $selectExpression->fieldIdentificationVariable) { |
|
1044 $resultAlias = $this->_scalarResultCounter++; |
|
1045 } else { |
|
1046 $resultAlias = $selectExpression->fieldIdentificationVariable; |
|
1047 } |
|
1048 |
|
1049 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
1050 |
|
1051 $sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; |
|
1052 |
|
1053 $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
1054 |
|
1055 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
1056 $this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
1057 } else { |
|
1058 // IdentificationVariable or PartialObjectExpression |
|
1059 if ($expr instanceof AST\PartialObjectExpression) { |
|
1060 $dqlAlias = $expr->identificationVariable; |
|
1061 $partialFieldSet = $expr->partialFieldSet; |
|
1062 } else { |
|
1063 $dqlAlias = $expr; |
|
1064 $partialFieldSet = array(); |
|
1065 } |
|
1066 |
|
1067 $queryComp = $this->_queryComponents[$dqlAlias]; |
|
1068 $class = $queryComp['metadata']; |
|
1069 |
|
1070 if ( ! isset($this->_selectedClasses[$dqlAlias])) { |
|
1071 $this->_selectedClasses[$dqlAlias] = $class; |
|
1072 } |
|
1073 |
|
1074 $beginning = true; |
|
1075 // Select all fields from the queried class |
|
1076 foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
1077 if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { |
|
1078 continue; |
|
1079 } |
|
1080 |
|
1081 if (isset($mapping['inherited'])) { |
|
1082 $tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name']; |
|
1083 } else { |
|
1084 $tableName = $class->table['name']; |
|
1085 } |
|
1086 |
|
1087 if ($beginning) $beginning = false; else $sql .= ', '; |
|
1088 |
|
1089 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); |
|
1090 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); |
|
1091 $sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) |
|
1092 . ' AS ' . $columnAlias; |
|
1093 |
|
1094 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
1095 $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); |
|
1096 } |
|
1097 |
|
1098 // Add any additional fields of subclasses (excluding inherited fields) |
|
1099 // 1) on Single Table Inheritance: always, since its marginal overhead |
|
1100 // 2) on Class Table Inheritance only if partial objects are disallowed, |
|
1101 // since it requires outer joining subtables. |
|
1102 if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { |
|
1103 foreach ($class->subClasses as $subClassName) { |
|
1104 $subClass = $this->_em->getClassMetadata($subClassName); |
|
1105 $sqlTableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); |
|
1106 foreach ($subClass->fieldMappings as $fieldName => $mapping) { |
|
1107 if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { |
|
1108 continue; |
|
1109 } |
|
1110 |
|
1111 if ($beginning) $beginning = false; else $sql .= ', '; |
|
1112 |
|
1113 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']); |
|
1114 $sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) |
|
1115 . ' AS ' . $columnAlias; |
|
1116 |
|
1117 $columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
1118 $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); |
|
1119 } |
|
1120 |
|
1121 // Add join columns (foreign keys) of the subclass |
|
1122 //TODO: Probably better do this in walkSelectClause to honor the INCLUDE_META_COLUMNS hint |
|
1123 foreach ($subClass->associationMappings as $fieldName => $assoc) { |
|
1124 if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { |
|
1125 foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { |
|
1126 if ($beginning) $beginning = false; else $sql .= ', '; |
|
1127 $columnAlias = $this->getSQLColumnAlias($srcColumn); |
|
1128 $sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; |
|
1129 $this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); |
|
1130 } |
|
1131 } |
|
1132 } |
|
1133 } |
|
1134 } |
|
1135 } |
|
1136 |
|
1137 return $sql; |
|
1138 } |
|
1139 |
|
1140 /** |
|
1141 * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. |
|
1142 * |
|
1143 * @param QuantifiedExpression |
|
1144 * @return string The SQL. |
|
1145 */ |
|
1146 public function walkQuantifiedExpression($qExpr) |
|
1147 { |
|
1148 return ' ' . strtoupper($qExpr->type) |
|
1149 . '(' . $this->walkSubselect($qExpr->subselect) . ')'; |
|
1150 } |
|
1151 |
|
1152 /** |
|
1153 * Walks down a Subselect AST node, thereby generating the appropriate SQL. |
|
1154 * |
|
1155 * @param Subselect |
|
1156 * @return string The SQL. |
|
1157 */ |
|
1158 public function walkSubselect($subselect) |
|
1159 { |
|
1160 $useAliasesBefore = $this->_useSqlTableAliases; |
|
1161 $this->_useSqlTableAliases = true; |
|
1162 |
|
1163 $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); |
|
1164 $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); |
|
1165 $sql .= $subselect->whereClause ? $this->walkWhereClause($subselect->whereClause) : ''; |
|
1166 $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; |
|
1167 $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; |
|
1168 $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; |
|
1169 |
|
1170 $this->_useSqlTableAliases = $useAliasesBefore; |
|
1171 |
|
1172 return $sql; |
|
1173 } |
|
1174 |
|
1175 /** |
|
1176 * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. |
|
1177 * |
|
1178 * @param SubselectFromClause |
|
1179 * @return string The SQL. |
|
1180 */ |
|
1181 public function walkSubselectFromClause($subselectFromClause) |
|
1182 { |
|
1183 $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations; |
|
1184 $sqlParts = array (); |
|
1185 |
|
1186 foreach ($identificationVarDecls as $subselectIdVarDecl) { |
|
1187 $sql = ''; |
|
1188 |
|
1189 $rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration; |
|
1190 $dqlAlias = $rangeDecl->aliasIdentificationVariable; |
|
1191 |
|
1192 $class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); |
|
1193 $sql .= $class->getQuotedTableName($this->_platform) . ' ' |
|
1194 . $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
1195 |
|
1196 if ($class->isInheritanceTypeJoined()) { |
|
1197 $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); |
|
1198 } |
|
1199 |
|
1200 foreach ($subselectIdVarDecl->joinVariableDeclarations as $joinVarDecl) { |
|
1201 $sql .= $this->walkJoinVariableDeclaration($joinVarDecl); |
|
1202 } |
|
1203 |
|
1204 $sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); |
|
1205 } |
|
1206 |
|
1207 return ' FROM ' . implode(', ', $sqlParts); |
|
1208 } |
|
1209 |
|
1210 /** |
|
1211 * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. |
|
1212 * |
|
1213 * @param SimpleSelectClause |
|
1214 * @return string The SQL. |
|
1215 */ |
|
1216 public function walkSimpleSelectClause($simpleSelectClause) |
|
1217 { |
|
1218 return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') |
|
1219 . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); |
|
1220 } |
|
1221 |
|
1222 /** |
|
1223 * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. |
|
1224 * |
|
1225 * @param SimpleSelectExpression |
|
1226 * @return string The SQL. |
|
1227 */ |
|
1228 public function walkSimpleSelectExpression($simpleSelectExpression) |
|
1229 { |
|
1230 $sql = ''; |
|
1231 $expr = $simpleSelectExpression->expression; |
|
1232 |
|
1233 if ($expr instanceof AST\PathExpression) { |
|
1234 $sql .= $this->walkPathExpression($expr); |
|
1235 } else if ($expr instanceof AST\AggregateExpression) { |
|
1236 if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
1237 $alias = $this->_scalarResultCounter++; |
|
1238 } else { |
|
1239 $alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
1240 } |
|
1241 |
|
1242 $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; |
|
1243 } else if ($expr instanceof AST\Subselect) { |
|
1244 if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
1245 $alias = $this->_scalarResultCounter++; |
|
1246 } else { |
|
1247 $alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
1248 } |
|
1249 |
|
1250 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
1251 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; |
|
1252 $this->_scalarResultAliasMap[$alias] = $columnAlias; |
|
1253 } else if ($expr instanceof AST\Functions\FunctionNode) { |
|
1254 if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
1255 $alias = $this->_scalarResultCounter++; |
|
1256 } else { |
|
1257 $alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
1258 } |
|
1259 |
|
1260 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
1261 $sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; |
|
1262 $this->_scalarResultAliasMap[$alias] = $columnAlias; |
|
1263 } else if ( |
|
1264 $expr instanceof AST\SimpleArithmeticExpression || |
|
1265 $expr instanceof AST\ArithmeticTerm || |
|
1266 $expr instanceof AST\ArithmeticFactor || |
|
1267 $expr instanceof AST\ArithmeticPrimary |
|
1268 ) { |
|
1269 if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
1270 $alias = $this->_scalarResultCounter++; |
|
1271 } else { |
|
1272 $alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
1273 } |
|
1274 |
|
1275 $columnAlias = 'sclr' . $this->_aliasCounter++; |
|
1276 $sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; |
|
1277 $this->_scalarResultAliasMap[$alias] = $columnAlias; |
|
1278 } else { |
|
1279 // IdentificationVariable |
|
1280 $class = $this->_queryComponents[$expr]['metadata']; |
|
1281 $tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); |
|
1282 $first = true; |
|
1283 |
|
1284 foreach ($class->identifier as $identifier) { |
|
1285 if ($first) $first = false; else $sql .= ', '; |
|
1286 $sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); |
|
1287 } |
|
1288 } |
|
1289 |
|
1290 return ' ' . $sql; |
|
1291 } |
|
1292 |
|
1293 /** |
|
1294 * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. |
|
1295 * |
|
1296 * @param AggregateExpression |
|
1297 * @return string The SQL. |
|
1298 */ |
|
1299 public function walkAggregateExpression($aggExpression) |
|
1300 { |
|
1301 return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') |
|
1302 . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')'; |
|
1303 } |
|
1304 |
|
1305 /** |
|
1306 * Walks down a GroupByClause AST node, thereby generating the appropriate SQL. |
|
1307 * |
|
1308 * @param GroupByClause |
|
1309 * @return string The SQL. |
|
1310 */ |
|
1311 public function walkGroupByClause($groupByClause) |
|
1312 { |
|
1313 $sql = ''; |
|
1314 foreach ($groupByClause->groupByItems AS $groupByItem) { |
|
1315 if (is_string($groupByItem)) { |
|
1316 foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { |
|
1317 if ($sql != '') { |
|
1318 $sql .= ', '; |
|
1319 } |
|
1320 $groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); |
|
1321 $groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; |
|
1322 $sql .= $this->walkGroupByItem($groupByItem); |
|
1323 } |
|
1324 } else { |
|
1325 if ($sql != '') { |
|
1326 $sql .= ', '; |
|
1327 } |
|
1328 $sql .= $this->walkGroupByItem($groupByItem); |
|
1329 } |
|
1330 } |
|
1331 return ' GROUP BY ' . $sql; |
|
1332 } |
|
1333 |
|
1334 /** |
|
1335 * Walks down a GroupByItem AST node, thereby generating the appropriate SQL. |
|
1336 * |
|
1337 * @param GroupByItem |
|
1338 * @return string The SQL. |
|
1339 */ |
|
1340 public function walkGroupByItem(AST\PathExpression $pathExpr) |
|
1341 { |
|
1342 return $this->walkPathExpression($pathExpr); |
|
1343 } |
|
1344 |
|
1345 /** |
|
1346 * Walks down a DeleteClause AST node, thereby generating the appropriate SQL. |
|
1347 * |
|
1348 * @param DeleteClause |
|
1349 * @return string The SQL. |
|
1350 */ |
|
1351 public function walkDeleteClause(AST\DeleteClause $deleteClause) |
|
1352 { |
|
1353 $sql = 'DELETE FROM '; |
|
1354 $class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); |
|
1355 $sql .= $class->getQuotedTableName($this->_platform); |
|
1356 |
|
1357 $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable); |
|
1358 |
|
1359 $this->_rootAliases[] = $deleteClause->aliasIdentificationVariable; |
|
1360 |
|
1361 return $sql; |
|
1362 } |
|
1363 |
|
1364 /** |
|
1365 * Walks down an UpdateClause AST node, thereby generating the appropriate SQL. |
|
1366 * |
|
1367 * @param UpdateClause |
|
1368 * @return string The SQL. |
|
1369 */ |
|
1370 public function walkUpdateClause($updateClause) |
|
1371 { |
|
1372 $sql = 'UPDATE '; |
|
1373 $class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); |
|
1374 $sql .= $class->getQuotedTableName($this->_platform); |
|
1375 |
|
1376 $this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable); |
|
1377 |
|
1378 $this->_rootAliases[] = $updateClause->aliasIdentificationVariable; |
|
1379 |
|
1380 $sql .= ' SET ' . implode( |
|
1381 ', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems) |
|
1382 ); |
|
1383 |
|
1384 return $sql; |
|
1385 } |
|
1386 |
|
1387 /** |
|
1388 * Walks down an UpdateItem AST node, thereby generating the appropriate SQL. |
|
1389 * |
|
1390 * @param UpdateItem |
|
1391 * @return string The SQL. |
|
1392 */ |
|
1393 public function walkUpdateItem($updateItem) |
|
1394 { |
|
1395 $useTableAliasesBefore = $this->_useSqlTableAliases; |
|
1396 $this->_useSqlTableAliases = false; |
|
1397 |
|
1398 $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; |
|
1399 |
|
1400 $newValue = $updateItem->newValue; |
|
1401 |
|
1402 if ($newValue === null) { |
|
1403 $sql .= 'NULL'; |
|
1404 } else if ($newValue instanceof AST\Node) { |
|
1405 $sql .= $newValue->dispatch($this); |
|
1406 } else { |
|
1407 $sql .= $this->_conn->quote($newValue); |
|
1408 } |
|
1409 |
|
1410 $this->_useSqlTableAliases = $useTableAliasesBefore; |
|
1411 |
|
1412 return $sql; |
|
1413 } |
|
1414 |
|
1415 /** |
|
1416 * Walks down a WhereClause AST node, thereby generating the appropriate SQL. |
|
1417 * |
|
1418 * @param WhereClause |
|
1419 * @return string The SQL. |
|
1420 */ |
|
1421 public function walkWhereClause($whereClause) |
|
1422 { |
|
1423 $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases); |
|
1424 $condSql = $this->walkConditionalExpression($whereClause->conditionalExpression); |
|
1425 |
|
1426 return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); |
|
1427 } |
|
1428 |
|
1429 /** |
|
1430 * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. |
|
1431 * |
|
1432 * @param ConditionalExpression |
|
1433 * @return string The SQL. |
|
1434 */ |
|
1435 public function walkConditionalExpression($condExpr) |
|
1436 { |
|
1437 // Phase 2 AST optimization: Skip processment of ConditionalExpression |
|
1438 // if only one ConditionalTerm is defined |
|
1439 return ( ! ($condExpr instanceof AST\ConditionalExpression)) |
|
1440 ? $this->walkConditionalTerm($condExpr) |
|
1441 : implode( |
|
1442 ' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) |
|
1443 ); |
|
1444 } |
|
1445 |
|
1446 /** |
|
1447 * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. |
|
1448 * |
|
1449 * @param ConditionalTerm |
|
1450 * @return string The SQL. |
|
1451 */ |
|
1452 public function walkConditionalTerm($condTerm) |
|
1453 { |
|
1454 // Phase 2 AST optimization: Skip processment of ConditionalTerm |
|
1455 // if only one ConditionalFactor is defined |
|
1456 return ( ! ($condTerm instanceof AST\ConditionalTerm)) |
|
1457 ? $this->walkConditionalFactor($condTerm) |
|
1458 : implode( |
|
1459 ' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors) |
|
1460 ); |
|
1461 } |
|
1462 |
|
1463 /** |
|
1464 * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. |
|
1465 * |
|
1466 * @param ConditionalFactor |
|
1467 * @return string The SQL. |
|
1468 */ |
|
1469 public function walkConditionalFactor($factor) |
|
1470 { |
|
1471 // Phase 2 AST optimization: Skip processment of ConditionalFactor |
|
1472 // if only one ConditionalPrimary is defined |
|
1473 return ( ! ($factor instanceof AST\ConditionalFactor)) |
|
1474 ? $this->walkConditionalPrimary($factor) |
|
1475 : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); |
|
1476 } |
|
1477 |
|
1478 /** |
|
1479 * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. |
|
1480 * |
|
1481 * @param ConditionalPrimary |
|
1482 * @return string The SQL. |
|
1483 */ |
|
1484 public function walkConditionalPrimary($primary) |
|
1485 { |
|
1486 if ($primary->isSimpleConditionalExpression()) { |
|
1487 return $primary->simpleConditionalExpression->dispatch($this); |
|
1488 } else if ($primary->isConditionalExpression()) { |
|
1489 $condExpr = $primary->conditionalExpression; |
|
1490 |
|
1491 return '(' . $this->walkConditionalExpression($condExpr) . ')'; |
|
1492 } |
|
1493 } |
|
1494 |
|
1495 /** |
|
1496 * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. |
|
1497 * |
|
1498 * @param ExistsExpression |
|
1499 * @return string The SQL. |
|
1500 */ |
|
1501 public function walkExistsExpression($existsExpr) |
|
1502 { |
|
1503 $sql = ($existsExpr->not) ? 'NOT ' : ''; |
|
1504 |
|
1505 $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')'; |
|
1506 |
|
1507 return $sql; |
|
1508 } |
|
1509 |
|
1510 /** |
|
1511 * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. |
|
1512 * |
|
1513 * @param CollectionMemberExpression |
|
1514 * @return string The SQL. |
|
1515 */ |
|
1516 public function walkCollectionMemberExpression($collMemberExpr) |
|
1517 { |
|
1518 $sql = $collMemberExpr->not ? 'NOT ' : ''; |
|
1519 $sql .= 'EXISTS (SELECT 1 FROM '; |
|
1520 $entityExpr = $collMemberExpr->entityExpression; |
|
1521 $collPathExpr = $collMemberExpr->collectionValuedPathExpression; |
|
1522 |
|
1523 $fieldName = $collPathExpr->field; |
|
1524 $dqlAlias = $collPathExpr->identificationVariable; |
|
1525 |
|
1526 $class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
1527 |
|
1528 if ($entityExpr instanceof AST\InputParameter) { |
|
1529 $dqlParamKey = $entityExpr->name; |
|
1530 $entity = $this->_query->getParameter($dqlParamKey); |
|
1531 } else { |
|
1532 //TODO |
|
1533 throw new \BadMethodCallException("Not implemented"); |
|
1534 } |
|
1535 |
|
1536 $assoc = $class->associationMappings[$fieldName]; |
|
1537 |
|
1538 if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { |
|
1539 $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); |
|
1540 $targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']); |
|
1541 $sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
1542 |
|
1543 $sql .= $targetClass->getQuotedTableName($this->_platform) |
|
1544 . ' ' . $targetTableAlias . ' WHERE '; |
|
1545 |
|
1546 $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; |
|
1547 |
|
1548 $first = true; |
|
1549 |
|
1550 foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { |
|
1551 if ($first) $first = false; else $sql .= ' AND '; |
|
1552 |
|
1553 $sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) |
|
1554 . ' = ' |
|
1555 . $targetTableAlias . '.' . $sourceColumn; |
|
1556 } |
|
1557 |
|
1558 $sql .= ' AND '; |
|
1559 $first = true; |
|
1560 |
|
1561 foreach ($targetClass->identifier as $idField) { |
|
1562 if ($first) $first = false; else $sql .= ' AND '; |
|
1563 |
|
1564 $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
1565 $sql .= $targetTableAlias . '.' |
|
1566 . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; |
|
1567 } |
|
1568 } else { // many-to-many |
|
1569 $targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); |
|
1570 |
|
1571 $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; |
|
1572 $joinTable = $owningAssoc['joinTable']; |
|
1573 |
|
1574 // SQL table aliases |
|
1575 $joinTableAlias = $this->getSQLTableAlias($joinTable['name']); |
|
1576 $targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']); |
|
1577 $sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
1578 |
|
1579 // join to target table |
|
1580 $sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) |
|
1581 . ' ' . $joinTableAlias . ' INNER JOIN ' |
|
1582 . $targetClass->getQuotedTableName($this->_platform) |
|
1583 . ' ' . $targetTableAlias . ' ON '; |
|
1584 |
|
1585 // join conditions |
|
1586 $joinColumns = $assoc['isOwningSide'] |
|
1587 ? $joinTable['inverseJoinColumns'] |
|
1588 : $joinTable['joinColumns']; |
|
1589 |
|
1590 $first = true; |
|
1591 foreach ($joinColumns as $joinColumn) { |
|
1592 if ($first) $first = false; else $sql .= ' AND '; |
|
1593 |
|
1594 $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' |
|
1595 . $targetTableAlias . '.' . $targetClass->getQuotedColumnName( |
|
1596 $targetClass->fieldNames[$joinColumn['referencedColumnName']], |
|
1597 $this->_platform); |
|
1598 } |
|
1599 |
|
1600 $sql .= ' WHERE '; |
|
1601 |
|
1602 $joinColumns = $assoc['isOwningSide'] |
|
1603 ? $joinTable['joinColumns'] |
|
1604 : $joinTable['inverseJoinColumns']; |
|
1605 |
|
1606 $first = true; |
|
1607 foreach ($joinColumns as $joinColumn) { |
|
1608 if ($first) $first = false; else $sql .= ' AND '; |
|
1609 |
|
1610 $sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' |
|
1611 . $sourceTableAlias . '.' . $class->getQuotedColumnName( |
|
1612 $class->fieldNames[$joinColumn['referencedColumnName']], |
|
1613 $this->_platform); |
|
1614 } |
|
1615 |
|
1616 $sql .= ' AND '; |
|
1617 $first = true; |
|
1618 |
|
1619 foreach ($targetClass->identifier as $idField) { |
|
1620 if ($first) $first = false; else $sql .= ' AND '; |
|
1621 |
|
1622 $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
1623 $sql .= $targetTableAlias . '.' |
|
1624 . $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; |
|
1625 } |
|
1626 } |
|
1627 |
|
1628 return $sql . ')'; |
|
1629 } |
|
1630 |
|
1631 /** |
|
1632 * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. |
|
1633 * |
|
1634 * @param EmptyCollectionComparisonExpression |
|
1635 * @return string The SQL. |
|
1636 */ |
|
1637 public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) |
|
1638 { |
|
1639 $sizeFunc = new AST\Functions\SizeFunction('size'); |
|
1640 $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression; |
|
1641 |
|
1642 return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0'); |
|
1643 } |
|
1644 |
|
1645 /** |
|
1646 * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. |
|
1647 * |
|
1648 * @param NullComparisonExpression |
|
1649 * @return string The SQL. |
|
1650 */ |
|
1651 public function walkNullComparisonExpression($nullCompExpr) |
|
1652 { |
|
1653 $sql = ''; |
|
1654 $innerExpr = $nullCompExpr->expression; |
|
1655 |
|
1656 if ($innerExpr instanceof AST\InputParameter) { |
|
1657 $dqlParamKey = $innerExpr->name; |
|
1658 $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
1659 $sql .= ' ?'; |
|
1660 } else { |
|
1661 $sql .= $this->walkPathExpression($innerExpr); |
|
1662 } |
|
1663 |
|
1664 $sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL'; |
|
1665 |
|
1666 return $sql; |
|
1667 } |
|
1668 |
|
1669 /** |
|
1670 * Walks down an InExpression AST node, thereby generating the appropriate SQL. |
|
1671 * |
|
1672 * @param InExpression |
|
1673 * @return string The SQL. |
|
1674 */ |
|
1675 public function walkInExpression($inExpr) |
|
1676 { |
|
1677 $sql = $this->walkPathExpression($inExpr->pathExpression) |
|
1678 . ($inExpr->not ? ' NOT' : '') . ' IN ('; |
|
1679 |
|
1680 if ($inExpr->subselect) { |
|
1681 $sql .= $this->walkSubselect($inExpr->subselect); |
|
1682 } else { |
|
1683 $sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); |
|
1684 } |
|
1685 |
|
1686 $sql .= ')'; |
|
1687 |
|
1688 return $sql; |
|
1689 } |
|
1690 |
|
1691 /** |
|
1692 * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. |
|
1693 * |
|
1694 * @param InstanceOfExpression |
|
1695 * @return string The SQL. |
|
1696 */ |
|
1697 public function walkInstanceOfExpression($instanceOfExpr) |
|
1698 { |
|
1699 $sql = ''; |
|
1700 |
|
1701 $dqlAlias = $instanceOfExpr->identificationVariable; |
|
1702 $discrClass = $class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
1703 $fieldName = null; |
|
1704 |
|
1705 if ($class->discriminatorColumn) { |
|
1706 $discrClass = $this->_em->getClassMetadata($class->rootEntityName); |
|
1707 } |
|
1708 |
|
1709 if ($this->_useSqlTableAliases) { |
|
1710 $sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.'; |
|
1711 } |
|
1712 |
|
1713 $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = '); |
|
1714 |
|
1715 if ($instanceOfExpr->value instanceof AST\InputParameter) { |
|
1716 // We need to modify the parameter value to be its correspondent mapped value |
|
1717 $dqlParamKey = $instanceOfExpr->value->name; |
|
1718 $paramValue = $this->_query->getParameter($dqlParamKey); |
|
1719 |
|
1720 if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) { |
|
1721 throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue)); |
|
1722 } |
|
1723 |
|
1724 $entityClassName = $paramValue->name; |
|
1725 } else { |
|
1726 // Get name from ClassMetadata to resolve aliases. |
|
1727 $entityClassName = $this->_em->getClassMetadata($instanceOfExpr->value)->name; |
|
1728 } |
|
1729 |
|
1730 if ($entityClassName == $class->name) { |
|
1731 $sql .= $this->_conn->quote($class->discriminatorValue); |
|
1732 } else { |
|
1733 $discrMap = array_flip($class->discriminatorMap); |
|
1734 if (!isset($discrMap[$entityClassName])) { |
|
1735 throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName); |
|
1736 } |
|
1737 |
|
1738 $sql .= $this->_conn->quote($discrMap[$entityClassName]); |
|
1739 } |
|
1740 |
|
1741 return $sql; |
|
1742 } |
|
1743 |
|
1744 /** |
|
1745 * Walks down an InParameter AST node, thereby generating the appropriate SQL. |
|
1746 * |
|
1747 * @param InParameter |
|
1748 * @return string The SQL. |
|
1749 */ |
|
1750 public function walkInParameter($inParam) |
|
1751 { |
|
1752 return $inParam instanceof AST\InputParameter ? |
|
1753 $this->walkInputParameter($inParam) : |
|
1754 $this->walkLiteral($inParam); |
|
1755 } |
|
1756 |
|
1757 /** |
|
1758 * Walks down a literal that represents an AST node, thereby generating the appropriate SQL. |
|
1759 * |
|
1760 * @param mixed |
|
1761 * @return string The SQL. |
|
1762 */ |
|
1763 public function walkLiteral($literal) |
|
1764 { |
|
1765 switch ($literal->type) { |
|
1766 case AST\Literal::STRING: |
|
1767 return $this->_conn->quote($literal->value); |
|
1768 case AST\Literal::BOOLEAN: |
|
1769 $bool = strtolower($literal->value) == 'true' ? true : false; |
|
1770 $boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); |
|
1771 return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal; |
|
1772 case AST\Literal::NUMERIC: |
|
1773 return $literal->value; |
|
1774 default: |
|
1775 throw QueryException::invalidLiteral($literal); |
|
1776 } |
|
1777 } |
|
1778 |
|
1779 /** |
|
1780 * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. |
|
1781 * |
|
1782 * @param BetweenExpression |
|
1783 * @return string The SQL. |
|
1784 */ |
|
1785 public function walkBetweenExpression($betweenExpr) |
|
1786 { |
|
1787 $sql = $this->walkArithmeticExpression($betweenExpr->expression); |
|
1788 |
|
1789 if ($betweenExpr->not) $sql .= ' NOT'; |
|
1790 |
|
1791 $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) |
|
1792 . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression); |
|
1793 |
|
1794 return $sql; |
|
1795 } |
|
1796 |
|
1797 /** |
|
1798 * Walks down a LikeExpression AST node, thereby generating the appropriate SQL. |
|
1799 * |
|
1800 * @param LikeExpression |
|
1801 * @return string The SQL. |
|
1802 */ |
|
1803 public function walkLikeExpression($likeExpr) |
|
1804 { |
|
1805 $stringExpr = $likeExpr->stringExpression; |
|
1806 $sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE '; |
|
1807 |
|
1808 if ($likeExpr->stringPattern instanceof AST\InputParameter) { |
|
1809 $inputParam = $likeExpr->stringPattern; |
|
1810 $dqlParamKey = $inputParam->name; |
|
1811 $this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
1812 $sql .= '?'; |
|
1813 } else { |
|
1814 $sql .= $this->_conn->quote($likeExpr->stringPattern); |
|
1815 } |
|
1816 |
|
1817 if ($likeExpr->escapeChar) { |
|
1818 $sql .= ' ESCAPE ' . $this->_conn->quote($likeExpr->escapeChar); |
|
1819 } |
|
1820 |
|
1821 return $sql; |
|
1822 } |
|
1823 |
|
1824 /** |
|
1825 * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. |
|
1826 * |
|
1827 * @param StateFieldPathExpression |
|
1828 * @return string The SQL. |
|
1829 */ |
|
1830 public function walkStateFieldPathExpression($stateFieldPathExpression) |
|
1831 { |
|
1832 return $this->walkPathExpression($stateFieldPathExpression); |
|
1833 } |
|
1834 |
|
1835 /** |
|
1836 * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. |
|
1837 * |
|
1838 * @param ComparisonExpression |
|
1839 * @return string The SQL. |
|
1840 */ |
|
1841 public function walkComparisonExpression($compExpr) |
|
1842 { |
|
1843 $sql = ''; |
|
1844 $leftExpr = $compExpr->leftExpression; |
|
1845 $rightExpr = $compExpr->rightExpression; |
|
1846 |
|
1847 if ($leftExpr instanceof AST\Node) { |
|
1848 $sql .= $leftExpr->dispatch($this); |
|
1849 } else { |
|
1850 $sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr); |
|
1851 } |
|
1852 |
|
1853 $sql .= ' ' . $compExpr->operator . ' '; |
|
1854 |
|
1855 if ($rightExpr instanceof AST\Node) { |
|
1856 $sql .= $rightExpr->dispatch($this); |
|
1857 } else { |
|
1858 $sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr); |
|
1859 } |
|
1860 |
|
1861 return $sql; |
|
1862 } |
|
1863 |
|
1864 /** |
|
1865 * Walks down an InputParameter AST node, thereby generating the appropriate SQL. |
|
1866 * |
|
1867 * @param InputParameter |
|
1868 * @return string The SQL. |
|
1869 */ |
|
1870 public function walkInputParameter($inputParam) |
|
1871 { |
|
1872 $this->_parserResult->addParameterMapping($inputParam->name, $this->_sqlParamIndex++); |
|
1873 |
|
1874 return '?'; |
|
1875 } |
|
1876 |
|
1877 /** |
|
1878 * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. |
|
1879 * |
|
1880 * @param ArithmeticExpression |
|
1881 * @return string The SQL. |
|
1882 */ |
|
1883 public function walkArithmeticExpression($arithmeticExpr) |
|
1884 { |
|
1885 return ($arithmeticExpr->isSimpleArithmeticExpression()) |
|
1886 ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) |
|
1887 : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')'; |
|
1888 } |
|
1889 |
|
1890 /** |
|
1891 * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. |
|
1892 * |
|
1893 * @param SimpleArithmeticExpression |
|
1894 * @return string The SQL. |
|
1895 */ |
|
1896 public function walkSimpleArithmeticExpression($simpleArithmeticExpr) |
|
1897 { |
|
1898 return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) |
|
1899 ? $this->walkArithmeticTerm($simpleArithmeticExpr) |
|
1900 : implode( |
|
1901 ' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms) |
|
1902 ); |
|
1903 } |
|
1904 |
|
1905 /** |
|
1906 * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. |
|
1907 * |
|
1908 * @param mixed |
|
1909 * @return string The SQL. |
|
1910 */ |
|
1911 public function walkArithmeticTerm($term) |
|
1912 { |
|
1913 if (is_string($term)) { |
|
1914 return $term; |
|
1915 } |
|
1916 |
|
1917 // Phase 2 AST optimization: Skip processment of ArithmeticTerm |
|
1918 // if only one ArithmeticFactor is defined |
|
1919 return ( ! ($term instanceof AST\ArithmeticTerm)) |
|
1920 ? $this->walkArithmeticFactor($term) |
|
1921 : implode( |
|
1922 ' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors) |
|
1923 ); |
|
1924 } |
|
1925 |
|
1926 /** |
|
1927 * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. |
|
1928 * |
|
1929 * @param mixed |
|
1930 * @return string The SQL. |
|
1931 */ |
|
1932 public function walkArithmeticFactor($factor) |
|
1933 { |
|
1934 if (is_string($factor)) { |
|
1935 return $factor; |
|
1936 } |
|
1937 |
|
1938 // Phase 2 AST optimization: Skip processment of ArithmeticFactor |
|
1939 // if only one ArithmeticPrimary is defined |
|
1940 return ( ! ($factor instanceof AST\ArithmeticFactor)) |
|
1941 ? $this->walkArithmeticPrimary($factor) |
|
1942 : ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) |
|
1943 . $this->walkArithmeticPrimary($factor->arithmeticPrimary); |
|
1944 } |
|
1945 |
|
1946 /** |
|
1947 * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. |
|
1948 * |
|
1949 * @param mixed |
|
1950 * @return string The SQL. |
|
1951 */ |
|
1952 public function walkArithmeticPrimary($primary) |
|
1953 { |
|
1954 if ($primary instanceof AST\SimpleArithmeticExpression) { |
|
1955 return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; |
|
1956 } else if ($primary instanceof AST\Node) { |
|
1957 return $primary->dispatch($this); |
|
1958 } |
|
1959 |
|
1960 // TODO: We need to deal with IdentificationVariable here |
|
1961 return ''; |
|
1962 } |
|
1963 |
|
1964 /** |
|
1965 * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. |
|
1966 * |
|
1967 * @param mixed |
|
1968 * @return string The SQL. |
|
1969 */ |
|
1970 public function walkStringPrimary($stringPrimary) |
|
1971 { |
|
1972 return (is_string($stringPrimary)) |
|
1973 ? $this->_conn->quote($stringPrimary) |
|
1974 : $stringPrimary->dispatch($this); |
|
1975 } |
|
1976 } |