Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

ORM/Tools/Pagination/LimitSubqueryOutputWalker.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Tools\Pagination;
21
22
use Doctrine\DBAL\Platforms\DB2Platform;
23
use Doctrine\DBAL\Platforms\OraclePlatform;
24
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
25
use Doctrine\DBAL\Platforms\SQLAnywherePlatform;
26
use Doctrine\DBAL\Platforms\SQLServerPlatform;
27
use Doctrine\ORM\Query\AST\OrderByClause;
28
use Doctrine\ORM\Query\AST\PartialObjectExpression;
29
use Doctrine\ORM\Query\AST\SelectExpression;
30
use Doctrine\ORM\Query\SqlWalker;
31
use Doctrine\ORM\Query\AST\SelectStatement;
32
33
/**
34
 * Wraps the query in order to select root entity IDs for pagination.
35
 *
36
 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
37
 * SELECT DISTINCT <id> FROM (<original SQL>) LIMIT x OFFSET y
38
 *
39
 * Works with composite keys but cannot deal with queries that have multiple
40
 * root entities (e.g. `SELECT f, b from Foo, Bar`)
41
 *
42
 * @author Sander Marechal <[email protected]>
43
 */
44
class LimitSubqueryOutputWalker extends SqlWalker
45
{
46
    private const ORDER_BY_PATH_EXPRESSION = '/(?<![a-z0-9_])%s\.%s(?![a-z0-9_])/i';
47
48
    /**
49
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
50
     */
51
    private $platform;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
52
53
    /**
54
     * @var \Doctrine\ORM\Query\ResultSetMapping
55
     */
56
    private $rsm;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
57
58
    /**
59
     * @var array
60
     */
61
    private $queryComponents;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
62
63
    /**
64
     * @var int
65
     */
66
    private $firstResult;
67
68
    /**
69
     * @var int
70
     */
71
    private $maxResults;
72
73
    /**
74
     * @var \Doctrine\ORM\EntityManager
75
     */
76
    private $em;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
77
78
    /**
79
     * The quote strategy.
80
     *
81
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
82
     */
83
    private $quoteStrategy;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
84
85
    /**
86
     * @var array
87
     */
88
    private $orderByPathExpressions = [];
89
90
    /**
91
     * @var bool We don't want to add path expressions from sub-selects into the select clause of the containing query.
92
     *           This state flag simply keeps track on whether we are walking on a subquery or not
93
     */
94
    private $inSubSelect = false;
95
96
    /**
97
     * Constructor.
98
     *
99
     * Stores various parameters that are otherwise unavailable
100
     * because Doctrine\ORM\Query\SqlWalker keeps everything private without
101
     * accessors.
102
     *
103
     * @param \Doctrine\ORM\Query              $query
104
     * @param \Doctrine\ORM\Query\ParserResult $parserResult
105
     * @param array                            $queryComponents
106
     */
107 65
    public function __construct($query, $parserResult, array $queryComponents)
108
    {
109 65
        $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
110 65
        $this->rsm = $parserResult->getResultSetMapping();
111 65
        $this->queryComponents = $queryComponents;
112
113
        // Reset limit and offset
114 65
        $this->firstResult = $query->getFirstResult();
115 65
        $this->maxResults = $query->getMaxResults();
116 65
        $query->setFirstResult(null)->setMaxResults(null);
117
118 65
        $this->em               = $query->getEntityManager();
119 65
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
120
121 65
        parent::__construct($query, $parserResult, $queryComponents);
122 65
    }
123
124
    /**
125
     * Check if the platform supports the ROW_NUMBER window function.
126
     *
127
     * @return bool
128
     */
129 65
    private function platformSupportsRowNumber()
130
    {
131 65
        return $this->platform instanceof PostgreSqlPlatform
132 58
            || $this->platform instanceof SQLServerPlatform
133 58
            || $this->platform instanceof OraclePlatform
134 52
            || $this->platform instanceof SQLAnywherePlatform
135 52
            || $this->platform instanceof DB2Platform
136 52
            || (method_exists($this->platform, 'supportsRowNumberFunction')
137 65
                && $this->platform->supportsRowNumberFunction());
0 ignored issues
show
The method supportsRowNumberFunction() does not seem to exist on object<Doctrine\DBAL\Platforms\AbstractPlatform>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
138
    }
139
140
    /**
141
     * Rebuilds a select statement's order by clause for use in a
142
     * ROW_NUMBER() OVER() expression.
143
     *
144
     * @param SelectStatement $AST
145
     */
146 11
    private function rebuildOrderByForRowNumber(SelectStatement $AST)
147
    {
148 11
        $orderByClause = $AST->orderByClause;
149 11
        $selectAliasToExpressionMap = [];
150
        // Get any aliases that are available for select expressions.
151 11
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
152 11
            $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
153
        }
154
155
        // Rebuild string orderby expressions to use the select expression they're referencing
156 11
        foreach ($orderByClause->orderByItems as $orderByItem) {
157 11
            if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) {
158 11
                $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression];
159
            }
