SqlWalker::walkJoin()   F
last analyzed

Complexity

Conditions 13
Paths 520

Size

Total Lines 53
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 13.0056

Importance

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

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

363
            $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...
364
365 41
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
366
367 41
            $sqlParts = [];
368
369 41
            foreach ($subClass->getIdentifierColumns($this->em) as $column) {
0 ignored issues
show
Bug introduced by
The method getIdentifierColumns() does not exist on Doctrine\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

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

519
        /** @scrutinizer ignore-call */ 
520
        $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...
520 656
        $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

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

784
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), /** @scrutinizer ignore-type */ $joinColumn->getType());
Loading history...
785
                }
786
            }
787
788
            // Add foreign key columns to SQL, if necessary
789 498
            if (! $addMetaColumns) {
790 187
                continue;
791
            }
792
793
            // Add foreign key columns of subclasses
794 351
            foreach ($class->getSubClasses() as $subClassName) {
795 38
                $subClass = $this->em->getClassMetadata($subClassName);
796
797 38
                foreach ($subClass->getPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getPropertiesIterator() does not exist on Doctrine\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

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

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

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

Loading history...
798
                    // Skip if association is inherited
799 38
                    if ($subClass->isInheritedProperty($association->getName())) {
0 ignored issues
show
Bug introduced by
The method isInheritedProperty() does not exist on Doctrine\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

799
                    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...
800 38
                        continue;
801
                    }
802
803 28
                    if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())) {
804 27
                        continue;
805
                    }
806
807 14
                    $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
808
809 14
                    foreach ($association->getJoinColumns() as $joinColumn) {
810
                        /** @var JoinColumnMetadata $joinColumn */
811 14
                        $columnName       = $joinColumn->getColumnName();
812 14
                        $quotedColumnName = $this->platform->quoteIdentifier($columnName);
813 14
                        $columnAlias      = $this->getSQLColumnAlias();
814 14
                        $sqlTableAlias    = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
815
816 14
                        $sqlSelectExpressions[] = sprintf(
817 14
                            '%s.%s AS %s',
818 14
                            $sqlTableAlias,
819 14
                            $quotedColumnName,
820 14
                            $columnAlias
821
                        );
822
823 14
                        $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
824
                    }
825
                }
826
            }
827
        }
828
829 656
        return $sql . implode(', ', $sqlSelectExpressions);
830
    }
831
832
    /**
833
     * {@inheritdoc}
834
     */
835 658
    public function walkFromClause($fromClause)
836
    {
837 658
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
838 658
        $sqlParts               = [];
839
840 658
        foreach ($identificationVarDecls as $identificationVariableDecl) {
841 658
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
842
        }
843
844 656
        return ' FROM ' . implode(', ', $sqlParts);
845
    }
846
847
    /**
848
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
849
     *
850
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
851
     *
852
     * @return string
853
     */
854 659
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
855
    {
856 659
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
857
858 659
        if ($identificationVariableDecl->indexBy) {
859 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
860
        }
861
862 659
        foreach ($identificationVariableDecl->joins as $join) {
863 245
            $sql .= $this->walkJoin($join);
864
        }
865
866 657
        return $sql;
867
    }
868
869
    /**
870
     * Walks down a IndexBy AST node.
871
     *
872
     * @param AST\IndexBy $indexBy
873
     */
874 8
    public function walkIndexBy($indexBy)
875
    {
876 8
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
877 8
        $alias          = $pathExpression->identificationVariable;
878 8
        $field          = $pathExpression->field;
879
880 8
        if (isset($this->scalarFields[$alias][$field])) {
881
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
882
883
            return;
884
        }
885
886 8
        $this->rsm->addIndexBy($alias, $field);
887 8
    }
888
889
    /**
890
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
891
     *
892
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
893
     *
894
     * @return string
895
     */
896 659
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
897
    {
898 659
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
899
    }
900
901
    /**
902
     * Generate appropriate SQL for RangeVariableDeclaration AST node
903
     *
904
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
905
     */
906 659
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
907
    {
908 659
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
909 659
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
910
911 659
        if ($rangeVariableDeclaration->isRoot) {
912 659
            $this->rootAliases[] = $dqlAlias;
913
        }
914
915 659
        $tableName  = $class->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
916 659
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
917 659
        $lockMode   = $this->query->getHint(Query::HINT_LOCK_MODE);
918 659
        $sql        = $lockMode !== false
919 7
            ? $this->platform->appendLockHint($tableName . ' ' . $tableAlias, $lockMode)
920 659
            : $tableName . ' ' . $tableAlias;
921
922 659
        if ($class->inheritanceType !== InheritanceType::JOINED) {
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
923 558
            return $sql;
924
        }
925
926 109
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
927
928 109
        if (! $buildNestedJoins) {
929 107
            return $sql . $classTableInheritanceJoins;
930
        }
931
932 3
        return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
933
    }
934
935
    /**
936
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
937
     *
938
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
939
     * @param int                            $joinType
940
     * @param AST\ConditionalExpression      $condExpr
941
     *
942
     * @return string
943
     *
944
     * @throws QueryException
945
     */
946 228
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
947
    {
948 228
        $sql = '';
949
950 228
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
951 228
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
952 228
        $indexBy                   = $joinAssociationDeclaration->indexBy;
953
954 228
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
955 228
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
956 228
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
957 228
        $targetTableName = $targetClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
958
959 228
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
960 228
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
961
962
        // Ensure we got the owning side, since it has all mapping info
963 228
        $owningAssociation = ! $association->isOwningSide()
964 118
            ? $targetClass->getProperty($association->getMappedBy())
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\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

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

2385
            /** @scrutinizer ignore-call */ 
2386
            $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...
2386
2387 10
            if ($entityClassName !== $rootClass->getClassName()) {
2388 7
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2389 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2390
                }
2391
            }
2392
2393 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2394
        }
2395
2396 13
        foreach (array_keys($discriminators) as $discriminator) {
2397 9
            $sqlParameterList[] = $this->conn->quote($discriminator);
2398
        }
2399
2400 13
        return '(' . implode(', ', $sqlParameterList) . ')';
2401
    }
2402
}
2403