Completed
Pull Request — master (#7915)
by
unknown
62:18
created

SqlWalker::walkNewObject()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 58
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 10.2918

Importance

Changes 0
Metric Value
cc 10
eloc 40
c 0
b 0
f 0
nc 8
nop 2
dl 0
loc 58
rs 7.6666
ccs 30
cts 35
cp 0.8571
crap 10.2918

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use BadMethodCallException;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\LockMode;
10
use Doctrine\DBAL\Platforms\AbstractPlatform;
11
use Doctrine\DBAL\Types\Type;
12
use Doctrine\ORM\AbstractQuery;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\Mapping\ClassMetadata;
15
use Doctrine\ORM\Mapping\FieldMetadata;
16
use Doctrine\ORM\Mapping\InheritanceType;
17
use Doctrine\ORM\Mapping\JoinColumnMetadata;
18
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
19
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
20
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
21
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
22
use Doctrine\ORM\OptimisticLockException;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
25
use Doctrine\ORM\Utility\PersisterHelper;
26
use function array_diff;
27
use function array_filter;
28
use function array_keys;
29
use function array_map;
30
use function array_merge;
31
use function count;
32
use function implode;
33
use function in_array;
34
use function is_array;
35
use function is_float;
36
use function is_numeric;
37
use function is_string;
38
use function reset;
39
use function sprintf;
40
use function strtolower;
41
use function strtoupper;
42
use function trim;
43
44
/**
45
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
46
 * the corresponding SQL.
47
 */
48
class SqlWalker implements TreeWalker
49
{
50
    public const HINT_DISTINCT = 'doctrine.distinct';
51
52
    /** @var ResultSetMapping */
53
    private $rsm;
54
55
    /**
56
     * Counter for generating unique column aliases.
57
     *
58
     * @var int
59
     */
60
    private $aliasCounter = 0;
61
62
    /**
63
     * Counter for generating unique table aliases.
64
     *
65
     * @var int
66
     */
67
    private $tableAliasCounter = 0;
68
69
    /**
70
     * Counter for generating unique scalar result.
71
     *
72
     * @var int
73
     */
74
    private $scalarResultCounter = 1;
75
76
    /**
77
     * Counter for generating unique parameter indexes.
78
     *
79
     * @var int
80
     */
81
    private $sqlParamIndex = 0;
82
83
    /**
84
     * Counter for generating indexes.
85
     *
86
     * @var int
87
     */
88
    private $newObjectCounter = 0;
89
90
    /** @var ParserResult */
91
    private $parserResult;
92
93
    /** @var EntityManagerInterface */
94
    private $em;
95
96
    /** @var Connection */
97
    private $conn;
98
99
    /** @var AbstractQuery */
100
    private $query;
101
102
    /** @var string[] */
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 AbstractPlatform
159
     */
160
    private $platform;
161
162
    /**
163
     * {@inheritDoc}
164
     */
165 717
    public function __construct(AbstractQuery $query, ParserResult $parserResult, array $queryComponents)
166
    {
167 717
        $this->query           = $query;
168 717
        $this->parserResult    = $parserResult;
169 717
        $this->queryComponents = $queryComponents;
170 717
        $this->rsm             = $parserResult->getResultSetMapping();
171 717
        $this->em              = $query->getEntityManager();
172 717
        $this->conn            = $this->em->getConnection();
173 717
        $this->platform        = $this->conn->getDatabasePlatform();
174 717
    }
175
176
    /**
177
     * Gets the Query instance used by the walker.
178
     *
179
     * @return Query
180
     */
181
    public function getQuery()
182
    {
183
        return $this->query;
184
    }
185
186
    /**
187
     * Gets the Connection used by the walker.
188
     *
189
     * @return Connection
190
     */
191 51
    public function getConnection()
192
    {
193 51
        return $this->conn;
194
    }
195
196
    /**
197
     * Gets the EntityManager used by the walker.
198
     *
199
     * @return 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 711
    public function getExecutor($AST)
244
    {
245
        switch (true) {
246 711
            case $AST instanceof AST\DeleteStatement:
247 39
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
248
249 39
                return $primaryClass->inheritanceType === InheritanceType::JOINED
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
250 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
251 39
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
252 676
            case $AST instanceof AST\UpdateStatement:
253 28
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
254
255 28
                return $primaryClass->inheritanceType === InheritanceType::JOINED
256 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
257 28
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
258
            default:
259 652
                return new Exec\SingleSelectExecutor($AST, $this);
260
        }
261
    }
262
263
    /**
264
     * Generates a unique, short SQL table alias.
265
     *
266
     * @param string $tableName Table name
267
     * @param string $dqlAlias  The DQL alias.
268
     *
269
     * @return string Generated table alias.
270
     */
271 663
    public function getSQLTableAlias($tableName, $dqlAlias = '')
272
    {
273 663
        $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
274
275 663
        if (! isset($this->tableAliasMap[$tableName])) {
276 663
            $this->tableAliasMap[$tableName] = 't' . $this->tableAliasCounter++;
277
        }
278
279 663
        return $this->tableAliasMap[$tableName];
280
    }
281
282
    /**
283
     * Forces the SqlWalker to use a specific alias for a table name, rather than
284
     * generating an alias on its own.
285
     *
286
     * @param string $tableName
287
     * @param string $alias
288
     * @param string $dqlAlias
289
     *
290
     * @return string
291
     */
292 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
293
    {
294 65
        $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
295
296 65
        $this->tableAliasMap[$tableName] = $alias;
297
298 65
        return $alias;
299
    }
300
301
    /**
302
     * Gets an SQL column alias for a column name.
303
     *
304
     * @return string
305
     */
306 652
    public function getSQLColumnAlias()
307
    {
308 652
        return $this->platform->getSQLResultCasing('c' . $this->aliasCounter++);
309
    }
310
311
    /**
312
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
313
     * for the given class.
314
     *
315
     * @param ClassMetadata $class    The class for which to generate the joins.
316
     * @param string        $dqlAlias The DQL alias of the class.
317
     *
318
     * @return string The SQL.
319
     */
320 113
    private function generateClassTableInheritanceJoins($class, $dqlAlias)
321
    {
322 113
        $sql = '';
323
324 113
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
325
326
        // INNER JOIN parent class tables
327 113
        $parentClass = $class;
328
329 113
        while (($parentClass = $parentClass->getParent()) !== null) {
330 78
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
331 78
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\ComponentMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\ComponentMetadata such as Doctrine\ORM\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

331
            $tableAlias = $this->getSQLTableAlias($parentClass->/** @scrutinizer ignore-call */ getTableName(), $dqlAlias);
Loading history...
332
333
            // If this is a joined association we must use left joins to preserve the correct result.
334 78
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
335 78
            $sql .= 'JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
336
337 78
            $sqlParts = [];
338
339 78
            foreach ($class->getIdentifierColumns($this->em) as $column) {
340 78
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
341
342 78
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
343
            }
344
345 78
            $filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias);
346
347
            // Add filters on the root class
348 78
            if ($filterSql) {
349 1
                $sqlParts[] = $filterSql;
350
            }
351
352 78
            $sql .= implode(' AND ', $sqlParts);
353
        }
354
355
        // Ignore subclassing inclusion if partial objects is disallowed
356 113
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
357 21
            return $sql;
358
        }
359
360
        // LEFT JOIN child class tables
361 92
        foreach ($class->getSubClasses() as $subClassName) {
362 41
            $subClass   = $this->em->getClassMetadata($subClassName);
363 41
            $tableName  = $subClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
364 41
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

364
            $tableAlias = $this->getSQLTableAlias($subClass->/** @scrutinizer ignore-call */ getTableName(), $dqlAlias);

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

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

Loading history...
365
366 41
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
367
368 41
            $sqlParts = [];
369
370 41
            foreach ($subClass->getIdentifierColumns($this->em) as $column) {
0 ignored issues
show
Bug introduced by
The method getIdentifierColumns() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getIdentifier()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

370
            foreach ($subClass->/** @scrutinizer ignore-call */ getIdentifierColumns($this->em) as $column) {

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

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

Loading history...
371 41
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
372
373 41
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
374
            }
375
376 41
            $sql .= implode(' AND ', $sqlParts);
377
        }
378
379 92
        return $sql;
380
    }
381
382
    /**
383
     * @return string
384
     */
385 646
    private function generateOrderedCollectionOrderByItems()
386
    {
387 646
        $orderedColumns = [];
388
389 646
        foreach ($this->selectedClasses as $selectedClass) {
390 490
            $dqlAlias    = $selectedClass['dqlAlias'];
391 490
            $qComp       = $this->queryComponents[$dqlAlias];
392 490
            $association = $qComp['relation'];
393
394 490
            if (! ($association instanceof ToManyAssociationMetadata)) {
395 490
                continue;
396
            }
397
398 98
            foreach ($association->getOrderBy() as $fieldName => $orientation) {
399 6
                $property      = $qComp['metadata']->getProperty($fieldName);
400 6
                $tableName     = $property->getTableName();
401 6
                $columnName    = $this->platform->quoteIdentifier($property->getColumnName());
402 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
403
404
                // OrderByClause should replace an ordered relation. see - DDC-2475
405 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
406 1
                    continue;
407
                }
408
409 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
410 6
                $orderedColumns[]                        = $orderedColumn . ' ' . $orientation;
411
            }
412
        }
413
414 646
        return implode(', ', $orderedColumns);
415
    }