160
        }
161 11
        $func = new RowNumberOverFunction('dctrn_rownum');
162 11
        $func->orderByClause = $AST->orderByClause;
163 11
        $AST->selectClause->selectExpressions[] = new SelectExpression($func, 'dctrn_rownum', true);
164
165
        // No need for an order by clause, we'll order by rownum in the outer query.
166 11
        $AST->orderByClause = null;
167 11
    }
168
169
    /**
170
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
171
     *
172
     * @param SelectStatement $AST
173
     *
174
     * @return string
175
     *
176
     * @throws \RuntimeException
177
     */
178 65
    public function walkSelectStatement(SelectStatement $AST)
179
    {
180 65
        if ($this->platformSupportsRowNumber()) {
181 13
            return $this->walkSelectStatementWithRowNumber($AST);
182
        }
183 52
        return $this->walkSelectStatementWithoutRowNumber($AST);
184
    }
185
186
    /**
187
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
188
     * This method is for use with platforms which support ROW_NUMBER.
189
     *
190
     * @param SelectStatement $AST
191
     *
192
     * @return string
193
     *
194
     * @throws \RuntimeException
195
     */
196 13
    public function walkSelectStatementWithRowNumber(SelectStatement $AST)
197
    {
198 13
        $hasOrderBy = false;
199 13
        $outerOrderBy = ' ORDER BY dctrn_minrownum ASC';
200 13
        $orderGroupBy = '';
201 13
        if ($AST->orderByClause instanceof OrderByClause) {
202 11
            $hasOrderBy = true;
203 11
            $this->rebuildOrderByForRowNumber($AST);
204
        }
205
206 13
        $innerSql = $this->getInnerSQL($AST);
207
208 13
        $sqlIdentifier = $this->getSQLIdentifier($AST);
209
210 13
        if ($hasOrderBy) {
211 11
            $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier);
212 11
            $sqlIdentifier[] = 'MIN(' . $this->walkResultVariable('dctrn_rownum') . ') AS dctrn_minrownum';
213
        }
214
215
        // Build the counter query
216 13
        $sql = sprintf(
217 13
            'SELECT DISTINCT %s FROM (%s) dctrn_result',
218 13
            implode(', ', $sqlIdentifier),
219 13
            $innerSql
220
        );
221
222 13
        if ($hasOrderBy) {
223 11
            $sql .= $orderGroupBy . $outerOrderBy;
224
        }
225
226
        // Apply the limit and offset.
227 13
        $sql = $this->platform->modifyLimitQuery(
228 13
            $sql,
229 13
            $this->maxResults,
230 13
            $this->firstResult
231
        );
232
233
        // Add the columns to the ResultSetMapping. It's not really nice but
234
        // it works. Preferably I'd clear the RSM or simply create a new one
235
        // but that is not possible from inside the output walker, so we dirty
236
        // up the one we have.
237 13
        foreach ($sqlIdentifier as $property => $alias) {
238 13
            $this->rsm->addScalarResult($alias, $property);
239
        }
240
241 13
        return $sql;
242
    }
243
244
    /**
245
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
246
     * This method is for platforms which DO NOT support ROW_NUMBER.
247
     *
248
     * @param SelectStatement $AST
249
     * @param bool $addMissingItemsFromOrderByToSelect
250
     *
251
     * @return string
252
     *
253
     * @throws \RuntimeException
254
     */
255 52
    public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, $addMissingItemsFromOrderByToSelect = true)
