Failed Conditions
Pull Request — master (#6743)
by Grégoire
18:17 queued 12:33
created

SqlWalker::walkNullComparisonExpression()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5.0488

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 6
nop 1
dl 0
loc 16
ccs 7
cts 8
cp 0.875
crap 5.0488
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use Doctrine\DBAL\LockMode;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use Doctrine\ORM\Mapping\FieldMetadata;
11
use Doctrine\ORM\Mapping\InheritanceType;
12
use Doctrine\ORM\Mapping\JoinColumnMetadata;
13
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
14
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
15
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
16
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
17
use Doctrine\ORM\OptimisticLockException;
18
use Doctrine\ORM\Query;
19
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
20
use Doctrine\ORM\Utility\PersisterHelper;
21
22
/**
23
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
24
 * the corresponding SQL.
25
 */
26
class SqlWalker implements TreeWalker
27
{
28
    /**
29
     * @var string
30
     */
31
    public const HINT_DISTINCT = 'doctrine.distinct';
32
33
    /**
34
     * @var ResultSetMapping
35
     */
36
    private $rsm;
37
38
    /**
39
     * Counter for generating unique column aliases.
40
     *
41
     * @var int
42
     */
43
    private $aliasCounter = 0;
44
45
    /**
46
     * Counter for generating unique table aliases.
47
     *
48
     * @var int
49
     */
50
    private $tableAliasCounter = 0;
51
52
    /**
53
     * Counter for generating unique scalar result.
54
     *
55
     * @var int
56
     */
57
    private $scalarResultCounter = 1;
58
59
    /**
60
     * Counter for generating unique parameter indexes.
61
     *
62
     * @var int
63
     */
64
    private $sqlParamIndex = 0;
65
66
    /**
67
     * Counter for generating indexes.
68
     *
69
     * @var int
70
     */
71
    private $newObjectCounter = 0;
72
73
    /**
74
     * @var ParserResult
75
     */
76
    private $parserResult;
77
78
    /**
79
     * @var \Doctrine\ORM\EntityManagerInterface
80
     */
81
    private $em;
82
83
    /**
84
     * @var \Doctrine\DBAL\Connection
85
     */
86
    private $conn;
87
88
    /**
89
     * @var \Doctrine\ORM\AbstractQuery
90
     */
91
    private $query;
92
93
    /**
94
     * @var string[]
95
     */
96
    private $tableAliasMap = [];
97
98
    /**
99
     * Map from result variable names to their SQL column alias names.
100
     *
101
     * @var string[]|string[][]
102
     */
103
    private $scalarResultAliasMap = [];
104
105
    /**
106
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
107
     *
108
     * @var mixed[]
109
     */
110
    private $orderedColumnsMap = [];
111
112
    /**
113
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
114
     *
115
     * @var string[][]
116
     */
117
    private $scalarFields = [];
118
119
    /**
120
     * Map of all components/classes that appear in the DQL query.
121
     *
122
     * @var mixed[][]
123
     */
124
    private $queryComponents;
125
126
    /**
127
     * A list of classes that appear in non-scalar SelectExpressions.
128
     *
129
     * @var mixed[][]
130
     */
131
    private $selectedClasses = [];
132
133
    /**
134
     * The DQL alias of the root class of the currently traversed query.
135
     *
136
     * @var string[]
137
     */
138
    private $rootAliases = [];
139
140
    /**
141
     * Flag that indicates whether to generate SQL table aliases in the SQL.
142
     * These should only be generated for SELECT queries, not for UPDATE/DELETE.
143
     *
144
     * @var bool
145
     */
146
    private $useSqlTableAliases = true;
147
148
    /**
149
     * The database platform abstraction.
150
     *
151
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
152
     */
153
    private $platform;
154
155
    /**
156
     * {@inheritDoc}
157
     */
158 667
    public function __construct($query, $parserResult, array $queryComponents)
159
    {
160 667
        $this->query           = $query;
161 667
        $this->parserResult    = $parserResult;
162 667
        $this->queryComponents = $queryComponents;
163 667
        $this->rsm             = $parserResult->getResultSetMapping();
164 667
        $this->em              = $query->getEntityManager();
165 667
        $this->conn            = $this->em->getConnection();
166 667
        $this->platform        = $this->conn->getDatabasePlatform();
167 667
    }
168
169
    /**
170
     * Gets the Query instance used by the walker.
171
     *
172
     * @return Query.
173
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment Query. at position 0 could not be parsed: Unknown type name 'Query.' at position 0 in Query..
Loading history...
174
    public function getQuery()
175
    {
176
        return $this->query;
177
    }
178
179
    /**
180
     * Gets the Connection used by the walker.
181
     *
182
     * @return \Doctrine\DBAL\Connection
183
     */
184 47
    public function getConnection()
185
    {
186 47
        return $this->conn;
187
    }
188
189
    /**
190
     * Gets the EntityManager used by the walker.
191
     *
192
     * @return \Doctrine\ORM\EntityManagerInterface
193
     */
194 23
    public function getEntityManager()
195
    {
196 23
        return $this->em;
197
    }
198
199
    /**
200
     * Gets the information about a single query component.
201
     *
202
     * @param string $dqlAlias The DQL alias.
203
     *
204
     * @return mixed[][]
205
     */
206 18
    public function getQueryComponent($dqlAlias)
207
    {
208 18
        return $this->queryComponents[$dqlAlias];
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function getQueryComponents()
215
    {
216
        return $this->queryComponents;
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    public function setQueryComponent($dqlAlias, array $queryComponent)
223
    {
224
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
225
226
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
227
            throw QueryException::invalidQueryComponent($dqlAlias);
228
        }
229
230
        $this->queryComponents[$dqlAlias] = $queryComponent;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236 661
    public function getExecutor($AST)
237
    {
238
        switch (true) {
239 661
            case ($AST instanceof AST\DeleteStatement):
240 36
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
241
242 36
                return ($primaryClass->inheritanceType === InheritanceType::JOINED)
243 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
244 36
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
245
246 629
            case ($AST instanceof AST\UpdateStatement):
247 27
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
248
249 27
                return ($primaryClass->inheritanceType === InheritanceType::JOINED)
250 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
251 27
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
252
253
            default:
254 606
                return new Exec\SingleSelectExecutor($AST, $this);
255
        }
256
    }
257
258
    /**
259
     * Generates a unique, short SQL table alias.
260
     *
261
     * @param string $tableName Table name
262
     * @param string $dqlAlias  The DQL alias.
263
     *
264
     * @return string Generated table alias.
265
     */
266 617
    public function getSQLTableAlias($tableName, $dqlAlias = '')
267
    {
268 617
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
269
270 617
        if (! isset($this->tableAliasMap[$tableName])) {
271 617
            $this->tableAliasMap[$tableName] = 't' . $this->tableAliasCounter++;
272
        }
273
274 617
        return $this->tableAliasMap[$tableName];
275
    }
276
277
    /**
278
     * Forces the SqlWalker to use a specific alias for a table name, rather than
279
     * generating an alias on its own.
280
     *
281
     * @param string $tableName
282
     * @param string $alias
283
     * @param string $dqlAlias
284
     *
285
     * @return string
286
     */
287 61
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
288
    {
289 61
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
290
291 61
        $this->tableAliasMap[$tableName] = $alias;
292
293 61
        return $alias;
294
    }
295
296
    /**
297
     * Gets an SQL column alias for a column name.
298
     *
299
     * @return string
300
     */
301 606
    public function getSQLColumnAlias()
302
    {
303 606
        return $this->platform->getSQLResultCasing('c' . $this->aliasCounter++);
304
    }
305
306
    /**
307
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
308
     * for the given class.
309
     *
310
     * @param ClassMetadata $class    The class for which to generate the joins.
311
     * @param string        $dqlAlias The DQL alias of the class.
312
     *
313
     * @return string The SQL.
314
     */
315 107
    private function generateClassTableInheritanceJoins($class, $dqlAlias)
316
    {
317 107
        $sql = '';
318
319 107
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
320
321
        // INNER JOIN parent class tables
322 107
        $parentClass = $class;
323
324 107
        while (($parentClass = $parentClass->getParent()) !== null) {
325 76
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
326 76
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
327
328
            // If this is a joined association we must use left joins to preserve the correct result.
329 76
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
330 76
            $sql .= 'JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
331
332 76
            $sqlParts = [];
333
334 76
            foreach ($class->getIdentifierColumns($this->em) as $column) {
335 76
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
336
337 76
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
338
            }
339
340 76
            $filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias);
341
342
            // Add filters on the root class
343 76
            if ($filterSql) {
344 1
                $sqlParts[] = $filterSql;
345
            }
346
347 76
            $sql .= implode(' AND ', $sqlParts);
348
        }
349
350
        // Ignore subclassing inclusion if partial objects is disallowed
351 107
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
352 20
            return $sql;
353
        }
354
355
        // LEFT JOIN child class tables
356 87
        foreach ($class->getSubClasses() as $subClassName) {
357 38
            $subClass   = $this->em->getClassMetadata($subClassName);
358 38
            $tableName  = $subClass->table->getQuotedQualifiedName($this->platform);
359 38
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
360
361 38
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
362
363 38
            $sqlParts = [];
364
365 38
            foreach ($subClass->getIdentifierColumns($this->em) as $column) {
366 38
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
367
368 38
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
369
            }
370
371 38
            $sql .= implode(' AND ', $sqlParts);
372
        }
373
374 87
        return $sql;
375
    }
376
377
    /**
378
     * @return string
379
     */
380 606
    private function generateOrderedCollectionOrderByItems()
381
    {
382 606
        $orderedColumns = [];
383
384 606
        foreach ($this->selectedClasses as $selectedClass) {
385 456
            $dqlAlias    = $selectedClass['dqlAlias'];
386 456
            $qComp       = $this->queryComponents[$dqlAlias];
387 456
            $association = $qComp['relation'];
388
389 456
            if (! ($association instanceof ToManyAssociationMetadata)) {
390 456
                continue;
391
            }
392
393 95
            foreach ($association->getOrderBy() as $fieldName => $orientation) {
394 6
                $property      = $qComp['metadata']->getProperty($fieldName);
395 6
                $tableName     = $property->getTableName();
396 6
                $columnName    = $this->platform->quoteIdentifier($property->getColumnName());
397 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
398
399
                // OrderByClause should replace an ordered relation. see - DDC-2475
400 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
401 1
                    continue;
402
                }
403
404 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
405 95
                $orderedColumns[]                        = $orderedColumn . ' ' . $orientation;
406
            }
407
        }
408
409 606
        return implode(', ', $orderedColumns);
410
    }
411
412
    /**
413
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
414
     *
415
     * @param string[] $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
416
     *
417
     * @return string
418
     */
419 661
    private function generateDiscriminatorColumnConditionSQL(array $dqlAliases)
420
    {
421 661
        $sqlParts = [];
422
423 661
        foreach ($dqlAliases as $dqlAlias) {
424 661
            $class = $this->queryComponents[$dqlAlias]['metadata'];
425
426 661
            if ($class->inheritanceType !== InheritanceType::SINGLE_TABLE) {
427 635
                continue;
428
            }
429
430 41
            $conn   = $this->em->getConnection();
431 41
            $values = [];
432
433 41
            if ($class->discriminatorValue !== null) { // discriminators can be 0
434 21
                $values[] = $conn->quote($class->discriminatorValue);
435
            }
436
437 41
            foreach ($class->getSubClasses() as $subclassName) {
438 30
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
439
            }
440
441 41
            $discrColumn      = $class->discriminatorColumn;
442 41
            $discrColumnType  = $discrColumn->getType();
443 41
            $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
444 41
            $sqlTableAlias    = ($this->useSqlTableAliases)
445 36
                ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
446 41
                : '';
447
448 41
            $sqlParts[] = sprintf(
449 41
                '%s IN (%s)',
450 41
                $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
451 41
                implode(', ', $values)
452
            );
453
        }
454
455 661
        $sql = implode(' AND ', $sqlParts);
456
457 661
        return isset($sqlParts[1]) ? '(' . $sql . ')' : $sql;
458
    }
459
460
    /**
461
     * Generates the filter SQL for a given entity and table alias.
462
     *
463
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
464
     * @param string        $targetTableAlias The table alias of the joined/selected table.
465
     *
466
     * @return string The SQL query part to add to a query.
467
     */
468 316
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
469
    {
470 316
        if (! $this->em->hasFilters()) {
471 282
            return '';
472
        }
473
474 39
        switch ($targetEntity->inheritanceType) {
475 39
            case InheritanceType::NONE:
476 29
                break;
477
478 10
            case InheritanceType::JOINED:
479
                // The classes in the inheritance will be added to the query one by one,
480
                // but only the root node is getting filtered
481 6
                if ($targetEntity->getClassName() !== $targetEntity->getRootClassName()) {
482 4
                    return '';
483
                }
484 6
                break;
485
486 4
            case InheritanceType::SINGLE_TABLE:
487
                // With STI the table will only be queried once, make sure that the filters
488
                // are added to the root entity
489 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->getRootClassName());
490 4
                break;
491
492
            default:
493
                //@todo: throw exception?
494
                return '';
495
        }
496
497 39
        $filterClauses = [];
498
499 39
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
500 10
            $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
501
502 10
            if ($filterExpr !== '') {
503 10
                $filterClauses[] = '(' . $filterExpr . ')';
504
            }
505
        }