416
417
    /**
418
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
419
     *
420
     * @param string[] $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
421
     *
422
     * @return string
423
     */
424 706
    private function generateDiscriminatorColumnConditionSQL(array $dqlAliases)
425
    {
426 706
        $sqlParts = [];
427
428 706
        foreach ($dqlAliases as $dqlAlias) {
429 706
            $class = $this->queryComponents[$dqlAlias]['metadata'];
430
431 706
            if ($class->inheritanceType !== InheritanceType::SINGLE_TABLE) {
432 680
                continue;
433
            }
434
435 41
            $conn   = $this->em->getConnection();
436 41
            $values = [];
437
438 41
            if ($class->discriminatorValue !== null) { // discriminators can be 0
439 21
                $values[] = $conn->quote($class->discriminatorValue);
440
            }
441
442 41
            foreach ($class->getSubClasses() as $subclassName) {
443 30
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
0 ignored issues
show
Bug introduced by
Accessing discriminatorValue on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
444
            }
445
446 41
            $discrColumn      = $class->discriminatorColumn;
447 41
            $discrColumnType  = $discrColumn->getType();
448 41
            $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
449 41
            $sqlTableAlias    = $this->useSqlTableAliases
450 36
                ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
451 41
                : '';
452
453 41
            $sqlParts[] = sprintf(
454 41
                '%s IN (%s)',
455 41
                $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
456 41
                implode(', ', $values)
457
            );
458
        }
459
460 706
        $sql = implode(' AND ', $sqlParts);
461
462 706
        return isset($sqlParts[1]) ? '(' . $sql . ')' : $sql;
463
    }