256
    {
257
        // We don't want to call this recursively!
258 52
        if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) {
259
            // In the case of ordering a query by columns from joined tables, we
260
            // must add those columns to the select clause of the query BEFORE
261
            // the SQL is generated.
262 44
            $this->addMissingItemsFromOrderByToSelect($AST);
263
        }
264
265
        // Remove order by clause from the inner query
266
        // It will be re-appended in the outer select generated by this method
267 52
        $orderByClause = $AST->orderByClause;
268 52
        $AST->orderByClause = null;
269
270 52
        $innerSql = $this->getInnerSQL($AST);
271
272 52
        $sqlIdentifier = $this->getSQLIdentifier($AST);
273
274
        // Build the counter query
275 52
        $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result',
276 52
            implode(', ', $sqlIdentifier), $innerSql);
277
278
        // http://www.doctrine-project.org/jira/browse/DDC-1958
279 52
        $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause);
280
281
        // Apply the limit and offset.
282 52
        $sql = $this->platform->modifyLimitQuery(
283 52
            $sql, $this->maxResults, $this->firstResult
284
        );
285
286
        // Add the columns to the ResultSetMapping. It's not really nice but
287
        // it works. Preferably I'd clear the RSM or simply create a new one
288
        // but that is not possible from inside the output walker, so we dirty
289
        // up the one we have.
290 52
        foreach ($sqlIdentifier as $property => $alias) {
291 52
            $this->rsm->addScalarResult($alias, $property);
292
        }
293
294
        // Restore orderByClause
295 52
        $AST->orderByClause = $orderByClause;
296
297 52
        return $sql;
298
    }
299
300
    /**
301
     * Finds all PathExpressions in an AST's OrderByClause, and ensures that
302
     * the referenced fields are present in the SelectClause of the passed AST.
303
     *
304
     * @param SelectStatement $AST
305
     */
306 44
    private function addMissingItemsFromOrderByToSelect(SelectStatement $AST)
307
    {
308 44
        $this->orderByPathExpressions = [];
309
310
        // We need to do this in another walker because otherwise we'll end up
311
        // polluting the state of this one.
312 44
        $walker = clone $this;
313
314
        // This will populate $orderByPathExpressions via
315
        // LimitSubqueryOutputWalker::walkPathExpression, which will be called
316
        // as the select statement is walked. We'll end up with an array of all
317
        // path expressions referenced in the query.
318 44
        $walker->walkSelectStatementWithoutRowNumber($AST, false);
319 44
        $orderByPathExpressions = $walker->getOrderByPathExpressions();
320
321
        // Get a map of referenced identifiers to field names.
322 44
        $selects = [];
323 44
        foreach ($orderByPathExpressions as $pathExpression) {
324 40
            $idVar = $pathExpression->identificationVariable;
325 40
            $field = $pathExpression->field;
326 40
            if (!isset($selects[$idVar])) {
327 40
                $selects[$idVar] = [];
328
            }
329 40
            $selects[$idVar][$field] = true;
330
        }
331
332
        // Loop the select clause of the AST and exclude items from $select
333
        // that are already being selected in the query.
334 44
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
335 44
            if ($selectExpression instanceof SelectExpression) {
336 44
                $idVar = $selectExpression->expression;
337 44
                if (!is_string($idVar)) {
338 4
                    continue;
339
                }
340 44
                $field = $selectExpression->fieldIdentificationVariable;
341 44
                if ($field === null) {
342
                    // No need to add this select, as we're already fetching the whole object.
343 44
                    unset($selects[$idVar]);
344
                } else {
345 44
                    unset($selects[$idVar][$field]);
346
                }
347
            }
348
        }
349
350
        // Add select items which were not excluded to the AST's select clause.
351 44
        foreach ($selects as $idVar => $fields) {
352 9
            $AST->selectClause->selectExpressions[] = new SelectExpression(new PartialObjectExpression($idVar, array_keys($fields)), null, true);
353
        }
354 44
    }
355
356
    /**
357
     * Generates new SQL for statements with an order by clause
358
     *
359
     * @param array              $sqlIdentifier
360
     * @param string             $innerSql
361
     * @param string             $sql
362
     * @param OrderByClause|null $orderByClause
363
     *
364
     * @return string
365
     */