506
507 39
        return implode(' AND ', $filterClauses);
508
    }
509
510
    /**
511
     * {@inheritdoc}
512
     */
513 606
    public function walkSelectStatement(AST\SelectStatement $AST)
514
    {
515 606
        $limit    = $this->query->getMaxResults();
516 606
        $offset   = $this->query->getFirstResult();
517 606
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
518 606
        $sql      = $this->walkSelectClause($AST->selectClause)
519 606
            . $this->walkFromClause($AST->fromClause)
520 606
            . $this->walkWhereClause($AST->whereClause);
521
522 606
        if ($AST->groupByClause) {
523 23
            $sql .= $this->walkGroupByClause($AST->groupByClause);
524
        }
525
526 606
        if ($AST->havingClause) {
527 14
            $sql .= $this->walkHavingClause($AST->havingClause);
528
        }
529
530 606
        if ($AST->orderByClause) {
531 136
            $sql .= $this->walkOrderByClause($AST->orderByClause);
532
        }
533
534 606
        if (! $AST->orderByClause) {
535 500
            $orderBySql = $this->generateOrderedCollectionOrderByItems();
536
537 500
            if ($orderBySql) {
538 6
                $sql .= ' ORDER BY ' . $orderBySql;
539
            }
540
        }
541
542 606
        if ($limit !== null || $offset !== null) {
543 50
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
544
        }
545
546 606
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
547 602
            return $sql;
548
        }
549
550 4
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
551 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
552
        }
553
554 1
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
555 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
556
        }
557
558
        if ($lockMode !== LockMode::OPTIMISTIC) {
559
            throw QueryException::invalidLockMode();
560
        }
561
562
        foreach ($this->selectedClasses as $selectedClass) {
563
            if (! $selectedClass['class']->isVersioned()) {
564
                throw OptimisticLockException::lockFailed($selectedClass['class']->getClassName());
565
            }
566
        }
567
568
        return $sql;
569
    }
570
571
    /**
572
     * {@inheritdoc}
573
     */
574 23
    public function walkUpdateStatement(AST\UpdateStatement $AST)
575
    {
576 23
        $this->useSqlTableAliases = false;
577 23
        $this->rsm->isSelect      = false;
578
579 23
        return $this->walkUpdateClause($AST->updateClause)
580 23
            . $this->walkWhereClause($AST->whereClause);
581
    }
582
583
    /**
584
     * {@inheritdoc}
585
     */
586 34
    public function walkDeleteStatement(AST\DeleteStatement $AST)
587
    {
588 34
        $this->useSqlTableAliases = false;
589 34
        $this->rsm->isSelect      = false;
590
591 34
        return $this->walkDeleteClause($AST->deleteClause)
592 34
            . $this->walkWhereClause($AST->whereClause);
593
    }
594
595
    /**
596
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
597
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
598
     *
599
     * @param string $identVariable
600
     *
601
     * @return string
602
     */
603 2
    public function walkEntityIdentificationVariable($identVariable)
604
    {
605 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
606 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
607 2
        $sqlParts   = [];
608
609 2
        foreach ($class->getIdentifierColumns($this->em) as $column) {
610 2
            $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
611
612 2
            $sqlParts[] = $tableAlias . '.' . $quotedColumnName;
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 405
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 405
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630 405
        if (! $fieldName) {
631
            return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
632
        }
633
634 405
        $property = $class->getProperty($fieldName);
635
636 405
        if ($class->inheritanceType === InheritanceType::JOINED && $class->isInheritedProperty($fieldName)) {
637 38
            $class = $property->getDeclaringClass();
638
        }
639
640 405
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
641
    }
642
643
    /**
644
     * {@inheritdoc}
645
     */
646 468
    public function walkPathExpression($pathExpr)
647
    {
648 468
        $sql = '';
649
650
        /* @var $pathExpr Query\AST\PathExpression */
651 468
        switch ($pathExpr->type) {
652 468
            case AST\PathExpression::TYPE_STATE_FIELD:
653 450
                $fieldName = $pathExpr->field;
654 450
                $dqlAlias  = $pathExpr->identificationVariable;
655 450
                $class     = $this->queryComponents[$dqlAlias]['metadata'];
656 450
                $property  = $class->getProperty($fieldName);
657
658 450
                if ($this->useSqlTableAliases) {
659 405
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
660
                }
661
662 450
                $sql .= $this->platform->quoteIdentifier($property->getColumnName());
663 450
                break;
664
665 59
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
666
                // 1- the owning side:
667
                //    Just use the foreign key, i.e. u.group_id
668 59
                $fieldName   = $pathExpr->field;
669 59
                $dqlAlias    = $pathExpr->identificationVariable;
670 59
                $class       = $this->queryComponents[$dqlAlias]['metadata'];
671 59
                $association = $class->getProperty($fieldName);
672
673 59
                if (! $association->isOwningSide()) {
674
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
675
                }
676
677 59
                $joinColumns = $association->getJoinColumns();
678
679
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
680 59
                if (count($joinColumns) > 1) {
681
                    throw QueryException::associationPathCompositeKeyNotSupported();
682
                }
683
684 59
                $joinColumn = reset($joinColumns);
685
686 59
                if ($this->useSqlTableAliases) {
687 56
                    $sql .= $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias) . '.';
688
                }
689
690 59
                $sql .= $this->platform->quoteIdentifier($joinColumn->getColumnName());
691 59
                break;
692
693
            default:
694
                throw QueryException::invalidPathExpression($pathExpr);
695
        }
696
697 468
        return $sql;
698
    }
