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

lib/Doctrine/ORM/Query/SqlWalker.php (1 issue)

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\Query;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\DBAL\Types\Type;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use Doctrine\ORM\OptimisticLockException;
27
use Doctrine\ORM\Query;
28
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
29
use Doctrine\ORM\Utility\PersisterHelper;
30
31
/**
32
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
33
 * the corresponding SQL.
34
 *
35
 * @author Guilherme Blanco <[email protected]>
36
 * @author Roman Borschel <[email protected]>
37
 * @author Benjamin Eberlei <[email protected]>
38
 * @author Alexander <[email protected]>
39
 * @author Fabio B. Silva <[email protected]>
40
 * @since  2.0
41
 */
42
class SqlWalker implements TreeWalker
43
{
44
    /**
45
     * @var string
46
     */
47
    const HINT_DISTINCT = 'doctrine.distinct';
48
49
    /**
50
     * @var ResultSetMapping
51
     */
52
    private $rsm;
53
54
    /**
55
     * Counter for generating unique column aliases.
56
     *
57
     * @var integer
58
     */
59
    private $aliasCounter = 0;
60
61
    /**
62
     * Counter for generating unique table aliases.
63
     *
64
     * @var integer
65
     */
66
    private $tableAliasCounter = 0;
67
68
    /**
69
     * Counter for generating unique scalar result.
70
     *
71
     * @var integer
72
     */
73
    private $scalarResultCounter = 1;
74
75
    /**
76
     * Counter for generating unique parameter indexes.
77
     *
78
     * @var integer
79
     */
80
    private $sqlParamIndex = 0;
81
82
    /**
83
     * Counter for generating indexes.
84
     *
85
     * @var integer
86
     */
87
    private $newObjectCounter = 0;
88
89
    /**
90
     * @var ParserResult
91
     */
92
    private $parserResult;
93
94
    /**
95
     * @var \Doctrine\ORM\EntityManager
96
     */
97
    private $em;
98
99
    /**
100
     * @var \Doctrine\DBAL\Connection
101
     */
102
    private $conn;
103
104
    /**
105
     * @var \Doctrine\ORM\AbstractQuery
106
     */
107
    private $query;
108
109
    /**
110
     * @var array
111
     */
112
    private $tableAliasMap = [];
113
114
    /**
115
     * Map from result variable names to their SQL column alias names.
116
     *
117
     * @var array
118
     */
119
    private $scalarResultAliasMap = [];
120
121
    /**
122
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
123
     *
124
     * @var array
125
     */
126
    private $orderedColumnsMap = [];
127
128
    /**
129
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
130
     *
131
     * @var array
132
     */
133
    private $scalarFields = [];
134
135
    /**
136
     * Map of all components/classes that appear in the DQL query.
137
     *
138
     * @var array
139
     */
140
    private $queryComponents;
141
142
    /**
143
     * A list of classes that appear in non-scalar SelectExpressions.
144
     *
145
     * @var array
146
     */
147
    private $selectedClasses = [];
148
149
    /**
150
     * The DQL alias of the root class of the currently traversed query.
151
     *
152
     * @var array
153
     */
154
    private $rootAliases = [];
155
156
    /**
157
     * Flag that indicates whether to generate SQL table aliases in the SQL.
158
     * These should only be generated for SELECT queries, not for UPDATE/DELETE.
159
     *
160
     * @var boolean
161
     */
162
    private $useSqlTableAliases = true;
163
164
    /**
165
     * The database platform abstraction.
166
     *
167
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
168
     */
169
    private $platform;
170
171
    /**
172
     * The quote strategy.
173
     *
174
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
175
     */
176
    private $quoteStrategy;
177
178
    /**
179
     * {@inheritDoc}
180
     */
181 699
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 699
        $this->query            = $query;
184 699
        $this->parserResult     = $parserResult;
185 699
        $this->queryComponents  = $queryComponents;
186 699
        $this->rsm              = $parserResult->getResultSetMapping();
187 699
        $this->em               = $query->getEntityManager();
188 699
        $this->conn             = $this->em->getConnection();
189 699
        $this->platform         = $this->conn->getDatabasePlatform();
190 699
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 699
    }
192
193
    /**
194
     * Gets the Query instance used by the walker.
195
     *
196
     * @return Query.
197
     */
198
    public function getQuery()
199
    {
200
        return $this->query;
201
    }
202
203
    /**
204
     * Gets the Connection used by the walker.
205
     *
206
     * @return \Doctrine\DBAL\Connection
207
     */
208 35
    public function getConnection()
209
    {
210 35
        return $this->conn;
211
    }
212
213
    /**
214
     * Gets the EntityManager used by the walker.
215
     *
216
     * @return \Doctrine\ORM\EntityManager
217
     */
218 22
    public function getEntityManager()
219
    {
220 22
        return $this->em;
221
    }
222
223
    /**
224
     * Gets the information about a single query component.
225
     *
226
     * @param string $dqlAlias The DQL alias.
227
     *
228
     * @return array
229
     */
230 17
    public function getQueryComponent($dqlAlias)
231
    {
232 17
        return $this->queryComponents[$dqlAlias];
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getQueryComponents()
239
    {
240
        return $this->queryComponents;
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 1 View Code Duplication
    public function setQueryComponent($dqlAlias, array $queryComponent)
247
    {
248 1
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
249
250 1
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
251 1
            throw QueryException::invalidQueryComponent($dqlAlias);
252
        }
253
254
        $this->queryComponents[$dqlAlias] = $queryComponent;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 693
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 693
            case ($AST instanceof AST\DeleteStatement):
264 38
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 38
                return ($primaryClass->isInheritanceTypeJoined())
267 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 38
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 659
            case ($AST instanceof AST\UpdateStatement):
271 29
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 29
                return ($primaryClass->isInheritanceTypeJoined())
274 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 29
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 634
                return new Exec\SingleSelectExecutor($AST, $this);
279
        }
280
    }
281
282
    /**
283
     * Generates a unique, short SQL table alias.
284
     *
285
     * @param string $tableName Table name
286
     * @param string $dqlAlias  The DQL alias.
287
     *
288
     * @return string Generated table alias.
289
     */
290 645
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 645
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 645
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 645
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 645
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 645
        return $this->tableAliasMap[$tableName];
300
    }
301
302
    /**
303
     * Forces the SqlWalker to use a specific alias for a table name, rather than
304
     * generating an alias on its own.
305
     *
306
     * @param string $tableName
307
     * @param string $alias
308
     * @param string $dqlAlias
309
     *
310
     * @return string
311
     */
312 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 65
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 65
        $this->tableAliasMap[$tableName] = $alias;
317
318 65
        return $alias;
319
    }
320
321
    /**
322
     * Gets an SQL column alias for a column name.
323
     *
324
     * @param string $columnName
325
     *
326
     * @return string
327
     */
328 634
    public function getSQLColumnAlias($columnName)
329
    {
330 634
        return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
331
    }
332
333
    /**
334
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
335
     * for the given class.
336
     *
337
     * @param ClassMetadata $class    The class for which to generate the joins.
338
     * @param string        $dqlAlias The DQL alias of the class.
339
     *
340
     * @return string The SQL.
341
     */