464
465
    /**
466
     * Generates the filter SQL for a given entity and table alias.
467
     *
468
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
469
     * @param string        $targetTableAlias The table alias of the joined/selected table.
470
     *
471
     * @return string The SQL query part to add to a query.
472
     */
473 330
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
474
    {
475 330
        if (! $this->em->hasFilters()) {
476 294
            return '';
477
        }
478
479 41
        switch ($targetEntity->inheritanceType) {
480 41
            case InheritanceType::NONE:
481 31
                break;
482
483 10
            case InheritanceType::JOINED:
484
                // The classes in the inheritance will be added to the query one by one,
485
                // but only the root node is getting filtered
486 6
                if ($targetEntity->getClassName() !== $targetEntity->getRootClassName()) {
487 4
                    return '';
488
                }
489 6
                break;
490
491 4
            case InheritanceType::SINGLE_TABLE:
492
                // With STI the table will only be queried once, make sure that the filters
493
                // are added to the root entity
494 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->getRootClassName());
495 4
                break;
496
497
            default:
498
                //@todo: throw exception?
499
                return '';
500
        }
501
502 41
        $filterClauses = [];
503
504 41
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
505 10
            $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
506
507 10
            if ($filterExpr !== '') {
508 9
                $filterClauses[] = '(' . $filterExpr . ')';
509
            }
510
        }
511
512 41
        return implode(' AND ', $filterClauses);
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518 652
    public function walkSelectStatement(AST\SelectStatement $AST)