699
700
    /**
701
     * {@inheritdoc}
702
     */
703 606
    public function walkSelectClause($selectClause)
704
    {
705 606
        $sql                  = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
706 606
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
707
708 606
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) {
709 1
            $this->query->setHint(self::HINT_DISTINCT, true);
710
        }
711
712
        $addMetaColumns = (
713 606
            ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
714 440
            $this->query->getHydrationMode() === Query::HYDRATE_OBJECT
715
        ) || (
716 295
            $this->query->getHydrationMode() !== Query::HYDRATE_OBJECT &&
717 606
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS)
718
        );
719
720 606
        foreach ($this->selectedClasses as $selectedClass) {
721 456
            $class       = $selectedClass['class'];
722 456
            $dqlAlias    = $selectedClass['dqlAlias'];
723 456
            $resultAlias = $selectedClass['resultAlias'];
724
725
            // Register as entity or joined entity result
726 456
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
727 456
                $this->rsm->addEntityResult($class->getClassName(), $dqlAlias, $resultAlias);
728
            } else {
729 148
                $this->rsm->addJoinedEntityResult(
730 148
                    $class->getClassName(),
731 148
                    $dqlAlias,
732 148
                    $this->queryComponents[$dqlAlias]['parent'],
733 148
                    $this->queryComponents[$dqlAlias]['relation']->getName()
734
                );
735
            }
736
737 456
            if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || $class->inheritanceType === InheritanceType::JOINED) {
738
                // Add discriminator columns to SQL
739 95
                $discrColumn      = $class->discriminatorColumn;
740 95
                $discrColumnName  = $discrColumn->getColumnName();
741 95
                $discrColumnType  = $discrColumn->getType();
742 95
                $quotedColumnName = $this->platform->quoteIdentifier($discrColumnName);
743 95
                $sqlTableAlias    = $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias);
744 95
                $sqlColumnAlias   = $this->getSQLColumnAlias();
745
746 95
                $sqlSelectExpressions[] = sprintf(
747 95
                    '%s AS %s',
748 95
                    $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . '.' . $quotedColumnName, $this->platform),
749 95
                    $sqlColumnAlias
750
                );
751
752 95
                $this->rsm->setDiscriminatorColumn($dqlAlias, $sqlColumnAlias);
753 95
                $this->rsm->addMetaResult($dqlAlias, $sqlColumnAlias, $discrColumnName, false, $discrColumnType);
754
            }
755
756
            // Add foreign key columns of class and also parent classes
757 456
            foreach ($class->getDeclaredPropertiesIterator() as $association) {
758 456
                if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())
759 456
                    || ( ! $addMetaColumns && ! $association->isPrimaryKey())) {
760 455
                    continue;
761
                }
762
763 261
                $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
764
765 261
                foreach ($association->getJoinColumns() as $joinColumn) {
766
                    /** @var JoinColumnMetadata $joinColumn */
767 261
                    $columnName           = $joinColumn->getColumnName();
768 261
                    $referencedColumnName = $joinColumn->getReferencedColumnName();
769 261
                    $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
770 261
                    $columnAlias          = $this->getSQLColumnAlias();
771 261
                    $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
772
773 261
                    if (! $joinColumn->getType()) {
774 19
                        $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
775
                    }
776
777 261
                    $sqlSelectExpressions[] = sprintf(
778 261
                        '%s.%s AS %s',
779 261
                        $sqlTableAlias,
780 261
                        $quotedColumnName,
781 261
                        $columnAlias
782
                    );
783
784 261
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
785
                }
786
            }
787
788
            // Add foreign key columns to SQL, if necessary
789 456
            if (! $addMetaColumns) {
790 176
                continue;
791
            }
792
793
            // Add foreign key columns of subclasses
794 325
            foreach ($class->getSubClasses() as $subClassName) {
795 36
                $subClass = $this->em->getClassMetadata($subClassName);
796
797 36
                foreach ($subClass->getDeclaredPropertiesIterator() as $association) {
798
                    // Skip if association is inherited
799 36
                    if ($subClass->isInheritedProperty($association->getName())) {
800 36
                        continue;
801
                    }
802
803 26
                    if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())) {
804 25
                        continue;
805
                    }
806
807 14
                    $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
808
809 14
                    foreach ($association->getJoinColumns() as $joinColumn) {
810
                        /** @var JoinColumnMetadata $joinColumn */
811 14
                        $columnName           = $joinColumn->getColumnName();
812 14
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
813 14
                        $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
814 14
                        $columnAlias          = $this->getSQLColumnAlias();
815 14
                        $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
816
817 14
                        if (! $joinColumn->getType()) {
818 1
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
819
                        }
820
821 14
                        $sqlSelectExpressions[] = sprintf(
822 14
                            '%s.%s AS %s',
823 14
                            $sqlTableAlias,
824 14
                            $quotedColumnName,
825 14
                            $columnAlias
826
                        );
827
828 325
                        $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
829
                    }
830
                }
831
            }
832
        }
833
834 606
        $sql .= implode(', ', $sqlSelectExpressions);
835
836 606
        return $sql;
837
    }
838
839
    /**
840
     * {@inheritdoc}
841
     */
842 608
    public function walkFromClause($fromClause)
843
    {
844 608
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
845 608
        $sqlParts               = [];
846
847 608
        foreach ($identificationVarDecls as $identificationVariableDecl) {
848 608
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
849
        }
850
851 608
        return ' FROM ' . implode(', ', $sqlParts);
852
    }
853
854
    /**
855
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
856
     *
857
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
858
     *
859
     * @return string
860
     */
861 609
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
862
    {
863 609
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
864
865 609
        if ($identificationVariableDecl->indexBy) {
866 2
            $this->walkIndexBy($identificationVariableDecl->indexBy);
867
        }
868
869 609
        foreach ($identificationVariableDecl->joins as $join) {
870 233
            $sql .= $this->walkJoin($join);
871
        }
872
873 609
        return $sql;
874
    }
875
876
    /**
877
     * Walks down a IndexBy AST node.
878
     *
879
     * @param AST\IndexBy $indexBy
880
     */
881 3
    public function walkIndexBy($indexBy)
882
    {
883 3
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
884 3
        $alias          = $pathExpression->identificationVariable;
885 3
        $field          = $pathExpression->field;
886
887 3
        if (isset($this->scalarFields[$alias][$field])) {
888
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
889
890
            return;
891
        }
892
893 3
        $this->rsm->addIndexBy($alias, $field);
894 3
    }
895
896
    /**
897
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
898
     *
899
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
900
     *
901
     * @return string
902
     */
903 609
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
904
    {
905 609
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
906
    }
907
908
    /**
909
     * Generate appropriate SQL for RangeVariableDeclaration AST node
910
     *
911
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
912
     */
913 609
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
914
    {
915 609
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
916 609
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
917
918 609
        if ($rangeVariableDeclaration->isRoot) {
919 609
            $this->rootAliases[] = $dqlAlias;
920
        }
921
922 609
        $tableName  = $class->table->getQuotedQualifiedName($this->platform);
923 609
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
924
925 609
        $sql = $this->platform->appendLockHint(
926 609
            $tableName . ' ' . $tableAlias,
927 609
            $this->query->getHint(Query::HINT_LOCK_MODE)
928
        );
929
930 609
        if ($class->inheritanceType !== InheritanceType::JOINED) {
931 514
            return $sql;
932
        }
933
934 103
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
935
936 103
        if (! $buildNestedJoins) {
937 101
            return $sql . $classTableInheritanceJoins;
938
        }
939
940 3
        return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
941
    }
942
943
    /**
944
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
945
     *
946
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
947
     * @param int                            $joinType
948
     * @param AST\ConditionalExpression      $condExpr
949
     *
950
     * @return string
951
     *
952
     * @throws QueryException
953
     */
954 216
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
955
    {
956 216
        $sql = '';
957
958 216
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
959 216
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
960 216
        $indexBy                   = $joinAssociationDeclaration->indexBy;
961
962 216
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
963 216
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
964 216
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
965 216
        $targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform);
966
967 216
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
968 216
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
969
970
        // Ensure we got the owning side, since it has all mapping info
971 216
        $owningAssociation = ! $association->isOwningSide()
972 109
            ? $targetClass->getProperty($association->getMappedBy())