342 100
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 100
        $sql = '';
345
346 100
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 100
        foreach ($class->parentClasses as $parentClassName) {
350 67
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 67
            $tableAlias  = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
352
353
            // If this is a joined association we must use left joins to preserve the correct result.
354 67
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 67
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 67
            $sqlParts = [];
358
359 67 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
360 67
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 67
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 67
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 100
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 79
        foreach ($class->subClasses as $subClassName) {
378 39
            $subClass   = $this->em->getClassMetadata($subClassName);
379 39
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 39
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 39
            $sqlParts = [];
384
385 39 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
386 39
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 39
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 79
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 628
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 628
        $orderedColumns = [];
401
402 628
        foreach ($this->selectedClasses as $selectedClass) {
403 490
            $dqlAlias  = $selectedClass['dqlAlias'];
404 490
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 490
            if ( ! isset($qComp['relation']['orderBy'])) {
407 490
                continue;
408
            }
409
410 6
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 6
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 6
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 6
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415 1
                    ? $persister->getOwningTable($fieldName)
416 6
                    : $qComp['metadata']->getTableName();
417
418 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 6
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 628
        return implode(', ', $orderedColumns);
431
    }
432
433
    /**
434
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
435
     *
436
     * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
437
     *
438
     * @return string
439
     */
440 688
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 688
        $sqlParts = [];
443
444 688
        foreach ($dqlAliases as $dqlAlias) {
445 688
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 688
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 40
            $conn   = $this->em->getConnection();
450 40
            $values = [];
451
452 40
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 21
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 40
            foreach ($class->subClasses as $subclassName) {
457 29
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 40
            $sqlTableAlias = ($this->useSqlTableAliases)
461 35
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 40
                : '';
463
464 40
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 688
        $sql = implode(' AND ', $sqlParts);
468
469 688
        return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
470
    }
471
472
    /**
473
     * Generates the filter SQL for a given entity and table alias.
474
     *
475
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
476
     * @param string        $targetTableAlias The table alias of the joined/selected table.
477
     *
478
     * @return string The SQL query part to add to a query.
479
     */
480 322
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 322
        if (!$this->em->hasFilters()) {
483 284
            return '';
484
        }
485
486 43
        switch($targetEntity->inheritanceType) {
487 43
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 33
                break;
489 10
            case ClassMetadata::INHERITANCE_TYPE_JOINED:
490
                // The classes in the inheritance will be added to the query one by one,
491
                // but only the root node is getting filtered
492 6
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493 4
                    return '';
494
                }
495 6
                break;
496 4
            case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
497
                // With STI the table will only be queried once, make sure that the filters
498
                // are added to the root entity
499 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500 4
                break;
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
        }
505
506 43
        $filterClauses = [];
507 43
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
508 10
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
509 10
                $filterClauses[] = '(' . $filterExpr . ')';
510
            }
511
        }
512
513 43
        return implode(' AND ', $filterClauses);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 634
    public function walkSelectStatement(AST\SelectStatement $AST)
520
    {
521 634
        $limit    = $this->query->getMaxResults();
522 634
        $offset   = $this->query->getFirstResult();
523 634
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
524 634
        $sql      = $this->walkSelectClause($AST->selectClause)
525 634
            . $this->walkFromClause($AST->fromClause)
526 632
            . $this->walkWhereClause($AST->whereClause);
527
528 629
        if ($AST->groupByClause) {
529 24
            $sql .= $this->walkGroupByClause($AST->groupByClause);
530
        }
531
532 629
        if ($AST->havingClause) {
533 14
            $sql .= $this->walkHavingClause($AST->havingClause);
534
        }
535
536 629
        if ($AST->orderByClause) {
537 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
538
        }
539
540 628
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
541 6
            $sql .= ' ORDER BY ' . $orderBySql;
542
        }
543
544 628 View Code Duplication
        if ($limit !== null || $offset !== null) {
545 39
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
546
        }
547
548 628
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
549 623
            return $sql;
550
        }
551
552 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
553 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
554
        }
555
556 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
557 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
558
        }
559
560 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
561
            throw QueryException::invalidLockMode();
562
        }
563
564 1
        foreach ($this->selectedClasses as $selectedClass) {
565 1
            if ( ! $selectedClass['class']->isVersioned) {
566 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->name);
567
            }
568
        }
569
570
        return $sql;
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576 25
    public function walkUpdateStatement(AST\UpdateStatement $AST)
577
    {
578 25
        $this->useSqlTableAliases = false;
579 25
        $this->rsm->isSelect      = false;
580
581 25
        return $this->walkUpdateClause($AST->updateClause)
582 25
            . $this->walkWhereClause($AST->whereClause);
583
    }
584
585
    /**
586
     * {@inheritdoc}
587
     */
588 36
    public function walkDeleteStatement(AST\DeleteStatement $AST)
589
    {
590 36
        $this->useSqlTableAliases = false;
591 36
        $this->rsm->isSelect      = false;
592
593 36
        return $this->walkDeleteClause($AST->deleteClause)
594 36
            . $this->walkWhereClause($AST->whereClause);
595
    }
596
597
    /**
598
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
599
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
600
     *
601
     * @param string $identVariable
602
     *
603
     * @return string
604
     */
605 2
    public function walkEntityIdentificationVariable($identVariable)
606
    {
607 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
608 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
609 2
        $sqlParts   = [];
610
611 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
612 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
613
        }
614
615 2
        return implode(', ', $sqlParts);
616
    }
617
618
    /**
619
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
620
     *
621
     * @param string $identificationVariable
622
     * @param string $fieldName
623
     *
624
     * @return string The SQL.
625
     */
626 427
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 427
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630
        if (
631 427
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
632 427
            isset($class->fieldMappings[$fieldName]['inherited'])
633
        ) {
634 38
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
635
        }
636
637 427
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
638
    }
639
640
    /**
641
     * {@inheritdoc}
642
     */
643 496
    public function walkPathExpression($pathExpr)
