summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Tools/Pagination
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
committerpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
commitbf6655a534a6775d30cafa67bd801276bda1d98d (patch)
treec6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/doctrine/orm/src/Tools/Pagination
parent94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff)
downloadAppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/doctrine/orm/src/Tools/Pagination')
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/CountOutputWalker.php125
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php68
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php16
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryOutputWalker.php544
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php155
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/Paginator.php263
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php48
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php40
-rw-r--r--vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php116
9 files changed, 1375 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}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php
new file mode 100644
index 0000000..d212943
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/CountWalker.php
@@ -0,0 +1,68 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\ORM\Query\AST\AggregateExpression;
8use Doctrine\ORM\Query\AST\PathExpression;
9use Doctrine\ORM\Query\AST\SelectExpression;
10use Doctrine\ORM\Query\AST\SelectStatement;
11use Doctrine\ORM\Query\TreeWalkerAdapter;
12use RuntimeException;
13
14use function count;
15use function reset;
16
17/**
18 * Replaces the selectClause of the AST with a COUNT statement.
19 */
20class CountWalker extends TreeWalkerAdapter
21{
22 /**
23 * Distinct mode hint name.
24 */
25 public const HINT_DISTINCT = 'doctrine_paginator.distinct';
26
27 public function walkSelectStatement(SelectStatement $selectStatement): void
28 {
29 if ($selectStatement->havingClause) {
30 throw new RuntimeException('Cannot count query that uses a HAVING clause. Use the output walkers for pagination');
31 }
32
33 // Get the root entity and alias from the AST fromClause
34 $from = $selectStatement->fromClause->identificationVariableDeclarations;
35
36 if (count($from) > 1) {
37 throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
38 }
39
40 $fromRoot = reset($from);
41 $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
42 $rootClass = $this->getMetadataForDqlAlias($rootAlias);
43 $identifierFieldName = $rootClass->getSingleIdentifierFieldName();
44
45 $pathType = PathExpression::TYPE_STATE_FIELD;
46 if (isset($rootClass->associationMappings[$identifierFieldName])) {
47 $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
48 }
49
50 $pathExpression = new PathExpression(
51 PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
52 $rootAlias,
53 $identifierFieldName,
54 );
55 $pathExpression->type = $pathType;
56
57 $distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT);
58 $selectStatement->selectClause->selectExpressions = [
59 new SelectExpression(
60 new AggregateExpression('count', $pathExpression, $distinct),
61 null,
62 ),
63 ];
64
65 // ORDER BY is not needed, only increases query execution through unnecessary sorting.
66 $selectStatement->orderByClause = null;
67 }
68}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php b/vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php
new file mode 100644
index 0000000..0e3da93
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/Exception/RowNumberOverFunctionNotEnabled.php
@@ -0,0 +1,16 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination\Exception;
6
7use Doctrine\ORM\Exception\ORMException;
8use LogicException;
9
10final class RowNumberOverFunctionNotEnabled extends LogicException implements ORMException
11{
12 public static function create(): self
13 {
14 return new self('The RowNumberOverFunction is not intended for, nor is it enabled for use in DQL.');
15 }
16}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryOutputWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryOutputWalker.php
new file mode 100644
index 0000000..8bbc44c
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryOutputWalker.php
@@ -0,0 +1,544 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\DBAL\Platforms\AbstractPlatform;
8use Doctrine\DBAL\Platforms\DB2Platform;
9use Doctrine\DBAL\Platforms\OraclePlatform;
10use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
11use Doctrine\DBAL\Platforms\SQLServerPlatform;
12use Doctrine\ORM\EntityManagerInterface;
13use Doctrine\ORM\Mapping\QuoteStrategy;
14use Doctrine\ORM\OptimisticLockException;
15use Doctrine\ORM\Query;
16use Doctrine\ORM\Query\AST\OrderByClause;
17use Doctrine\ORM\Query\AST\PathExpression;
18use Doctrine\ORM\Query\AST\SelectExpression;
19use Doctrine\ORM\Query\AST\SelectStatement;
20use Doctrine\ORM\Query\AST\Subselect;
21use Doctrine\ORM\Query\Parser;
22use Doctrine\ORM\Query\ParserResult;
23use Doctrine\ORM\Query\QueryException;
24use Doctrine\ORM\Query\ResultSetMapping;
25use Doctrine\ORM\Query\SqlWalker;
26use RuntimeException;
27
28use function array_diff;
29use function array_keys;
30use function assert;
31use function count;
32use function implode;
33use function in_array;
34use function is_string;
35use function method_exists;
36use function preg_replace;
37use function reset;
38use function sprintf;
39use function strrpos;
40use function substr;
41
42/**
43 * Wraps the query in order to select root entity IDs for pagination.
44 *
45 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
46 * SELECT DISTINCT <id> FROM (<original SQL>) LIMIT x OFFSET y
47 *
48 * Works with composite keys but cannot deal with queries that have multiple
49 * root entities (e.g. `SELECT f, b from Foo, Bar`)
50 *
51 * @psalm-import-type QueryComponent from Parser
52 */
53class LimitSubqueryOutputWalker extends SqlWalker
54{
55 private const ORDER_BY_PATH_EXPRESSION = '/(?<![a-z0-9_])%s\.%s(?![a-z0-9_])/i';
56
57 private readonly AbstractPlatform $platform;
58 private readonly ResultSetMapping $rsm;
59 private readonly int $firstResult;
60 private readonly int|null $maxResults;
61 private readonly EntityManagerInterface $em;
62 private readonly QuoteStrategy $quoteStrategy;
63
64 /** @var list<PathExpression> */
65 private array $orderByPathExpressions = [];
66
67 /**
68 * We don't want to add path expressions from sub-selects into the select clause of the containing query.
69 * This state flag simply keeps track on whether we are walking on a subquery or not
70 */
71 private bool $inSubSelect = false;
72
73 /**
74 * Stores various parameters that are otherwise unavailable
75 * because Doctrine\ORM\Query\SqlWalker keeps everything private without
76 * accessors.
77 *
78 * {@inheritDoc}
79 */
80 public function __construct(
81 Query $query,
82 ParserResult $parserResult,
83 array $queryComponents,
84 ) {
85 $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
86 $this->rsm = $parserResult->getResultSetMapping();
87
88 // Reset limit and offset
89 $this->firstResult = $query->getFirstResult();
90 $this->maxResults = $query->getMaxResults();
91 $query->setFirstResult(0)->setMaxResults(null);
92
93 $this->em = $query->getEntityManager();
94 $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
95
96 parent::__construct($query, $parserResult, $queryComponents);
97 }
98
99 /**
100 * Check if the platform supports the ROW_NUMBER window function.
101 */
102 private function platformSupportsRowNumber(): bool
103 {
104 return $this->platform instanceof PostgreSQLPlatform
105 || $this->platform instanceof SQLServerPlatform
106 || $this->platform instanceof OraclePlatform
107 || $this->platform instanceof DB2Platform
108 || (method_exists($this->platform, 'supportsRowNumberFunction')
109 && $this->platform->supportsRowNumberFunction());
110 }
111
112 /**
113 * Rebuilds a select statement's order by clause for use in a
114 * ROW_NUMBER() OVER() expression.
115 */
116 private function rebuildOrderByForRowNumber(SelectStatement $AST): void
117 {
118 $orderByClause = $AST->orderByClause;
119 $selectAliasToExpressionMap = [];
120 // Get any aliases that are available for select expressions.
121 foreach ($AST->selectClause->selectExpressions as $selectExpression) {
122 $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
123 }
124
125 // Rebuild string orderby expressions to use the select expression they're referencing
126 foreach ($orderByClause->orderByItems as $orderByItem) {
127 if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) {
128 $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression];
129 }
130 }
131
132 $func = new RowNumberOverFunction('dctrn_rownum');
133 $func->orderByClause = $AST->orderByClause;
134 $AST->selectClause->selectExpressions[] = new SelectExpression($func, 'dctrn_rownum', true);
135
136 // No need for an order by clause, we'll order by rownum in the outer query.
137 $AST->orderByClause = null;
138 }
139
140 public function walkSelectStatement(SelectStatement $selectStatement): string
141 {
142 if ($this->platformSupportsRowNumber()) {
143 return $this->walkSelectStatementWithRowNumber($selectStatement);
144 }
145
146 return $this->walkSelectStatementWithoutRowNumber($selectStatement);
147 }
148
149 /**
150 * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
151 * This method is for use with platforms which support ROW_NUMBER.
152 *
153 * @throws RuntimeException
154 */
155 public function walkSelectStatementWithRowNumber(SelectStatement $AST): string
156 {
157 $hasOrderBy = false;
158 $outerOrderBy = ' ORDER BY dctrn_minrownum ASC';
159 $orderGroupBy = '';
160 if ($AST->orderByClause instanceof OrderByClause) {
161 $hasOrderBy = true;
162 $this->rebuildOrderByForRowNumber($AST);
163 }
164
165 $innerSql = $this->getInnerSQL($AST);
166
167 $sqlIdentifier = $this->getSQLIdentifier($AST);
168
169 if ($hasOrderBy) {
170 $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier);
171 $sqlIdentifier[] = 'MIN(' . $this->walkResultVariable('dctrn_rownum') . ') AS dctrn_minrownum';
172 }
173
174 // Build the counter query
175 $sql = sprintf(
176 'SELECT DISTINCT %s FROM (%s) dctrn_result',
177 implode(', ', $sqlIdentifier),
178 $innerSql,
179 );
180
181 if ($hasOrderBy) {
182 $sql .= $orderGroupBy . $outerOrderBy;
183 }
184
185 // Apply the limit and offset.
186 $sql = $this->platform->modifyLimitQuery(
187 $sql,
188 $this->maxResults,
189 $this->firstResult,
190 );
191
192 // Add the columns to the ResultSetMapping. It's not really nice but
193 // it works. Preferably I'd clear the RSM or simply create a new one
194 // but that is not possible from inside the output walker, so we dirty
195 // up the one we have.
196 foreach ($sqlIdentifier as $property => $alias) {
197 $this->rsm->addScalarResult($alias, $property);
198 }
199
200 return $sql;
201 }
202
203 /**
204 * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
205 * This method is for platforms which DO NOT support ROW_NUMBER.
206 *
207 * @throws RuntimeException
208 */
209 public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, bool $addMissingItemsFromOrderByToSelect = true): string
210 {
211 // We don't want to call this recursively!
212 if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) {
213 // In the case of ordering a query by columns from joined tables, we
214 // must add those columns to the select clause of the query BEFORE
215 // the SQL is generated.
216 $this->addMissingItemsFromOrderByToSelect($AST);
217 }
218
219 // Remove order by clause from the inner query
220 // It will be re-appended in the outer select generated by this method
221 $orderByClause = $AST->orderByClause;
222 $AST->orderByClause = null;
223
224 $innerSql = $this->getInnerSQL($AST);
225
226 $sqlIdentifier = $this->getSQLIdentifier($AST);
227
228 // Build the counter query
229 $sql = sprintf(
230 'SELECT DISTINCT %s FROM (%s) dctrn_result',
231 implode(', ', $sqlIdentifier),
232 $innerSql,
233 );
234
235 // https://github.com/doctrine/orm/issues/2630
236 $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause);
237
238 // Apply the limit and offset.
239 $sql = $this->platform->modifyLimitQuery(
240 $sql,
241 $this->maxResults,
242 $this->firstResult,
243 );
244
245 // Add the columns to the ResultSetMapping. It's not really nice but
246 // it works. Preferably I'd clear the RSM or simply create a new one
247 // but that is not possible from inside the output walker, so we dirty
248 // up the one we have.
249 foreach ($sqlIdentifier as $property => $alias) {
250 $this->rsm->addScalarResult($alias, $property);
251 }
252
253 // Restore orderByClause
254 $AST->orderByClause = $orderByClause;
255
256 return $sql;
257 }
258
259 /**
260 * Finds all PathExpressions in an AST's OrderByClause, and ensures that
261 * the referenced fields are present in the SelectClause of the passed AST.
262 */
263 private function addMissingItemsFromOrderByToSelect(SelectStatement $AST): void
264 {
265 $this->orderByPathExpressions = [];
266
267 // We need to do this in another walker because otherwise we'll end up
268 // polluting the state of this one.
269 $walker = clone $this;
270
271 // This will populate $orderByPathExpressions via
272 // LimitSubqueryOutputWalker::walkPathExpression, which will be called
273 // as the select statement is walked. We'll end up with an array of all
274 // path expressions referenced in the query.
275 $walker->walkSelectStatementWithoutRowNumber($AST, false);
276 $orderByPathExpressions = $walker->getOrderByPathExpressions();
277
278 // Get a map of referenced identifiers to field names.
279 $selects = [];
280 foreach ($orderByPathExpressions as $pathExpression) {
281 assert($pathExpression->field !== null);
282 $idVar = $pathExpression->identificationVariable;
283 $field = $pathExpression->field;
284 if (! isset($selects[$idVar])) {
285 $selects[$idVar] = [];
286 }
287
288 $selects[$idVar][$field] = true;
289 }
290
291 // Loop the select clause of the AST and exclude items from $select
292 // that are already being selected in the query.
293 foreach ($AST->selectClause->selectExpressions as $selectExpression) {
294 if ($selectExpression instanceof SelectExpression) {
295 $idVar = $selectExpression->expression;
296 if (! is_string($idVar)) {
297 continue;
298 }
299
300 $field = $selectExpression->fieldIdentificationVariable;
301 if ($field === null) {
302 // No need to add this select, as we're already fetching the whole object.
303 unset($selects[$idVar]);
304 } else {
305 unset($selects[$idVar][$field]);
306 }
307 }
308 }
309
310 // Add select items which were not excluded to the AST's select clause.
311 foreach ($selects as $idVar => $fields) {
312 $AST->selectClause->selectExpressions[] = new SelectExpression($idVar, null, true);
313 }
314 }
315
316 /**
317 * Generates new SQL for statements with an order by clause
318 *
319 * @param mixed[] $sqlIdentifier
320 */
321 private function preserveSqlOrdering(
322 array $sqlIdentifier,
323 string $innerSql,
324 string $sql,
325 OrderByClause|null $orderByClause,
326 ): string {
327 // If the sql statement has an order by clause, we need to wrap it in a new select distinct statement
328 if (! $orderByClause) {
329 return $sql;
330 }
331
332 // now only select distinct identifier
333 return sprintf(
334 'SELECT DISTINCT %s FROM (%s) dctrn_result',
335 implode(', ', $sqlIdentifier),
336 $this->recreateInnerSql($orderByClause, $sqlIdentifier, $innerSql),
337 );
338 }
339
340 /**
341 * Generates a new SQL statement for the inner query to keep the correct sorting
342 *
343 * @param mixed[] $identifiers
344 */
345 private function recreateInnerSql(
346 OrderByClause $orderByClause,
347 array $identifiers,
348 string $innerSql,
349 ): string {
350 [$searchPatterns, $replacements] = $this->generateSqlAliasReplacements();
351 $orderByItems = [];
352
353 foreach ($orderByClause->orderByItems as $orderByItem) {
354 // Walk order by item to get string representation of it and
355 // replace path expressions in the order by clause with their column alias
356 $orderByItemString = preg_replace(
357 $searchPatterns,
358 $replacements,
359 $this->walkOrderByItem($orderByItem),
360 );
361
362 $orderByItems[] = $orderByItemString;
363 $identifier = substr($orderByItemString, 0, strrpos($orderByItemString, ' '));
364
365 if (! in_array($identifier, $identifiers, true)) {
366 $identifiers[] = $identifier;
367 }
368 }
369
370 return $sql = sprintf(
371 'SELECT DISTINCT %s FROM (%s) dctrn_result_inner ORDER BY %s',
372 implode(', ', $identifiers),
373 $innerSql,
374 implode(', ', $orderByItems),
375 );
376 }
377
378 /**
379 * @return string[][]
380 * @psalm-return array{0: list<non-empty-string>, 1: list<string>}
381 */
382 private function generateSqlAliasReplacements(): array
383 {
384 $aliasMap = $searchPatterns = $replacements = $metadataList = [];
385
386 // Generate DQL alias -> SQL table alias mapping
387 foreach (array_keys($this->rsm->aliasMap) as $dqlAlias) {
388 $metadataList[$dqlAlias] = $class = $this->getMetadataForDqlAlias($dqlAlias);
389 $aliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
390 }
391
392 // Generate search patterns for each field's path expression in the order by clause
393 foreach ($this->rsm->fieldMappings as $fieldAlias => $fieldName) {
394 $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias];
395 $class = $metadataList[$dqlAliasForFieldAlias];
396
397 // If the field is from a joined child table, we won't be ordering on it.
398 if (! isset($class->fieldMappings[$fieldName])) {
399 continue;
400 }
401
402 $fieldMapping = $class->fieldMappings[$fieldName];
403
404 // Get the proper column name as will appear in the select list
405 $columnName = $this->quoteStrategy->getColumnName(
406 $fieldName,
407 $metadataList[$dqlAliasForFieldAlias],
408 $this->em->getConnection()->getDatabasePlatform(),
409 );
410
411 // Get the SQL table alias for the entity and field
412 $sqlTableAliasForFieldAlias = $aliasMap[$dqlAliasForFieldAlias];
413
414 if (isset($fieldMapping->declared) && $fieldMapping->declared !== $class->name) {
415 // Field was declared in a parent class, so we need to get the proper SQL table alias
416 // for the joined parent table.
417 $otherClassMetadata = $this->em->getClassMetadata($fieldMapping->declared);
418
419 if (! $otherClassMetadata->isMappedSuperclass) {
420 $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
421 }
422 }
423
424 // Compose search and replace patterns
425 $searchPatterns[] = sprintf(self::ORDER_BY_PATH_EXPRESSION, $sqlTableAliasForFieldAlias, $columnName);
426 $replacements[] = $fieldAlias;
427 }
428
429 return [$searchPatterns, $replacements];
430 }
431
432 /**
433 * getter for $orderByPathExpressions
434 *
435 * @return list<PathExpression>
436 */
437 public function getOrderByPathExpressions(): array
438 {
439 return $this->orderByPathExpressions;
440 }
441
442 /**
443 * @throws OptimisticLockException
444 * @throws QueryException
445 */
446 private function getInnerSQL(SelectStatement $AST): string
447 {
448 // Set every select expression as visible(hidden = false) to
449 // make $AST have scalar mappings properly - this is relevant for referencing selected
450 // fields from outside the subquery, for example in the ORDER BY segment
451 $hiddens = [];
452
453 foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
454 $hiddens[$idx] = $expr->hiddenAliasResultVariable;
455 $expr->hiddenAliasResultVariable = false;
456 }
457
458 $innerSql = parent::walkSelectStatement($AST);
459
460 // Restore hiddens
461 foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
462 $expr->hiddenAliasResultVariable = $hiddens[$idx];
463 }
464
465 return $innerSql;
466 }
467
468 /** @return string[] */
469 private function getSQLIdentifier(SelectStatement $AST): array
470 {
471 // Find out the SQL alias of the identifier column of the root entity.
472 // It may be possible to make this work with multiple root entities but that
473 // would probably require issuing multiple queries or doing a UNION SELECT.
474 // So for now, it's not supported.
475
476 // Get the root entity and alias from the AST fromClause.
477 $from = $AST->fromClause->identificationVariableDeclarations;
478 if (count($from) !== 1) {
479 throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
480 }
481
482 $fromRoot = reset($from);
483 $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
484 $rootClass = $this->getMetadataForDqlAlias($rootAlias);
485 $rootIdentifier = $rootClass->identifier;
486
487 // For every identifier, find out the SQL alias by combing through the ResultSetMapping
488 $sqlIdentifier = [];
489 foreach ($rootIdentifier as $property) {
490 if (isset($rootClass->fieldMappings[$property])) {
491 foreach (array_keys($this->rsm->fieldMappings, $property, true) as $alias) {
492 if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
493 $sqlIdentifier[$property] = $alias;
494 }
495 }
496 }
497
498 if (isset($rootClass->associationMappings[$property])) {
499 $association = $rootClass->associationMappings[$property];
500 assert($association->isToOneOwningSide());
501 $joinColumn = $association->joinColumns[0]->name;
502
503 foreach (array_keys($this->rsm->metaMappings, $joinColumn, true) as $alias) {
504 if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
505 $sqlIdentifier[$property] = $alias;
506 }
507 }
508 }
509 }
510
511 if (count($sqlIdentifier) === 0) {
512 throw new RuntimeException('The Paginator does not support Queries which only yield ScalarResults.');
513 }
514
515 if (count($rootIdentifier) !== count($sqlIdentifier)) {
516 throw new RuntimeException(sprintf(
517 'Not all identifier properties can be found in the ResultSetMapping: %s',
518 implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier))),
519 ));
520 }
521
522 return $sqlIdentifier;
523 }
524
525 public function walkPathExpression(PathExpression $pathExpr): string
526 {
527 if (! $this->inSubSelect && ! $this->platformSupportsRowNumber() && ! in_array($pathExpr, $this->orderByPathExpressions, true)) {
528 $this->orderByPathExpressions[] = $pathExpr;
529 }
530
531 return parent::walkPathExpression($pathExpr);
532 }
533
534 public function walkSubSelect(Subselect $subselect): string
535 {
536 $this->inSubSelect = true;
537
538 $sql = parent::walkSubselect($subselect);
539
540 $this->inSubSelect = false;
541
542 return $sql;
543 }
544}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php
new file mode 100644
index 0000000..3fb0eee
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/LimitSubqueryWalker.php
@@ -0,0 +1,155 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\DBAL\Types\Type;
8use Doctrine\ORM\Query;
9use Doctrine\ORM\Query\AST\Functions\IdentityFunction;
10use Doctrine\ORM\Query\AST\Node;
11use Doctrine\ORM\Query\AST\PathExpression;
12use Doctrine\ORM\Query\AST\SelectExpression;
13use Doctrine\ORM\Query\AST\SelectStatement;
14use Doctrine\ORM\Query\TreeWalkerAdapter;
15use RuntimeException;
16
17use function count;
18use function is_string;
19use function reset;
20
21/**
22 * Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent.
23 */
24class LimitSubqueryWalker extends TreeWalkerAdapter
25{
26 public const IDENTIFIER_TYPE = 'doctrine_paginator.id.type';
27
28 public const FORCE_DBAL_TYPE_CONVERSION = 'doctrine_paginator.scalar_result.force_dbal_type_conversion';
29
30 /**
31 * Counter for generating unique order column aliases.
32 */
33 private int $aliasCounter = 0;
34
35 public function walkSelectStatement(SelectStatement $selectStatement): void
36 {
37 // Get the root entity and alias from the AST fromClause
38 $from = $selectStatement->fromClause->identificationVariableDeclarations;
39 $fromRoot = reset($from);
40 $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
41 $rootClass = $this->getMetadataForDqlAlias($rootAlias);
42
43 $this->validate($selectStatement);
44 $identifier = $rootClass->getSingleIdentifierFieldName();
45
46 if (isset($rootClass->associationMappings[$identifier])) {
47 throw new RuntimeException('Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.');
48 }
49
50 $query = $this->_getQuery();
51
52 $query->setHint(
53 self::IDENTIFIER_TYPE,
54 Type::getType($rootClass->fieldMappings[$identifier]->type),
55 );
56
57 $query->setHint(self::FORCE_DBAL_TYPE_CONVERSION, true);
58
59 $pathExpression = new PathExpression(
60 PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
61 $rootAlias,
62 $identifier,
63 );
64
65 $pathExpression->type = PathExpression::TYPE_STATE_FIELD;
66
67 $selectStatement->selectClause->selectExpressions = [new SelectExpression($pathExpression, '_dctrn_id')];
68 $selectStatement->selectClause->isDistinct = ($query->getHints()[Paginator::HINT_ENABLE_DISTINCT] ?? true) === true;
69
70 if (! isset($selectStatement->orderByClause)) {
71 return;
72 }
73
74 $queryComponents = $this->getQueryComponents();
75 foreach ($selectStatement->orderByClause->orderByItems as $item) {
76 if ($item->expression instanceof PathExpression) {
77 $selectStatement->selectClause->selectExpressions[] = new SelectExpression(
78 $this->createSelectExpressionItem($item->expression),
79 '_dctrn_ord' . $this->aliasCounter++,
80 );
81
82 continue;
83 }
84
85 if (is_string($item->expression) && isset($queryComponents[$item->expression])) {
86 $qComp = $queryComponents[$item->expression];
87
88 if (isset($qComp['resultVariable'])) {
89 $selectStatement->selectClause->selectExpressions[] = new SelectExpression(
90 $qComp['resultVariable'],
91 $item->expression,
92 );
93 }
94 }
95 }
96 }
97
98 /**
99 * Validate the AST to ensure that this walker is able to properly manipulate it.
100 */
101 private function validate(SelectStatement $AST): void
102 {
103 // Prevent LimitSubqueryWalker from being used with queries that include
104 // a limit, a fetched to-many join, and an order by condition that
105 // references a column from the fetch joined table.
106 $queryComponents = $this->getQueryComponents();
107 $query = $this->_getQuery();
108 $from = $AST->fromClause->identificationVariableDeclarations;
109 $fromRoot = reset($from);
110
111 if (
112 $query instanceof Query
113 && $query->getMaxResults() !== null
114 && $AST->orderByClause
115 && count($fromRoot->joins)
116 ) {
117 // Check each orderby item.
118 // TODO: check complex orderby items too...
119 foreach ($AST->orderByClause->orderByItems as $orderByItem) {
120 $expression = $orderByItem->expression;
121 if (
122 $orderByItem->expression instanceof PathExpression
123 && isset($queryComponents[$expression->identificationVariable])
124 ) {
125 $queryComponent = $queryComponents[$expression->identificationVariable];
126 if (
127 isset($queryComponent['parent'])
128 && isset($queryComponent['relation'])
129 && $queryComponent['relation']->isToMany()
130 ) {
131 throw new RuntimeException('Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers.');
132 }
133 }
134 }
135 }
136 }
137
138 /**
139 * Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name).
140 *
141 * @return IdentityFunction|PathExpression
142 */
143 private function createSelectExpressionItem(PathExpression $pathExpression): Node
144 {
145 if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
146 $identity = new IdentityFunction('identity');
147
148 $identity->pathExpression = clone $pathExpression;
149
150 return $identity;
151 }
152
153 return clone $pathExpression;
154 }
155}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/Paginator.php b/vendor/doctrine/orm/src/Tools/Pagination/Paginator.php
new file mode 100644
index 0000000..db1b34d
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/Paginator.php
@@ -0,0 +1,263 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use ArrayIterator;
8use Countable;
9use Doctrine\Common\Collections\Collection;
10use Doctrine\ORM\Internal\SQLResultCasing;
11use Doctrine\ORM\NoResultException;
12use Doctrine\ORM\Query;
13use Doctrine\ORM\Query\Parameter;
14use Doctrine\ORM\Query\Parser;
15use Doctrine\ORM\Query\ResultSetMapping;
16use Doctrine\ORM\QueryBuilder;
17use IteratorAggregate;
18use Traversable;
19
20use function array_key_exists;
21use function array_map;
22use function array_sum;
23use function assert;
24use function is_string;
25
26/**
27 * The paginator can handle various complex scenarios with DQL.
28 *
29 * @template-covariant T
30 * @implements IteratorAggregate<array-key, T>
31 */
32class Paginator implements Countable, IteratorAggregate
33{
34 use SQLResultCasing;
35
36 public const HINT_ENABLE_DISTINCT = 'paginator.distinct.enable';
37
38 private readonly Query $query;
39 private bool|null $useOutputWalkers = null;
40 private int|null $count = null;
41
42 /** @param bool $fetchJoinCollection Whether the query joins a collection (true by default). */
43 public function __construct(
44 Query|QueryBuilder $query,
45 private readonly bool $fetchJoinCollection = true,
46 ) {
47 if ($query instanceof QueryBuilder) {
48 $query = $query->getQuery();
49 }
50
51 $this->query = $query;
52 }
53
54 /**
55 * Returns the query.
56 */
57 public function getQuery(): Query
58 {
59 return $this->query;
60 }
61
62 /**
63 * Returns whether the query joins a collection.
64 *
65 * @return bool Whether the query joins a collection.
66 */
67 public function getFetchJoinCollection(): bool
68 {
69 return $this->fetchJoinCollection;
70 }
71
72 /**
73 * Returns whether the paginator will use an output walker.
74 */
75 public function getUseOutputWalkers(): bool|null
76 {
77 return $this->useOutputWalkers;
78 }
79
80 /**
81 * Sets whether the paginator will use an output walker.
82 *
83 * @return $this
84 */
85 public function setUseOutputWalkers(bool|null $useOutputWalkers): static
86 {
87 $this->useOutputWalkers = $useOutputWalkers;
88
89 return $this;
90 }
91
92 public function count(): int
93 {
94 if ($this->count === null) {
95 try {
96 $this->count = (int) array_sum(array_map('current', $this->getCountQuery()->getScalarResult()));
97 } catch (NoResultException) {
98 $this->count = 0;
99 }
100 }
101
102 return $this->count;
103 }
104
105 /**
106 * {@inheritDoc}
107 *
108 * @psalm-return Traversable<array-key, T>
109 */
110 public function getIterator(): Traversable
111 {
112 $offset = $this->query->getFirstResult();
113 $length = $this->query->getMaxResults();
114
115 if ($this->fetchJoinCollection && $length !== null) {
116 $subQuery = $this->cloneQuery($this->query);
117
118 if ($this->useOutputWalker($subQuery)) {
119 $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class);
120 } else {
121 $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class);
122 $this->unbindUnusedQueryParams($subQuery);
123 }
124
125 $subQuery->setFirstResult($offset)->setMaxResults($length);
126
127 $foundIdRows = $subQuery->getScalarResult();
128
129 // don't do this for an empty id array
130 if ($foundIdRows === []) {
131 return new ArrayIterator([]);
132 }
133
134 $whereInQuery = $this->cloneQuery($this->query);
135 $ids = array_map('current', $foundIdRows);
136
137 $this->appendTreeWalker($whereInQuery, WhereInWalker::class);
138 $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_HAS_IDS, true);
139 $whereInQuery->setFirstResult(0)->setMaxResults(null);
140 $whereInQuery->setCacheable($this->query->isCacheable());
141
142 $databaseIds = $this->convertWhereInIdentifiersToDatabaseValues($ids);
143 $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $databaseIds);
144
145 $result = $whereInQuery->getResult($this->query->getHydrationMode());
146 } else {
147 $result = $this->cloneQuery($this->query)
148 ->setMaxResults($length)
149 ->setFirstResult($offset)
150 ->setCacheable($this->query->isCacheable())
151 ->getResult($this->query->getHydrationMode());
152 }
153
154 return new ArrayIterator($result);
155 }
156
157 private function cloneQuery(Query $query): Query
158 {
159 $cloneQuery = clone $query;
160
161 $cloneQuery->setParameters(clone $query->getParameters());
162 $cloneQuery->setCacheable(false);
163
164 foreach ($query->getHints() as $name => $value) {
165 $cloneQuery->setHint($name, $value);
166 }
167
168 return $cloneQuery;
169 }
170
171 /**
172 * Determines whether to use an output walker for the query.
173 */
174 private function useOutputWalker(Query $query): bool
175 {
176 if ($this->useOutputWalkers === null) {
177 return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
178 }
179
180 return $this->useOutputWalkers;
181 }
182
183 /**
184 * Appends a custom tree walker to the tree walkers hint.
185 *
186 * @psalm-param class-string $walkerClass
187 */
188 private function appendTreeWalker(Query $query, string $walkerClass): void
189 {
190 $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
191
192 if ($hints === false) {
193 $hints = [];
194 }
195
196 $hints[] = $walkerClass;
197 $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
198 }
199
200 /**
201 * Returns Query prepared to count.
202 */
203 private function getCountQuery(): Query
204 {
205 $countQuery = $this->cloneQuery($this->query);
206
207 if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
208 $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
209 }
210
211 if ($this->useOutputWalker($countQuery)) {
212 $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
213
214 $rsm = new ResultSetMapping();
215 $rsm->addScalarResult($this->getSQLResultCasing($platform, 'dctrn_count'), 'count');
216
217 $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
218 $countQuery->setResultSetMapping($rsm);
219 } else {
220 $this->appendTreeWalker($countQuery, CountWalker::class);
221 $this->unbindUnusedQueryParams($countQuery);
222 }
223
224 $countQuery->setFirstResult(0)->setMaxResults(null);
225
226 return $countQuery;
227 }
228
229 private function unbindUnusedQueryParams(Query $query): void
230 {
231 $parser = new Parser($query);
232 $parameterMappings = $parser->parse()->getParameterMappings();
233 /** @var Collection|Parameter[] $parameters */
234 $parameters = $query->getParameters();
235
236 foreach ($parameters as $key => $parameter) {
237 $parameterName = $parameter->getName();
238
239 if (! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
240 unset($parameters[$key]);
241 }
242 }
243
244 $query->setParameters($parameters);
245 }
246
247 /**
248 * @param mixed[] $identifiers
249 *
250 * @return mixed[]
251 */
252 private function convertWhereInIdentifiersToDatabaseValues(array $identifiers): array
253 {
254 $query = $this->cloneQuery($this->query);
255 $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class);
256
257 $connection = $this->query->getEntityManager()->getConnection();
258 $type = $query->getSQL();
259 assert(is_string($type));
260
261 return array_map(static fn ($id): mixed => $connection->convertToDatabaseValue($id, $type), $identifiers);
262 }
263}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php
new file mode 100644
index 0000000..f630ee1
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/RootTypeWalker.php
@@ -0,0 +1,48 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\ORM\Query\AST;
8use Doctrine\ORM\Query\SqlWalker;
9use Doctrine\ORM\Utility\PersisterHelper;
10use RuntimeException;
11
12use function count;
13use function reset;
14
15/**
16 * Infers the DBAL type of the #Id (identifier) column of the given query's root entity, and
17 * returns it in place of a real SQL statement.
18 *
19 * Obtaining this type is a necessary intermediate step for \Doctrine\ORM\Tools\Pagination\Paginator.
20 * We can best do this from a tree walker because it gives us access to the AST.
21 *
22 * Returning the type instead of a "real" SQL statement is a slight hack. However, it has the
23 * benefit that the DQL -> root entity id type resolution can be cached in the query cache.
24 */
25final class RootTypeWalker extends SqlWalker
26{
27 public function walkSelectStatement(AST\SelectStatement $selectStatement): string
28 {
29 // Get the root entity and alias from the AST fromClause
30 $from = $selectStatement->fromClause->identificationVariableDeclarations;
31
32 if (count($from) > 1) {
33 throw new RuntimeException('Can only process queries that select only one FROM component');
34 }
35
36 $fromRoot = reset($from);
37 $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
38 $rootClass = $this->getMetadataForDqlAlias($rootAlias);
39 $identifierFieldName = $rootClass->getSingleIdentifierFieldName();
40
41 return PersisterHelper::getTypeOfField(
42 $identifierFieldName,
43 $rootClass,
44 $this->getQuery()
45 ->getEntityManager(),
46 )[0];
47 }
48}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php b/vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php
new file mode 100644
index 0000000..a0fdd01
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/RowNumberOverFunction.php
@@ -0,0 +1,40 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\ORM\Query\AST\Functions\FunctionNode;
8use Doctrine\ORM\Query\AST\OrderByClause;
9use Doctrine\ORM\Query\Parser;
10use Doctrine\ORM\Query\SqlWalker;
11use Doctrine\ORM\Tools\Pagination\Exception\RowNumberOverFunctionNotEnabled;
12
13use function trim;
14
15/**
16 * RowNumberOverFunction
17 *
18 * Provides ROW_NUMBER() OVER(ORDER BY...) construct for use in LimitSubqueryOutputWalker
19 */
20class RowNumberOverFunction extends FunctionNode
21{
22 public OrderByClause $orderByClause;
23
24 public function getSql(SqlWalker $sqlWalker): string
25 {
26 return 'ROW_NUMBER() OVER(' . trim($sqlWalker->walkOrderByClause(
27 $this->orderByClause,
28 )) . ')';
29 }
30
31 /**
32 * @throws RowNumberOverFunctionNotEnabled
33 *
34 * @inheritdoc
35 */
36 public function parse(Parser $parser): void
37 {
38 throw RowNumberOverFunctionNotEnabled::create();
39 }
40}
diff --git a/vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php b/vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php
new file mode 100644
index 0000000..01741ca
--- /dev/null
+++ b/vendor/doctrine/orm/src/Tools/Pagination/WhereInWalker.php
@@ -0,0 +1,116 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Tools\Pagination;
6
7use Doctrine\ORM\Query\AST\ArithmeticExpression;
8use Doctrine\ORM\Query\AST\ConditionalExpression;
9use Doctrine\ORM\Query\AST\ConditionalPrimary;
10use Doctrine\ORM\Query\AST\ConditionalTerm;
11use Doctrine\ORM\Query\AST\InListExpression;
12use Doctrine\ORM\Query\AST\InputParameter;
13use Doctrine\ORM\Query\AST\NullComparisonExpression;
14use Doctrine\ORM\Query\AST\PathExpression;
15use Doctrine\ORM\Query\AST\SelectStatement;
16use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
17use Doctrine\ORM\Query\AST\WhereClause;
18use Doctrine\ORM\Query\TreeWalkerAdapter;
19use RuntimeException;
20
21use function count;
22use function reset;
23
24/**
25 * Appends a condition equivalent to "WHERE IN (:dpid_1, :dpid_2, ...)" to the whereClause of the AST.
26 *
27 * The parameter namespace (dpid) is defined by
28 * the PAGINATOR_ID_ALIAS
29 *
30 * The HINT_PAGINATOR_HAS_IDS query hint indicates whether there are
31 * any ids in the parameter at all.
32 */
33class WhereInWalker extends TreeWalkerAdapter
34{
35 /**
36 * ID Count hint name.
37 */
38 public const HINT_PAGINATOR_HAS_IDS = 'doctrine.paginator_has_ids';
39
40 /**
41 * Primary key alias for query.
42 */
43 public const PAGINATOR_ID_ALIAS = 'dpid';
44
45 public function walkSelectStatement(SelectStatement $selectStatement): void
46 {
47 // Get the root entity and alias from the AST fromClause
48 $from = $selectStatement->fromClause->identificationVariableDeclarations;
49
50 if (count($from) > 1) {
51 throw new RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
52 }
53
54 $fromRoot = reset($from);
55 $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
56 $rootClass = $this->getMetadataForDqlAlias($rootAlias);
57 $identifierFieldName = $rootClass->getSingleIdentifierFieldName();
58
59 $pathType = PathExpression::TYPE_STATE_FIELD;
60 if (isset($rootClass->associationMappings[$identifierFieldName])) {
61 $pathType = PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
62 }
63
64 $pathExpression = new PathExpression(PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $rootAlias, $identifierFieldName);
65 $pathExpression->type = $pathType;
66
67 $hasIds = $this->_getQuery()->getHint(self::HINT_PAGINATOR_HAS_IDS);
68
69 if ($hasIds) {
70 $arithmeticExpression = new ArithmeticExpression();
71 $arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression(
72 [$pathExpression],
73 );
74 $expression = new InListExpression(
75 $arithmeticExpression,
76 [new InputParameter(':' . self::PAGINATOR_ID_ALIAS)],
77 );
78 } else {
79 $expression = new NullComparisonExpression($pathExpression);
80 }
81
82 $conditionalPrimary = new ConditionalPrimary();
83 $conditionalPrimary->simpleConditionalExpression = $expression;
84 if ($selectStatement->whereClause) {
85 if ($selectStatement->whereClause->conditionalExpression instanceof ConditionalTerm) {
86 $selectStatement->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary;
87 } elseif ($selectStatement->whereClause->conditionalExpression instanceof ConditionalPrimary) {
88 $selectStatement->whereClause->conditionalExpression = new ConditionalExpression(
89 [
90 new ConditionalTerm(
91 [
92 $selectStatement->whereClause->conditionalExpression,
93 $conditionalPrimary,
94 ],
95 ),
96 ],
97 );
98 } else {
99 $tmpPrimary = new ConditionalPrimary();
100 $tmpPrimary->conditionalExpression = $selectStatement->whereClause->conditionalExpression;
101 $selectStatement->whereClause->conditionalExpression = new ConditionalTerm(
102 [
103 $tmpPrimary,
104 $conditionalPrimary,
105 ],
106 );
107 }
108 } else {
109 $selectStatement->whereClause = new WhereClause(
110 new ConditionalExpression(
111 [new ConditionalTerm([$conditionalPrimary])],
112 ),
113 );
114 }
115 }
116}