973 216
            : $association
974
        ;
975
976 216
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true &&
977 216
            (! $this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
978
            if ($association instanceof ToManyAssociationMetadata) {
979
                throw QueryException::iterateWithFetchJoinNotAllowed($owningAssociation);
980
            }
981
        }
982
983 216
        $targetTableJoin = null;
984
985
        // This condition is not checking ManyToOneAssociationMetadata, because by definition it cannot
986
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
987
        // The owning side is necessary at this point because only it contains the JoinColumn information.
988 216
        if ($owningAssociation instanceof ToOneAssociationMetadata) {
989 169
            $conditions = [];
990
991 169
            foreach ($owningAssociation->getJoinColumns() as $joinColumn) {
992 169
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
993 169
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
994
995 169
                if ($association->isOwningSide()) {
996 98
                    $conditions[] = sprintf(
997 98
                        '%s.%s = %s.%s',
998 98
                        $sourceTableAlias,
999 98
                        $quotedColumnName,
1000 98
                        $targetTableAlias,
1001 98
                        $quotedReferencedColumnName
1002
                    );
1003
1004 98
                    continue;
1005
                }
1006
1007 100
                $conditions[] = sprintf(
1008 100
                    '%s.%s = %s.%s',
1009 100
                    $sourceTableAlias,
1010 100
                    $quotedReferencedColumnName,
1011 100
                    $targetTableAlias,
1012 100
                    $quotedColumnName
1013
                );
1014
            }
1015
1016
            // Apply remaining inheritance restrictions
1017 169
            $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1018
1019 169
            if ($discrSql) {
1020 3
                $conditions[] = $discrSql;
1021
            }
1022
1023
            // Apply the filters
1024 169
            $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1025
1026 169
            if ($filterExpr) {
1027 1
                $conditions[] = $filterExpr;
1028
            }
1029
1030
            $targetTableJoin = [
1031 169
                'table' => $targetTableName . ' ' . $targetTableAlias,
1032 169
                'condition' => implode(' AND ', $conditions),
1033
            ];
1034 57
        } elseif ($owningAssociation instanceof ManyToManyAssociationMetadata) {
1035
            // Join relation table
1036 57
            $joinTable      = $owningAssociation->getJoinTable();
1037 57
            $joinTableName  = $joinTable->getQuotedQualifiedName($this->platform);
1038 57
            $joinTableAlias = $this->getSQLTableAlias($joinTable->getName(), $joinedDqlAlias);
1039
1040 57
            $conditions  = [];
1041 57
            $joinColumns = $association->isOwningSide()
1042 48
                ? $joinTable->getJoinColumns()
1043 57
                : $joinTable->getInverseJoinColumns()
1044
            ;
1045
1046 57
            foreach ($joinColumns as $joinColumn) {
1047 57
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
1048 57
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
1049
1050 57
                $conditions[] = sprintf(
1051 57
                    '%s.%s = %s.%s',
1052 57
                    $sourceTableAlias,
1053 57
                    $quotedReferencedColumnName,
1054 57
                    $joinTableAlias,
1055 57
                    $quotedColumnName
1056
                );
1057
            }
1058
1059 57
            $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
1060
1061
            // Join target table
1062 57
            $sql .= ($joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
1063
1064 57
            $conditions  = [];
1065 57
            $joinColumns = $association->isOwningSide()
1066 48
                ? $joinTable->getInverseJoinColumns()
1067 57
                : $joinTable->getJoinColumns()
1068
            ;
1069
1070 57
            foreach ($joinColumns as $joinColumn) {
1071 57
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
1072 57
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
1073
1074 57
                $conditions[] = sprintf(
1075 57
                    '%s.%s = %s.%s',
1076 57
                    $targetTableAlias,
1077 57
                    $quotedReferencedColumnName,
1078 57
                    $joinTableAlias,
1079 57
                    $quotedColumnName
1080
                );
1081
            }
1082
1083
            // Apply remaining inheritance restrictions
1084 57
            $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1085
1086 57
            if ($discrSql) {
1087 1
                $conditions[] = $discrSql;
1088
            }
1089
1090
            // Apply the filters
1091 57
            $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1092
1093 57
            if ($filterExpr) {
1094 1
                $conditions[] = $filterExpr;
1095
            }
1096
1097
            $targetTableJoin = [
1098 57
                'table' => $targetTableName . ' ' . $targetTableAlias,
1099 57
                'condition' => implode(' AND ', $conditions),
1100
            ];
1101
        } else {
1102
            throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1103
        }
1104
1105
        // Handle WITH clause
1106 216
        $withCondition = ($condExpr === null) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1107
1108 216
        if ($targetClass->inheritanceType === InheritanceType::JOINED) {
1109 10
            $ctiJoins = $this->generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1110
1111
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1112 10
            if ($withCondition) {
1113 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1114
            } else {
1115 10
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1116
            }
1117
        } else {
1118 206
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1119
        }
1120
1121 216
        if ($withCondition) {
1122 6
            $sql .= ' AND ' . $withCondition;
1123
        }
1124
1125
        // Apply the indexes
1126 216
        if ($indexBy) {
1127
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1128 2
            $this->walkIndexBy($indexBy);
1129 214
        } elseif ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy()) {
1130 3
            $this->rsm->addIndexBy($joinedDqlAlias, $association->getIndexedBy());
1131
        }
1132
1133 216
        return $sql;
1134
    }
1135
1136
    /**
1137
     * {@inheritdoc}
1138
     */
1139 130
    public function walkFunction($function)
1140
    {
1141 130
        return $function->getSql($this);
1142
    }
1143
1144
    /**
1145
     * {@inheritdoc}
1146
     */
1147 147
    public function walkOrderByClause($orderByClause)
1148
    {
1149 147
        $orderByItems           = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1150 147
        $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems();
1151
1152 147
        if ($collectionOrderByItems !== '') {
1153
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1154
        }
1155
1156 147
        return ' ORDER BY ' . implode(', ', $orderByItems);
1157
    }
1158
1159
    /**
1160
     * {@inheritdoc}
1161
     */
1162 165
    public function walkOrderByItem($orderByItem)
1163
    {
1164 165
        $type = strtoupper($orderByItem->type);
1165 165
        $expr = $orderByItem->expression;
1166 165
        $sql  = ($expr instanceof AST\Node)
1167 156
            ? $expr->dispatch($this)
1168 165
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1169
1170 165
        $this->orderedColumnsMap[$sql] = $type;
1171
1172 165
        if ($expr instanceof AST\Subselect) {
1173 2
            return '(' . $sql . ') ' . $type;
1174
        }
1175
1176 163
        return $sql . ' ' . $type;
1177
    }
1178
1179
    /**
1180
     * {@inheritdoc}
1181
     */
1182 14
    public function walkHavingClause($havingClause)
1183
    {
1184 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1185
    }
1186
1187
    /**
1188
     * {@inheritdoc}
1189
     */
1190 233
    public function walkJoin($join)
1191
    {
1192 233
        $joinType        = $join->joinType;
1193 233
        $joinDeclaration = $join->joinAssociationDeclaration;
1194
1195 233
        $sql = ($joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER)
1196 49
            ? ' LEFT JOIN '
1197 233
            : ' INNER JOIN ';
1198
1199
        switch (true) {
1200 233
            case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
1201 18
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1202 18
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1203 18
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1204 18
                $conditions = [];
1205
1206 18
                if ($join->conditionalExpression) {
1207 16
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1208
                }
1209
1210 18
                $isUnconditionalJoin = empty($conditions);
1211 18
                $condExprConjunction = ($class->inheritanceType === InheritanceType::JOINED && $joinType !== AST\Join::JOIN_TYPE_LEFT && $joinType !== AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin)
1212 2
                    ? ' AND '
1213 18
                    : ' ON ';
1214
1215 18
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, ! $isUnconditionalJoin);
1216
1217
                // Apply remaining inheritance restrictions
1218 18
                $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1219
1220 18
                if ($discrSql) {
1221 3
                    $conditions[] = $discrSql;
1222
                }
1223
1224
                // Apply the filters
1225 18
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1226
1227 18
                if ($filterExpr) {
1228
                    $conditions[] = $filterExpr;
1229
                }
1230
1231 18
                if ($conditions) {
1232 16
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1233
                }
1234
1235 18
                break;
1236
1237 216
            case ($joinDeclaration instanceof AST\JoinAssociationDeclaration):
1238 216
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1239 216
                break;
1240
        }
1241
1242 233
        return $sql;
1243
    }
1244
1245
    /**
1246
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1247
     *
1248
     * @param AST\CoalesceExpression $coalesceExpression
1249
     *
1250
     * @return string The SQL.
1251
     */
1252 2
    public function walkCoalesceExpression($coalesceExpression)
1253
    {
1254 2
        $sql = 'COALESCE(';
1255
1256 2
        $scalarExpressions = [];
1257
1258 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1259 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1260
        }
1261
1262 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1263
1264 2
        return $sql;
1265
    }
1266
1267
    /**
1268
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1269
     *
1270
     * @param AST\NullIfExpression $nullIfExpression
1271
     *
1272
     * @return string The SQL.
1273
     */