644
    {
645 496
        $sql = '';
646
647
        /* @var $pathExpr Query\AST\PathExpression */
648 496
        switch ($pathExpr->type) {
649 496
            case AST\PathExpression::TYPE_STATE_FIELD:
650 475
                $fieldName = $pathExpr->field;
651 475
                $dqlAlias = $pathExpr->identificationVariable;
652 475
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 475
                if ($this->useSqlTableAliases) {
655 427
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 475
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 475
                break;
660
661 62
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 62
                $fieldName = $pathExpr->field;
665 62
                $dqlAlias = $pathExpr->identificationVariable;
666 62
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 62
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 62
                $assoc = $class->associationMappings[$fieldName];
673
674 62
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 60
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 59
                if ($this->useSqlTableAliases) {
684 56
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 59
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 59
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 493
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 634
    public function walkSelectClause($selectClause)
701
    {
702 634
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 634
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
704
705 634
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 634
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 471
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 290
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 634
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 634
        foreach ($this->selectedClasses as $selectedClass) {
716 496
            $class       = $selectedClass['class'];
717 496
            $dqlAlias    = $selectedClass['dqlAlias'];
718 496
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 496
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 496
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 159
                $this->rsm->addJoinedEntityResult(
725 159
                    $class->name,
726 159
                    $dqlAlias,
727 159
                    $this->queryComponents[$dqlAlias]['parent'],
728 159
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 496
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 97
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 97
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 97
                $discrColumn = $rootClass->discriminatorColumn;
737 97
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 97
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 97
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 97
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 496
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 181
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 365
            foreach ($class->associationMappings as $assoc) {
752 319
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)
753 319
                    || ( ! $addMetaColumns && !isset($assoc['id']))) {
754 268
                    continue;
755
                }
756
757 286
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
758 286
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
759 286
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
760 286
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
761
762 286 View Code Duplication
                foreach ($assoc['joinColumns'] as $joinColumn) {
763 286
                    $columnName  = $joinColumn['name'];
764 286
                    $columnAlias = $this->getSQLColumnAlias($columnName);
765 286
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
766
767 286
                    $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
768 286
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
769
770 286
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 365
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 360
            foreach ($class->subClasses as $subClassName) {
781 37
                $subClass      = $this->em->getClassMetadata($subClassName);
782 37
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 37
                foreach ($subClass->associationMappings as $assoc) {
785
                    // Skip if association is inherited
786 27
                    if (isset($assoc['inherited'])) continue;
787
788 16
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
789 14
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
790
791 14 View Code Duplication
                        foreach ($assoc['joinColumns'] as $joinColumn) {
792 14
                            $columnName  = $joinColumn['name'];
793 14
                            $columnAlias = $this->getSQLColumnAlias($columnName);
794 14
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
795
796 14
                            $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
797 14
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
798
799 360
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
800
                        }
801
                    }
802
                }
803
            }
804
        }
805
806 634
        $sql .= implode(', ', $sqlSelectExpressions);
807
808 634
        return $sql;
809
    }
810
811
    /**
812
     * {@inheritdoc}
813
     */
814 636 View Code Duplication
    public function walkFromClause($fromClause)
815
    {
816 636
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
817 636
        $sqlParts = [];
818
819 636
        foreach ($identificationVarDecls as $identificationVariableDecl) {
820 636
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
821
        }
822
823 634
        return ' FROM ' . implode(', ', $sqlParts);
824
    }
825
826
    /**
827
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
828
     *
829
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
830
     *
831
     * @return string
832
     */
833 637
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
834
    {
835 637
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
836
837 637
        if ($identificationVariableDecl->indexBy) {
838 6
            $this->walkIndexBy($identificationVariableDecl->indexBy);
839
        }
840
841 637
        foreach ($identificationVariableDecl->joins as $join) {
842 247
            $sql .= $this->walkJoin($join);
843
        }
844
845 635
        return $sql;
846
    }
847
848
    /**
849
     * Walks down a IndexBy AST node.
850
     *
851
     * @param AST\IndexBy $indexBy
852
     *
853
     * @return void
854
     */
855 9
    public function walkIndexBy($indexBy)
856
    {
857 9
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
858 9
        $alias          = $pathExpression->identificationVariable;
859 9
        $field          = $pathExpression->field;
860
861 9
        if (isset($this->scalarFields[$alias][$field])) {
862
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
863
864
            return;
865
        }
866
867 9
        $this->rsm->addIndexBy($alias, $field);
868 9
    }
869
870
    /**
871
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
872
     *
873
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
874
     *
875
     * @return string
876
     */
877 637
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
878
    {
879 637
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
880
    }
881
882
    /**
883
     * Generate appropriate SQL for RangeVariableDeclaration AST node
884
     *
885
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
886
     * @param bool $buildNestedJoins
887
     *
888
     * @return string
889
     */
890 637
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
891
    {
892 637
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
893 637
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
894
895 637
        if ($rangeVariableDeclaration->isRoot) {
896 637
            $this->rootAliases[] = $dqlAlias;
897
        }
898
899 637
        $sql = $this->platform->appendLockHint(
900 637
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
901 637
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
902 637
            $this->query->getHint(Query::HINT_LOCK_MODE)
903
        );
904
905 637
        if ($class->isInheritanceTypeJoined()) {
906 96
            if ($buildNestedJoins) {
907 3
                $sql = '(' . $sql . $this->_generateClassTableInheritanceJoins($class, $dqlAlias) . ')';
908
            } else {
909 94
                $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
910
            }
911
        }
912
913 637
        return $sql;
914
    }
915
916
    /**
917
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
918
     *
919
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
920
     * @param int                            $joinType
921
     * @param AST\ConditionalExpression      $condExpr
922
     *
923
     * @return string
924
     *
925
     * @throws QueryException
926
     */
927 229
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
928
    {
929 229
        $sql = '';
930
931 229
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
932 229
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
933 229
        $indexBy                   = $joinAssociationDeclaration->indexBy;
934
935 229
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
936 229
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
937 229
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
938 229
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
939
940 229
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
941 229
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
942
943
        // Ensure we got the owning side, since it has all mapping info
944 229
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
945
946 229
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
947 3
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
948 2
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
949
            }
950
        }
951
952 227
        $targetTableJoin = null;
0 ignored issues
show
$targetTableJoin is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
953
954
        // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
955
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
956
        // The owning side is necessary at this point because only it contains the JoinColumn information.
957
        switch (true) {
958 227
            case ($assoc['type'] & ClassMetadata::TO_ONE):
959 180
                $conditions = [];
960
961 180
                foreach ($assoc['joinColumns'] as $joinColumn) {
962 180
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
963 180
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
964
965 180
                    if ($relation['isOwningSide']) {
966 104
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
967
968 104
                        continue;
969
                    }
970
971 110
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
972
                }
973
974
                // Apply remaining inheritance restrictions
975 180
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
976
977 180
                if ($discrSql) {
978 3
                    $conditions[] = $discrSql;
979
                }
980
981
                // Apply the filters
982 180
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
983
984 180
                if ($filterExpr) {
985 1
                    $conditions[] = $filterExpr;
986
                }
987
988
                $targetTableJoin = [
989 180
                    'table' => $targetTableName . ' ' . $targetTableAlias,
990 180
                    'condition' => implode(' AND ', $conditions),
991
                ];
992 180
                break;
993
994 57
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
995
                // Join relation table
996 57
                $joinTable      = $assoc['joinTable'];
997 57
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
998 57
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
999
1000 57
                $conditions      = [];
1001 57
                $relationColumns = ($relation['isOwningSide'])
1002 48
                    ? $assoc['joinTable']['joinColumns']
1003 57
                    : $assoc['joinTable']['inverseJoinColumns'];
1004
1005 57 View Code Duplication
                foreach ($relationColumns as $joinColumn) {
1006 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1007 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1008
1009 57
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1010
                }
1011
1012 57
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
1013
1014
                // Join target table
1015 57
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
1016
1017 57
                $conditions      = [];
1018 57
                $relationColumns = ($relation['isOwningSide'])
1019 48
                    ? $assoc['joinTable']['inverseJoinColumns']
1020 57
                    : $assoc['joinTable']['joinColumns'];
1021
1022 57 View Code Duplication
                foreach ($relationColumns as $joinColumn) {
1023 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1024 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1025
1026 57
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1027
                }
1028
1029
                // Apply remaining inheritance restrictions
1030 57
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1031
1032 57
                if ($discrSql) {
1033 1
                    $conditions[] = $discrSql;
1034
                }
1035
1036
                // Apply the filters
1037 57
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1038
1039 57
                if ($filterExpr) {
1040 1
                    $conditions[] = $filterExpr;
1041
                }
1042
1043
                $targetTableJoin = [
1044 57
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1045 57
                    'condition' => implode(' AND ', $conditions),
1046
                ];
1047 57
                break;
1048
1049
            default:
1050
                throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1051
        }
1052
1053
        // Handle WITH clause
1054 227
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1055
1056 227
        if ($targetClass->isInheritanceTypeJoined()) {
1057 9
            $ctiJoins = $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1058
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1059 9
            if ($withCondition) {
1060 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1061
            } else {
1062 9
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1063
            }
1064
        } else {
1065 218
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1066
        }
1067
1068 227
        if ($withCondition) {
1069 5
            $sql .= ' AND ' . $withCondition;
1070
        }
1071
1072
        // Apply the indexes
1073 227
        if ($indexBy) {
1074
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1075 6
            $this->walkIndexBy($indexBy);
1076 222
        } else if (isset($relation['indexBy'])) {
1077 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1078
        }
1079
1080 227
        return $sql;
1081
    }
1082
1083
    /**
1084
     * {@inheritdoc}
1085
     */
1086 118
    public function walkFunction($function)
1087
    {
1088 118
        return $function->getSql($this);
1089
    }
1090
1091
    /**
1092
     * {@inheritdoc}
1093
     */
1094 153
    public function walkOrderByClause($orderByClause)
1095
    {
1096 153
        $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1097
1098 152
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1099
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1100
        }
1101
1102 152
        return ' ORDER BY ' . implode(', ', $orderByItems);
1103
    }
1104
1105
    /**
1106
     * {@inheritdoc}
1107
     */
1108 171
    public function walkOrderByItem($orderByItem)
1109
    {
1110 171
        $type = strtoupper($orderByItem->type);
1111 171
        $expr = $orderByItem->expression;
1112 171
        $sql  = ($expr instanceof AST\Node)
1113 164
            ? $expr->dispatch($this)
1114 170
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1115
1116 170
        $this->orderedColumnsMap[$sql] = $type;
1117
1118 170
        if ($expr instanceof AST\Subselect) {
1119 2
            return '(' . $sql . ') ' . $type;
1120
        }
1121
1122 168
        return $sql . ' ' . $type;
1123
    }
1124
1125
    /**
1126
     * {@inheritdoc}
1127
     */
1128 14
    public function walkHavingClause($havingClause)
1129
    {
1130 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1131
    }
1132
1133
    /**
1134
     * {@inheritdoc}
1135
     */
1136 247
    public function walkJoin($join)
1137
    {
1138 247
        $joinType        = $join->joinType;
1139 247
        $joinDeclaration = $join->joinAssociationDeclaration;
1140
1141 247
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1142 57
            ? ' LEFT JOIN '
1143 247
            : ' INNER JOIN ';
1144
1145
        switch (true) {
1146 247
            case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
1147 18
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1148 18
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1149 18
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1150 18
                $conditions = [];
1151
1152 18
                if ($join->conditionalExpression) {
1153 16
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1154
                }
1155
1156 18
                $isUnconditionalJoin = empty($conditions);
1157 18
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin)
1158 2
                    ? ' AND '
1159 18
                    : ' ON ';
1160
1161 18
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, !$isUnconditionalJoin);
1162
1163
                // Apply remaining inheritance restrictions
1164 18
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1165
1166 18
                if ($discrSql) {
1167 3
                    $conditions[] = $discrSql;
1168
                }
1169
1170
                // Apply the filters
1171 18
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1172
1173 18
                if ($filterExpr) {
1174
                    $conditions[] = $filterExpr;
1175
                }
1176
1177 18
                if ($conditions) {
1178 16
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1179
                }
1180
1181 18
                break;
1182
1183 229
            case ($joinDeclaration instanceof AST\JoinAssociationDeclaration):
1184 229
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1185 227
                break;
1186
        }
