summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php')
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php125
1 files changed, 125 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php
new file mode 100644
index 0000000..c7f31db
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php
@@ -0,0 +1,125 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Platforms\SQLServerPlatform;
9use Doctrine\ORM\Query;
10use Doctrine\ORM\Query\AST\SelectStatement;
11use Doctrine\ORM\Query\Parser;
12use Doctrine\ORM\Query\ParserResult;
13use Doctrine\ORM\Query\ResultSetMapping;
14use Doctrine\ORM\Query\SqlWalker;
15use RuntimeException;
16
17use function array_diff;
18use function array_keys;
19use function assert;
20use function count;
21use function implode;
22use function reset;
23use function sprintf;
24
25/**
26 * Wraps the query in order to accurately count the root objects.
27 *
28 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
29 * SELECT COUNT(*) (SELECT DISTINCT <id> FROM (<original SQL>))
30 *
31 * Works with composite keys but cannot deal with queries that have multiple
32 * root entities (e.g. `SELECT f, b from Foo, Bar`)
33 *
34 * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL)
35 * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery
36 * that will most likely be executed next can be read from the native SQL cache.
37 *
38 * @psalm-import-type QueryComponent from Parser
39 */
40class CountOutputWalker extends SqlWalker
41{
42 private readonly AbstractPlatform $platform;
43 private readonly ResultSetMapping $rsm;
44
45 /**
46 * {@inheritDoc}
47 */
48 public function __construct(Query $query, ParserResult $parserResult, array $queryComponents)
49 {
50 $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
51 $this->rsm = $parserResult->getResultSetMapping();
52
53 parent::__construct($query, $parserResult, $queryComponents);
54 }
55
56 public function walkSelectStatement(SelectStatement $selectStatement): string
57 {
58 if ($this->platform instanceof SQLServerPlatform) {
59 $selectStatement->orderByClause = null;
60 }
61
62 $sql = parent::walkSelectStatement($selectStatement);
63
64 if ($selectStatement->groupByClause) {
65 return sprintf(
66 'SELECT COUNT(*) AS dctrn_count FROM (%s) dctrn_table',
67 $sql,
68 );
69 }
70
71 // Find out the SQL alias of the identifier column of the root entity
72 // It may be possible to make this work with multiple root entities but that
73 // would probably require issuing multiple queries or doing a UNION SELECT
74 // so for now, It's not supported.
75
76 // Get the root entity and alias from the AST fromClause
77 $from = $selectStatement->fromClause->identificationVariableDeclarations;
78 if (count($from) > 1) {
79 throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
80 }
81
82 $fromRoot = reset($from);
83 $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
84 $rootClass = $this->getMetadataForDqlAlias($rootAlias);
85 $rootIdentifier = $rootClass->identifier;
86
87 // For every identifier, find out the SQL alias by combing through the ResultSetMapping
88 $sqlIdentifier = [];
89 foreach ($rootIdentifier as $property) {
90 if (isset($rootClass->fieldMappings[$property])) {
91 foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) {
92 if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
93 $sqlIdentifier[$property] = $alias;
94 }
95 }
96 }
97
98 if (isset($rootClass->associationMappings[$property])) {
99 $association = $rootClass->associationMappings[$property];
100 assert($association->isToOneOwningSide());
101 $joinColumn = $association->joinColumns[0]->name;
102
103 foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) {
104 if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
105 $sqlIdentifier[$property] = $alias;
106 }
107 }
108 }
109 }
110
111 if (count($rootIdentifier) !== count($sqlIdentifier)) {
112 throw new RuntimeException(sprintf(
113 'Not all identifier properties can be found in the ResultSetMapping: %s',
114 implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))),
115 ));
116 }
117
118 // Build the counter query
119 return sprintf(
120 'SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table',
121 implode(', ', $sqlIdentifier),
122 $sql,
123 );
124 }
125}