1274 3
    public function walkNullIfExpression($nullIfExpression)
1275
    {
1276 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1277
            ? $this->conn->quote($nullIfExpression->firstExpression)
1278 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1279
1280 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1281
            ? $this->conn->quote($nullIfExpression->secondExpression)
1282 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1283
1284 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1285
    }
1286
1287
    /**
1288
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1289
     *
1290
     * @return string The SQL.
1291
     */
1292 9
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1293
    {
1294 9
        $sql = 'CASE';
1295
1296 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1297 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1298 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1299
        }
1300
1301 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1302
1303 9
        return $sql;
1304
    }
1305
1306
    /**
1307
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1308
     *
1309
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1310
     *
1311
     * @return string The SQL.
1312
     */
1313 5
    public function walkSimpleCaseExpression($simpleCaseExpression)
1314
    {
1315 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1316
1317 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1318 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1319 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1320
        }
1321
1322 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1323
1324 5
        return $sql;
1325
    }
1326
1327
    /**
1328
     * {@inheritdoc}
1329
     */
1330 606
    public function walkSelectExpression($selectExpression)
1331
    {
1332 606
        $sql    = '';
1333 606
        $expr   = $selectExpression->expression;
1334 606
        $hidden = $selectExpression->hiddenAliasResultVariable;
1335
1336
        switch (true) {
1337 606
            case ($expr instanceof AST\PathExpression):
1338 107
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1339
                    throw QueryException::invalidPathExpression($expr);
1340
                }
1341
1342 107
                $fieldName   = $expr->field;
1343 107
                $dqlAlias    = $expr->identificationVariable;
1344 107
                $qComp       = $this->queryComponents[$dqlAlias];
1345 107
                $class       = $qComp['metadata'];
1346 107
                $property    = $class->getProperty($fieldName);
1347 107
                $columnAlias = $this->getSQLColumnAlias();
1348 107
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1349 107
                $col         = sprintf(
1350 107
                    '%s.%s',
1351 107
                    $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1352 107
                    $this->platform->quoteIdentifier($property->getColumnName())
1353
                );
1354
1355 107
                $sql .= sprintf(
1356 107
                    '%s AS %s',
1357 107
                    $property->getType()->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()),
1358 107
                    $columnAlias
1359
                );
1360
1361 107
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1362
1363 107
                if (! $hidden) {
1364 107
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $property->getType());
1365 107
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1366
                }
1367
1368 107
                break;
1369
1370 554
            case ($expr instanceof AST\AggregateExpression):
1371 545
            case ($expr instanceof AST\Functions\FunctionNode):
1372 493
            case ($expr instanceof AST\SimpleArithmeticExpression):
1373 493
            case ($expr instanceof AST\ArithmeticTerm):
1374 491
            case ($expr instanceof AST\ArithmeticFactor):
1375 490
            case ($expr instanceof AST\ParenthesisExpression):
1376 489
            case ($expr instanceof AST\Literal):
1377 488
            case ($expr instanceof AST\NullIfExpression):
1378 487
            case ($expr instanceof AST\CoalesceExpression):
1379 486
            case ($expr instanceof AST\GeneralCaseExpression):
1380 482
            case ($expr instanceof AST\SimpleCaseExpression):
1381 118
                $columnAlias = $this->getSQLColumnAlias();
1382 118
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1383
1384 118
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1385
1386 118
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1387
1388 118
                if (! $hidden) {
1389
                    // Conceptually we could resolve field type here by traverse through AST to retrieve field type,
1390
                    // but this is not a feasible solution; assume 'string'.
1391 118
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getType('string'));
1392
                }
1393 118
                break;
1394
1395 481
            case ($expr instanceof AST\Subselect):
1396 16
                $columnAlias = $this->getSQLColumnAlias();
1397 16
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1398
1399 16
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1400
1401 16
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1402
1403 16
                if (! $hidden) {
1404
                    // We cannot resolve field type here; assume 'string'.
1405 14
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getType('string'));
1406
                }
1407 16
                break;
1408
1409 476
            case ($expr instanceof AST\NewObjectExpression):
1410 20
                $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable);
1411 20
                break;
1412
1413
            default:
1414
                // IdentificationVariable or PartialObjectExpression
1415 456
                if ($expr instanceof AST\PartialObjectExpression) {
1416 14
                    $dqlAlias        = $expr->identificationVariable;
1417 14
                    $partialFieldSet = $expr->partialFieldSet;
1418
                } else {
1419 452
                    $dqlAlias        = $expr;
1420 452
                    $partialFieldSet = [];
1421
                }
1422
1423 456
                $queryComp   = $this->queryComponents[$dqlAlias];
1424 456
                $class       = $queryComp['metadata'];
1425 456
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1426
1427 456
                if (! isset($this->selectedClasses[$dqlAlias])) {
1428 456
                    $this->selectedClasses[$dqlAlias] = [
1429 456
                        'class'       => $class,
1430 456
                        'dqlAlias'    => $dqlAlias,
1431 456
                        'resultAlias' => $resultAlias,
1432
                    ];
1433
                }
1434
1435 456
                $sqlParts = [];
1436
1437
                // Select all fields from the queried class
1438 456
                foreach ($class->getDeclaredPropertiesIterator() as $fieldName => $property) {
1439 456
                    if (! ($property instanceof FieldMetadata)) {
1440 411
                        continue;
1441
                    }
1442
1443 455
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet, true)) {
1444 12
                        continue;
1445
                    }
1446
1447 454
                    $columnAlias = $this->getSQLColumnAlias();
1448 454
                    $col         = sprintf(
1449 454
                        '%s.%s',
1450 454
                        $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1451 454
                        $this->platform->quoteIdentifier($property->getColumnName())
1452
                    );
1453
1454 454
                    $sqlParts[] = sprintf(
1455 454
                        '%s AS %s',
1456 454
                        $property->getType()->convertToPHPValueSQL($col, $this->platform),
1457 454
                        $columnAlias
1458
                    );
1459
1460 454
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1461
1462 454
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->getClassName());
1463
                }
1464
1465
                // Add any additional fields of subclasses (excluding inherited fields)
1466
                // 1) on Single Table Inheritance: always, since its marginal overhead
1467
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1468
                //    since it requires outer joining subtables.
1469 456
                if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1470 366
                    foreach ($class->getSubClasses() as $subClassName) {
1471 48
                        $subClass = $this->em->getClassMetadata($subClassName);
1472
1473 48
                        foreach ($subClass->getDeclaredPropertiesIterator() as $fieldName => $property) {
1474 48
                            if (! ($property instanceof FieldMetadata)) {
1475 39
                                continue;
1476
                            }
1477
1478 48
                            if ($subClass->isInheritedProperty($fieldName) || ($partialFieldSet && ! in_array($fieldName, $partialFieldSet, true))) {
1479 48
                                continue;
1480
                            }
1481
1482 35
                            $columnAlias = $this->getSQLColumnAlias();
1483 35
                            $col         = sprintf(
1484 35
                                '%s.%s',
1485 35
                                $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1486 35
                                $this->platform->quoteIdentifier($property->getColumnName())
1487
                            );
1488
1489 35
                            $sqlParts[] = sprintf(
1490 35
                                '%s AS %s',
1491 35
                                $property->getType()->convertToPHPValueSQL($col, $this->platform),
1492 35
                                $columnAlias
1493
                            );
1494
1495 35
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1496
1497 48
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1498
                        }
1499
                    }
1500
                }
1501
1502 456
                $sql .= implode(', ', $sqlParts);
1503
        }
1504
1505 606
        return $sql;
1506
    }
1507
1508
    /**
1509
     * {@inheritdoc}
1510
     */
1511
    public function walkQuantifiedExpression($qExpr)
1512
    {
1513
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1514
    }
1515
1516
    /**
1517
     * {@inheritdoc}
1518
     */
1519 34
    public function walkSubselect($subselect)
1520
    {
1521 34
        $useAliasesBefore  = $this->useSqlTableAliases;
1522 34
        $rootAliasesBefore = $this->rootAliases;
1523
1524 34
        $this->rootAliases        = []; // reset the rootAliases for the subselect
1525 34
        $this->useSqlTableAliases = true;
1526
1527 34
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1528 34
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1529 34
        $sql .= $this->walkWhereClause($subselect->whereClause);
1530
1531 34
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1532 34
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1533 34
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1534
1535 34
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1536 34
        $this->useSqlTableAliases = $useAliasesBefore;
1537
1538 34
        return $sql;
1539
    }
1540
1541
    /**
1542
     * {@inheritdoc}
1543
     */
1544 34
    public function walkSubselectFromClause($subselectFromClause)
1545
    {
1546 34
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1547 34
        $sqlParts               = [];
1548
1549 34
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1550 34
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1551
        }
1552
1553 34
        return ' FROM ' . implode(', ', $sqlParts);
1554
    }
1555
1556
    /**
1557
     * {@inheritdoc}
1558
     */
1559 34
    public function walkSimpleSelectClause($simpleSelectClause)