519
    {
520 652
        $limit    = $this->query->getMaxResults();
0 ignored issues
show
Bug introduced by
The method getMaxResults() does not exist on Doctrine\ORM\AbstractQuery. Did you maybe mean getResult()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

520
        /** @scrutinizer ignore-call */ 
521
        $limit    = $this->query->getMaxResults();

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

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

Loading history...
521 652
        $offset   = $this->query->getFirstResult();
0 ignored issues
show
Bug introduced by
The method getFirstResult() does not exist on Doctrine\ORM\AbstractQuery. It seems like you code against a sub-type of Doctrine\ORM\AbstractQuery such as Doctrine\ORM\Query. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

521
        /** @scrutinizer ignore-call */ 
522
        $offset   = $this->query->getFirstResult();
Loading history...
522 652
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
523 652
        $sql      = $this->walkSelectClause($AST->selectClause)
524 652
            . $this->walkFromClause($AST->fromClause)
525 650
            . $this->walkWhereClause($AST->whereClause);
526
527 647
        if ($AST->groupByClause) {
528 27
            $sql .= $this->walkGroupByClause($AST->groupByClause);
529
        }
530
531 647
        if ($AST->havingClause) {
532 14
            $sql .= $this->walkHavingClause($AST->havingClause);
533
        }
534
535 647
        if ($AST->orderByClause) {
536 144
            $sql .= $this->walkOrderByClause($AST->orderByClause);
537
        }
538
539 646
        if (! $AST->orderByClause) {
540 533
            $orderBySql = $this->generateOrderedCollectionOrderByItems();
541
542 533
            if ($orderBySql) {
543 6
                $sql .= ' ORDER BY ' . $orderBySql;
544
            }
545
        }
546
547 646
        if ($limit !== null || $offset !== null) {
548 52
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset ?? 0);
549
        }
550
551 646
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
552 641
            return $sql;
553
        }
554
555 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
556 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
557
        }
558
559 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
560 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
561
        }
562
563 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
564
            throw QueryException::invalidLockMode();
565
        }
566
567 1
        foreach ($this->selectedClasses as $selectedClass) {
568 1
            if (! $selectedClass['class']->isVersioned()) {
569 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->getClassName());
570
            }
571
        }
572
573
        return $sql;
574
    }
575
576
    /**
577
     * {@inheritdoc}
578
     */
579 24
    public function walkUpdateStatement(AST\UpdateStatement $AST)
580
    {
581 24
        $this->useSqlTableAliases = false;
582 24
        $this->rsm->isSelect      = false;
583
584 24
        return $this->walkUpdateClause($AST->updateClause)
585 24
            . $this->walkWhereClause($AST->whereClause);
586
    }
587
588
    /**
589
     * {@inheritdoc}
590
     */
591 37
    public function walkDeleteStatement(AST\DeleteStatement $AST)
592
    {
593 37
        $this->useSqlTableAliases = false;
594 37
        $this->rsm->isSelect      = false;
595
596 37
        return $this->walkDeleteClause($AST->deleteClause)
597 37
            . $this->walkWhereClause($AST->whereClause);
598
    }
599
600
    /**
601
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
602
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
603
     *
604
     * @param string $identVariable
605
     *
606
     * @return string
607
     */
608 2
    public function walkEntityIdentificationVariable($identVariable)
609
    {
610 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
611 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
612 2
        $sqlParts   = [];
613
614 2
        foreach ($class->getIdentifierColumns($this->em) as $column) {
615 2
            $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
616
617 2
            $sqlParts[] = $tableAlias . '.' . $quotedColumnName;
618
        }
619
620 2
        return implode(', ', $sqlParts);
621
    }
622
623
    /**
624
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
625
     *
626
     * @param string $identificationVariable
627
     * @param string $fieldName
628
     *
629
     * @return string The SQL.
630
     */
631 435
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
632
    {
633 435
        $class = $this->queryComponents[$identificationVariable]['metadata'];
634
635 435
        if (! $fieldName) {
636
            return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
637
        }
638
639 435
        $property = $class->getProperty($fieldName);
640
641 435
        if ($class->inheritanceType === InheritanceType::JOINED && $class->isInheritedProperty($fieldName)) {
642 38
            $class = $property->getDeclaringClass();
643
        }
644
645 435
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
646
    }
647
648
    /**
649
     * {@inheritdoc}
650
     */
651 503
    public function walkPathExpression($pathExpr)