1187
1188 245
        return $sql;
1189
    }
1190
1191
    /**
1192
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1193
     *
1194
     * @param AST\CoalesceExpression $coalesceExpression
1195
     *
1196
     * @return string The SQL.
1197
     */
1198 2
    public function walkCoalesceExpression($coalesceExpression)
1199
    {
1200 2
        $sql = 'COALESCE(';
1201
1202 2
        $scalarExpressions = [];
1203
1204 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1205 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1206
        }
1207
1208 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1209
1210 2
        return $sql;
1211
    }
1212
1213
    /**
1214
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1215
     *
1216
     * @param AST\NullIfExpression $nullIfExpression
1217
     *
1218
     * @return string The SQL.
1219
     */
1220 3
    public function walkNullIfExpression($nullIfExpression)
1221
    {
1222 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1223
            ? $this->conn->quote($nullIfExpression->firstExpression)
1224 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1225
1226 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1227
            ? $this->conn->quote($nullIfExpression->secondExpression)
1228 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1229
1230 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1231
    }
1232
1233
    /**
1234
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1235
     *
1236
     * @param AST\GeneralCaseExpression $generalCaseExpression
1237
     *
1238
     * @return string The SQL.
1239
     */
1240 9 View Code Duplication
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1241
    {
1242 9
        $sql = 'CASE';
1243
1244 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1245 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1246 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1247
        }
1248
1249 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1250
1251 9
        return $sql;
1252
    }
1253
1254
    /**
1255
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1256
     *
1257
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1258
     *
1259
     * @return string The SQL.
1260
     */
1261 5 View Code Duplication
    public function walkSimpleCaseExpression($simpleCaseExpression)
1262
    {
1263 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1264
1265 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1266 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1267 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1268
        }
1269
1270 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1271
1272 5
        return $sql;
1273
    }
1274
1275
    /**
1276
     * {@inheritdoc}
1277
     */
1278 634
    public function walkSelectExpression($selectExpression)
