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

SqlWalker::walkNewObject()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 58
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 10.0135

Importance

Changes 0
Metric Value
cc 10
eloc 40
nc 8
nop 2
dl 0
loc 58
ccs 37
cts 39
cp 0.9487
crap 10.0135
rs 7.6666
c 0
b 0
f 0

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

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

To visualize

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

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

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

will produce no issues.

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

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

To visualize

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

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

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

will produce no issues.

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

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

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

}

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

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

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

Loading history...
798
                }
799
            }
800
801
            // Add foreign key columns to SQL, if necessary
802 496
            if (! $addMetaColumns) {
803 185
                continue;
804
            }
805
806
            // Add foreign key columns of subclasses
807 351
            foreach ($class->getSubClasses() as $subClassName) {
808 38
                $subClass = $this->em->getClassMetadata($subClassName);
809
810 38
                foreach ($subClass->getDeclaredPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

938
            /** @scrutinizer ignore-type */ $this->query->getHint(Query::HINT_LOCK_MODE)
Loading history...
939
        );
940
941 655
        if ($class->inheritanceType !== InheritanceType::JOINED) {
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
942 554
            return $sql;
943
        }
944
945 109
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
946
947 109
        if (! $buildNestedJoins) {
948 107
            return $sql . $classTableInheritanceJoins;
949
        }
950
951 3
        return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
952
    }
953
954
    /**
955
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
956
     *
957
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
958
     * @param int                            $joinType
959
     * @param AST\ConditionalExpression      $condExpr
960
     *
961
     * @return string
962
     *
963
     * @throws QueryException
964
     */
965 228
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
966
    {
967 228
        $sql = '';
968
969 228
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
970 228
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
971 228
        $indexBy                   = $joinAssociationDeclaration->indexBy;
972
973 228
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
974 228
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
975 228
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
976 228
        $targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
977
978 228
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
979 228
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
980
981
        // Ensure we got the owning side, since it has all mapping info
982 228
        $owningAssociation = ! $association->isOwningSide()
983 118
            ? $targetClass->getProperty($association->getMappedBy())
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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