1560
    {
1561 34
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1562 34
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1563
    }
1564
1565
    /**
1566
     * @return string.
1567
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment string. at position 0 could not be parsed: Unknown type name 'string.' at position 0 in string..
Loading history...
1568 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1569
    {
1570 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1571
    }
1572
1573
    /**
1574
     * @param AST\NewObjectExpression $newObjectExpression
1575
     * @param string|null             $newObjectResultAlias
1576
     * @return string The SQL.
1577
     */
1578 20
    public function walkNewObject($newObjectExpression, $newObjectResultAlias = null)
1579
    {
1580 20
        $sqlSelectExpressions = [];
1581 20
        $objIndex             = $newObjectResultAlias ?: $this->newObjectCounter++;
1582
1583 20
        foreach ($newObjectExpression->args as $argIndex => $e) {
1584 20
            $resultAlias = $this->scalarResultCounter++;
1585 20
            $columnAlias = $this->getSQLColumnAlias();
1586 20
            $fieldType   = Type::getType('string');
1587
1588
            switch (true) {
1589 20
                case ($e instanceof AST\NewObjectExpression):
1590
                    $sqlSelectExpressions[] = $e->dispatch($this);
1591
                    break;
1592
1593 20
                case ($e instanceof AST\Subselect):
1594 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1595 1
                    break;
1596
1597 20
                case ($e instanceof AST\PathExpression):
1598 20
                    $dqlAlias  = $e->identificationVariable;
1599 20
                    $qComp     = $this->queryComponents[$dqlAlias];
1600 20
                    $class     = $qComp['metadata'];
1601 20
                    $fieldType = $class->getProperty($e->field)->getType();
1602
1603 20
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1604 20
                    break;
1605
1606 6
                case ($e instanceof AST\Literal):
1607 1
                    switch ($e->type) {
1608 1
                        case AST\Literal::BOOLEAN:
1609 1
                            $fieldType = Type::getType('boolean');
1610 1
                            break;
1611
1612 1
                        case AST\Literal::NUMERIC:
1613 1
                            $fieldType = Type::getType(is_float($e->value) ? 'float' : 'integer');
1614 1
                            break;
1615
                    }
1616
1617 1
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1618 1
                    break;
1619
1620
                default:
1621 5
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1622 5
                    break;
1623
            }
1624
1625 20
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1626 20
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1627
1628 20
            $this->rsm->newObjectMappings[$columnAlias] = [
1629 20
                'className' => $newObjectExpression->className,
1630 20
                'objIndex'  => $objIndex,
1631 20
                'argIndex'  => $argIndex,
1632
            ];
1633
        }
1634
1635 20
        return implode(', ', $sqlSelectExpressions);
1636
    }
1637
1638
    /**
1639
     * {@inheritdoc}
1640
     */
1641 34
    public function walkSimpleSelectExpression($simpleSelectExpression)
1642
    {
1643 34
        $expr = $simpleSelectExpression->expression;
1644 34
        $sql  = ' ';
1645
1646
        switch (true) {
1647 34
            case ($expr instanceof AST\PathExpression):
1648 9
                $sql .= $this->walkPathExpression($expr);
1649 9
                break;
1650
1651 25
            case ($expr instanceof AST\Subselect):
1652
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1653
1654
                $columnAlias                        = 'sclr' . $this->aliasCounter++;
1655
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1656
1657
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1658
                break;
1659
1660 25
            case ($expr instanceof AST\Functions\FunctionNode):
1661 11
            case ($expr instanceof AST\SimpleArithmeticExpression):
1662 10
            case ($expr instanceof AST\ArithmeticTerm):
1663 9
            case ($expr instanceof AST\ArithmeticFactor):
1664 9
            case ($expr instanceof AST\Literal):
1665 7
            case ($expr instanceof AST\NullIfExpression):
1666 7
            case ($expr instanceof AST\CoalesceExpression):
1667 7
            case ($expr instanceof AST\GeneralCaseExpression):
1668 5
            case ($expr instanceof AST\SimpleCaseExpression):
1669 22
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1670
1671 22
                $columnAlias                        = $this->getSQLColumnAlias();
1672 22
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1673
1674 22
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1675 22
                break;
1676
1677 3
            case ($expr instanceof AST\ParenthesisExpression):
1678 1
                $sql .= $this->walkParenthesisExpression($expr);
1679 1
                break;
1680
1681
            default: // IdentificationVariable
1682 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1683 2
                break;
1684
        }
1685
1686 34
        return $sql;
1687
    }
1688
1689
    /**
1690
     * {@inheritdoc}
1691
     */
1692 76
    public function walkAggregateExpression($aggExpression)
1693
    {
1694 76
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1695 76
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1696
    }
1697
1698
    /**
1699
     * {@inheritdoc}
1700
     */
1701 23
    public function walkGroupByClause($groupByClause)
1702
    {
1703 23
        $sqlParts = [];
1704
1705 23
        foreach ($groupByClause->groupByItems as $groupByItem) {
1706 23
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1707
        }
1708
1709 23
        return ' GROUP BY ' . implode(', ', $sqlParts);
1710
    }
1711
1712
    /**
1713
     * {@inheritdoc}
1714
     */
1715 23
    public function walkGroupByItem($groupByItem)
1716
    {
1717
        // StateFieldPathExpression
1718 23
        if (! is_string($groupByItem)) {
1719 11
            return $this->walkPathExpression($groupByItem);
1720
        }
1721
1722
        // ResultVariable
1723 12
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1724 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1725
1726 2
            if ($resultVariable instanceof AST\PathExpression) {
1727 1
                return $this->walkPathExpression($resultVariable);
1728
            }
1729
1730 1
            if (isset($resultVariable->pathExpression)) {
1731
                return $this->walkPathExpression($resultVariable->pathExpression);
1732
            }
1733
1734 1
            return $this->walkResultVariable($groupByItem);
1735
        }
1736
1737
        // IdentificationVariable
1738
        /** @var ClassMetadata $classMetadata */
1739 10
        $classMetadata = $this->queryComponents[$groupByItem]['metadata'];
1740 10
        $sqlParts      = [];
1741
1742 10
        foreach ($classMetadata->getDeclaredPropertiesIterator() as $property) {
1743
            switch (true) {
1744 10
                case ($property instanceof FieldMetadata):
1745 10
                    $type       = AST\PathExpression::TYPE_STATE_FIELD;
1746 10
                    $item       = new AST\PathExpression($type, $groupByItem, $property->getName());
1747 10
                    $item->type = $type;
1748
1749 10
                    $sqlParts[] = $this->walkPathExpression($item);
1750 10
                    break;
1751
1752 10
                case ($property instanceof ToOneAssociationMetadata && $property->isOwningSide()):
1753 7
                    $type       = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1754 7
                    $item       = new AST\PathExpression($type, $groupByItem, $property->getName());
1755 7
                    $item->type = $type;
1756
1757 7
                    $sqlParts[] = $this->walkPathExpression($item);
1758 10
                    break;
1759
            }
1760
        }
1761
1762 10
        return implode(', ', $sqlParts);
1763
    }
1764
1765
    /**
1766
     * {@inheritdoc}
1767
     */
1768 34
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1769
    {
1770 34
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1771 34
        $tableName = $class->getTableName();
1772 34
        $sql       = 'DELETE FROM ' . $class->table->getQuotedQualifiedName($this->platform);
1773
1774 34
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1775
1776 34
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1777
1778 34
        return $sql;
1779
    }
1780
1781
    /**
1782
     * {@inheritdoc}
1783
     */
1784 23
    public function walkUpdateClause($updateClause)
1785
    {
1786 23
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1787 23
        $tableName = $class->getTableName();
1788 23
        $sql       = 'UPDATE ' . $class->table->getQuotedQualifiedName($this->platform);
1789
1790 23
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1791 23
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1792
1793 23
        $sql .= ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1794
1795 23
        return $sql;
1796
    }
1797
1798
    /**
1799
     * {@inheritdoc}
1800
     */
1801 27
    public function walkUpdateItem($updateItem)
1802
    {
1803 27
        $useTableAliasesBefore    = $this->useSqlTableAliases;
1804 27
        $this->useSqlTableAliases = false;
1805
1806 27
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1807 27
        $newValue = $updateItem->newValue;
1808
1809
        switch (true) {
1810 27
            case ($newValue instanceof AST\Node):
1811 26
                $sql .= $newValue->dispatch($this);
1812 26
                break;
1813
1814 1
            case ($newValue === null):
1815 1
                $sql .= 'NULL';
1816 1
                break;
1817
1818
            default:
1819
                $sql .= $this->conn->quote($newValue);
1820
                break;
1821
        }
1822
1823 27
        $this->useSqlTableAliases = $useTableAliasesBefore;
1824
1825 27
        return $sql;
1826
    }
1827
1828
    /**
1829
     * {@inheritdoc}
1830
     */
1831 661
    public function walkWhereClause($whereClause)