1279
    {
1280 634
        $sql    = '';
1281 634
        $expr   = $selectExpression->expression;
1282 634
        $hidden = $selectExpression->hiddenAliasResultVariable;
1283
1284
        switch (true) {
1285 634
            case ($expr instanceof AST\PathExpression):
1286 102
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1287
                    throw QueryException::invalidPathExpression($expr);
1288
                }
1289
1290 102
                $fieldName = $expr->field;
1291 102
                $dqlAlias  = $expr->identificationVariable;
1292 102
                $qComp     = $this->queryComponents[$dqlAlias];
1293 102
                $class     = $qComp['metadata'];
1294
1295 102
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1296 102
                $tableName   = ($class->isInheritanceTypeJoined())
1297 11
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1298 102
                    : $class->getTableName();
1299
1300 102
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1301 102
                $fieldMapping  = $class->fieldMappings[$fieldName];
1302 102
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1303 102
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1304 102
                $col           = $sqlTableAlias . '.' . $columnName;
1305
1306 102
                if (isset($fieldMapping['requireSQLConversion'])) {
1307 2
                    $type = Type::getType($fieldMapping['type']);
1308 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1309
                }
1310
1311 102
                $sql .= $col . ' AS ' . $columnAlias;
1312
1313 102
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1314
1315 102
                if ( ! $hidden) {
1316 102
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1317 102
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1318
                }
1319
1320 102
                break;
1321
1322 583
            case ($expr instanceof AST\AggregateExpression):
1323 574
            case ($expr instanceof AST\Functions\FunctionNode):
1324 534
            case ($expr instanceof AST\SimpleArithmeticExpression):
1325 534
            case ($expr instanceof AST\ArithmeticTerm):
1326 532
            case ($expr instanceof AST\ArithmeticFactor):
1327 531
            case ($expr instanceof AST\ParenthesisExpression):
1328 530
            case ($expr instanceof AST\Literal):
1329 529
            case ($expr instanceof AST\NullIfExpression):
1330 528
            case ($expr instanceof AST\CoalesceExpression):
1331 527
            case ($expr instanceof AST\GeneralCaseExpression):
1332 523 View Code Duplication
            case ($expr instanceof AST\SimpleCaseExpression):
1333 107
                $columnAlias = $this->getSQLColumnAlias('sclr');
1334 107
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1335
1336 107
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1337
1338 107
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1339
1340 107
                if ( ! $hidden) {
1341
                    // We cannot resolve field type here; assume 'string'.
1342 107
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1343
                }
1344 107
                break;
1345
1346 522 View Code Duplication
            case ($expr instanceof AST\Subselect):
1347 15
                $columnAlias = $this->getSQLColumnAlias('sclr');
1348 15
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1349
1350 15
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1351
1352 15
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1353
1354 15
                if ( ! $hidden) {
1355
                    // We cannot resolve field type here; assume 'string'.
1356 13
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1357
                }
1358 15
                break;
1359
1360 518
            case ($expr instanceof AST\NewObjectExpression):
1361 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1362 22
                break;
1363
1364
            default:
1365
                // IdentificationVariable or PartialObjectExpression
1366 496
                if ($expr instanceof AST\PartialObjectExpression) {
1367 16
                    $dqlAlias = $expr->identificationVariable;
1368 16
                    $partialFieldSet = $expr->partialFieldSet;
1369
                } else {
1370 491
                    $dqlAlias = $expr;
1371 491
                    $partialFieldSet = [];
1372
                }
1373
1374 496
                $queryComp   = $this->queryComponents[$dqlAlias];
1375 496
                $class       = $queryComp['metadata'];
1376 496
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1377
1378 496
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1379 496
                    $this->selectedClasses[$dqlAlias] = [
1380 496
                        'class'       => $class,
1381 496
                        'dqlAlias'    => $dqlAlias,
1382 496
                        'resultAlias' => $resultAlias
1383
                    ];
1384
                }
1385
1386 496
                $sqlParts = [];
1387
1388
                // Select all fields from the queried class
1389 496 View Code Duplication
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1390 495
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
1391 14
                        continue;
1392
                    }
1393
1394 494
                    $tableName = (isset($mapping['inherited']))
1395 52
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1396 494
                        : $class->getTableName();
1397
1398 494
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1399 494
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1400 494
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1401
1402 494
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1403
1404 494
                    if (isset($mapping['requireSQLConversion'])) {
1405 5
                        $type = Type::getType($mapping['type']);
1406 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1407
                    }
1408
1409 494
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1410
1411 494
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1412
1413 494
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
1414
                }
1415
1416
                // Add any additional fields of subclasses (excluding inherited fields)
1417
                // 1) on Single Table Inheritance: always, since its marginal overhead
1418
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1419
                //    since it requires outer joining subtables.
1420 496
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1421 403 View Code Duplication
                    foreach ($class->subClasses as $subClassName) {
1422 48
                        $subClass      = $this->em->getClassMetadata($subClassName);
1423 48
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1424
1425 48
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1426 48
                            if (isset($mapping['inherited']) || ($partialFieldSet && !in_array($fieldName, $partialFieldSet))) {
1427 48
                                continue;
1428
                            }
1429
1430 35
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1431 35
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1432
1433 35
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1434
1435 35
                            if (isset($mapping['requireSQLConversion'])) {
1436
                                $type = Type::getType($mapping['type']);
1437
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1438
                            }
1439
1440 35
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1441
1442 35
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1443
1444 48
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1445
                        }
1446
                    }
1447
                }
1448
1449 496
                $sql .= implode(', ', $sqlParts);
1450
        }
1451
1452 634
        return $sql;
1453
    }
1454
1455
    /**
1456
     * {@inheritdoc}
1457
     */
1458
    public function walkQuantifiedExpression($qExpr)
1459
    {
1460
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1461
    }
1462
1463
    /**
1464
     * {@inheritdoc}
1465
     */
1466 33
    public function walkSubselect($subselect)
1467
    {
1468 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1469 33
        $rootAliasesBefore = $this->rootAliases;
1470
1471 33
        $this->rootAliases = []; // reset the rootAliases for the subselect
1472 33
        $this->useSqlTableAliases = true;
1473
1474 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1475 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1476 33
        $sql .= $this->walkWhereClause($subselect->whereClause);
1477
1478 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1479 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1480 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1481
1482 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1483 33
        $this->useSqlTableAliases = $useAliasesBefore;
1484
1485 33
        return $sql;
1486
    }
1487
1488
    /**
1489
     * {@inheritdoc}
1490
     */
1491 33 View Code Duplication
    public function walkSubselectFromClause($subselectFromClause)
1492
    {
1493 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1494 33
        $sqlParts               = [];
1495
1496 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1497 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1498
        }
1499
1500 33
        return ' FROM ' . implode(', ', $sqlParts);
1501
    }
1502
1503
    /**
1504
     * {@inheritdoc}
1505
     */
1506 33
    public function walkSimpleSelectClause($simpleSelectClause)
1507
    {
1508 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1509 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1510
    }
1511
1512
    /**
1513
     * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression
1514
     *
1515
     * @return string.
1516
     */
1517 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1518
    {
1519 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1520
    }
1521
1522
    /**
1523
     * @param AST\NewObjectExpression   $newObjectExpression
1524
     * @param null|string               $newObjectResultAlias
1525
     * @return string The SQL.
1526
     */
1527 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1528
    {
1529 22
        $sqlSelectExpressions = [];
1530 22
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1531
1532 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1533 22
            $resultAlias = $this->scalarResultCounter++;
1534 22
            $columnAlias = $this->getSQLColumnAlias('sclr');
1535 22
            $fieldType   = 'string';
1536
1537
            switch (true) {
1538 22
                case ($e instanceof AST\NewObjectExpression):
1539
                    $sqlSelectExpressions[] = $e->dispatch($this);
1540
                    break;
1541
1542 22
                case ($e instanceof AST\Subselect):
1543 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1544 1
                    break;
1545
1546 22
                case ($e instanceof AST\PathExpression):
1547 22
                    $dqlAlias  = $e->identificationVariable;
1548 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1549 22
                    $class     = $qComp['metadata'];
1550 22
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1551
1552 22
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1553 22
                    break;
1554
1555 6
                case ($e instanceof AST\Literal):
1556 1
                    switch ($e->type) {
1557 1
                        case AST\Literal::BOOLEAN:
1558 1
                            $fieldType = 'boolean';
1559 1
                            break;
1560
1561 1
                        case AST\Literal::NUMERIC:
1562 1
                            $fieldType = is_float($e->value) ? 'float' : 'integer';
1563 1
                            break;
1564
                    }
1565
1566 1
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1567 1
                    break;
1568
1569
                default:
1570 5
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1571 5
                    break;
1572
            }
1573
1574 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1575 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1576
1577 22
            $this->rsm->newObjectMappings[$columnAlias] = [
1578 22
                'className' => $newObjectExpression->className,
1579 22
                'objIndex'  => $objIndex,
1580 22
                'argIndex'  => $argIndex
1581
            ];
1582
        }