652
    {
653 503
        $sql = '';
654
655
        /** @var Query\AST\PathExpression $pathExpr */
656 503
        switch ($pathExpr->type) {
657 503
            case AST\PathExpression::TYPE_STATE_FIELD:
658 482
                $fieldName = $pathExpr->field;
659 482
                $dqlAlias  = $pathExpr->identificationVariable;
660 482
                $class     = $this->queryComponents[$dqlAlias]['metadata'];
661 482
                $property  = $class->getProperty($fieldName);
662
663 482
                if ($this->useSqlTableAliases) {
664 435
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
665
                }
666
667 482
                $sql .= $this->platform->quoteIdentifier($property->getColumnName());
668 482
                break;
669
670 63
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
671
                // 1- the owning side:
672
                //    Just use the foreign key, i.e. u.group_id
673 63
                $fieldName   = $pathExpr->field;
674 63
                $dqlAlias    = $pathExpr->identificationVariable;
675 63
                $class       = $this->queryComponents[$dqlAlias]['metadata'];
676 63
                $association = $class->getProperty($fieldName);
677
678 63
                if (! $association->isOwningSide()) {
679 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
680
                }
681
682 61
                $joinColumns = $association->getJoinColumns();
683
684
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
685 61
                if (count($joinColumns) > 1) {
686 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
687
                }
688
689 60
                $joinColumn = reset($joinColumns);
690
691 60
                if ($this->useSqlTableAliases) {
692 57
                    $sql .= $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias) . '.';
693
                }
694
695 60
                $sql .= $this->platform->quoteIdentifier($joinColumn->getColumnName());
696 60
                break;
697
698
            default:
699
                throw QueryException::invalidPathExpression($pathExpr);
700
        }
701
702 500
        return $sql;
703
    }
704
705
    /**
706
     * {@inheritdoc}
707
     */
708 652
    public function walkSelectClause($selectClause)
709
    {
710 652
        $sql                  = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : '');
711 652
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
712
713 652
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) {
714 1
            $this->query->setHint(self::HINT_DISTINCT, true);
715
        }
716
717
        $addMetaColumns = (
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $addMetaColumns = (! $th..._INCLUDE_META_COLUMNS)), Probably Intended Meaning: $addMetaColumns = ! $thi..._INCLUDE_META_COLUMNS))
Loading history...
718 652
            ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
719 483
            $this->query->getHydrationMode() === Query::HYDRATE_OBJECT
720
        ) || (
721 310
            $this->query->getHydrationMode() !== Query::HYDRATE_OBJECT &&
722 652
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS)
723
        );
724
725 652
        foreach ($this->selectedClasses as $selectedClass) {
726 496
            $class       = $selectedClass['class'];
727 496
            $dqlAlias    = $selectedClass['dqlAlias'];
728 496
            $resultAlias = $selectedClass['resultAlias'];
729
730
            // Register as entity or joined entity result
731 496
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
732 496
                $this->rsm->addEntityResult($class->getClassName(), $dqlAlias, $resultAlias);
733
            } else {
734 157
                $this->rsm->addJoinedEntityResult(
735 157
                    $class->getClassName(),
736 157
                    $dqlAlias,
737 157
                    $this->queryComponents[$dqlAlias]['parent'],
738 157
                    $this->queryComponents[$dqlAlias]['relation']->getName()
739
                );
740
            }
741
742 496
            if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || $class->inheritanceType === InheritanceType::JOINED) {
743
                // Add discriminator columns to SQL
744 99
                $discrColumn      = $class->discriminatorColumn;
745 99
                $discrColumnName  = $discrColumn->getColumnName();
746 99
                $discrColumnType  = $discrColumn->getType();
747 99
                $quotedColumnName = $this->platform->quoteIdentifier($discrColumnName);
748 99
                $sqlTableAlias    = $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias);
749 99
                $sqlColumnAlias   = $this->getSQLColumnAlias();
750
751 99
                $sqlSelectExpressions[] = sprintf(
752 99
                    '%s AS %s',
753 99
                    $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . '.' . $quotedColumnName, $this->platform),
754 99
                    $sqlColumnAlias
755
                );
756
757 99
                $this->rsm->setDiscriminatorColumn($dqlAlias, $sqlColumnAlias);
758 99
                $this->rsm->addMetaResult($dqlAlias, $sqlColumnAlias, $discrColumnName, false, $discrColumnType);
