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