1
0
mirror of synced 2024-12-14 07:06:04 +03:00
doctrine2/lib/Doctrine/ORM/Tools/Pagination/CountOutputWalker.php

136 lines
4.8 KiB
PHP
Raw Normal View History

<?php
/**
* Doctrine ORM
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to kontakt@beberlei.de so I can send you a copy immediately.
*/
namespace Doctrine\ORM\Tools\Pagination;
2012-10-12 15:53:20 +04:00
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\AST\SelectStatement;
/**
* Wraps the query in order to accurately count the root objects.
*
* Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
* SELECT COUNT(*) (SELECT DISTINCT <id> FROM (<original SQL>))
*
* Works with composite keys but cannot deal with queries that have multiple
* root entities (e.g. `SELECT f, b from Foo, Bar`)
*
* @author Sander Marechal <s.marechal@jejik.com>
*/
2012-03-12 11:33:35 +04:00
class CountOutputWalker extends SqlWalker
{
/**
2012-09-21 03:20:06 +04:00
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
*/
private $platform;
/**
2012-09-21 03:20:06 +04:00
* @var \Doctrine\ORM\Query\ResultSetMapping
*/
private $rsm;
/**
* @var array
*/
private $queryComponents;
/**
* Constructor.
*
* Stores various parameters that are otherwise unavailable
* because Doctrine\ORM\Query\SqlWalker keeps everything private without
* accessors.
*
* @param \Doctrine\ORM\Query $query
2012-09-21 03:20:06 +04:00
* @param \Doctrine\ORM\Query\ParserResult $parserResult
* @param array $queryComponents
*/
public function __construct($query, $parserResult, array $queryComponents)
{
$this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
$this->rsm = $parserResult->getResultSetMapping();
$this->queryComponents = $queryComponents;
parent::__construct($query, $parserResult, $queryComponents);
}
/**
* 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
*
* @throws \RuntimeException
*/
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");
}
$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
return sprintf('SELECT %s AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table',
$this->platform->getCountExpression('*'),
implode(', ', $sqlIdentifier),
$sql
);
}
}