1583
1584 22
        return implode(', ', $sqlSelectExpressions);
1585
    }
1586
1587
    /**
1588
     * {@inheritdoc}
1589
     */
1590 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1591
    {
1592 33
        $expr = $simpleSelectExpression->expression;
1593 33
        $sql  = ' ';
1594
1595
        switch (true) {
1596 33
            case ($expr instanceof AST\PathExpression):
1597 9
                $sql .= $this->walkPathExpression($expr);
1598 9
                break;
1599
1600 24 View Code Duplication
            case ($expr instanceof AST\Subselect):
1601
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1602
1603
                $columnAlias = 'sclr' . $this->aliasCounter++;
1604
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1605
1606
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1607
                break;
1608
1609 24
            case ($expr instanceof AST\Functions\FunctionNode):
1610 11
            case ($expr instanceof AST\SimpleArithmeticExpression):
1611 10
            case ($expr instanceof AST\ArithmeticTerm):
1612 9
            case ($expr instanceof AST\ArithmeticFactor):
1613 9
            case ($expr instanceof AST\Literal):
1614 7
            case ($expr instanceof AST\NullIfExpression):
1615 7
            case ($expr instanceof AST\CoalesceExpression):
1616 7
            case ($expr instanceof AST\GeneralCaseExpression):
1617 5 View Code Duplication
            case ($expr instanceof AST\SimpleCaseExpression):
1618 21
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1619
1620 21
                $columnAlias = $this->getSQLColumnAlias('sclr');
1621 21
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1622
1623 21
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1624 21
                break;
1625
1626 3
            case ($expr instanceof AST\ParenthesisExpression):
1627 1
                $sql .= $this->walkParenthesisExpression($expr);
1628 1
                break;
1629
1630
            default: // IdentificationVariable
1631 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1632 2
                break;
1633
        }
1634
1635 33
        return $sql;
1636
    }
1637
1638
    /**
1639
     * {@inheritdoc}
1640
     */
1641 76
    public function walkAggregateExpression($aggExpression)
1642
    {
1643 76
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1644 76
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1645
    }
1646
1647
    /**
1648
     * {@inheritdoc}
1649
     */
1650 24
    public function walkGroupByClause($groupByClause)
1651
    {
1652 24
        $sqlParts = [];
1653
1654 24
        foreach ($groupByClause->groupByItems as $groupByItem) {
1655 24
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1656
        }
1657
1658 24
        return ' GROUP BY ' . implode(', ', $sqlParts);
1659
    }
1660
1661
    /**
1662
     * {@inheritdoc}
1663
     */
1664 24
    public function walkGroupByItem($groupByItem)
1665
    {
1666
        // StateFieldPathExpression
1667 24
        if ( ! is_string($groupByItem)) {
1668 11
            return $this->walkPathExpression($groupByItem);
1669
        }
1670
1671
        // ResultVariable
1672 13
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1673 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1674
1675 2
            if ($resultVariable instanceof AST\PathExpression) {
1676 1
                return $this->walkPathExpression($resultVariable);
1677
            }
1678
1679 1
            if (isset($resultVariable->pathExpression)) {
1680
                return $this->walkPathExpression($resultVariable->pathExpression);
1681
            }
1682
1683 1
            return $this->walkResultVariable($groupByItem);
1684
        }
1685
1686
        // IdentificationVariable
1687 11
        $sqlParts = [];
1688
1689 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) {
1690 11
            $item       = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1691 11
            $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1692
1693 11
            $sqlParts[] = $this->walkPathExpression($item);
1694
        }
1695
1696 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) {
1697 11
            if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
1698 7
                $item       = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
1699 7
                $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1700
1701 11
                $sqlParts[] = $this->walkPathExpression($item);
1702
            }
1703
        }
1704
1705 11
        return implode(', ', $sqlParts);
1706
    }
1707
1708
    /**
1709
     * {@inheritdoc}
1710
     */
1711 36
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1712
    {
1713 36
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1714 36
        $tableName = $class->getTableName();
1715 36
        $sql       = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1716
1717 36
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1718 36
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1719
1720 36
        return $sql;
1721
    }
1722
1723
    /**
1724
     * {@inheritdoc}
1725
     */
1726 25
    public function walkUpdateClause($updateClause)
1727
    {
1728 25
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1729 25
        $tableName = $class->getTableName();
1730 25
        $sql       = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1731
1732 25
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1733 25
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1734
1735 25
        $sql .= ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1736
1737 25
        return $sql;
1738
    }
1739
1740
    /**
1741
     * {@inheritdoc}
1742
     */
1743 29
    public function walkUpdateItem($updateItem)
1744
    {
1745 29
        $useTableAliasesBefore = $this->useSqlTableAliases;
1746 29
        $this->useSqlTableAliases = false;
1747
1748 29
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1749 29
        $newValue = $updateItem->newValue;
1750
1751
        switch (true) {
1752 29
            case ($newValue instanceof AST\Node):
1753 28
                $sql .= $newValue->dispatch($this);
1754 28
                break;
1755
1756 1
            case ($newValue === null):
1757 1
                $sql .= 'NULL';
1758 1
                break;
1759
1760
            default:
1761
                $sql .= $this->conn->quote($newValue);
1762
                break;
1763
        }
1764
1765 29
        $this->useSqlTableAliases = $useTableAliasesBefore;
1766
1767 29
        return $sql;
1768
    }
1769
1770
    /**
1771
     * {@inheritdoc}
1772
     */
1773 691
    public function walkWhereClause($whereClause)
1774
    {
1775 691
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1776 688
        $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->rootAliases);
1777
1778 688
        if ($this->em->hasFilters()) {
1779 43
            $filterClauses = [];
1780 43
            foreach ($this->rootAliases as $dqlAlias) {
1781 43
                $class = $this->queryComponents[$dqlAlias]['metadata'];
1782 43
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1783
1784 43
                if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1785 43
                    $filterClauses[] = $filterExpr;
1786
                }
1787
            }
1788
1789 43
            if (count($filterClauses)) {
1790 6
                if ($condSql) {
1791 2
                    $condSql = '(' . $condSql . ') AND ';
1792
                }
1793
1794 6
                $condSql .= implode(' AND ', $filterClauses);
1795
            }
1796
        }
1797
1798 688
        if ($condSql) {
1799 340
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1800
        }
