/** * Walks down a SelectStatement AST node, wrapping it in a COUNT (SELECT DISTINCT) * * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL) * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery * that will most likely be executed next can be read from the native SQL cache. * * @param SelectStatement $AST * @return string */ public function walkSelectStatement(SelectStatement $AST) { $sql = parent::walkSelectStatement($AST); // Find out the SQL alias of the identifier column of the root entity // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT // so for now, It's not supported. // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) > 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $rootClass = $from[0]->rangeVariableDeclaration->abstractSchemaName; $rootAlias = $from[0]->rangeVariableDeclaration->aliasIdentificationVariable; // Get the identity properties from the metadata $rootIdentifier = $this->queryComponents[$rootAlias]['metadata']->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf('Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))))); } // Build the counter query return sprintf('SELECT %s AS _dctrn_count FROM (SELECT DISTINCT %s FROM (%s) AS _dctrn_result) AS _dctrn_table', $this->platform->getCountExpression('*'), implode(', ', $sqlIdentifier), $sql); }
/** * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT * * @param SelectStatement $AST * @return string */ public function walkSelectStatement(SelectStatement $AST) { $innerSql = parent::walkSelectStatement($AST); // Find out the SQL alias of the identifier column of the root entity // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT // so for now, It's not supported. // Get the root entity and alias from the AST fromClause $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) !== 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $rootAlias = $from[0]->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->queryComponents[$rootAlias]['metadata']; $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf('Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))))); } // Build the counter query $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $innerSql); if ($this->platform instanceof PostgreSqlPlatform) { //http://www.doctrine-project.org/jira/browse/DDC-1958 $this->getPostgresqlSql($AST, $sqlIdentifier, $innerSql, $sql); } // Apply the limit and offset $sql = $this->platform->modifyLimitQuery($sql, $this->maxResults, $this->firstResult); // Add the columns to the ResultSetMapping. It's not really nice but // it works. Preferably I'd clear the RSM or simply create a new one // but that is not possible from inside the output walker, so we dirty // up the one we have. foreach ($sqlIdentifier as $property => $alias) { $this->rsm->addScalarResult($alias, $property); } return $sql; }
/** * {@inheritDoc} */ public function walkSelectStatement(SelectStatement $AST) { $result = parent::walkSelectStatement($AST); if (!count($this->translatedComponents)) { return $result; } $hydrationMode = $this->getQuery()->getHydrationMode(); if ($hydrationMode === Query::HYDRATE_OBJECT) { $this->getQuery()->setHydrationMode(self::HYDRATE_OBJECT_TRANSLATION); $this->getEntityManager()->getConfiguration()->addCustomHydrationMode(self::HYDRATE_OBJECT_TRANSLATION, 'Gedmo\\Translatable\\Hydrator\\ORM\\ObjectHydrator'); $this->getQuery()->setHint(Query::HINT_REFRESH, true); } elseif ($hydrationMode === Query::HYDRATE_SIMPLEOBJECT) { $this->getQuery()->setHydrationMode(self::HYDRATE_SIMPLE_OBJECT_TRANSLATION); $this->getEntityManager()->getConfiguration()->addCustomHydrationMode(self::HYDRATE_SIMPLE_OBJECT_TRANSLATION, 'Gedmo\\Translatable\\Hydrator\\ORM\\SimpleObjectHydrator'); $this->getQuery()->setHint(Query::HINT_REFRESH, true); } return $result; }
public function __construct(SelectStatement $AST, SqlWalker $sqlWalker) { $this->_sqlStatements = $sqlWalker->walkSelectStatement($AST); }
public function walkSelectStatement(Query\AST\SelectStatement $selectStatement) { parent::walkSelectStatement($selectStatement); $this->setQueryComponent('x', array()); }
/** * @param SelectStatement $AST * * @return string * * @throws \Doctrine\ORM\OptimisticLockException * @throws \Doctrine\ORM\Query\QueryException */ private function getInnerSQL(SelectStatement $AST) { // Set every select expression as visible(hidden = false) to // make $AST have scalar mappings properly - this is relevant for referencing selected // fields from outside the subquery, for example in the ORDER BY segment $hiddens = []; foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $hiddens[$idx] = $expr->hiddenAliasResultVariable; $expr->hiddenAliasResultVariable = false; } $innerSql = parent::walkSelectStatement($AST); // Restore hiddens foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $expr->hiddenAliasResultVariable = $hiddens[$idx]; } return $innerSql; }
/** * {@inheritDoc} */ public function walkSelectStatement(SelectStatement $AST) { $result = parent::walkSelectStatement($AST); if (!count($this->translatedComponents)) { return $result; } $hydrationMode = $this->getQuery()->getHydrationMode(); if ($this->needsFallback()) { // in case if fallback is used and hydration is array, it needs custom hydrator if ($hydrationMode === Query::HYDRATE_ARRAY) { $this->getQuery()->setHydrationMode(self::HYDRATE_ARRAY_TRANSLATION); $this->getEntityManager()->getConfiguration()->addCustomHydrationMode(self::HYDRATE_ARRAY_TRANSLATION, 'Gedmo\\Translatable\\Hydrator\\ORM\\ArrayHydrator'); } } $this->getQuery()->setHint(self::HINT_TRANSLATION_LISTENER, $this->listener); if ($hydrationMode === Query::HYDRATE_OBJECT) { $this->getQuery()->setHydrationMode(self::HYDRATE_OBJECT_TRANSLATION); $this->getEntityManager()->getConfiguration()->addCustomHydrationMode(self::HYDRATE_OBJECT_TRANSLATION, 'Gedmo\\Translatable\\Hydrator\\ORM\\ObjectHydrator'); $this->getQuery()->setHint(Query::HINT_REFRESH, true); } elseif ($hydrationMode === Query::HYDRATE_SIMPLEOBJECT) { $this->getQuery()->setHydrationMode(self::HYDRATE_SIMPLE_OBJECT_TRANSLATION); $this->getEntityManager()->getConfiguration()->addCustomHydrationMode(self::HYDRATE_SIMPLE_OBJECT_TRANSLATION, 'Gedmo\\Translatable\\Hydrator\\ORM\\SimpleObjectHydrator'); $this->getQuery()->setHint(Query::HINT_REFRESH, true); } return $result; }
/** * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT. * * @param SelectStatement $AST * @param bool $addMissingItemsFromOrderByToSelect * * @return string * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST, $addMissingItemsFromOrderByToSelect = true) { // We don't want to call this recursively! if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) { // In the case of ordering a query by columns from joined tables, we // must add those columns to the select clause of the query BEFORE // the SQL is generated. $this->addMissingItemsFromOrderByToSelect($AST); } // Remove order by clause from the inner query // It will be re-appended in the outer select generated by this method $orderByClause = $AST->orderByClause; $AST->orderByClause = null; // Set every select expression as visible(hidden = false) to // make $AST have scalar mappings properly - this is relevant for referencing selected // fields from outside the subquery, for example in the ORDER BY segment $hiddens = array(); foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $hiddens[$idx] = $expr->hiddenAliasResultVariable; $expr->hiddenAliasResultVariable = false; } $innerSql = parent::walkSelectStatement($AST); // Restore orderByClause $AST->orderByClause = $orderByClause; // Restore hiddens foreach ($AST->selectClause->selectExpressions as $idx => $expr) { $expr->hiddenAliasResultVariable = $hiddens[$idx]; } // Find out the SQL alias of the identifier column of the root entity. // It may be possible to make this work with multiple root entities but that // would probably require issuing multiple queries or doing a UNION SELECT. // So for now, it's not supported. // Get the root entity and alias from the AST fromClause. $from = $AST->fromClause->identificationVariableDeclarations; if (count($from) !== 1) { throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); } $fromRoot = reset($from); $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable; $rootClass = $this->queryComponents[$rootAlias]['metadata']; $rootIdentifier = $rootClass->identifier; // For every identifier, find out the SQL alias by combing through the ResultSetMapping $sqlIdentifier = array(); foreach ($rootIdentifier as $property) { if (isset($rootClass->fieldMappings[$property])) { foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } if (isset($rootClass->associationMappings[$property])) { $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name']; foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) { if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) { $sqlIdentifier[$property] = $alias; } } } } if (count($sqlIdentifier) === 0) { throw new \RuntimeException('The Paginator does not support Queries which only yield ScalarResults.'); } if (count($rootIdentifier) != count($sqlIdentifier)) { throw new \RuntimeException(sprintf('Not all identifier properties can be found in the ResultSetMapping: %s', implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))))); } // Build the counter query $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $innerSql); // http://www.doctrine-project.org/jira/browse/DDC-1958 $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause); // Apply the limit and offset. $sql = $this->platform->modifyLimitQuery($sql, $this->maxResults, $this->firstResult); // Add the columns to the ResultSetMapping. It's not really nice but // it works. Preferably I'd clear the RSM or simply create a new one // but that is not possible from inside the output walker, so we dirty // up the one we have. foreach ($sqlIdentifier as $property => $alias) { $this->rsm->addScalarResult($alias, $property); } return $sql; }