1832
    {
1833 661
        $condSql  = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1834 661
        $discrSql = $this->generateDiscriminatorColumnConditionSql($this->rootAliases);
1835
1836 661
        if ($this->em->hasFilters()) {
1837 39
            $filterClauses = [];
1838 39
            foreach ($this->rootAliases as $dqlAlias) {
1839 39
                $class      = $this->queryComponents[$dqlAlias]['metadata'];
1840 39
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1841 39
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1842
1843 39
                if ($filterExpr) {
1844 39
                    $filterClauses[] = $filterExpr;
1845
                }
1846
            }
1847
1848 39
            if ($filterClauses) {
1849 6
                if ($condSql) {
1850 2
                    $condSql = '(' . $condSql . ') AND ';
1851
                }
1852
1853 6
                $condSql .= implode(' AND ', $filterClauses);
1854
            }
1855
        }
1856
1857 661
        if ($condSql) {
1858 323
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1859
        }
1860
1861 415
        if ($discrSql) {
1862 24
            return ' WHERE ' . $discrSql;
1863
        }
1864
1865 395
        return '';
1866
    }
1867
1868
    /**
1869
     * {@inheritdoc}
1870
     */
1871 353
    public function walkConditionalExpression($condExpr)
1872
    {
1873
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1874
        // if only one ConditionalTerm is defined
1875 353
        if (! ($condExpr instanceof AST\ConditionalExpression)) {
1876 298
            return $this->walkConditionalTerm($condExpr);
1877
        }
1878
1879 72
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1880
    }
1881
1882
    /**
1883
     * {@inheritdoc}
1884
     */
1885 353
    public function walkConditionalTerm($condTerm)
1886
    {
1887
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1888
        // if only one ConditionalFactor is defined
1889 353
        if (! ($condTerm instanceof AST\ConditionalTerm)) {
1890 285
            return $this->walkConditionalFactor($condTerm);
1891
        }
1892
1893 90
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1894
    }
1895
1896
    /**
1897
     * {@inheritdoc}
1898
     */
1899 353
    public function walkConditionalFactor($factor)
1900
    {
1901
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1902
        // if only one ConditionalPrimary is defined
1903 353
        return ( ! ($factor instanceof AST\ConditionalFactor))
1904 350
            ? $this->walkConditionalPrimary($factor)
1905 353
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1906
    }
1907
1908
    /**
1909
     * {@inheritdoc}
1910
     */
1911 353
    public function walkConditionalPrimary($primary)
1912
    {
1913 353
        if ($primary->isSimpleConditionalExpression()) {
1914 353
            return $primary->simpleConditionalExpression->dispatch($this);
1915
        }
1916
1917 25
        if ($primary->isConditionalExpression()) {
1918 25
            $condExpr = $primary->conditionalExpression;
1919
1920 25
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
1921
        }
1922
    }
1923
1924
    /**
1925
     * {@inheritdoc}
1926
     */
1927 5
    public function walkExistsExpression($existsExpr)
1928
    {
1929 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1930
1931 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1932
1933 5
        return $sql;
1934
    }
1935
1936
    /**
1937
     * {@inheritdoc}
1938
     */
1939 7
    public function walkCollectionMemberExpression($collMemberExpr)
1940
    {
1941 7
        $sql  = $collMemberExpr->not ? 'NOT ' : '';
1942 7
        $sql .= 'EXISTS (SELECT 1 FROM ';
1943
1944 7
        $entityExpr   = $collMemberExpr->entityExpression;
1945 7
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1946
1947 7
        $fieldName = $collPathExpr->field;
1948 7
        $dqlAlias  = $collPathExpr->identificationVariable;
1949
1950 7
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1951
1952
        switch (true) {
1953
            // InputParameter
1954 7
            case ($entityExpr instanceof AST\InputParameter):
1955 4
                $dqlParamKey = $entityExpr->name;
1956 4
                $entitySql   = '?';
1957 4
                break;
1958
1959
            // SingleValuedAssociationPathExpression | IdentificationVariable
1960 3
            case ($entityExpr instanceof AST\PathExpression):
1961 3
                $entitySql = $this->walkPathExpression($entityExpr);
1962 3
                break;
1963
1964
            default:
1965
                throw new \BadMethodCallException('Not implemented');
1966
        }
1967
1968 7
        $association       = $class->getProperty($fieldName);
1969 7
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
1970 7
        $owningAssociation = $association->isOwningSide()
1971 6
            ? $association
1972 7
            : $targetClass->getProperty($association->getMappedBy())
1973
        ;
1974
1975 7
        if ($association instanceof OneToManyAssociationMetadata) {
1976 1
            $targetTableName  = $targetClass->table->getQuotedQualifiedName($this->platform);
1977 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1978 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1979
1980 1
            $sql .= $targetTableName . ' ' . $targetTableAlias . ' WHERE ';
1981
1982 1
            $sqlParts = [];
1983
1984 1
            foreach ($owningAssociation->getJoinColumns() as $joinColumn) {
1985 1
                $sqlParts[] = sprintf(
1986 1
                    '%s.%s = %s.%s',
1987 1
                    $sourceTableAlias,
1988 1
                    $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName()),
1989 1
                    $targetTableAlias,
1990 1
                    $this->platform->quoteIdentifier($joinColumn->getColumnName())
1991
                );
1992
            }
1993
1994 1
            foreach ($targetClass->getIdentifierColumns($this->em) as $targetColumn) {
1995 1
                $quotedTargetColumnName = $this->platform->quoteIdentifier($targetColumn->getColumnName());
1996
1997 1
                if (isset($dqlParamKey)) {
1998 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1999
                }
2000
2001 1
                $sqlParts[] = $targetTableAlias . '.' . $quotedTargetColumnName . ' = ' . $entitySql;
2002
            }
2003
2004 1
            $sql .= implode(' AND ', $sqlParts);
2005
        } else { // many-to-many
2006
            // SQL table aliases
2007 6
            $joinTable        = $owningAssociation->getJoinTable();
2008 6
            $joinTableName    = $joinTable->getQuotedQualifiedName($this->platform);
2009 6
            $joinTableAlias   = $this->getSQLTableAlias($joinTable->getName());
2010 6
            $targetTableName  = $targetClass->table->getQuotedQualifiedName($this->platform);
2011 6
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
2012 6
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
2013
2014
            // join to target table
2015 6
            $sql .= $joinTableName . ' ' . $joinTableAlias . ' INNER JOIN ' . $targetTableName . ' ' . $targetTableAlias . ' ON ';
2016
2017
            // join conditions
2018 6
            $joinSqlParts = [];
2019 6
            $joinColumns  = $association->isOwningSide()
2020 6
                ? $joinTable->getInverseJoinColumns()
2021 6
                : $joinTable->getJoinColumns()
2022
            ;
2023
2024 6
            foreach ($joinColumns as $joinColumn) {
2025 6
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
2026 6
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
2027
2028 6
                $joinSqlParts[] = sprintf(
2029 6
                    '%s.%s = %s.%s',
2030 6
                    $joinTableAlias,
2031 6
                    $quotedColumnName,
2032 6
                    $targetTableAlias,
2033 6
                    $quotedReferencedColumnName
2034
                );
2035
            }
2036
2037 6
            $sql .= implode(' AND ', $joinSqlParts);
2038 6
            $sql .= ' WHERE ';
2039
2040 6
            $sqlParts    = [];
2041 6
            $joinColumns = $association->isOwningSide()
2042 6
                ? $joinTable->getJoinColumns()
2043 6
                : $joinTable->getInverseJoinColumns()
2044
            ;
2045
2046 6
            foreach ($joinColumns as $joinColumn) {
2047 6
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
2048 6
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
2049
2050 6
                $sqlParts[] = sprintf(
2051 6
                    '%s.%s = %s.%s',
2052 6
                    $joinTableAlias,
2053 6
                    $quotedColumnName,
2054 6
                    $sourceTableAlias,
2055 6
                    $quotedReferencedColumnName
2056
                );
2057
            }
2058
2059 6
            foreach ($targetClass->getIdentifierColumns($this->em) as $targetColumn) {
2060 6
                $quotedTargetColumnName = $this->platform->quoteIdentifier($targetColumn->getColumnName());
2061
2062 6
                if (isset($dqlParamKey)) {
2063 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
2064
                }
2065
2066 6
                $sqlParts[] = $targetTableAlias . '.' . $quotedTargetColumnName . ' = ' . $entitySql;
2067
            }
2068
2069 6
            $sql .= implode(' AND ', $sqlParts);
2070
        }
2071
2072 7
        return $sql . ')';
2073
    }
2074
2075
    /**
2076
     * {@inheritdoc}
2077
     */
2078 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
2079
    {
2080 3
        $sizeFunc                           = new AST\Functions\SizeFunction('size');
2081 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
2082
2083 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
2084
    }
2085
2086
    /**
2087
     * {@inheritdoc}
2088
     */
2089 11
    public function walkNullComparisonExpression($nullCompExpr)
2090
    {
2091 11
        $expression = $nullCompExpr->expression;
2092 11
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2093
2094
        // Handle ResultVariable
2095 11
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2096 2
            return $this->walkResultVariable($expression) . $comparison;
2097
        }
2098
2099
        // Handle InputParameter mapping inclusion to ParserResult