759
            }
760
761
            // Add foreign key columns of class and also parent classes
762 496
            foreach ($class->getPropertiesIterator() as $association) {
763 496
                if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())
764 496
                    || ( ! $addMetaColumns && ! $association->isPrimaryKey())) {
765 495
                    continue;
766
                }
767
768 280
                $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
769
770 280
                foreach ($association->getJoinColumns() as $joinColumn) {
771
                    /** @var JoinColumnMetadata $joinColumn */
772 280
                    $columnName           = $joinColumn->getColumnName();
773 280
                    $referencedColumnName = $joinColumn->getReferencedColumnName();
774 280
                    $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
775 280
                    $columnAlias          = $this->getSQLColumnAlias();
776 280
                    $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
777
778 280
                    if (! $joinColumn->getType()) {
779 17
                        $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
780
                    }
781
782 280
                    $sqlSelectExpressions[] = sprintf(
783 280
                        '%s.%s AS %s',
784 280
                        $sqlTableAlias,
785 280
                        $quotedColumnName,
786 280
                        $columnAlias
787
                    );
788
789 280
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $joinColumn->getType() targeting Doctrine\ORM\Mapping\ColumnMetadata::getType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
790
                }
791
            }
792
793
            // Add foreign key columns to SQL, if necessary
794 496
            if (! $addMetaColumns) {
795 185
                continue;
796
            }
797
798
            // Add foreign key columns of subclasses
799 351
            foreach ($class->getSubClasses() as $subClassName) {
800 38
                $subClass = $this->em->getClassMetadata($subClassName);
801
802 38
                foreach ($subClass->getPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getPropertiesIterator() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

802
                foreach ($subClass->/** @scrutinizer ignore-call */ getPropertiesIterator() as $association) {

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

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

Loading history...
803
                    // Skip if association is inherited
804 38
                    if ($subClass->isInheritedProperty($association->getName())) {
0 ignored issues
show
Bug introduced by
The method isInheritedProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

804
                    if ($subClass->/** @scrutinizer ignore-call */ isInheritedProperty($association->getName())) {

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

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

Loading history...
805 38
                        continue;
806
                    }
807
808 28
                    if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())) {
809 27
                        continue;
810
                    }
811
812 14
                    $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
813
814 14
                    foreach ($association->getJoinColumns() as $joinColumn) {
815
                        /** @var JoinColumnMetadata $joinColumn */
816 14
                        $columnName           = $joinColumn->getColumnName();
817 14
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
818 14
                        $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
819 14
                        $columnAlias          = $this->getSQLColumnAlias();
820 14
                        $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
821
822 14
                        if (! $joinColumn->getType()) {
823 1
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
824
                        }
825
826 14
                        $sqlSelectExpressions[] = sprintf(
827 14
                            '%s.%s AS %s',
828 14
                            $sqlTableAlias,
829 14
                            $quotedColumnName,
830 14
                            $columnAlias
831
                        );
832
833 14
                        $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
834
                    }
835
                }
836
            }
837
        }
838
839 652
        return $sql . implode(', ', $sqlSelectExpressions);
840
    }
841
842
    /**
843
     * {@inheritdoc}
844
     */
845 654
    public function walkFromClause($fromClause)
846
    {
847 654
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
848 654
        $sqlParts               = [];
849
850 654
        foreach ($identificationVarDecls as $identificationVariableDecl) {
851 654
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
852
        }
853
854 652
        return ' FROM ' . implode(', ', $sqlParts);
855
    }
856
857
    /**
858
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
859
     *
860
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
861
     *
862
     * @return string
863
     */
864 655
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
865
    {
866 655
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
867
868 655
        if ($identificationVariableDecl->indexBy) {
869 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
870
        }
871
872 655
        foreach ($identificationVariableDecl->joins as $join) {
873 245
            $sql .= $this->walkJoin($join);
874
        }
875
876 653
        return $sql;
877
    }
878
879
    /**
880
     * Walks down a IndexBy AST node.
881
     *
882
     * @param AST\IndexBy $indexBy
883
     */
884 8
    public function walkIndexBy($indexBy)