366 52
    private function preserveSqlOrdering(
367
        array $sqlIdentifier,
368
        string $innerSql,
369
        string $sql,
370
        ?OrderByClause $orderByClause
371
    ) : string {
372
        // If the sql statement has an order by clause, we need to wrap it in a new select distinct statement
373 52
        if (! $orderByClause) {
374 8
            return $sql;
375
        }
376
377
        // now only select distinct identifier
378 44
        return \sprintf(
379 44
            'SELECT DISTINCT %s FROM (%s) dctrn_result',
380 44
            \implode(', ', $sqlIdentifier),
381 44
            $this->recreateInnerSql($orderByClause, $sqlIdentifier, $innerSql)
382
        );
383
    }
384
385
    /**
386
     * Generates a new SQL statement for the inner query to keep the correct sorting
387
     *
388
     * @param OrderByClause $orderByClause
389
     * @param array         $identifiers
390
     * @param string        $innerSql
391
     *
392
     * @return string
393
     */
394 44
    private function recreateInnerSql(
395
        OrderByClause $orderByClause,
396
        array $identifiers,
397
        string $innerSql
398
    ) : string {
399 44
        [$searchPatterns, $replacements] = $this->generateSqlAliasReplacements();
400
401 44
        $orderByItems = [];
402
403 44
        foreach ($orderByClause->orderByItems as $orderByItem) {
404
            // Walk order by item to get string representation of it and
405
            // replace path expressions in the order by clause with their column alias
406 44
            $orderByItemString = \preg_replace(
407 44
                $searchPatterns,
408 44
                $replacements,
409 44
                $this->walkOrderByItem($orderByItem)
410
            );
411
412 44
            $orderByItems[] = $orderByItemString;
413 44
            $identifier     = \substr($orderByItemString, 0, \strrpos($orderByItemString, ' '));
414
415 44
            if (! \in_array($identifier, $identifiers, true)) {
416 44
                $identifiers[] = $identifier;
417
            }
418
        }
419
420 44
        return $sql = \sprintf(
421 44
            'SELECT DISTINCT %s FROM (%s) dctrn_result_inner ORDER BY %s',
422 44
            \implode(', ', $identifiers),
423 44
            $innerSql,
424 44
            \implode(', ', $orderByItems)
425
        );
426
    }
427
428
    /**
429
     * @return string[][]
430
     */
431 44
    private function generateSqlAliasReplacements() : array
432
    {
433 44
        $aliasMap = $searchPatterns = $replacements = $metadataList = [];
434
435
        // Generate DQL alias -> SQL table alias mapping
436 44
        foreach (\array_keys($this->rsm->aliasMap) as $dqlAlias) {
437 44
            $metadataList[$dqlAlias] = $class = $this->queryComponents[$dqlAlias]['metadata'];
438 44
            $aliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
439
        }
440
441
        // Generate search patterns for each field's path expression in the order by clause
442 44
        foreach ($this->rsm->fieldMappings as $fieldAlias => $fieldName) {
443 44
            $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias];
444 44
            $class = $metadataList[$dqlAliasForFieldAlias];
445
446
            // If the field is from a joined child table, we won't be ordering on it.
447 44
            if (! isset($class->fieldMappings[$fieldName])) {
448 1
                continue;
449
            }
450
451 44
            $fieldMapping = $class->fieldMappings[$fieldName];
452
453
            // Get the proper column name as will appear in the select list
454 44
            $columnName = $this->quoteStrategy->getColumnName(
455 44
                $fieldName,
456 44
                $metadataList[$dqlAliasForFieldAlias],
457 44
                $this->em->getConnection()->getDatabasePlatform()
458
            );
459
460
            // Get the SQL table alias for the entity and field
461 44
            $sqlTableAliasForFieldAlias = $aliasMap[$dqlAliasForFieldAlias];
462
463 44
            if (isset($fieldMapping['declared']) && $fieldMapping['declared'] !== $class->name) {
464
                // Field was declared in a parent class, so we need to get the proper SQL table alias
465
                // for the joined parent table.
466 2
                $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']);
467
468 2
                if (! $otherClassMetadata->isMappedSuperclass) {
469 1
                    $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
470
                }
471
            }
472
473
            // Compose search and replace patterns
474 44
            $searchPatterns[] = \sprintf(self::ORDER_BY_PATH_EXPRESSION, $sqlTableAliasForFieldAlias, $columnName);
475 44
            $replacements[]   = $fieldAlias;
476
        }
