Completed
Pull Request — master (#7915)
by
unknown
61:22
created

SqlWalker::walkNewObject()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 58
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 10.2918

Importance

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

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use BadMethodCallException;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\LockMode;
10
use Doctrine\DBAL\Platforms\AbstractPlatform;
11
use Doctrine\DBAL\Types\Type;
12
use Doctrine\ORM\AbstractQuery;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\Mapping\ClassMetadata;
15
use Doctrine\ORM\Mapping\FieldMetadata;
16
use Doctrine\ORM\Mapping\InheritanceType;
17
use Doctrine\ORM\Mapping\JoinColumnMetadata;
18
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
19
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
20
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
21
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
22
use Doctrine\ORM\OptimisticLockException;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
25
use Doctrine\ORM\Utility\PersisterHelper;
26
use function array_diff;
27
use function array_filter;
28
use function array_keys;
29
use function array_map;
30
use function array_merge;
31
use function count;
32
use function implode;
33
use function in_array;
34
use function is_array;
35
use function is_float;
36
use function is_numeric;
37
use function is_string;
38
use function reset;
39
use function sprintf;
40
use function strtolower;
41
use function strtoupper;
42
use function trim;
43
44
/**
45
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
46
 * the corresponding SQL.
47
 */
48
class SqlWalker implements TreeWalker
49
{
50
    public const HINT_DISTINCT = 'doctrine.distinct';
51
52
    /** @var ResultSetMapping */
53
    private $rsm;
54
55
    /**
56
     * Counter for generating unique column aliases.
57
     *
58
     * @var int
59
     */
60
    private $aliasCounter = 0;
61
62
    /**
63
     * Counter for generating unique table aliases.
64
     *
65
     * @var int
66
     */
67
    private $tableAliasCounter = 0;
68
69
    /**
70
     * Counter for generating unique scalar result.
71
     *
72
     * @var int
73
     */
74
    private $scalarResultCounter = 1;
75
76
    /**
77
     * Counter for generating unique parameter indexes.
78
     *
79
     * @var int
80
     */
81
    private $sqlParamIndex = 0;
82
83
    /**
84
     * Counter for generating indexes.
85
     *
86
     * @var int
87
     */
88
    private $newObjectCounter = 0;
89
90
    /** @var ParserResult */
91
    private $parserResult;
92
93
    /** @var EntityManagerInterface */
94
    private $em;
95
96
    /** @var Connection */
97
    private $conn;
98
99
    /** @var AbstractQuery */
100
    private $query;
101
102
    /** @var string[] */
103
    private $tableAliasMap = [];
104
105
    /**
106
     * Map from result variable names to their SQL column alias names.
107
     *
108
     * @var string[]|string[][]
109
     */
110
    private $scalarResultAliasMap = [];
111
112
    /**
113
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
114
     *
115
     * @var mixed[]
116
     */
117
    private $orderedColumnsMap = [];
118
119
    /**
120
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
121
     *
122
     * @var string[][]
123
     */
124
    private $scalarFields = [];
125
126
    /**
127
     * Map of all components/classes that appear in the DQL query.
128
     *
129
     * @var mixed[][]
130
     */
131
    private $queryComponents;
132
133
    /**
134
     * A list of classes that appear in non-scalar SelectExpressions.
135
     *
136
     * @var mixed[][]
137
     */
138
    private $selectedClasses = [];
139
140
    /**
141
     * The DQL alias of the root class of the currently traversed query.
142
     *
143
     * @var string[]
144
     */
145
    private $rootAliases = [];
146
147
    /**
148
     * Flag that indicates whether to generate SQL table aliases in the SQL.
149
     * These should only be generated for SELECT queries, not for UPDATE/DELETE.
150
     *
151
     * @var bool
152
     */
153
    private $useSqlTableAliases = true;
154
155
    /**
156
     * The database platform abstraction.
157
     *
158
     * @var AbstractPlatform
159
     */
160
    private $platform;
161
162
    /**
163
     * {@inheritDoc}
164
     */
165 717
    public function __construct(AbstractQuery $query, ParserResult $parserResult, array $queryComponents)
166
    {
167 717
        $this->query           = $query;
168 717
        $this->parserResult    = $parserResult;
169 717
        $this->queryComponents = $queryComponents;
170 717
        $this->rsm             = $parserResult->getResultSetMapping();
171 717
        $this->em              = $query->getEntityManager();
172 717
        $this->conn            = $this->em->getConnection();
173 717
        $this->platform        = $this->conn->getDatabasePlatform();
174 717
    }
175
176
    /**
177
     * Gets the Query instance used by the walker.
178
     *
179
     * @return Query
180
     */
181
    public function getQuery()
182
    {
183
        return $this->query;
184
    }
185
186
    /**
187
     * Gets the Connection used by the walker.
188
     *
189
     * @return Connection
190
     */
191 51
    public function getConnection()
192
    {
193 51
        return $this->conn;
194
    }
195
196
    /**
197
     * Gets the EntityManager used by the walker.
198
     *
199
     * @return EntityManagerInterface
200
     */
201 23
    public function getEntityManager()
202
    {
203 23
        return $this->em;
204
    }
205
206
    /**
207
     * Gets the information about a single query component.
208
     *
209
     * @param string $dqlAlias The DQL alias.
210
     *
211
     * @return mixed[][]
212
     */
213 18
    public function getQueryComponent($dqlAlias)
214
    {
215 18
        return $this->queryComponents[$dqlAlias];
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function getQueryComponents()
222
    {
223
        return $this->queryComponents;
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 1
    public function setQueryComponent($dqlAlias, array $queryComponent)
230
    {
231 1
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
232
233 1
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
234 1
            throw QueryException::invalidQueryComponent($dqlAlias);
235
        }
236
237
        $this->queryComponents[$dqlAlias] = $queryComponent;
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 711
    public function getExecutor($AST)
244
    {
245
        switch (true) {
246 711
            case $AST instanceof AST\DeleteStatement:
247 39
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
248
249 39
                return $primaryClass->inheritanceType === InheritanceType::JOINED
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
250 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
251 39
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
252 676
            case $AST instanceof AST\UpdateStatement:
253 28
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
254
255 28
                return $primaryClass->inheritanceType === InheritanceType::JOINED
256 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
257 28
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
258
            default:
259 652
                return new Exec\SingleSelectExecutor($AST, $this);
260
        }
261
    }
262
263
    /**
264
     * Generates a unique, short SQL table alias.
265
     *
266
     * @param string $tableName Table name
267
     * @param string $dqlAlias  The DQL alias.
268
     *
269
     * @return string Generated table alias.
270
     */
271 663
    public function getSQLTableAlias($tableName, $dqlAlias = '')
272
    {
273 663
        $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
274
275 663
        if (! isset($this->tableAliasMap[$tableName])) {
276 663
            $this->tableAliasMap[$tableName] = 't' . $this->tableAliasCounter++;
277
        }
278
279 663
        return $this->tableAliasMap[$tableName];
280
    }
281
282
    /**
283
     * Forces the SqlWalker to use a specific alias for a table name, rather than
284
     * generating an alias on its own.
285
     *
286
     * @param string $tableName
287
     * @param string $alias
288
     * @param string $dqlAlias
289
     *
290
     * @return string
291
     */
292 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
293
    {
294 65
        $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
295
296 65
        $this->tableAliasMap[$tableName] = $alias;
297
298 65
        return $alias;
299
    }
300
301
    /**
302
     * Gets an SQL column alias for a column name.
303
     *
304
     * @return string
305
     */
306 652
    public function getSQLColumnAlias()
307
    {
308 652
        return $this->platform->getSQLResultCasing('c' . $this->aliasCounter++);
309
    }
310
311
    /**
312
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
313
     * for the given class.
314
     *
315
     * @param ClassMetadata $class    The class for which to generate the joins.
316
     * @param string        $dqlAlias The DQL alias of the class.
317
     *
318
     * @return string The SQL.
319
     */
320 113
    private function generateClassTableInheritanceJoins($class, $dqlAlias)
321
    {
322 113
        $sql = '';
323
324 113
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
325
326
        // INNER JOIN parent class tables
327 113
        $parentClass = $class;
328
329 113
        while (($parentClass = $parentClass->getParent()) !== null) {
330 78
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
331 78
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\ComponentMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\ComponentMetadata such as Doctrine\ORM\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2425
            /** @scrutinizer ignore-call */ 
2426
            $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...
2426
2427
            if ($entityClassName !== $rootClass->getClassName()) {
2428
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2429
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2430
                }
2431
            }
2432
2433
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2434
        }
2435
2436
        foreach (array_keys($discriminators) as $discriminator) {
2437
            $sqlParameterList[] = $this->conn->quote($discriminator);
2438
        }
2439
2440
        return '(' . implode(', ', $sqlParameterList) . ')';
2441
    }
2442
}
2443