2100 9
        if ($expression instanceof AST\InputParameter) {
2101
            return $this->walkInputParameter($expression) . $comparison;
2102
        }
2103
2104 9
        return $expression->dispatch($this) . $comparison;
2105
    }
2106
2107
    /**
2108
     * {@inheritdoc}
2109
     */
2110 83
    public function walkInExpression($inExpr)
2111
    {
2112 83
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2113
2114 83
        $sql .= ($inExpr->subselect)
2115 7
            ? $this->walkSubselect($inExpr->subselect)
2116 83
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2117
2118 83
        $sql .= ')';
2119
2120 83
        return $sql;
2121
    }
2122
2123
    /**
2124
     * {@inheritdoc}
2125
     * @throws \Doctrine\ORM\Query\QueryException
2126
     */
2127 13
    public function walkInstanceOfExpression($instanceOfExpr)
2128
    {
2129 13
        $dqlAlias         = $instanceOfExpr->identificationVariable;
2130 13
        $class            = $this->queryComponents[$dqlAlias]['metadata'];
2131 13
        $discrClass       = $this->em->getClassMetadata($class->getRootClassName());
2132 13
        $discrColumn      = $class->discriminatorColumn;
2133 13
        $discrColumnType  = $discrColumn->getType();
2134 13
        $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
2135 13
        $sqlTableAlias    = $this->useSqlTableAliases
2136 13
            ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
2137 13
            : '';
2138
2139 13
        return sprintf(
2140 13
            '%s %sIN %s',
2141 13
            $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
2142 13
            ($instanceOfExpr->not ? 'NOT ' : ''),
2143 13
            $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr)
2144
        );
2145
    }
2146
2147
    /**
2148
     * {@inheritdoc}
2149
     */
2150 76
    public function walkInParameter($inParam)
2151
    {
2152 76
        return $inParam instanceof AST\InputParameter
2153 69
            ? $this->walkInputParameter($inParam)
2154 76
            : $this->walkLiteral($inParam);
2155
    }
2156
2157
    /**
2158
     * {@inheritdoc}
2159
     */
2160 154
    public function walkLiteral($literal)
2161
    {
2162 154
        switch ($literal->type) {
2163 154
            case AST\Literal::STRING:
2164 50
                return $this->conn->quote($literal->value);
2165
2166 117
            case AST\Literal::BOOLEAN:
2167 8
                return $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true');
2168
2169 110
            case AST\Literal::NUMERIC:
2170 110
                return $literal->value;
2171
2172
            default:
2173
                throw QueryException::invalidLiteral($literal);
2174
        }
2175
    }
2176
2177
    /**
2178
     * {@inheritdoc}
2179
     */
2180 6
    public function walkBetweenExpression($betweenExpr)
2181
    {
2182 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2183
2184 6
        if ($betweenExpr->not) {
2185 2
            $sql .= ' NOT';
2186
        }
2187
2188 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2189 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2190
2191 6
        return $sql;
2192
    }
2193
2194
    /**
2195
     * {@inheritdoc}
2196
     */
2197 9
    public function walkLikeExpression($likeExpr)
2198
    {
2199 9
        $stringExpr = $likeExpr->stringExpression;
2200 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2201 1
            ? $this->walkResultVariable($stringExpr)
2202 9
            : $stringExpr->dispatch($this);
2203
2204 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2205
2206 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2207 3
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2208 7
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2209 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2210 7
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2211 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2212
        } else {
2213 7
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2214
        }
2215
2216 9
        if ($likeExpr->escapeChar) {
2217 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2218
        }
2219
2220 9
        return $sql;
2221
    }
2222
2223
    /**
2224
     * {@inheritdoc}
2225
     */
2226 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2227
    {
2228 5
        return $this->walkPathExpression($stateFieldPathExpression);
2229
    }
2230
2231
    /**
2232
     * {@inheritdoc}
2233
     */
2234 247
    public function walkComparisonExpression($compExpr)
2235
    {
2236 247
        $leftExpr  = $compExpr->leftExpression;
2237 247
        $rightExpr = $compExpr->rightExpression;
2238 247
        $sql       = '';
2239
2240 247
        $sql .= ($leftExpr instanceof AST\Node)
2241 247
            ? $leftExpr->dispatch($this)
2242 247
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2243
2244 247
        $sql .= ' ' . $compExpr->operator . ' ';
2245
2246 247
        $sql .= ($rightExpr instanceof AST\Node)
2247 245
            ? $rightExpr->dispatch($this)
2248 247
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2249
2250 247
        return $sql;
2251
    }
2252
2253
    /**
2254
     * {@inheritdoc}
2255
     */
2256 206
    public function walkInputParameter($inputParam)
2257
    {
2258 206
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2259
2260 206
        $parameter = $this->query->getParameter($inputParam->name);
2261
2262 206
        if ($parameter) {
2263 131
            $type = $parameter->getType();
2264
2265 131
            if (Type::hasType($type)) {
2266 49
                return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2267
            }
2268
        }
2269
2270 164
        return '?';
2271
    }
2272
2273
    /**
2274
     * {@inheritdoc}
2275
     */
2276 311
    public function walkArithmeticExpression($arithmeticExpr)
2277
    {
2278 311
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2279 311
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
2280 311
            : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
2281
    }
2282
2283
    /**
2284
     * {@inheritdoc}
2285
     */
2286 372
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2287
    {
2288 372
        if (! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2289 322
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2290
        }
2291
2292 75
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2293
    }
2294
2295
    /**
2296
     * {@inheritdoc}
2297
     */
2298 393
    public function walkArithmeticTerm($term)
2299
    {
2300 393
        if (is_string($term)) {
2301 21
            return (isset($this->queryComponents[$term]))
2302 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2303 21
                : $term;
2304
        }
2305
2306
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2307
        // if only one ArithmeticFactor is defined
2308 392
        if (! ($term instanceof AST\ArithmeticTerm)) {
2309 370
            return $this->walkArithmeticFactor($term);
2310
        }
2311
2312 47
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2313
    }
2314
2315
    /**
2316
     * {@inheritdoc}
2317
     */
2318 393
    public function walkArithmeticFactor($factor)
2319
    {
2320 393
        if (is_string($factor)) {
2321 47
            return (isset($this->queryComponents[$factor]))
2322 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2323 47
                : $factor;
2324
        }
2325
2326
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2327
        // if only one ArithmeticPrimary is defined
2328 393
        if (! ($factor instanceof AST\ArithmeticFactor)) {
2329 392
            return $this->walkArithmeticPrimary($factor);
2330
        }
2331
2332 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2333
2334 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2335
    }
2336
2337
    /**
2338
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2339
     *
2340
     * @param mixed $primary
2341
     *
2342
     * @return string The SQL.
2343
     */
2344 393
    public function walkArithmeticPrimary($primary)
2345
    {
2346 393
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2347
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2348
        }
2349
2350 393
        if ($primary instanceof AST\Node) {
2351 393
            return $primary->dispatch($this);
2352
        }
2353
2354
        return $this->walkEntityIdentificationVariable($primary);
2355
    }
2356
2357
    /**
2358
     * {@inheritdoc}
2359
     */
2360 21
    public function walkStringPrimary($stringPrimary)
2361
    {
2362 21
        return (is_string($stringPrimary))
2363
            ? $this->conn->quote($stringPrimary)
2364 21
            : $stringPrimary->dispatch($this);
2365
    }
2366
2367
    /**
2368
     * {@inheritdoc}
2369
     */
2370 32
    public function walkResultVariable($resultVariable)
2371
    {
2372 32
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2373
2374 32
        if (is_array($resultAlias)) {
2375 1
            return implode(', ', $resultAlias);
2376
        }
2377
2378 31
        return $resultAlias;
2379
    }
2380
2381
    /**
2382
     *
2383
     * @return string The list in parentheses of valid child discriminators from the given class
2384
     *
2385
     * @throws QueryException
2386
     */
2387 13
    private function getChildDiscriminatorsFromClassMetadata(ClassMetadata $rootClass, AST\InstanceOfExpression $instanceOfExpr) : string
2388
    {
2389 13
        $sqlParameterList = [];
2390 13
        $discriminators   = [];
2391
2392 13
        foreach ($instanceOfExpr->value as $parameter) {
2393 13
            if ($parameter instanceof AST\InputParameter) {
2394 4
                $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
2395
2396 4
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2397
2398 4
                continue;
2399
            }
2400
2401
            // Get name from ClassMetadata to resolve aliases.
2402 9
            $entityClass     = $this->em->getClassMetadata($parameter);
2403 9
            $entityClassName = $entityClass->getClassName();
2404
2405 9
            if ($entityClassName !== $rootClass->getClassName()) {
2406 6
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2407
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2408
                }
2409
            }
2410
2411 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2412
        }
2413
2414 13
        foreach (array_keys($discriminators) as $discriminator) {
2415 9
            $sqlParameterList[] = $this->conn->quote($discriminator);
2416
        }
2417
2418 13
        return '(' . implode(', ', $sqlParameterList) . ')';
2419
    }
2420
}
2421