Passed
Pull Request — master (#7757)
by
unknown
10:48
created

SqlWalker::walkPathExpression()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 60
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 8.0439

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 34
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 60
ccs 31
cts 34
cp 0.9118
crap 8.0439
rs 8.1315

How to fix   Long Method   

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 482
                $col = '';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
663
664 482
                if ($this->useSqlTableAliases) {
665 435
                    $col .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
666
                }
667
668 482
                $col .= $this->platform->quoteIdentifier($property->getColumnName());
669
670 482
                if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) {
671
                    $type = Type::getType($class->getTypeOfField($fieldName));
672
                    $col = $type->convertToPHPValueSQL($col, $this->platform);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
673
                }
674
675 482
                $sql .= $col;
676 482
                break;
677
678 63
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
679
                // 1- the owning side:
680
                //    Just use the foreign key, i.e. u.group_id
681 63
                $fieldName   = $pathExpr->field;
682 63
                $dqlAlias    = $pathExpr->identificationVariable;
683 63
                $class       = $this->queryComponents[$dqlAlias]['metadata'];
684 63
                $association = $class->getProperty($fieldName);
685
686 63
                if (! $association->isOwningSide()) {
687 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
688
                }
689
690 61
                $joinColumns = $association->getJoinColumns();
691
692
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
693 61
                if (count($joinColumns) > 1) {
694 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
695
                }
696
697 60
                $joinColumn = reset($joinColumns);
698
699 60
                if ($this->useSqlTableAliases) {
700 57
                    $sql .= $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias) . '.';
701
                }
702
703 60
                $sql .= $this->platform->quoteIdentifier($joinColumn->getColumnName());
704 60
                break;
705
706
            default:
707
                throw QueryException::invalidPathExpression($pathExpr);
708
        }
709
710 500
        return $sql;
711
    }
712
713
    /**
714
     * {@inheritdoc}
715
     */
716 652
    public function walkSelectClause($selectClause)
717
    {
718 652
        $sql                  = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : '');
719 652
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
720
721 652
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) {
722 1
            $this->query->setHint(self::HINT_DISTINCT, true);
723
        }
724
725
        $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...
726 652
            ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
727 483
            $this->query->getHydrationMode() === Query::HYDRATE_OBJECT
728
        ) || (
729 310
            $this->query->getHydrationMode() !== Query::HYDRATE_OBJECT &&
730 652
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS)
731
        );
732
733 652
        foreach ($this->selectedClasses as $selectedClass) {
734 496
            $class       = $selectedClass['class'];
735 496
            $dqlAlias    = $selectedClass['dqlAlias'];
736 496
            $resultAlias = $selectedClass['resultAlias'];
737
738
            // Register as entity or joined entity result
739 496
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
740 496
                $this->rsm->addEntityResult($class->getClassName(), $dqlAlias, $resultAlias);
741
            } else {
742 157
                $this->rsm->addJoinedEntityResult(
743 157
                    $class->getClassName(),
744 157
                    $dqlAlias,
745 157
                    $this->queryComponents[$dqlAlias]['parent'],
746 157
                    $this->queryComponents[$dqlAlias]['relation']->getName()
747
                );
748
            }
749
750 496
            if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || $class->inheritanceType === InheritanceType::JOINED) {
751
                // Add discriminator columns to SQL
752 99
                $discrColumn      = $class->discriminatorColumn;
753 99
                $discrColumnName  = $discrColumn->getColumnName();
754 99
                $discrColumnType  = $discrColumn->getType();
755 99
                $quotedColumnName = $this->platform->quoteIdentifier($discrColumnName);
756 99
                $sqlTableAlias    = $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias);
757 99
                $sqlColumnAlias   = $this->getSQLColumnAlias();
758
759 99
                $sqlSelectExpressions[] = sprintf(
760 99
                    '%s AS %s',
761 99
                    $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . '.' . $quotedColumnName, $this->platform),
762 99
                    $sqlColumnAlias
763
                );
764
765 99
                $this->rsm->setDiscriminatorColumn($dqlAlias, $sqlColumnAlias);
766 99
                $this->rsm->addMetaResult($dqlAlias, $sqlColumnAlias, $discrColumnName, false, $discrColumnType);
767
            }
768
769
            // Add foreign key columns of class and also parent classes
770 496
            foreach ($class->getDeclaredPropertiesIterator() as $association) {
771 496
                if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())
772 496
                    || ( ! $addMetaColumns && ! $association->isPrimaryKey())) {
773 495
                    continue;
774
                }
775
776 280
                $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
777
778 280
                foreach ($association->getJoinColumns() as $joinColumn) {
779
                    /** @var JoinColumnMetadata $joinColumn */
780 280
                    $columnName           = $joinColumn->getColumnName();
781 280
                    $referencedColumnName = $joinColumn->getReferencedColumnName();
782 280
                    $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
783 280
                    $columnAlias          = $this->getSQLColumnAlias();
784 280
                    $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
785
786 280
                    if (! $joinColumn->getType()) {
787 20
                        $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
788
                    }
789
790 280
                    $sqlSelectExpressions[] = sprintf(
791 280
                        '%s.%s AS %s',
792 280
                        $sqlTableAlias,
793 280
                        $quotedColumnName,
794 280
                        $columnAlias
795
                    );
796
797 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...
798
                }