477
478 44
        return [$searchPatterns, $replacements];
479
    }
480
481
    /**
482
     * getter for $orderByPathExpressions
483
     *
484
     * @return array
485
     */
486 44
    public function getOrderByPathExpressions()
487
    {
488 44
        return $this->orderByPathExpressions;
489
    }
490
491
    /**
492
     * @param SelectStatement $AST
493
     *
494
     * @return string
495
     *
496
     * @throws \Doctrine\ORM\OptimisticLockException
497
     * @throws \Doctrine\ORM\Query\QueryException
498
     */
499 65
    private function getInnerSQL(SelectStatement $AST)
500
    {
501
        // Set every select expression as visible(hidden = false) to
502
        // make $AST have scalar mappings properly - this is relevant for referencing selected
503
        // fields from outside the subquery, for example in the ORDER BY segment
504 65
        $hiddens = [];
505
506 65
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
507 65
            $hiddens[$idx] = $expr->hiddenAliasResultVariable;
508 65
            $expr->hiddenAliasResultVariable = false;
509
        }
510
511 65
        $innerSql = parent::walkSelectStatement($AST);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (walkSelectStatement() instead of getInnerSQL()). Are you sure this is correct? If so, you might want to change this to $this->walkSelectStatement().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
512
513
        // Restore hiddens
514 65
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
515 65
            $expr->hiddenAliasResultVariable = $hiddens[$idx];
516
        }
517
518 65
        return $innerSql;
519
    }
520
521
    /**
522
     * @param SelectStatement $AST
523
     *
524
     * @return array
525
     */
526 65
    private function getSQLIdentifier(SelectStatement $AST)
527
    {
528
        // Find out the SQL alias of the identifier column of the root entity.
529
        // It may be possible to make this work with multiple root entities but that
530
        // would probably require issuing multiple queries or doing a UNION SELECT.
531
        // So for now, it's not supported.
532
533
        // Get the root entity and alias from the AST fromClause.
534 65
        $from = $AST->fromClause->identificationVariableDeclarations;
535 65
        if (count($from) !== 1) {
536
            throw new \RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
537
        }
538
539 65
        $fromRoot       = reset($from);
540 65
        $rootAlias      = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
541 65
        $rootClass      = $this->queryComponents[$rootAlias]['metadata'];
542 65
        $rootIdentifier = $rootClass->identifier;
543
544
        // For every identifier, find out the SQL alias by combing through the ResultSetMapping
545 65
        $sqlIdentifier = [];
546 65
        foreach ($rootIdentifier as $property) {
547 65
            if (isset($rootClass->fieldMappings[$property])) {
548 64
                foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) {
549 64
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
550 64
                        $sqlIdentifier[$property] = $alias;
551
                    }
552
                }
553
            }
554
555 65
            if (isset($rootClass->associationMappings[$property])) {
556 1
                $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name'];
557
558 1
                foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) {
559 1
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
560 65
                        $sqlIdentifier[$property] = $alias;
561
                    }
562
                }
563
            }
564
        }
565
566 65
        if (count($sqlIdentifier) === 0) {
567
            throw new \RuntimeException('The Paginator does not support Queries which only yield ScalarResults.');
568
        }
569
570 65
        if (count($rootIdentifier) != count($sqlIdentifier)) {
571
            throw new \RuntimeException(sprintf(
572
                'Not all identifier properties can be found in the ResultSetMapping: %s',
573
                implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
574
            ));
575
        }
576
577 65
        return $sqlIdentifier;
578
    }
579
580
    /**
581
     * {@inheritdoc}
582
     */
583 55
    public function walkPathExpression($pathExpr)
584
    {
585 55
        if (!$this->inSubSelect && !$this->platformSupportsRowNumber() && !in_array($pathExpr, $this->orderByPathExpressions)) {
586 42
            $this->orderByPathExpressions[] = $pathExpr;
587
        }
588
589 55
        return parent::walkPathExpression($pathExpr);
590
    }
591
592
    /**
593
     * {@inheritdoc}
594
     */
595 7
    public function walkSubSelect($subselect)
596
    {
597 7
        $this->inSubSelect = true;
598
599 7
        $sql = parent::walkSubselect($subselect);
600
601 7
        $this->inSubSelect = false;
602
603 7
        return $sql;
604
    }
605
}
606