7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * If you did not receive a copy of the license and are unable to
10 * obtain it through the world-wide-web, please send an email
11 * to kontakt@beberlei.de so I can send you a copy immediately.
14 namespace Doctrine\ORM\Tools\Pagination
;
16 use Doctrine\ORM\Query\SqlWalker
;
17 use Doctrine\ORM\Query\AST\SelectStatement
;
20 * Wraps the query in order to accurately count the root objects.
22 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
23 * SELECT COUNT(*) (SELECT DISTINCT <id> FROM (<original SQL>))
25 * Works with composite keys but cannot deal with queries that have multiple
26 * root entities (e.g. `SELECT f, b from Foo, Bar`)
28 * @author Sander Marechal <s.marechal@jejik.com>
30 class CountOutputWalker
extends SqlWalker
33 * @var \Doctrine\DBAL\Platforms\AbstractPlatform
38 * @var \Doctrine\ORM\Query\ResultSetMapping
45 private $queryComponents;
50 * Stores various parameters that are otherwise unavailable
51 * because Doctrine\ORM\Query\SqlWalker keeps everything private without
54 * @param \Doctrine\ORM\Query $query
55 * @param \Doctrine\ORM\Query\ParserResult $parserResult
56 * @param array $queryComponents
58 public function __construct($query, $parserResult, array $queryComponents)
60 $this->platform
= $query->getEntityManager()->getConnection()->getDatabasePlatform();
61 $this->rsm
= $parserResult->getResultSetMapping();
62 $this->queryComponents
= $queryComponents;
64 parent
::__construct($query, $parserResult, $queryComponents);
68 * Walks down a SelectStatement AST node, wrapping it in a COUNT (SELECT DISTINCT).
70 * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL)
71 * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery
72 * that will most likely be executed next can be read from the native SQL cache.
74 * @param SelectStatement $AST
78 * @throws \RuntimeException
80 public function walkSelectStatement(SelectStatement
$AST)
82 if ($this->platform
->getName() === "mssql") {
83 $AST->orderByClause
= null;
86 $sql = parent
::walkSelectStatement($AST);
88 if ($AST->groupByClause
) {
90 'SELECT %s AS dctrn_count FROM (%s) dctrn_table',
91 $this->platform
->getCountExpression('*'),
96 // Find out the SQL alias of the identifier column of the root entity
97 // It may be possible to make this work with multiple root entities but that
98 // would probably require issuing multiple queries or doing a UNION SELECT
99 // so for now, It's not supported.
101 // Get the root entity and alias from the AST fromClause
102 $from = $AST->fromClause
->identificationVariableDeclarations
;
103 if (count($from) > 1) {
104 throw new \
RuntimeException("Cannot count query which selects two FROM components, cannot make distinction");
107 $fromRoot = reset($from);
108 $rootAlias = $fromRoot->rangeVariableDeclaration
->aliasIdentificationVariable
;
109 $rootClass = $this->queryComponents
[$rootAlias]['metadata'];
110 $rootIdentifier = $rootClass->identifier
;
112 // For every identifier, find out the SQL alias by combing through the ResultSetMapping
113 $sqlIdentifier = array();
114 foreach ($rootIdentifier as $property) {
115 if (isset($rootClass->fieldMappings
[$property])) {
116 foreach (array_keys($this->rsm
->fieldMappings
, $property) as $alias) {
117 if ($this->rsm
->columnOwnerMap
[$alias] == $rootAlias) {
118 $sqlIdentifier[$property] = $alias;
123 if (isset($rootClass->associationMappings
[$property])) {
124 $joinColumn = $rootClass->associationMappings
[$property]['joinColumns'][0]['name'];
126 foreach (array_keys($this->rsm
->metaMappings
, $joinColumn) as $alias) {
127 if ($this->rsm
->columnOwnerMap
[$alias] == $rootAlias) {
128 $sqlIdentifier[$property] = $alias;
134 if (count($rootIdentifier) != count($sqlIdentifier)) {
135 throw new \
RuntimeException(sprintf(
136 'Not all identifier properties can be found in the ResultSetMapping: %s',
137 implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
141 // Build the counter query
142 return sprintf('SELECT %s AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table',
143 $this->platform
->getCountExpression('*'),
144 implode(', ', $sqlIdentifier),