799
            }
800
801
            // Add foreign key columns to SQL, if necessary
802 496
            if (! $addMetaColumns) {
803 185
                continue;
804
            }
805
806
            // Add foreign key columns of subclasses
807 351
            foreach ($class->getSubClasses() as $subClassName) {
808 38
                $subClass = $this->em->getClassMetadata($subClassName);
809
810 38
                foreach ($subClass->getDeclaredPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() 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

810
                foreach ($subClass->/** @scrutinizer ignore-call */ getDeclaredPropertiesIterator() 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...
811
                    // Skip if association is inherited
812 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

812
                    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...
813 38
                        continue;
814
                    }
815
816 28
                    if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())) {
817 27
                        continue;
818
                    }
819
820 14
                    $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
821
822 14
                    foreach ($association->getJoinColumns() as $joinColumn) {
823
                        /** @var JoinColumnMetadata $joinColumn */
824 14
                        $columnName           = $joinColumn->getColumnName();
825 14
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
826 14
                        $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
827 14
                        $columnAlias          = $this->getSQLColumnAlias();
828 14
                        $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
829
830 14
                        if (! $joinColumn->getType()) {
831 1
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
832
                        }
833
834 14
                        $sqlSelectExpressions[] = sprintf(
835 14
                            '%s.%s AS %s',
836 14
                            $sqlTableAlias,
837 14
                            $quotedColumnName,
838 14
                            $columnAlias
839
                        );
840
841 14
                        $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
842
                    }
843
                }
844
            }
845
        }
846
847 652
        return $sql . implode(', ', $sqlSelectExpressions);
848
    }
849
850
    /**
851
     * {@inheritdoc}
852
     */
853 654
    public function walkFromClause($fromClause)
854
    {
855 654
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
856 654
        $sqlParts               = [];
857
858 654
        foreach ($identificationVarDecls as $identificationVariableDecl) {
859 654
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
860
        }
861
862 652
        return ' FROM ' . implode(', ', $sqlParts);
863
    }
864
865
    /**
866
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
867
     *
868
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
869
     *
870
     * @return string
871
     */
872 655
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
873
    {
874 655
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
875
876 655
        if ($identificationVariableDecl->indexBy) {
877 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
878
        }
879
880 655
        foreach ($identificationVariableDecl->joins as $join) {
881 245
            $sql .= $this->walkJoin($join);
882
        }
883
884 653
        return $sql;
885
    }
886
887
    /**
888
     * Walks down a IndexBy AST node.
889
     *
890
     * @param AST\IndexBy $indexBy
891
     */
892 8
    public function walkIndexBy($indexBy)
893
    {
894 8
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
895 8
        $alias          = $pathExpression->identificationVariable;
896 8
        $field          = $pathExpression->field;
897
898 8
        if (isset($this->scalarFields[$alias][$field])) {
899
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
900
901
            return;
902
        }
903
904 8
        $this->rsm->addIndexBy($alias, $field);
905 8
    }
906
907
    /**
908
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
909
     *
910
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
911
     *
912
     * @return string
913
     */
914 655
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
915
    {
916 655
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
917
    }
918
919
    /**
920
     * Generate appropriate SQL for RangeVariableDeclaration AST node
921
     *
922
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
923
     */
924 655
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
925
    {
926 655
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
927 655
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
928
929 655
        if ($rangeVariableDeclaration->isRoot) {
930 655
            $this->rootAliases[] = $dqlAlias;
931
        }
932
933 655
        $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...
934 655
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
935
936 655
        $sql = $this->platform->appendLockHint(
937 655
            $tableName . ' ' . $tableAlias,
938 655
            $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

938
            /** @scrutinizer ignore-type */ $this->query->getHint(Query::HINT_LOCK_MODE)
Loading history...
939
        );
940
941 655
        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...
942 554
            return $sql;
943
        }
944
945 109
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
946
947 109
        if (! $buildNestedJoins) {
948 107
            return $sql . $classTableInheritanceJoins;
949
        }
950
951 3
        return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
952
    }
953
954
    /**
955
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
956
     *
957
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
958
     * @param int                            $joinType
959
     * @param AST\ConditionalExpression      $condExpr
960
     *
961
     * @return string
962
     *
963
     * @throws QueryException
964
     */
965 228
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
966
    {
967 228
        $sql = '';
968
969 228
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
970 228
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
971 228
        $indexBy                   = $joinAssociationDeclaration->indexBy;
972
973 228
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
974 228
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
975 228
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
976 228
        $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...
977
978 228
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
979 228
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
980
981
        // Ensure we got the owning side, since it has all mapping info
982 228
        $owningAssociation = ! $association->isOwningSide()
983 118
            ? $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

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

2404
            /** @scrutinizer ignore-call */ 
2405
            $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...
2405
2406 10
            if ($entityClassName !== $rootClass->getClassName()) {
2407 7
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2408 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2409
                }
2410
            }
2411
2412 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2413
        }
2414
2415 13
        foreach (array_keys($discriminators) as $discriminator) {
2416 9
            $sqlParameterList[] = $this->conn->quote($discriminator);
2417
        }
2418
2419 13
        return '(' . implode(', ', $sqlParameterList) . ')';
2420
    }
2421
}
2422