Passed
Pull Request — master (#6709)
by Sergey
12:37
created

SqlWalker::walkStringPrimary()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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