885
    {
886 8
        $pathExpression = $indexBy->singleValuedPathExpression;
887 8
        $alias          = $pathExpression->identificationVariable;
888 8
889
        /** @var Query\AST\PathExpression $pathExpr */
890 8
        switch ($pathExpression->type) {
891
            case AST\PathExpression::TYPE_STATE_FIELD:
892
                $field = $pathExpression->field;
893
                break;
894
895
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
896 8
                // 1- the owning side:
897 8
                //    Just use the foreign key, i.e. u.group_id
898
                $fieldName   = $pathExpression->field;
899
                $class       = $this->queryComponents[$alias]['metadata'];
900
                $association = $class->getProperty($fieldName);
901
902
                if (! $association->isOwningSide()) {
903
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pathExpr does not exist. Did you maybe mean $pathExpression?
Loading history...
904
                }
905
906 655
                $joinColumns = $association->getJoinColumns();
907
908 655
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
909
                if (count($joinColumns) > 1) {
910
                    throw QueryException::associationPathCompositeKeyNotSupported();
911
                }
912
913
                $field = reset($joinColumns)->getColumnName();
914
                break;
915
916 655
            default:
917
                throw QueryException::invalidPathExpression($pathExpr);
918 655
        }
919 655
920
        if (isset($this->scalarFields[$alias][$field])) {
921 655
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
922 655
923
            return;
924
        }
925 655
926 655
        $this->rsm->addIndexBy($alias, $field);
927
    }
928 655
929 655
    /**
930 655
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
931
     *
932
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
933 655
     *
934 554
     * @return string
935
     */
936
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
937 109
    {
938
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
939 109
    }
940 107
941
    /**
942
     * Generate appropriate SQL for RangeVariableDeclaration AST node
943 3
     *
944
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
945
     */
946
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
947
    {
948
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
949
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
950
951
        if ($rangeVariableDeclaration->isRoot) {
952
            $this->rootAliases[] = $dqlAlias;
953
        }
954
955
        $tableName  = $class->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
956
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
957 228
958
        $sql = $this->platform->appendLockHint(
959 228
            $tableName . ' ' . $tableAlias,
960
            $this->query->getHint(Query::HINT_LOCK_MODE)
0 ignored issues
show
Bug introduced by
It seems like $this->query->getHint(Do...\Query::HINT_LOCK_MODE) can also be of type false; however, parameter $lockMode of Doctrine\DBAL\Platforms\...tform::appendLockHint() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

960
            /** @scrutinizer ignore-type */ $this->query->getHint(Query::HINT_LOCK_MODE)
Loading history...
961 228
        );
962 228
963 228
        if ($class->inheritanceType !== InheritanceType::JOINED) {
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
964
            return $sql;
965 228
        }
966 228
967 228
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
968 228
969
        if (! $buildNestedJoins) {
970 228
            return $sql . $classTableInheritanceJoins;
971 228
        }
972
973
        return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
974 228
    }
975 118
976 228
    /**
977
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
978 228
     *
979 228
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
980 3
     * @param int                            $joinType
981 2
     * @param AST\ConditionalExpression      $condExpr
982
     *
983
     * @return string
984
     *
985 226
     * @throws QueryException
986
     */
987
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
988
    {
989
        $sql = '';
990 226
991 179
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
992
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
993 179
        $indexBy                   = $joinAssociationDeclaration->indexBy;
994 179
995 179
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
996
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
997 179
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
998 103
        $targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
999 103
1000 103
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
1001 103
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
1002 103
1003 103
        // Ensure we got the owning side, since it has all mapping info
1004
        $owningAssociation = ! $association->isOwningSide()
1005
            ? $targetClass->getProperty($association->getMappedBy())
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1005
            ? $targetClass->/** @scrutinizer ignore-call */ getProperty($association->getMappedBy())

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

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2426
            /** @scrutinizer ignore-call */ 
2427
            $entityClassName = $entityClass->getClassName();

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

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

Loading history...
2427
2428
            if ($entityClassName !== $rootClass->getClassName()) {
2429
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2430
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2431
                }
2432
            }
2433
2434
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2435
        }
2436
2437
        foreach (array_keys($discriminators) as $discriminator) {
2438
            $sqlParameterList[] = $this->conn->quote($discriminator);
2439
        }
2440
2441
        return '(' . implode(', ', $sqlParameterList) . ')';
2442
    }
2443
}
2444