1801
1802 424
        if ($discrSql) {
1803 23
            return ' WHERE ' . $discrSql;
1804
        }
1805
1806 404
        return '';
1807
    }
1808
1809
    /**
1810
     * {@inheritdoc}
1811
     */
1812 372
    public function walkConditionalExpression($condExpr)
1813
    {
1814
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1815
        // if only one ConditionalTerm is defined
1816 372
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1817 317
            return $this->walkConditionalTerm($condExpr);
1818
        }
1819
1820 72
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1821
    }
1822
1823
    /**
1824
     * {@inheritdoc}
1825
     */
1826 372
    public function walkConditionalTerm($condTerm)
1827
    {
1828
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1829
        // if only one ConditionalFactor is defined
1830 372
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1831 305
            return $this->walkConditionalFactor($condTerm);
1832
        }
1833
1834 90
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1835
    }
1836
1837
    /**
1838
     * {@inheritdoc}
1839
     */
1840 372
    public function walkConditionalFactor($factor)
1841
    {
1842
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1843
        // if only one ConditionalPrimary is defined
1844 372
        return ( ! ($factor instanceof AST\ConditionalFactor))
1845 369
            ? $this->walkConditionalPrimary($factor)
1846 369
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1847
    }
1848
1849
    /**
1850
     * {@inheritdoc}
1851
     */
1852 372
    public function walkConditionalPrimary($primary)
1853
    {
1854 372
        if ($primary->isSimpleConditionalExpression()) {
1855 372
            return $primary->simpleConditionalExpression->dispatch($this);
1856
        }
1857
1858 25
        if ($primary->isConditionalExpression()) {
1859 25
            $condExpr = $primary->conditionalExpression;
1860
1861 25
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
1862
        }
1863
    }
1864
1865
    /**
1866
     * {@inheritdoc}
1867
     */
1868 5
    public function walkExistsExpression($existsExpr)
1869
    {
1870 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1871
1872 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1873
1874 5
        return $sql;
1875
    }
1876
1877
    /**
1878
     * {@inheritdoc}
1879
     */
1880 6
    public function walkCollectionMemberExpression($collMemberExpr)
1881
    {
1882 6
        $sql = $collMemberExpr->not ? 'NOT ' : '';
1883 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1884
1885 6
        $entityExpr   = $collMemberExpr->entityExpression;
1886 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1887
1888 6
        $fieldName = $collPathExpr->field;
1889 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1890
1891 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1892
1893
        switch (true) {
1894
            // InputParameter
1895 6
            case ($entityExpr instanceof AST\InputParameter):
1896 4
                $dqlParamKey = $entityExpr->name;
1897 4
                $entitySql   = '?';
1898 4
                break;
1899
1900
            // SingleValuedAssociationPathExpression | IdentificationVariable
1901 2
            case ($entityExpr instanceof AST\PathExpression):
1902 2
                $entitySql = $this->walkPathExpression($entityExpr);
1903 2
                break;
1904
1905
            default:
1906
                throw new \BadMethodCallException("Not implemented");
1907
        }
1908
1909 6
        $assoc = $class->associationMappings[$fieldName];
1910
1911 6
        if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
1912 1
            $targetClass      = $this->em->getClassMetadata($assoc['targetEntity']);
1913 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1914 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1915
1916 1
            $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1917
1918 1
            $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1919 1
            $sqlParts    = [];
1920
1921 1 View Code Duplication
            foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
1922 1
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1923
1924 1
                $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1925
            }
1926
1927 1 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1928 1
                if (isset($dqlParamKey)) {
1929 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1930
                }
1931
1932 1
                $sqlParts[] = $targetTableAlias . '.'  . $targetColumnName . ' = ' . $entitySql;
1933
            }
1934
1935 1
            $sql .= implode(' AND ', $sqlParts);
1936
        } else { // many-to-many
1937 5
            $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1938
1939 5
            $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1940 5
            $joinTable = $owningAssoc['joinTable'];
1941
1942
            // SQL table aliases
1943 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable['name']);
1944 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1945 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1946
1947
            // join to target table
1948 5
            $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias
1949 5
                . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON ';
1950
1951
            // join conditions
1952 5
            $joinColumns  = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1953 5
            $joinSqlParts = [];
1954
1955 5 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
1956 5
                $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1957
1958 5
                $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1959
            }
1960
1961 5
            $sql .= implode(' AND ', $joinSqlParts);
1962 5
            $sql .= ' WHERE ';
1963
1964 5
            $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1965 5
            $sqlParts    = [];
1966
1967 5 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
1968 5
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1969
1970 5
                $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1971
            }
1972
1973 5 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1974 5
                if (isset($dqlParamKey)) {
1975 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1976
                }
1977
1978 5
                $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' IN (' . $entitySql . ')';
1979
            }
1980
1981 5
            $sql .= implode(' AND ', $sqlParts);
1982
        }
1983
1984 6
        return $sql . ')';
1985
    }
1986
1987
    /**
1988
     * {@inheritdoc}
1989
     */
1990 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1991
    {
1992 3
        $sizeFunc = new AST\Functions\SizeFunction('size');
1993 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1994
1995 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1996
    }
1997
1998
    /**
1999
     * {@inheritdoc}
2000
     */
2001 11
    public function walkNullComparisonExpression($nullCompExpr)
2002
    {
2003 11
        $expression = $nullCompExpr->expression;
2004 11
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2005
2006
        // Handle ResultVariable
2007 11
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2008 2
            return $this->walkResultVariable($expression) . $comparison;
2009
        }
2010
2011
        // Handle InputParameter mapping inclusion to ParserResult
2012 9
        if ($expression instanceof AST\InputParameter) {
2013
            return $this->walkInputParameter($expression) . $comparison;
2014
        }
2015
2016 9
        return $expression->dispatch($this) . $comparison;
2017
    }
2018
2019
    /**
2020
     * {@inheritdoc}
2021
     */
2022 86
    public function walkInExpression($inExpr)
2023
    {
2024 86
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2025
2026 85
        $sql .= ($inExpr->subselect)
2027 7
            ? $this->walkSubselect($inExpr->subselect)
2028 85
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2029
2030 85
        $sql .= ')';
2031
2032 85
        return $sql;
2033
    }
2034
2035
    /**
2036
     * {@inheritdoc}
2037
     * @throws \Doctrine\ORM\Query\QueryException
2038
     */
2039 14
    public function walkInstanceOfExpression($instanceOfExpr)
2040
    {
2041 14
        $sql = '';
2042
2043 14
        $dqlAlias = $instanceOfExpr->identificationVariable;
2044 14
        $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
2045
2046 14
        if ($class->discriminatorColumn) {
2047 14
            $discrClass = $this->em->getClassMetadata($class->rootEntityName);
2048
        }
2049
2050 14
        if ($this->useSqlTableAliases) {
2051 14
            $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
2052
        }
2053
2054 14
        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
2055 14
        $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr);
2056
2057 13
        return $sql;
2058
    }
2059
2060
    /**
2061
     * {@inheritdoc}
2062
     */
2063 82
    public function walkInParameter($inParam)
