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