source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/Query/SqlWalker.php @ 345

Last change on this file since 345 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 80.7 KB
Line 
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
20namespace Doctrine\ORM\Query;
21
22use 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 */
40class 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}
Note: See TracBrowser for help on using the repository browser.