2064
    {
2065 82
        return $inParam instanceof AST\InputParameter
2066 73
            ? $this->walkInputParameter($inParam)
2067 82
            : $this->walkLiteral($inParam);
2068
    }
2069
2070
    /**
2071
     * {@inheritdoc}
2072
     */
2073 148
    public function walkLiteral($literal)
2074
    {
2075 148
        switch ($literal->type) {
2076 148
            case AST\Literal::STRING:
2077 50
                return $this->conn->quote($literal->value);
2078
2079 111
            case AST\Literal::BOOLEAN:
2080 8
                return $this->conn->getDatabasePlatform()->convertBooleans('true' === strtolower($literal->value));
2081
2082 104
            case AST\Literal::NUMERIC:
2083 104
                return $literal->value;
2084
2085
            default:
2086
                throw QueryException::invalidLiteral($literal);
2087
        }
2088
    }
2089
2090
    /**
2091
     * {@inheritdoc}
2092
     */
2093 6
    public function walkBetweenExpression($betweenExpr)
2094
    {
2095 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2096
2097 6
        if ($betweenExpr->not) {
2098 2
            $sql .= ' NOT';
2099
        }
2100
2101 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2102 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2103
2104 6
        return $sql;
2105
    }
2106
2107
    /**
2108
     * {@inheritdoc}
2109
     */
2110 9
    public function walkLikeExpression($likeExpr)
2111
    {
2112 9
        $stringExpr = $likeExpr->stringExpression;
2113 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2114 1
            ? $this->walkResultVariable($stringExpr)
2115 9
            : $stringExpr->dispatch($this);
2116
2117 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2118
2119 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2120 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2121 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2122 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2123 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2124 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2125
        } else {
2126 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2127
        }
2128
2129 9
        if ($likeExpr->escapeChar) {
2130 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2131
        }
2132
2133 9
        return $sql;
2134
    }
2135
2136
    /**
2137
     * {@inheritdoc}
2138
     */
2139 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2140
    {
2141 5
        return $this->walkPathExpression($stateFieldPathExpression);
2142
    }
2143
2144
    /**
2145
     * {@inheritdoc}
2146
     */
2147 263
    public function walkComparisonExpression($compExpr)
2148
    {
2149 263
        $leftExpr  = $compExpr->leftExpression;
2150 263
        $rightExpr = $compExpr->rightExpression;
2151 263
        $sql       = '';
2152
2153 263
        $sql .= ($leftExpr instanceof AST\Node)
2154 263
            ? $leftExpr->dispatch($this)
2155 262
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2156
2157 262
        $sql .= ' ' . $compExpr->operator . ' ';
2158
2159 262
        $sql .= ($rightExpr instanceof AST\Node)
2160 260
            ? $rightExpr->dispatch($this)
2161 262
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2162
2163 262
        return $sql;
2164
    }
2165
2166
    /**
2167
     * {@inheritdoc}
2168
     */
2169 219
    public function walkInputParameter($inputParam)
2170
    {
2171 219
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2172
2173 219
        $parameter = $this->query->getParameter($inputParam->name);
2174
2175 219
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2176 56
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2177
        }
2178
2179 169
        return '?';
2180
    }
2181
2182
    /**
2183
     * {@inheritdoc}
2184
     */
2185 330
    public function walkArithmeticExpression($arithmeticExpr)
2186
    {
2187 330
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2188 330
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
2189 328
            : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
2190
    }
2191
2192
    /**
2193
     * {@inheritdoc}
2194
     */
2195 393
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2196
    {
2197 393
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2198 343
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2199
        }
2200
2201 75
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2202
    }
2203
2204
    /**
2205
     * {@inheritdoc}
2206
     */
2207 414
    public function walkArithmeticTerm($term)
2208
    {
2209 414 View Code Duplication
        if (is_string($term)) {
2210 21
            return (isset($this->queryComponents[$term]))
2211 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2212 21
                : $term;
2213
        }
2214
2215
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2216
        // if only one ArithmeticFactor is defined
2217 413
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2218 391
            return $this->walkArithmeticFactor($term);
2219
        }
2220
2221 47
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2222
    }
2223
2224
    /**
2225
     * {@inheritdoc}
2226
     */
2227 414
    public function walkArithmeticFactor($factor)
2228
    {
2229 414 View Code Duplication
        if (is_string($factor)) {
2230 47
            return (isset($this->queryComponents[$factor]))
2231 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2232 47
                : $factor;
2233
        }
2234
2235
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2236
        // if only one ArithmeticPrimary is defined
2237 414
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2238 413
            return $this->walkArithmeticPrimary($factor);
2239
        }
2240
2241 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2242
2243 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2244
    }
2245
2246
    /**
2247
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2248
     *
2249
     * @param mixed $primary
2250
     *
2251
     * @return string The SQL.
2252
     */
2253 414
    public function walkArithmeticPrimary($primary)
2254
    {
2255 414
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2256
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2257
        }
2258
2259 414
        if ($primary instanceof AST\Node) {
2260 414
            return $primary->dispatch($this);
2261
        }
2262
2263
        return $this->walkEntityIdentificationVariable($primary);
2264
    }
2265
2266
    /**
2267
     * {@inheritdoc}
2268
     */
2269 18
    public function walkStringPrimary($stringPrimary)
2270
    {
2271 18
        return (is_string($stringPrimary))
2272
            ? $this->conn->quote($stringPrimary)
2273 18
            : $stringPrimary->dispatch($this);
2274
    }
2275
2276
    /**
2277
     * {@inheritdoc}
2278
     */
2279 30
    public function walkResultVariable($resultVariable)
2280
    {
2281 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2282
2283 30
        if (is_array($resultAlias)) {
2284 1
            return implode(', ', $resultAlias);
2285
        }
2286
2287 29
        return $resultAlias;
2288
    }
2289
2290
    /**
2291
     * @param ClassMetadataInfo $rootClass
2292
     * @param AST\InstanceOfExpression $instanceOfExpr
2293
     * @return string The list in parentheses of valid child discriminators from the given class
2294
     * @throws QueryException
2295
     */
2296 14
    private function getChildDiscriminatorsFromClassMetadata(ClassMetadataInfo $rootClass, AST\InstanceOfExpression $instanceOfExpr): string
2297
    {
2298 14
        $sqlParameterList = [];
2299 14
        $discriminators = [];
2300 14
        foreach ($instanceOfExpr->value as $parameter) {
2301 14
            if ($parameter instanceof AST\InputParameter) {
2302 4
                $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
2303 4
                $sqlParameterList[] = $this->walkInParameter($parameter);
2304 4
                continue;
2305
            }
2306
2307 10
            $metadata = $this->em->getClassMetadata($parameter);
2308
2309 10
            if ($metadata->getName() !== $rootClass->name && ! $metadata->getReflectionClass()->isSubclassOf($rootClass->name)) {
2310 1
                throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name);
2311
            }
2312
2313 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em);
2314
        }
2315
2316 13
        foreach (array_keys($discriminators) as $dis) {
2317 9
            $sqlParameterList[] = $this->conn->quote($dis);
2318
        }
2319
2320 13
        return '(' . implode(', ', $sqlParameterList) . ')';
2321
    }
2322
}
2323