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.
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>.
20 namespace Doctrine\ORM\Query;
22 use Doctrine\DBAL\LockMode,
23 Doctrine\DBAL\Types\Type,
24 Doctrine\ORM\Mapping\ClassMetadata,
26 Doctrine\ORM\Query\QueryException,
27 Doctrine\ORM\Mapping\ClassMetadataInfo;
30 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
31 * the corresponding SQL.
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>
38 * @todo Rename: SQLWalker
40 class SqlWalker implements TreeWalker
45 const HINT_DISTINCT = 'doctrine.distinct';
48 * @var ResultSetMapping
53 * Counters for generating unique column aliases.
57 private $aliasCounter = 0;
60 * Counters for generating unique table aliases.
64 private $tableAliasCounter = 0;
67 * Counters for generating unique scalar result.
71 private $scalarResultCounter = 1;
74 * Counters for generating unique parameter indexes.
78 private $sqlParamIndex = 0;
83 private $parserResult;
91 * @var \Doctrine\DBAL\Connection
103 private $tableAliasMap = array();
106 * Map from result variable names to their SQL column alias names.
110 private $scalarResultAliasMap = array();
113 * Map from DQL-Alias + Field-Name to SQL Column Alias
117 private $scalarFields = array();
120 * Map of all components/classes that appear in the DQL query.
124 private $queryComponents;
127 * A list of classes that appear in non-scalar SelectExpressions.
131 private $selectedClasses = array();
134 * The DQL alias of the root class of the currently traversed query.
138 private $rootAliases = array();
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.
146 private $useSqlTableAliases = true;
149 * The database platform abstraction.
151 * @var AbstractPlatform
156 * The quote strategy.
158 * @var \Doctrine\ORM\Mapping\QuoteStrategy
160 private $quoteStrategy;
165 public function __construct($query, $parserResult, array $queryComponents)
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();
178 * Gets the Query instance used by the walker.
182 public function getQuery()
188 * Gets the Connection used by the walker.
192 public function getConnection()
198 * Gets the EntityManager used by the walker.
200 * @return EntityManager
202 public function getEntityManager()
208 * Gets the information about a single query component.
210 * @param string $dqlAlias The DQL alias.
213 public function getQueryComponent($dqlAlias)
215 return $this->queryComponents[$dqlAlias];
219 * Gets an executor that can be used to execute the result of this walker.
221 * @return AbstractExecutor
223 public function getExecutor($AST)
226 case ($AST instanceof AST\DeleteStatement):
227 $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
229 return ($primaryClass->isInheritanceTypeJoined())
230 ? new Exec\MultiTableDeleteExecutor($AST, $this)
231 : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
233 case ($AST instanceof AST\UpdateStatement):
234 $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
236 return ($primaryClass->isInheritanceTypeJoined())
237 ? new Exec\MultiTableUpdateExecutor($AST, $this)
238 : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
241 return new Exec\SingleSelectExecutor($AST, $this);
246 * Generates a unique, short SQL table alias.
248 * @param string $tableName Table name
249 * @param string $dqlAlias The DQL alias.
250 * @return string Generated table alias.
252 public function getSQLTableAlias($tableName, $dqlAlias = '')
254 $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
256 if ( ! isset($this->tableAliasMap[$tableName])) {
257 $this->tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->tableAliasCounter++ . '_';
260 return $this->tableAliasMap[$tableName];
264 * Forces the SqlWalker to use a specific alias for a table name, rather than
265 * generating an alias on its own.
267 * @param string $tableName
268 * @param string $alias
269 * @param string $dqlAlias
272 public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
274 $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
276 $this->tableAliasMap[$tableName] = $alias;
282 * Gets an SQL column alias for a column name.
284 * @param string $columnName
287 public function getSQLColumnAlias($columnName)
289 return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
293 * Generates the SQL JOINs that are necessary for Class Table Inheritance
294 * for the given class.
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.
300 private function _generateClassTableInheritanceJoins($class, $dqlAlias)
304 $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
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);
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 ';
317 foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
318 $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
321 // Add filters on the root class
322 if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
323 $sqlParts[] = $filterSql;
326 $sql .= implode(' AND ', $sqlParts);
329 // Ignore subclassing inclusion if partial objects is disallowed
330 if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
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);
339 $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
343 foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
344 $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
347 $sql .= implode(' AND ', $sqlParts);
353 private function _generateOrderedCollectionOrderByItems()
357 foreach ($this->selectedClasses as $selectedClass) {
358 $dqlAlias = $selectedClass['dqlAlias'];
359 $qComp = $this->queryComponents[$dqlAlias];
361 if ( ! isset($qComp['relation']['orderBy'])) continue;
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();
369 $sqlParts[] = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName . ' ' . $orientation;
373 return implode(', ', $sqlParts);
377 * Generates a discriminator column SQL condition for the class with the given DQL alias.
379 * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
382 private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
386 foreach ($dqlAliases as $dqlAlias) {
387 $class = $this->queryComponents[$dqlAlias]['metadata'];
389 if ( ! $class->isInheritanceTypeSingleTable()) continue;
391 $conn = $this->em->getConnection();
394 if ($class->discriminatorValue !== null) { // discrimnators can be 0
395 $values[] = $conn->quote($class->discriminatorValue);
398 foreach ($class->subClasses as $subclassName) {
399 $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
402 $sqlParts[] = (($this->useSqlTableAliases) ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '')
403 . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
406 $sql = implode(' AND ', $sqlParts);
408 return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
412 * Generates the filter SQL for a given entity and table alias.
414 * @param ClassMetadata $targetEntity Metadata of the target entity.
415 * @param string $targetTableAlias The table alias of the joined/selected table.
417 * @return string The SQL query part to add to a query.
419 private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
421 if (!$this->em->hasFilters()) {
425 switch($targetEntity->inheritanceType) {
426 case ClassMetadata::INHERITANCE_TYPE_NONE:
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) {
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);
441 //@todo: throw exception?
446 $filterClauses = array();
447 foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
448 if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
449 $filterClauses[] = '(' . $filterExpr . ')';
453 return implode(' AND ', $filterClauses);
456 * Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
458 * @return string The SQL.
460 public function walkSelectStatement(AST\SelectStatement $AST)
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) : '';
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;
474 $sql = $this->platform->modifyLimitQuery(
475 $sql, $this->query->getMaxResults(), $this->query->getFirstResult()
478 if (($lockMode = $this->query->getHint(Query::HINT_LOCK_MODE)) !== false) {
480 case LockMode::PESSIMISTIC_READ:
481 $sql .= ' ' . $this->platform->getReadLockSQL();
484 case LockMode::PESSIMISTIC_WRITE:
485 $sql .= ' ' . $this->platform->getWriteLockSQL();
488 case LockMode::OPTIMISTIC:
489 foreach ($this->selectedClasses as $selectedClass) {
490 if ( ! $selectedClass['class']->isVersioned) {
491 throw \Doctrine\ORM\OptimisticLockException::lockFailed($selectedClass['class']->name);
499 throw \Doctrine\ORM\Query\QueryException::invalidLockMode();
507 * Walks down an UpdateStatement AST node, thereby generating the appropriate SQL.
509 * @param UpdateStatement
510 * @return string The SQL.
512 public function walkUpdateStatement(AST\UpdateStatement $AST)
514 $this->useSqlTableAliases = false;
516 return $this->walkUpdateClause($AST->updateClause)
517 . $this->walkWhereClause($AST->whereClause);
521 * Walks down a DeleteStatement AST node, thereby generating the appropriate SQL.
523 * @param DeleteStatement
524 * @return string The SQL.
526 public function walkDeleteStatement(AST\DeleteStatement $AST)
528 $this->useSqlTableAliases = false;
530 return $this->walkDeleteClause($AST->deleteClause)
531 . $this->walkWhereClause($AST->whereClause);
535 * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
536 * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
538 * @param string $identVariable
541 public function walkEntityIdentificationVariable($identVariable)
543 $class = $this->queryComponents[$identVariable]['metadata'];
544 $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
547 foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
548 $sqlParts[] = $tableAlias . '.' . $columnName;
551 return implode(', ', $sqlParts);
555 * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
557 * @param string $identificationVariable
558 * @param string $fieldName
559 * @return string The SQL.
561 public function walkIdentificationVariable($identificationVariable, $fieldName = null)
563 $class = $this->queryComponents[$identificationVariable]['metadata'];
566 $fieldName !== null && $class->isInheritanceTypeJoined() &&
567 isset($class->fieldMappings[$fieldName]['inherited'])
569 $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
572 return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
576 * Walks down a PathExpression AST node, thereby generating the appropriate SQL.
579 * @return string The SQL.
581 public function walkPathExpression($pathExpr)
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'];
591 if ($this->useSqlTableAliases) {
592 $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
595 $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
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'];
605 if (isset($class->associationMappings[$fieldName]['inherited'])) {
606 $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
609 $assoc = $class->associationMappings[$fieldName];
611 if ( ! $assoc['isOwningSide']) {
612 throw QueryException::associationPathInverseSideNotSupported();
615 // COMPOSITE KEYS NOT (YET?) SUPPORTED
616 if (count($assoc['sourceToTargetKeyColumns']) > 1) {
617 throw QueryException::associationPathCompositeKeyNotSupported();
620 if ($this->useSqlTableAliases) {
621 $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
624 $sql .= reset($assoc['targetToSourceKeyColumns']);
628 throw QueryException::invalidPathExpression($pathExpr);
635 * Walks down a SelectClause AST node, thereby generating the appropriate SQL.
637 * @param $selectClause
638 * @return string The SQL.
640 public function walkSelectClause($selectClause)
642 $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
643 $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
645 if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
646 $this->query->setHint(self::HINT_DISTINCT, true);
649 $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
650 $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
652 $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
653 $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
655 foreach ($this->selectedClasses as $selectedClass) {
656 $class = $selectedClass['class'];
657 $dqlAlias = $selectedClass['dqlAlias'];
658 $resultAlias = $selectedClass['resultAlias'];
660 // Register as entity or joined entity result
661 if ($this->queryComponents[$dqlAlias]['relation'] === null) {
662 $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
664 $this->rsm->addJoinedEntityResult(
667 $this->queryComponents[$dqlAlias]['parent'],
668 $this->queryComponents[$dqlAlias]['relation']['fieldName']
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']);
679 $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
681 $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
682 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']);
685 // Add foreign key columns to SQL, if necessary
686 if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
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)) {
694 } else if ( !$addMetaColumns && !isset($assoc['id'])) {
698 $owningClass = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
699 $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
701 foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
702 $columnAlias = $this->getSQLColumnAlias($srcColumn);
704 $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
706 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn, (isset($assoc['id']) && $assoc['id'] === true));
710 // Add foreign key columns to SQL, if necessary
711 if ( ! $addMetaColumns) {
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);
720 foreach ($subClass->associationMappings as $assoc) {
721 // Skip if association is inherited
722 if (isset($assoc['inherited'])) continue;
724 if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue;
726 foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
727 $columnAlias = $this->getSQLColumnAlias($srcColumn);
729 $sqlSelectExpressions[] = $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias;
731 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $srcColumn);
737 $sql .= implode(', ', $sqlSelectExpressions);
743 * Walks down a FromClause AST node, thereby generating the appropriate SQL.
745 * @return string The SQL.
747 public function walkFromClause($fromClause)
749 $identificationVarDecls = $fromClause->identificationVariableDeclarations;
752 foreach ($identificationVarDecls as $identificationVariableDecl) {
753 $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
755 foreach ($identificationVariableDecl->joins as $join) {
756 $sql .= $this->walkJoin($join);
759 if ($identificationVariableDecl->indexBy) {
760 $alias = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable;
761 $field = $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field;
763 if (isset($this->scalarFields[$alias][$field])) {
764 $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
766 $this->rsm->addIndexBy(
767 $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable,
768 $identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field
773 $sqlParts[] = $this->platform->appendLockHint($sql, $this->query->getHint(Query::HINT_LOCK_MODE));
776 return ' FROM ' . implode(', ', $sqlParts);
780 * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
784 public function walkRangeVariableDeclaration($rangeVariableDeclaration)
786 $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
787 $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
789 $this->rootAliases[] = $dqlAlias;
791 $sql = $class->getQuotedTableName($this->platform) . ' '
792 . $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
794 if ($class->isInheritanceTypeJoined()) {
795 $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
802 * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
806 public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER)
810 $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
811 $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable;
812 $indexBy = $joinAssociationDeclaration->indexBy;
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);
819 $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
820 $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
822 // Ensure we got the owning side, since it has all mapping info
823 $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
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);
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.
835 case ($assoc['type'] & ClassMetadata::TO_ONE):
836 $conditions = array();
838 foreach ($assoc['joinColumns'] as $joinColumn) {
839 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
840 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
842 if ($relation['isOwningSide']) {
843 $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
848 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
851 // Apply remaining inheritance restrictions
852 $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
855 $conditions[] = $discrSql;
859 $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
862 $conditions[] = $filterExpr;
865 $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions);
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);
874 $conditions = array();
875 $relationColumns = ($relation['isOwningSide'])
876 ? $assoc['joinTable']['joinColumns']
877 : $assoc['joinTable']['inverseJoinColumns'];
879 foreach ($relationColumns as $joinColumn) {
880 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
881 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
883 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
886 $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
889 $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
891 $conditions = array();
892 $relationColumns = ($relation['isOwningSide'])
893 ? $assoc['joinTable']['inverseJoinColumns']
894 : $assoc['joinTable']['joinColumns'];
896 foreach ($relationColumns as $joinColumn) {
897 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
898 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
900 $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
903 // Apply remaining inheritance restrictions
904 $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
907 $conditions[] = $discrSql;
911 $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
914 $conditions[] = $filterExpr;
917 $sql .= $targetTableName . ' ' . $targetTableAlias . ' ON ' . implode(' AND ', $conditions);
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);
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
933 } else if (isset($relation['indexBy'])) {
934 $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
941 * Walks down a FunctionNode AST node, thereby generating the appropriate SQL.
943 * @return string The SQL.
945 public function walkFunction($function)
947 return $function->getSql($this);
951 * Walks down an OrderByClause AST node, thereby generating the appropriate SQL.
953 * @param OrderByClause
954 * @return string The SQL.
956 public function walkOrderByClause($orderByClause)
958 $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems);
960 if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
961 $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
964 return ' ORDER BY ' . implode(', ', $orderByItems);
968 * Walks down an OrderByItem AST node, thereby generating the appropriate SQL.
971 * @return string The SQL.
973 public function walkOrderByItem($orderByItem)
975 $expr = $orderByItem->expression;
976 $sql = ($expr instanceof AST\Node)
977 ? $expr->dispatch($this)
978 : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
980 return $sql . ' ' . strtoupper($orderByItem->type);
984 * Walks down a HavingClause AST node, thereby generating the appropriate SQL.
986 * @param HavingClause
987 * @return string The SQL.
989 public function walkHavingClause($havingClause)
991 return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
995 * Walks down a Join AST node and creates the corresponding SQL.
997 * @return string The SQL.
999 public function walkJoin($join)
1001 $joinType = $join->joinType;
1002 $joinDeclaration = $join->joinAssociationDeclaration;
1004 $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1009 case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
1010 $sql .= $this->walkRangeVariableDeclaration($joinDeclaration)
1011 . ' ON (' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1014 case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
1015 $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType);
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) . ')';
1030 * Walks down a CaseExpression AST node and generates the corresponding SQL.
1032 * @param CoalesceExpression|NullIfExpression|GeneralCaseExpression|SimpleCaseExpression $expression
1033 * @return string The SQL.
1035 public function walkCaseExpression($expression)
1038 case ($expression instanceof AST\CoalesceExpression):
1039 return $this->walkCoalesceExpression($expression);
1041 case ($expression instanceof AST\NullIfExpression):
1042 return $this->walkNullIfExpression($expression);
1044 case ($expression instanceof AST\GeneralCaseExpression):
1045 return $this->walkGeneralCaseExpression($expression);
1047 case ($expression instanceof AST\SimpleCaseExpression):
1048 return $this->walkSimpleCaseExpression($expression);
1056 * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1058 * @param CoalesceExpression $coalesceExpression
1059 * @return string The SQL.
1061 public function walkCoalesceExpression($coalesceExpression)
1065 $scalarExpressions = array();
1067 foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1068 $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1071 $sql .= implode(', ', $scalarExpressions) . ')';
1077 * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1079 * @param NullIfExpression $nullIfExpression
1080 * @return string The SQL.
1082 public function walkNullIfExpression($nullIfExpression)
1084 $firstExpression = is_string($nullIfExpression->firstExpression)
1085 ? $this->conn->quote($nullIfExpression->firstExpression)
1086 : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1088 $secondExpression = is_string($nullIfExpression->secondExpression)
1089 ? $this->conn->quote($nullIfExpression->secondExpression)
1090 : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1092 return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1096 * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1098 * @param GeneralCaseExpression $generalCaseExpression
1099 * @return string The SQL.
1101 public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1105 foreach ($generalCaseExpression->whenClauses as $whenClause) {
1106 $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1107 $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1110 $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1116 * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1118 * @param SimpleCaseExpression $simpleCaseExpression
1119 * @return string The SQL.
1121 public function walkSimpleCaseExpression($simpleCaseExpression)
1123 $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1125 foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1126 $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1127 $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1130 $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1136 * Walks down a SelectExpression AST node and generates the corresponding SQL.
1138 * @param SelectExpression $selectExpression
1139 * @return string The SQL.
1141 public function walkSelectExpression($selectExpression)
1144 $expr = $selectExpression->expression;
1145 $hidden = $selectExpression->hiddenAliasResultVariable;
1148 case ($expr instanceof AST\PathExpression):
1149 if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1150 throw QueryException::invalidPathExpression($expr->type);
1153 $fieldName = $expr->field;
1154 $dqlAlias = $expr->identificationVariable;
1155 $qComp = $this->queryComponents[$dqlAlias];
1156 $class = $qComp['metadata'];
1158 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1159 $tableName = ($class->isInheritanceTypeJoined())
1160 ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1161 : $class->getTableName();
1163 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1164 $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1165 $columnAlias = $this->getSQLColumnAlias($class->fieldMappings[$fieldName]['columnName']);
1167 $col = $sqlTableAlias . '.' . $columnName;
1169 $fieldType = $class->getTypeOfField($fieldName);
1171 if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
1172 $type = Type::getType($fieldType);
1173 $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1176 $sql .= $col . ' AS ' . $columnAlias;
1178 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1181 $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1182 $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
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++;
1200 $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1202 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1205 // We cannot resolve field type here; assume 'string'.
1206 $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1210 case ($expr instanceof AST\Subselect):
1211 $columnAlias = $this->getSQLColumnAlias('sclr');
1212 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1214 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1216 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1219 // We cannot resolve field type here; assume 'string'.
1220 $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1225 // IdentificationVariable or PartialObjectExpression
1226 if ($expr instanceof AST\PartialObjectExpression) {
1227 $dqlAlias = $expr->identificationVariable;
1228 $partialFieldSet = $expr->partialFieldSet;
1231 $partialFieldSet = array();
1234 $queryComp = $this->queryComponents[$dqlAlias];
1235 $class = $queryComp['metadata'];
1236 $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1238 if ( ! isset($this->selectedClasses[$dqlAlias])) {
1239 $this->selectedClasses[$dqlAlias] = array(
1241 'dqlAlias' => $dqlAlias,
1242 'resultAlias' => $resultAlias
1246 $sqlParts = array();
1248 // Select all fields from the queried class
1249 foreach ($class->fieldMappings as $fieldName => $mapping) {
1250 if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
1254 $tableName = (isset($mapping['inherited']))
1255 ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1256 : $class->getTableName();
1258 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1259 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
1260 $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1262 $col = $sqlTableAlias . '.' . $quotedColumnName;
1264 if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
1265 $type = Type::getType($class->getTypeOfField($fieldName));
1266 $col = $type->convertToPHPValueSQL($col, $this->platform);
1269 $sqlParts[] = $col . ' AS '. $columnAlias;
1271 $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1273 $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
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);
1285 foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1286 if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) {
1290 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
1291 $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1293 $col = $sqlTableAlias . '.' . $quotedColumnName;
1295 if (isset($subClass->fieldMappings[$fieldName]['requireSQLConversion'])) {
1296 $type = Type::getType($subClass->getTypeOfField($fieldName));
1297 $col = $type->convertToPHPValueSQL($col, $this->platform);
1300 $sqlParts[] = $col . ' AS ' . $columnAlias;
1302 $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1304 $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1309 $sql .= implode(', ', $sqlParts);
1316 * Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL.
1318 * @param QuantifiedExpression
1319 * @return string The SQL.
1321 public function walkQuantifiedExpression($qExpr)
1323 return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1327 * Walks down a Subselect AST node, thereby generating the appropriate SQL.
1330 * @return string The SQL.
1332 public function walkSubselect($subselect)
1334 $useAliasesBefore = $this->useSqlTableAliases;
1335 $rootAliasesBefore = $this->rootAliases;
1337 $this->rootAliases = array(); // reset the rootAliases for the subselect
1338 $this->useSqlTableAliases = true;
1340 $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1341 $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1342 $sql .= $this->walkWhereClause($subselect->whereClause);
1344 $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1345 $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1346 $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1348 $this->rootAliases = $rootAliasesBefore; // put the main aliases back
1349 $this->useSqlTableAliases = $useAliasesBefore;
1355 * Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL.
1357 * @param SubselectFromClause
1358 * @return string The SQL.
1360 public function walkSubselectFromClause($subselectFromClause)
1362 $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1363 $sqlParts = array ();
1365 foreach ($identificationVarDecls as $subselectIdVarDecl) {
1366 $sql = $this->walkRangeVariableDeclaration($subselectIdVarDecl->rangeVariableDeclaration);
1368 foreach ($subselectIdVarDecl->joins as $join) {
1369 $sql .= $this->walkJoin($join);
1372 $sqlParts[] = $this->platform->appendLockHint($sql, $this->query->getHint(Query::HINT_LOCK_MODE));
1375 return ' FROM ' . implode(', ', $sqlParts);
1379 * Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL.
1381 * @param SimpleSelectClause
1382 * @return string The SQL.
1384 public function walkSimpleSelectClause($simpleSelectClause)
1386 return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1387 . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1391 * Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL.
1393 * @param SimpleSelectExpression
1394 * @return string The SQL.
1396 public function walkSimpleSelectExpression($simpleSelectExpression)
1398 $expr = $simpleSelectExpression->expression;
1402 case ($expr instanceof AST\PathExpression):
1403 $sql .= $this->walkPathExpression($expr);
1406 case ($expr instanceof AST\AggregateExpression):
1407 $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1409 $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
1412 case ($expr instanceof AST\Subselect):
1413 $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1415 $columnAlias = 'sclr' . $this->aliasCounter++;
1416 $this->scalarResultAliasMap[$alias] = $columnAlias;
1418 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
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++;
1433 $columnAlias = $this->getSQLColumnAlias('sclr');
1434 $this->scalarResultAliasMap[$alias] = $columnAlias;
1436 $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1439 default: // IdentificationVariable
1440 $sql .= $this->walkEntityIdentificationVariable($expr);
1448 * Walks down an AggregateExpression AST node, thereby generating the appropriate SQL.
1450 * @param AggregateExpression
1451 * @return string The SQL.
1453 public function walkAggregateExpression($aggExpression)
1455 return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1456 . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1460 * Walks down a GroupByClause AST node, thereby generating the appropriate SQL.
1462 * @param GroupByClause
1463 * @return string The SQL.
1465 public function walkGroupByClause($groupByClause)
1467 $sqlParts = array();
1469 foreach ($groupByClause->groupByItems as $groupByItem) {
1470 $sqlParts[] = $this->walkGroupByItem($groupByItem);
1473 return ' GROUP BY ' . implode(', ', $sqlParts);
1477 * Walks down a GroupByItem AST node, thereby generating the appropriate SQL.
1479 * @param GroupByItem
1480 * @return string The SQL.
1482 public function walkGroupByItem($groupByItem)
1484 // StateFieldPathExpression
1485 if ( ! is_string($groupByItem)) {
1486 return $this->walkPathExpression($groupByItem);
1490 if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1491 return $this->walkResultVariable($groupByItem);
1494 // IdentificationVariable
1495 $sqlParts = array();
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;
1501 $sqlParts[] = $this->walkPathExpression($item);
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;
1509 $sqlParts[] = $this->walkPathExpression($item);
1513 return implode(', ', $sqlParts);
1517 * Walks down a DeleteClause AST node, thereby generating the appropriate SQL.
1519 * @param DeleteClause
1520 * @return string The SQL.
1522 public function walkDeleteClause(AST\DeleteClause $deleteClause)
1524 $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1525 $tableName = $class->getTableName();
1526 $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1528 $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1529 $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1535 * Walks down an UpdateClause AST node, thereby generating the appropriate SQL.
1537 * @param UpdateClause
1538 * @return string The SQL.
1540 public function walkUpdateClause($updateClause)
1542 $class = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1543 $tableName = $class->getTableName();
1544 $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1546 $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1547 $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1549 $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems));
1555 * Walks down an UpdateItem AST node, thereby generating the appropriate SQL.
1558 * @return string The SQL.
1560 public function walkUpdateItem($updateItem)
1562 $useTableAliasesBefore = $this->useSqlTableAliases;
1563 $this->useSqlTableAliases = false;
1565 $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1566 $newValue = $updateItem->newValue;
1569 case ($newValue instanceof AST\Node):
1570 $sql .= $newValue->dispatch($this);
1573 case ($newValue === null):
1578 $sql .= $this->conn->quote($newValue);
1582 $this->useSqlTableAliases = $useTableAliasesBefore;
1588 * Walks down a WhereClause AST node, thereby generating the appropriate SQL.
1589 * WhereClause or not, the appropriate discriminator sql is added.
1591 * @param WhereClause
1592 * @return string The SQL.
1594 public function walkWhereClause($whereClause)
1596 $condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1597 $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases);
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);
1605 if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1606 $filterClauses[] = $filterExpr;
1610 if (count($filterClauses)) {
1612 $condSql .= ' AND ';
1615 $condSql .= implode(' AND ', $filterClauses);
1620 return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1624 return ' WHERE ' . $discrSql;
1631 * Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL.
1633 * @param ConditionalExpression
1634 * @return string The SQL.
1636 public function walkConditionalExpression($condExpr)
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);
1644 return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms));
1648 * Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL.
1650 * @param ConditionalTerm
1651 * @return string The SQL.
1653 public function walkConditionalTerm($condTerm)
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);
1661 return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors));
1665 * Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL.
1667 * @param ConditionalFactor
1668 * @return string The SQL.
1670 public function walkConditionalFactor($factor)
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);
1680 * Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL.
1682 * @param ConditionalPrimary
1683 * @return string The SQL.
1685 public function walkConditionalPrimary($primary)
1687 if ($primary->isSimpleConditionalExpression()) {
1688 return $primary->simpleConditionalExpression->dispatch($this);
1691 if ($primary->isConditionalExpression()) {
1692 $condExpr = $primary->conditionalExpression;
1694 return '(' . $this->walkConditionalExpression($condExpr) . ')';
1699 * Walks down an ExistsExpression AST node, thereby generating the appropriate SQL.
1701 * @param ExistsExpression
1702 * @return string The SQL.
1704 public function walkExistsExpression($existsExpr)
1706 $sql = ($existsExpr->not) ? 'NOT ' : '';
1708 $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1714 * Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL.
1716 * @param CollectionMemberExpression
1717 * @return string The SQL.
1719 public function walkCollectionMemberExpression($collMemberExpr)
1721 $sql = $collMemberExpr->not ? 'NOT ' : '';
1722 $sql .= 'EXISTS (SELECT 1 FROM ';
1724 $entityExpr = $collMemberExpr->entityExpression;
1725 $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1727 $fieldName = $collPathExpr->field;
1728 $dqlAlias = $collPathExpr->identificationVariable;
1730 $class = $this->queryComponents[$dqlAlias]['metadata'];
1734 case ($entityExpr instanceof AST\InputParameter):
1735 $dqlParamKey = $entityExpr->name;
1739 // SingleValuedAssociationPathExpression | IdentificationVariable
1740 case ($entityExpr instanceof AST\PathExpression):
1741 $entitySql = $this->walkPathExpression($entityExpr);
1745 throw new \BadMethodCallException("Not implemented");
1748 $assoc = $class->associationMappings[$fieldName];
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);
1755 $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1757 $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1758 $sqlParts = array();
1760 foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
1761 $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1763 $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1766 foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1767 if (isset($dqlParamKey)) {
1768 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1771 $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql;
1774 $sql .= implode(' AND ', $sqlParts);
1775 } else { // many-to-many
1776 $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1778 $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1779 $joinTable = $owningAssoc['joinTable'];
1781 // SQL table aliases
1782 $joinTableAlias = $this->getSQLTableAlias($joinTable['name']);
1783 $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1784 $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
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 ';
1791 $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1792 $joinSqlParts = array();
1794 foreach ($joinColumns as $joinColumn) {
1795 $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1797 $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1800 $sql .= implode(' AND ', $joinSqlParts);
1803 $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1804 $sqlParts = array();
1806 foreach ($joinColumns as $joinColumn) {
1807 $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1809 $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1812 foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1813 if (isset($dqlParamKey)) {
1814 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1817 $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql;
1820 $sql .= implode(' AND ', $sqlParts);
1827 * Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL.
1829 * @param EmptyCollectionComparisonExpression
1830 * @return string The SQL.
1832 public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1834 $sizeFunc = new AST\Functions\SizeFunction('size');
1835 $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1837 return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1841 * Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL.
1843 * @param NullComparisonExpression
1844 * @return string The SQL.
1846 public function walkNullComparisonExpression($nullCompExpr)
1849 $innerExpr = $nullCompExpr->expression;
1851 if ($innerExpr instanceof AST\InputParameter) {
1852 $dqlParamKey = $innerExpr->name;
1853 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1856 $sql .= $this->walkPathExpression($innerExpr);
1859 $sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
1865 * Walks down an InExpression AST node, thereby generating the appropriate SQL.
1867 * @param InExpression
1868 * @return string The SQL.
1870 public function walkInExpression($inExpr)
1872 $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
1874 $sql .= ($inExpr->subselect)
1875 ? $this->walkSubselect($inExpr->subselect)
1876 : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
1884 * Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
1886 * @param InstanceOfExpression
1887 * @return string The SQL.
1889 public function walkInstanceOfExpression($instanceOfExpr)
1893 $dqlAlias = $instanceOfExpr->identificationVariable;
1894 $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
1896 if ($class->discriminatorColumn) {
1897 $discrClass = $this->em->getClassMetadata($class->rootEntityName);
1900 if ($this->useSqlTableAliases) {
1901 $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
1904 $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
1906 $sqlParameterList = array();
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());
1915 if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) {
1916 throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue));
1919 $entityClassName = $paramValue->name;
1921 // Get name from ClassMetadata to resolve aliases.
1922 $entityClassName = $this->em->getClassMetadata($parameter)->name;
1925 if ($entityClassName == $class->name) {
1926 $sqlParameterList[] = $this->conn->quote($class->discriminatorValue);
1928 $discrMap = array_flip($class->discriminatorMap);
1930 if (!isset($discrMap[$entityClassName])) {
1931 throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
1934 $sqlParameterList[] = $this->conn->quote($discrMap[$entityClassName]);
1938 $sql .= '(' . implode(', ', $sqlParameterList) . ')';
1944 * Walks down an InParameter AST node, thereby generating the appropriate SQL.
1946 * @param InParameter
1947 * @return string The SQL.
1949 public function walkInParameter($inParam)
1951 return $inParam instanceof AST\InputParameter
1952 ? $this->walkInputParameter($inParam)
1953 : $this->walkLiteral($inParam);
1957 * Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
1960 * @return string The SQL.
1962 public function walkLiteral($literal)
1964 switch ($literal->type) {
1965 case AST\Literal::STRING:
1966 return $this->conn->quote($literal->value);
1968 case AST\Literal::BOOLEAN:
1969 $bool = strtolower($literal->value) == 'true' ? true : false;
1970 $boolVal = $this->conn->getDatabasePlatform()->convertBooleans($bool);
1974 case AST\Literal::NUMERIC:
1975 return $literal->value;
1978 throw QueryException::invalidLiteral($literal);
1983 * Walks down a BetweenExpression AST node, thereby generating the appropriate SQL.
1985 * @param BetweenExpression
1986 * @return string The SQL.
1988 public function walkBetweenExpression($betweenExpr)
1990 $sql = $this->walkArithmeticExpression($betweenExpr->expression);
1992 if ($betweenExpr->not) $sql .= ' NOT';
1994 $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
1995 . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2001 * Walks down a LikeExpression AST node, thereby generating the appropriate SQL.
2003 * @param LikeExpression
2004 * @return string The SQL.
2006 public function walkLikeExpression($likeExpr)
2008 $stringExpr = $likeExpr->stringExpression;
2009 $sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2011 if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2012 $inputParam = $likeExpr->stringPattern;
2013 $dqlParamKey = $inputParam->name;
2014 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
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);
2021 $sql .= $this->walkLiteral($likeExpr->stringPattern);
2024 if ($likeExpr->escapeChar) {
2025 $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2032 * Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL.
2034 * @param StateFieldPathExpression
2035 * @return string The SQL.
2037 public function walkStateFieldPathExpression($stateFieldPathExpression)
2039 return $this->walkPathExpression($stateFieldPathExpression);
2043 * Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL.
2045 * @param ComparisonExpression
2046 * @return string The SQL.
2048 public function walkComparisonExpression($compExpr)
2050 $leftExpr = $compExpr->leftExpression;
2051 $rightExpr = $compExpr->rightExpression;
2054 $sql .= ($leftExpr instanceof AST\Node)
2055 ? $leftExpr->dispatch($this)
2056 : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2058 $sql .= ' ' . $compExpr->operator . ' ';
2060 $sql .= ($rightExpr instanceof AST\Node)
2061 ? $rightExpr->dispatch($this)
2062 : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2068 * Walks down an InputParameter AST node, thereby generating the appropriate SQL.
2070 * @param InputParameter
2071 * @return string The SQL.
2073 public function walkInputParameter($inputParam)
2075 $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2081 * Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL.
2083 * @param ArithmeticExpression
2084 * @return string The SQL.
2086 public function walkArithmeticExpression($arithmeticExpr)
2088 return ($arithmeticExpr->isSimpleArithmeticExpression())
2089 ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
2090 : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
2094 * Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
2096 * @param SimpleArithmeticExpression
2097 * @return string The SQL.
2099 public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2101 if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2102 return $this->walkArithmeticTerm($simpleArithmeticExpr);
2105 return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms));
2109 * Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL.
2112 * @return string The SQL.
2114 public function walkArithmeticTerm($term)
2116 if (is_string($term)) {
2117 return (isset($this->queryComponents[$term]))
2118 ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
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);
2128 return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors));
2132 * Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL.
2135 * @return string The SQL.
2137 public function walkArithmeticFactor($factor)
2139 if (is_string($factor)) {
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);
2149 $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2151 return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2155 * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2158 * @return string The SQL.
2160 public function walkArithmeticPrimary($primary)
2162 if ($primary instanceof AST\SimpleArithmeticExpression) {
2163 return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2166 if ($primary instanceof AST\Node) {
2167 return $primary->dispatch($this);
2170 return $this->walkEntityIdentificationVariable($primary);
2174 * Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL.
2177 * @return string The SQL.
2179 public function walkStringPrimary($stringPrimary)
2181 return (is_string($stringPrimary))
2182 ? $this->conn->quote($stringPrimary)
2183 : $stringPrimary->dispatch($this);
2187 * Walks down a ResultVriable that represents an AST node, thereby generating the appropriate SQL.
2189 * @param string $resultVariable
2190 * @return string The SQL.
2192 public function walkResultVariable($resultVariable)
2194 $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2196 if (is_array($resultAlias)) {
2197 return implode(', ', $resultAlias);
2200 return $resultAlias;