Completed
Pull Request — master (#8105)
by
unknown
10:29
created

SqlWalker::generateClassTableInheritanceJoins()   B

Complexity

Conditions 8
Paths 36

Size

Total Lines 60
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8.0023

Importance

Changes 0
Metric Value
cc 8
eloc 29
c 0
b 0
f 0
nc 36
nop 2
dl 0
loc 60
ccs 29
cts 30
cp 0.9667
crap 8.0023
rs 8.2114

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use BadMethodCallException;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\DBAL\LockMode;
10
use Doctrine\DBAL\Platforms\AbstractPlatform;
11
use Doctrine\DBAL\Types\Type;
12
use Doctrine\ORM\AbstractQuery;
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\Mapping\ClassMetadata;
15
use Doctrine\ORM\Mapping\FieldMetadata;
16
use Doctrine\ORM\Mapping\InheritanceType;
17
use Doctrine\ORM\Mapping\JoinColumnMetadata;
18
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
19
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
20
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
21
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
22
use Doctrine\ORM\OptimisticLockException;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
25
use 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 339
    public function __construct(AbstractQuery $query, ParserResult $parserResult, array $queryComponents)
165
    {
166 339
        $this->query           = $query;
167 339
        $this->parserResult    = $parserResult;
168 339
        $this->queryComponents = $queryComponents;
169 339
        $this->rsm             = $parserResult->getResultSetMapping();
170 339
        $this->em              = $query->getEntityManager();
171 339
        $this->conn            = $this->em->getConnection();
172 339
        $this->platform        = $this->conn->getDatabasePlatform();
173 339
    }
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 25
    public function getConnection()
191
    {
192 25
        return $this->conn;
193
    }
194
195
    /**
196
     * Gets the EntityManager used by the walker.
197
     *
198
     * @return EntityManagerInterface
199
     */
200 14
    public function getEntityManager()
201
    {
202 14
        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 14
    public function getQueryComponent($dqlAlias)
213
    {
214 14
        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 333
    public function getExecutor($AST)
243
    {
244
        switch (true) {
245 333
            case $AST instanceof AST\DeleteStatement:
246 30
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
247
248 30
                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
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
250 30
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
251 303
            case $AST instanceof AST\UpdateStatement:
252 20
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
253
254 20
                return $primaryClass->inheritanceType === InheritanceType::JOINED
255
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
256 20
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
257
            default:
258 284
                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 293
    public function getSQLTableAlias($tableName, $dqlAlias = '')
271
    {
272 293
        $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
273
274 293
        if (! isset($this->tableAliasMap[$tableName])) {
275 293
            $this->tableAliasMap[$tableName] = 't' . $this->tableAliasCounter++;
276
        }
277
278 293
        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 50
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
292
    {
293 50
        $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
294
295 50
        $this->tableAliasMap[$tableName] = $alias;
296
297 50
        return $alias;
298
    }
299
300
    /**
301
     * Gets an SQL column alias for a column name.
302
     *
303
     * @return string
304
     */
305 284
    public function getSQLColumnAlias()
306
    {
307 284
        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 30
    private function generateClassTableInheritanceJoins($class, $dqlAlias)
320
    {
321 30
        $sql = '';
322
323 30
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
324
325
        // INNER JOIN parent class tables
326 30
        $parentClass = $class;
327
328 30
        while (($parentClass = $parentClass->getParent()) !== null) {
329 18
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
330 18
            $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 18
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
334 18
            $sql .= 'JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
335
336 18
            $sqlParts = [];
337
338 18
            foreach ($class->getIdentifierColumns($this->em) as $column) {
339 18
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
340
341 18
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
342
            }
343
344 18
            $filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias);
345
346
            // Add filters on the root class
347 18
            if ($filterSql) {
348
                $sqlParts[] = $filterSql;
349
            }
350
351 18
            $sql .= implode(' AND ', $sqlParts);
352
        }
353
354
        // Ignore subclassing inclusion if partial objects is disallowed
355 30
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
356 21
            return $sql;
357
        }
358
359
        // LEFT JOIN child class tables
360 9
        foreach ($class->getSubClasses() as $subClassName) {
361 8
            $subClass   = $this->em->getClassMetadata($subClassName);
362 8
            $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 8
            $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 8
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
366
367 8
            $sqlParts = [];
368
369 8
            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 8
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
371
372 8
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
373
            }
374
375 8
            $sql .= implode(' AND ', $sqlParts);
376
        }
377
378 9
        return $sql;
379
    }
380
381
    /**
382
     * @return string
383
     */
384 279
    private function generateOrderedCollectionOrderByItems()
385
    {
386 279
        $orderedColumns = [];
387
388 279
        foreach ($this->selectedClasses as $selectedClass) {
389 186
            $dqlAlias    = $selectedClass['dqlAlias'];
390 186
            $qComp       = $this->queryComponents[$dqlAlias];
391 186
            $association = $qComp['relation'];
392
393 186
            if (! ($association instanceof ToManyAssociationMetadata)) {
394 186
                continue;
395
            }
396
397 18
            foreach ($association->getOrderBy() as $fieldName => $orientation) {
398 2
                $property      = $qComp['metadata']->getProperty($fieldName);
399 2
                $tableName     = $property->getTableName();
400 2
                $columnName    = $this->platform->quoteIdentifier($property->getColumnName());
401 2
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
402
403
                // OrderByClause should replace an ordered relation. see - DDC-2475
404 2
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
405 1
                    continue;
406
                }
407
408 2
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
409 2
                $orderedColumns[]                        = $orderedColumn . ' ' . $orientation;
410
            }
411
        }
412
413 279
        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 329
    private function generateDiscriminatorColumnConditionSQL(array $dqlAliases)
424
    {
425 329
        $sqlParts = [];
426
427 329
        foreach ($dqlAliases as $dqlAlias) {
428 329
            $class = $this->queryComponents[$dqlAlias]['metadata'];
429
430 329
            if ($class->inheritanceType !== InheritanceType::SINGLE_TABLE) {
431 322
                continue;
432
            }
433
434 17
            $conn   = $this->em->getConnection();
435 17
            $values = [];
436
437 17
            if ($class->discriminatorValue !== null) { // discriminators can be 0
438 8
                $values[] = $conn->quote($class->discriminatorValue);
439
            }
440
441 17
            foreach ($class->getSubClasses() as $subclassName) {
442 13
                $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 17
            $discrColumn      = $class->discriminatorColumn;
446 17
            $discrColumnType  = $discrColumn->getType();
447 17
            $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
448 17
            $sqlTableAlias    = $this->useSqlTableAliases
449 16
                ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
450 17
                : '';
451
452 17
            $sqlParts[] = sprintf(
453 17
                '%s IN (%s)',
454 17
                $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
455 17
                implode(', ', $values)
456
            );
457
        }
458
459 329
        $sql = implode(' AND ', $sqlParts);
460
461 329
        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 87
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
473
    {
474 87
        if (! $this->em->hasFilters()) {
475 86
            return '';
476
        }
477
478 1
        switch ($targetEntity->inheritanceType) {
479 1
            case InheritanceType::NONE:
480 1
                break;
481
482
            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
                if ($targetEntity->getClassName() !== $targetEntity->getRootClassName()) {
486
                    return '';
487
                }
488
                break;
489
490
            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
                $targetEntity = $this->em->getClassMetadata($targetEntity->getRootClassName());
494
                break;
495
496
            default:
497
                //@todo: throw exception?
498
                return '';
499
        }
500
501 1
        $filterClauses = [];
502
503 1
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
504 1
            $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
505
506 1
            if ($filterExpr !== '') {
507 1
                $filterClauses[] = '(' . $filterExpr . ')';
508
            }
509
        }
510
511 1
        return implode(' AND ', $filterClauses);
512
    }
513
514
    /**
515
     * {@inheritdoc}
516
     */
517 284
    public function walkSelectStatement(AST\SelectStatement $AST)
518
    {
519 284
        $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 284
        $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 284
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
522 284
        $sql      = $this->walkSelectClause($AST->selectClause)
523 284
            . $this->walkFromClause($AST->fromClause)
524 282
            . $this->walkWhereClause($AST->whereClause);
525
526 280
        if ($AST->groupByClause) {
527 17
            $sql .= $this->walkGroupByClause($AST->groupByClause);
528
        }
529
530 280
        if ($AST->havingClause) {
531 10
            $sql .= $this->walkHavingClause($AST->havingClause);
532
        }
533
534 280
        if ($AST->orderByClause) {
535 26
            $sql .= $this->walkOrderByClause($AST->orderByClause);
536
        }
537
538 279
        if (! $AST->orderByClause) {
539 257
            $orderBySql = $this->generateOrderedCollectionOrderByItems();
540
541 257
            if ($orderBySql) {
542 2
                $sql .= ' ORDER BY ' . $orderBySql;
543
            }
544
        }
545
546 279
        if ($limit !== null || $offset !== null) {
547 4
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset ?? 0);
548
        }
549
550 279
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
551 273
            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 20
    public function walkUpdateStatement(AST\UpdateStatement $AST)
579
    {
580 20
        $this->useSqlTableAliases = false;
581 20
        $this->rsm->isSelect      = false;
582
583 20
        return $this->walkUpdateClause($AST->updateClause)
584 20
            . $this->walkWhereClause($AST->whereClause);
585
    }
586
587
    /**
588
     * {@inheritdoc}
589
     */
590 30
    public function walkDeleteStatement(AST\DeleteStatement $AST)
591
    {
592 30
        $this->useSqlTableAliases = false;
593 30
        $this->rsm->isSelect      = false;
594
595 30
        return $this->walkDeleteClause($AST->deleteClause)
596 30
            . $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 188
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
631
    {
632 188
        $class = $this->queryComponents[$identificationVariable]['metadata'];
633
634 188
        if (! $fieldName) {
635
            return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
636
        }
637
638 188
        $property = $class->getProperty($fieldName);
639
640 188
        if ($class->inheritanceType === InheritanceType::JOINED && $class->isInheritedProperty($fieldName)) {
641 6
            $class = $property->getDeclaringClass();
642
        }
643
644 188
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
645
    }
646
647
    /**
648
     * {@inheritdoc}
649
     */
650 239
    public function walkPathExpression($pathExpr)
651
    {
652 239
        $sql = '';
653
654
        /** @var Query\AST\PathExpression $pathExpr */
655 239
        switch ($pathExpr->type) {
656 239
            case AST\PathExpression::TYPE_STATE_FIELD:
657 230
                $fieldName = $pathExpr->field;
658 230
                $dqlAlias  = $pathExpr->identificationVariable;
659 230
                $class     = $this->queryComponents[$dqlAlias]['metadata'];
660 230
                $property  = $class->getProperty($fieldName);
661
662 230
                if ($this->useSqlTableAliases) {
663 188
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
664
                }
665
666 230
                $sql .= $this->platform->quoteIdentifier($property->getColumnName());
667 230
                break;
668
669 34
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
670
                // 1- the owning side:
671
                //    Just use the foreign key, i.e. u.group_id
672 34
                $fieldName   = $pathExpr->field;
673 34
                $dqlAlias    = $pathExpr->identificationVariable;
674 34
                $class       = $this->queryComponents[$dqlAlias]['metadata'];
675 34
                $association = $class->getProperty($fieldName);
676
677 34
                if (! $association->isOwningSide()) {
678 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
679
                }
680
681 32
                $joinColumns = $association->getJoinColumns();
682
683
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
684 32
                if (count($joinColumns) > 1) {
685
                    throw QueryException::associationPathCompositeKeyNotSupported();
686
                }
687
688 32
                $joinColumn = reset($joinColumns);
689
690 32
                if ($this->useSqlTableAliases) {
691 30
                    $sql .= $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias) . '.';
692
                }
693
694 32
                $sql .= $this->platform->quoteIdentifier($joinColumn->getColumnName());
695 32
                break;
696
697
            default:
698
                throw QueryException::invalidPathExpression($pathExpr);
699
        }
700
701 237
        return $sql;
702
    }
703
704
    /**
705
     * {@inheritdoc}
706
     */
707 284
    public function walkSelectClause($selectClause)
708
    {
709 284
        $sql                  = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : '');
710 284
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
711
712 284
        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 284
            ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
718 113
            $this->query->getHydrationMode() === Query::HYDRATE_OBJECT
719
        ) || (
720 177
            $this->query->getHydrationMode() !== Query::HYDRATE_OBJECT &&
721 284
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS)
722
        );
723
724 284
        foreach ($this->selectedClasses as $selectedClass) {
725 191
            $class       = $selectedClass['class'];
726 191
            $dqlAlias    = $selectedClass['dqlAlias'];
727 191
            $resultAlias = $selectedClass['resultAlias'];
728
729
            // Register as entity or joined entity result
730 191
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
731 191
                $this->rsm->addEntityResult($class->getClassName(), $dqlAlias, $resultAlias);
732
            } else {
733 33
                $this->rsm->addJoinedEntityResult(
734 33
                    $class->getClassName(),
735
                    $dqlAlias,
736 33
                    $this->queryComponents[$dqlAlias]['parent'],
737 33
                    $this->queryComponents[$dqlAlias]['relation']->getName()
738
                );
739
            }
740
741 191
            if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || $class->inheritanceType === InheritanceType::JOINED) {
742
                // Add discriminator columns to SQL
743 31
                $discrColumn      = $class->discriminatorColumn;
744 31
                $discrColumnName  = $discrColumn->getColumnName();
745 31
                $discrColumnType  = $discrColumn->getType();
746 31
                $quotedColumnName = $this->platform->quoteIdentifier($discrColumnName);
747 31
                $sqlTableAlias    = $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias);
748 31
                $sqlColumnAlias   = $this->getSQLColumnAlias();
749
750 31
                $sqlSelectExpressions[] = sprintf(
751 31
                    '%s AS %s',
752 31
                    $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . '.' . $quotedColumnName, $this->platform),
753 31
                    $sqlColumnAlias
754
                );
755
756 31
                $this->rsm->setDiscriminatorColumn($dqlAlias, $sqlColumnAlias);
757 31
                $this->rsm->addMetaResult($dqlAlias, $sqlColumnAlias, $discrColumnName, false, $discrColumnType);
758
            }
759
760
            // Add foreign key columns of class and also parent classes
761 191
            foreach ($class->getPropertiesIterator() as $association) {
762 191
                if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())
763 191
                    || ( ! $addMetaColumns && ! $association->isPrimaryKey())) {
764 191
                    continue;
765
                }
766
767 50
                $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 50
                foreach ($association->getJoinColumns() as $joinColumn) {
770
                    /** @var JoinColumnMetadata $joinColumn */
771 50
                    $columnName           = $joinColumn->getColumnName();
772 50
                    $referencedColumnName = $joinColumn->getReferencedColumnName();
0 ignored issues
show
Unused Code introduced by
The assignment to $referencedColumnName is dead and can be removed.
Loading history...
773 50
                    $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
774 50
                    $columnAlias          = $this->getSQLColumnAlias();
775 50
                    $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
776
777 50
                    $sqlSelectExpressions[] = sprintf(
778 50
                        '%s.%s AS %s',
779 50
                        $sqlTableAlias,
780 50
                        $quotedColumnName,
781 50
                        $columnAlias
782
                    );
783
784 50
                    $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 191
            if (! $addMetaColumns) {
790 106
                continue;
791
            }
792
793
            // Add foreign key columns of subclasses
794 85
            foreach ($class->getSubClasses() as $subClassName) {
795 6
                $subClass = $this->em->getClassMetadata($subClassName);
796
797 6
                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 6
                    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 6
                        continue;
801
                    }
802
803 6
                    if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())) {
804 6
                        continue;
805
                    }
806
807 4
                    $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
808
809 4
                    foreach ($association->getJoinColumns() as $joinColumn) {
810
                        /** @var JoinColumnMetadata $joinColumn */
811 4
                        $columnName       = $joinColumn->getColumnName();
812 4
                        $quotedColumnName = $this->platform->quoteIdentifier($columnName);
813 4
                        $columnAlias      = $this->getSQLColumnAlias();
814 4
                        $sqlTableAlias    = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
815
816 4
                        $sqlSelectExpressions[] = sprintf(
817 4
                            '%s.%s AS %s',
818 4
                            $sqlTableAlias,
819 4
                            $quotedColumnName,
820 4
                            $columnAlias
821
                        );
822
823 4
                        $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
824
                    }
825
                }
826
            }
827
        }
828
829 284
        return $sql . implode(', ', $sqlSelectExpressions);
830
    }
831
832
    /**
833
     * {@inheritdoc}
834
     */
835 284
    public function walkFromClause($fromClause)
836
    {
837 284
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
838 284
        $sqlParts               = [];
839
840 284
        foreach ($identificationVarDecls as $identificationVariableDecl) {
841 284
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
842
        }
843
844 282
        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 285
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
855
    {
856 285
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
857
858 285
        if ($identificationVariableDecl->indexBy) {
859
            $this->walkIndexBy($identificationVariableDecl->indexBy);
860
        }
861
862 285
        foreach ($identificationVariableDecl->joins as $join) {
863 77
            $sql .= $this->walkJoin($join);
864
        }
865
866 283
        return $sql;
867
    }
868
869
    /**
870
     * Walks down a IndexBy AST node.
871
     *
872
     * @param AST\IndexBy $indexBy
873
     */
874
    public function walkIndexBy($indexBy)
875
    {
876
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
877
        $alias          = $pathExpression->identificationVariable;
878
        $field          = $pathExpression->field;
879
880
        if (isset($this->scalarFields[$alias][$field])) {
881
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
882
883
            return;
884
        }
885
886
        $this->rsm->addIndexBy($alias, $field);
887
    }
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 285
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
897
    {
898 285
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
899
    }
900
901
    /**
902
     * Generate appropriate SQL for RangeVariableDeclaration AST node
903
     *
904
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
905
     */
906 285
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
907
    {
908 285
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
909 285
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
910
911 285
        if ($rangeVariableDeclaration->isRoot) {
912 285
            $this->rootAliases[] = $dqlAlias;
913
        }
914
915 285
        $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 285
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
917 285
        $lockMode   = $this->query->getHint(Query::HINT_LOCK_MODE);
918 285
        $sql        = $lockMode !== false
919 7
            ? $this->platform->appendLockHint($tableName . ' ' . $tableAlias, $lockMode)
920 285
            : $tableName . ' ' . $tableAlias;
921
922 285
        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 262
            return $sql;
924
        }
925
926 28
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
927
928 28
        if (! $buildNestedJoins) {
929 27
            return $sql . $classTableInheritanceJoins;
930
        }
931
932 2
        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 63
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
947
    {
948 63
        $sql = '';
949
950 63
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
951 63
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
952 63
        $indexBy                   = $joinAssociationDeclaration->indexBy;
953
954 63
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
955 63
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
956 63
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
957 63
        $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 63
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
960 63
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
961
962
        // Ensure we got the owning side, since it has all mapping info
963 63
        $owningAssociation = ! $association->isOwningSide()
964 27
            ? $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 63
            : $association;
966
967 63
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true &&
968 63
            (! $this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
969 2
            if ($association instanceof ToManyAssociationMetadata) {
970 2
                throw QueryException::iterateWithFetchJoinNotAllowed($owningAssociation);
971
            }
972
        }
973
974 61
        $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 61
        if ($owningAssociation instanceof ToOneAssociationMetadata) {
980 43
            $conditions = [];
981
982 43
            foreach ($owningAssociation->getJoinColumns() as $joinColumn) {
983 43
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
984 43
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
985
986 43
                if ($association->isOwningSide()) {
987 25
                    $conditions[] = sprintf(
988 25
                        '%s.%s = %s.%s',
989 25
                        $sourceTableAlias,
990 25
                        $quotedColumnName,
991 25
                        $targetTableAlias,
992 25
                        $quotedReferencedColumnName
993
                    );
994
995 25
                    continue;
996
                }
997
998 20
                $conditions[] = sprintf(
999 20
                    '%s.%s = %s.%s',
1000 20
                    $sourceTableAlias,
1001 20
                    $quotedReferencedColumnName,
1002 20
                    $targetTableAlias,
1003 20
                    $quotedColumnName
1004
                );
1005
            }
1006
1007
            // Apply remaining inheritance restrictions
1008 43
            $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1009
1010 43
            if ($discrSql) {
1011 1
                $conditions[] = $discrSql;
1012
            }
1013
1014
            // Apply the filters
1015 43
            $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1016
1017 43
            if ($filterExpr) {
1018
                $conditions[] = $filterExpr;
1019
            }
1020
1021
            $targetTableJoin = [
1022 43
                'table' => $targetTableName . ' ' . $targetTableAlias,
1023 43
                'condition' => implode(' AND ', $conditions),
1024
            ];
1025 19
        } elseif ($owningAssociation instanceof ManyToManyAssociationMetadata) {
1026
            // Join relation table
1027 19
            $joinTable      = $owningAssociation->getJoinTable();
1028 19
            $joinTableName  = $joinTable->getQuotedQualifiedName($this->platform);
1029 19
            $joinTableAlias = $this->getSQLTableAlias($joinTable->getName(), $joinedDqlAlias);
1030
1031 19
            $conditions  = [];
1032 19
            $joinColumns = $association->isOwningSide()
1033 14
                ? $joinTable->getJoinColumns()
1034 19
                : $joinTable->getInverseJoinColumns();
1035
1036 19
            foreach ($joinColumns as $joinColumn) {
1037 19
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
1038 19
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
1039
1040 19
                $conditions[] = sprintf(
1041 19
                    '%s.%s = %s.%s',
1042 19
                    $sourceTableAlias,
1043 19
                    $quotedReferencedColumnName,
1044 19
                    $joinTableAlias,
1045 19
                    $quotedColumnName
1046
                );
1047
            }
1048
1049 19
            $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
1050
1051
            // Join target table
1052 19
            $sql .= $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN ';
1053
1054 19
            $conditions  = [];
1055 19
            $joinColumns = $association->isOwningSide()
1056 14
                ? $joinTable->getInverseJoinColumns()
1057 19
                : $joinTable->getJoinColumns();
1058
1059 19
            foreach ($joinColumns as $joinColumn) {
1060 19
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
1061 19
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
1062
1063 19
                $conditions[] = sprintf(
1064 19
                    '%s.%s = %s.%s',
1065 19
                    $targetTableAlias,
1066 19
                    $quotedReferencedColumnName,
1067 19
                    $joinTableAlias,
1068 19
                    $quotedColumnName
1069
                );
1070
            }
1071
1072
            // Apply remaining inheritance restrictions
1073 19
            $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1074
1075 19
            if ($discrSql) {
1076 1
                $conditions[] = $discrSql;
1077
            }
1078
1079
            // Apply the filters
1080 19
            $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1081
1082 19
            if ($filterExpr) {
1083
                $conditions[] = $filterExpr;
1084
            }
1085
1086
            $targetTableJoin = [
1087 19
                'table' => $targetTableName . ' ' . $targetTableAlias,
1088 19
                '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 61
        $withCondition = $condExpr === null ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1096
1097 61
        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 4
            $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 4
            if ($withCondition) {
1102 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1103
            } else {
1104 4
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1105
            }
1106
        } else {
1107 57
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1108
        }
1109
1110 61
        if ($withCondition) {
1111 5
            $sql .= ' AND ' . $withCondition;
1112
        }
1113
1114
        // Apply the indexes
1115 61
        if ($indexBy) {
1116
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1117
            $this->walkIndexBy($indexBy);
1118 61
        } elseif ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy()) {
1119
            $this->rsm->addIndexBy($joinedDqlAlias, $association->getIndexedBy());
1120
        }
1121
1122 61
        return $sql;
1123
    }
1124
1125
    /**
1126
     * {@inheritdoc}
1127
     */
1128 82
    public function walkFunction($function)
1129
    {
1130 82
        return $function->getSql($this);
1131
    }
1132
1133
    /**
1134
     * {@inheritdoc}
1135
     */
1136 37
    public function walkOrderByClause($orderByClause)
1137
    {
1138 37
        $orderByItems           = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1139 36
        $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems();
1140
1141 36
        if ($collectionOrderByItems !== '') {
1142
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1143
        }
1144
1145 36
        return ' ORDER BY ' . implode(', ', $orderByItems);
1146
    }
1147
1148
    /**
1149
     * {@inheritdoc}
1150
     */
1151 47
    public function walkOrderByItem($orderByItem)
1152
    {
1153 47
        $type = strtoupper($orderByItem->type);
1154 47
        $expr = $orderByItem->expression;
1155 47
        $sql  = $expr instanceof AST\Node
1156 42
            ? $expr->dispatch($this)
1157 46
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1158
1159 46
        $this->orderedColumnsMap[$sql] = $type;
1160
1161 46
        if ($expr instanceof AST\Subselect) {
1162 2
            return '(' . $sql . ') ' . $type;
1163
        }
1164
1165 44
        return $sql . ' ' . $type;
1166
    }
1167
1168
    /**
1169
     * {@inheritdoc}
1170
     */
1171 10
    public function walkHavingClause($havingClause)
1172
    {
1173 10
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1174
    }
1175
1176
    /**
1177
     * {@inheritdoc}
1178
     */
1179 77
    public function walkJoin($join)
1180
    {
1181 77
        $joinType        = $join->joinType;
1182 77
        $joinDeclaration = $join->joinAssociationDeclaration;
1183
1184 77
        $sql = $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER
1185 21
            ? ' LEFT JOIN '
1186 77
            : ' INNER JOIN ';
1187
1188
        switch (true) {
1189 77
            case $joinDeclaration instanceof AST\RangeVariableDeclaration:
1190 15
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1191 15
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1192 15
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1193 15
                $conditions = [];
1194
1195 15
                if ($join->conditionalExpression) {
1196 13
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1197
                }
1198
1199 15
                $isUnconditionalJoin = empty($conditions);
1200 15
                $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 15
                    : ' ON ';
1203
1204 15
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, ! $isUnconditionalJoin);
1205
1206
                // Apply remaining inheritance restrictions
1207 15
                $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1208
1209 15
                if ($discrSql) {
1210 3
                    $conditions[] = $discrSql;
1211
                }
1212
1213
                // Apply the filters
1214 15
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1215
1216 15
                if ($filterExpr) {
1217
                    $conditions[] = $filterExpr;
1218
                }
1219
1220 15
                if ($conditions) {
1221 13
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1222
                }
1223
1224 15
                break;
1225
1226 63
            case $joinDeclaration instanceof AST\JoinAssociationDeclaration:
1227 63
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1228 61
                break;
1229
        }
1230
1231 75
        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 7
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1280
    {
1281 7
        $sql = 'CASE';
1282
1283 7
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1284 7
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1285 7
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1286
        }
1287
1288 7
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1289
1290 7
        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 284
    public function walkSelectExpression($selectExpression)
1318
    {
1319 284
        $sql    = '';
1320 284
        $expr   = $selectExpression->expression;
1321 284
        $hidden = $selectExpression->hiddenAliasResultVariable;
1322
1323
        switch (true) {
1324 284
            case $expr instanceof AST\PathExpression:
1325 73
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1326
                    throw QueryException::invalidPathExpression($expr);
1327
                }
1328
1329 73
                $fieldName   = $expr->field;
1330 73
                $dqlAlias    = $expr->identificationVariable;
1331 73
                $qComp       = $this->queryComponents[$dqlAlias];
1332 73
                $class       = $qComp['metadata'];
1333 73
                $property    = $class->getProperty($fieldName);
1334 73
                $columnAlias = $this->getSQLColumnAlias();
1335 73
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1336 73
                $col         = sprintf(
1337 73
                    '%s.%s',
1338 73
                    $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1339 73
                    $this->platform->quoteIdentifier($property->getColumnName())
1340
                );
1341
1342 73
                $sql .= sprintf(
1343 73
                    '%s AS %s',
1344 73
                    $property->getType()->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()),
1345 73
                    $columnAlias
1346
                );
1347
1348 73
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1349
1350 73
                if (! $hidden) {
1351 73
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $property->getType());
1352 73
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1353
                }
1354
1355 73
                break;
1356
1357 240
            case $expr instanceof AST\AggregateExpression:
1358 234
            case $expr instanceof AST\Functions\FunctionNode:
1359 209
            case $expr instanceof AST\SimpleArithmeticExpression:
1360 209
            case $expr instanceof AST\ArithmeticTerm:
1361 207
            case $expr instanceof AST\ArithmeticFactor:
1362 206
            case $expr instanceof AST\ParenthesisExpression:
1363 205
            case $expr instanceof AST\Literal:
1364 204
            case $expr instanceof AST\NullIfExpression:
1365 203
            case $expr instanceof AST\CoalesceExpression:
1366 202
            case $expr instanceof AST\GeneralCaseExpression:
1367 198
            case $expr instanceof AST\SimpleCaseExpression:
1368 70
                $columnAlias = $this->getSQLColumnAlias();
1369 70
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1370
1371 70
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1372
1373 70
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1374
1375 70
                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 70
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getType('string'));
1379
                }
1380 70
                break;
1381
1382 197
            case $expr instanceof AST\Subselect:
1383 12
                $columnAlias = $this->getSQLColumnAlias();
1384 12
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1385
1386 12
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1387
1388 12
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1389
1390 12
                if (! $hidden) {
1391
                    // We cannot resolve field type here; assume 'string'.
1392 11
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getType('string'));
1393
                }
1394 12
                break;
1395
1396 192
            case $expr instanceof AST\NewObjectExpression:
1397 1
                $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable);
1398 1
                break;
1399
1400
            default:
1401
                // IdentificationVariable or PartialObjectExpression
1402 191
                if ($expr instanceof AST\PartialObjectExpression) {
1403 5
                    $dqlAlias        = $expr->identificationVariable;
1404 5
                    $partialFieldSet = $expr->partialFieldSet;
1405
                } else {
1406 189
                    $dqlAlias        = $expr;
1407 189
                    $partialFieldSet = [];
1408
                }
1409
1410 191
                $queryComp   = $this->queryComponents[$dqlAlias];
1411 191
                $class       = $queryComp['metadata'];
1412 191
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1413
1414 191
                if (! isset($this->selectedClasses[$dqlAlias])) {
1415 191
                    $this->selectedClasses[$dqlAlias] = [
1416 191
                        'class'       => $class,
1417 191
                        'dqlAlias'    => $dqlAlias,
1418 191
                        'resultAlias' => $resultAlias,
1419
                    ];
1420
                }
1421
1422 191
                $sqlParts = [];
1423
1424
                // Select all fields from the queried class
1425 191
                foreach ($class->getPropertiesIterator() as $fieldName => $property) {
1426 191
                    if (! ($property instanceof FieldMetadata)) {
1427 163
                        continue;
1428
                    }
1429
1430 191
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet, true)) {
1431 4
                        continue;
1432
                    }
1433
1434 191
                    $columnAlias = $this->getSQLColumnAlias();
1435 191
                    $col         = sprintf(
1436 191
                        '%s.%s',
1437 191
                        $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1438 191
                        $this->platform->quoteIdentifier($property->getColumnName())
1439
                    );
1440
1441 191
                    $sqlParts[] = sprintf(
1442 191
                        '%s AS %s',
1443 191
                        $property->getType()->convertToPHPValueSQL($col, $this->platform),
1444 191
                        $columnAlias
1445
                    );
1446
1447 191
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1448
1449 191
                    $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 191
                if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1457 98
                    foreach ($class->getSubClasses() as $subClassName) {
1458 16
                        $subClass = $this->em->getClassMetadata($subClassName);
1459
1460 16
                        foreach ($subClass->getPropertiesIterator() as $fieldName => $property) {
1461 16
                            if (! ($property instanceof FieldMetadata)) {
1462 16
                                continue;
1463
                            }
1464
1465 16
                            if ($subClass->isInheritedProperty($fieldName) || ($partialFieldSet && ! in_array($fieldName, $partialFieldSet, true))) {
1466 16
                                continue;
1467
                            }
1468
1469 15
                            $columnAlias = $this->getSQLColumnAlias();
1470 15
                            $col         = sprintf(
1471 15
                                '%s.%s',
1472 15
                                $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1473 15
                                $this->platform->quoteIdentifier($property->getColumnName())
1474
                            );
1475
1476 15
                            $sqlParts[] = sprintf(
1477 15
                                '%s AS %s',
1478 15
                                $property->getType()->convertToPHPValueSQL($col, $this->platform),
1479 15
                                $columnAlias
1480
                            );
1481
1482 15
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1483
1484 15
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1485
                        }
1486
                    }
1487
                }
1488
1489 191
                $sql .= implode(', ', $sqlParts);
1490
        }
1491
1492 284
        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 27
    public function walkSubselect($subselect)
1507
    {
1508 27
        $useAliasesBefore  = $this->useSqlTableAliases;
1509 27
        $rootAliasesBefore = $this->rootAliases;
1510
1511 27
        $this->rootAliases        = []; // reset the rootAliases for the subselect
1512 27
        $this->useSqlTableAliases = true;
1513
1514 27
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1515 27
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1516 27
        $sql .= $this->walkWhereClause($subselect->whereClause);
1517
1518 27
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1519 27
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1520 27
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1521
1522 27
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1523 27
        $this->useSqlTableAliases = $useAliasesBefore;
1524
1525 27
        return $sql;
1526
    }
1527
1528
    /**
1529
     * {@inheritdoc}
1530
     */
1531 27
    public function walkSubselectFromClause($subselectFromClause)
1532
    {
1533 27
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1534 27
        $sqlParts               = [];
1535
1536 27
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1537 27
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1538
        }
1539
1540 27
        return ' FROM ' . implode(', ', $sqlParts);
1541
    }
1542
1543
    /**
1544
     * {@inheritdoc}
1545
     */
1546 27
    public function walkSimpleSelectClause($simpleSelectClause)
1547
    {
1548 27
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1549 27
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1550
    }
1551
1552
    /**
1553
     * @return string
1554
     */
1555 18
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1556
    {
1557 18
        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 1
    public function walkNewObject($newObjectExpression, $newObjectResultAlias = null)
1567
    {
1568 1
        $sqlSelectExpressions = [];
1569 1
        $objIndex             = $newObjectResultAlias ?: $this->newObjectCounter++;
1570
1571 1
        foreach ($newObjectExpression->args as $argIndex => $e) {
1572 1
            $resultAlias = $this->scalarResultCounter++;
1573 1
            $columnAlias = $this->getSQLColumnAlias();
1574 1
            $fieldType   = Type::getType('string');
1575
1576
            switch (true) {
1577 1
                case $e instanceof AST\NewObjectExpression:
1578
                    $sqlSelectExpressions[] = $e->dispatch($this);
1579
                    break;
1580
1581 1
                case $e instanceof AST\Subselect:
1582 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1583 1
                    break;
1584
1585 1
                case $e instanceof AST\PathExpression:
1586 1
                    $dqlAlias  = $e->identificationVariable;
1587 1
                    $qComp     = $this->queryComponents[$dqlAlias];
1588 1
                    $class     = $qComp['metadata'];
1589 1
                    $fieldType = $class->getProperty($e->field)->getType();
1590
1591 1
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1592 1
                    break;
1593
1594 1
                case $e instanceof AST\Literal:
1595
                    switch ($e->type) {
1596
                        case AST\Literal::BOOLEAN:
1597
                            $fieldType = Type::getType('boolean');
1598
                            break;
1599
1600
                        case AST\Literal::NUMERIC:
1601
                            $fieldType = Type::getType(is_float($e->value) ? 'float' : 'integer');
1602
                            break;
1603
                    }
1604
1605
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1606
                    break;
1607
1608
                default:
1609 1
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1610 1
                    break;
1611
            }
1612
1613 1
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1614 1
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1615
1616 1
            $this->rsm->newObjectMappings[$columnAlias] = [
1617 1
                'className' => $newObjectExpression->className,
1618 1
                'objIndex'  => $objIndex,
1619 1
                'argIndex'  => $argIndex,
1620
            ];
1621
        }
1622
1623 1
        return implode(', ', $sqlSelectExpressions);
1624
    }
1625
1626
    /**
1627
     * {@inheritdoc}
1628
     */
1629 27
    public function walkSimpleSelectExpression($simpleSelectExpression)
1630
    {
1631 27
        $expr = $simpleSelectExpression->expression;
1632 27
        $sql  = ' ';
1633
1634
        switch (true) {
1635 27
            case $expr instanceof AST\PathExpression:
1636 7
                $sql .= $this->walkPathExpression($expr);
1637 7
                break;
1638
1639 20
            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 20
            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 17
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1658
1659 17
                $columnAlias                        = $this->getSQLColumnAlias();
1660 17
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1661
1662 17
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1663 17
                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 27
        return $sql;
1675
    }
1676
1677
    /**
1678
     * {@inheritdoc}
1679
     */
1680 51
    public function walkAggregateExpression($aggExpression)
1681
    {
1682 51
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1683 51
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1684
    }
1685
1686
    /**
1687
     * {@inheritdoc}
1688
     */
1689 17
    public function walkGroupByClause($groupByClause)
1690
    {
1691 17
        $sqlParts = [];
1692
1693 17
        foreach ($groupByClause->groupByItems as $groupByItem) {
1694 17
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1695
        }
1696
1697 17
        return ' GROUP BY ' . implode(', ', $sqlParts);
1698
    }
1699
1700
    /**
1701
     * {@inheritdoc}
1702
     */
1703 17
    public function walkGroupByItem($groupByItem)
1704
    {
1705
        // StateFieldPathExpression
1706 17
        if (! is_string($groupByItem)) {
1707 11
            return $this->walkPathExpression($groupByItem);
1708
        }
1709
1710
        // ResultVariable
1711 6
        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 4
        $classMetadata = $this->queryComponents[$groupByItem]['metadata'];
1728 4
        $sqlParts      = [];
1729
1730 4
        foreach ($classMetadata->getPropertiesIterator() as $property) {
1731
            switch (true) {
1732 4
                case $property instanceof FieldMetadata:
1733 4
                    $type       = AST\PathExpression::TYPE_STATE_FIELD;
1734 4
                    $item       = new AST\PathExpression($type, $groupByItem, $property->getName());
1735 4
                    $item->type = $type;
1736
1737 4
                    $sqlParts[] = $this->walkPathExpression($item);
1738 4
                    break;
1739
1740 4
                case $property instanceof ToOneAssociationMetadata && $property->isOwningSide():
1741 2
                    $type       = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1742 2
                    $item       = new AST\PathExpression($type, $groupByItem, $property->getName());
1743 2
                    $item->type = $type;
1744
1745 2
                    $sqlParts[] = $this->walkPathExpression($item);
1746 2
                    break;
1747
            }
1748
        }
1749
1750 4
        return implode(', ', $sqlParts);
1751
    }
1752
1753
    /**
1754
     * {@inheritdoc}
1755
     */
1756 30
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1757
    {
1758 30
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1759 30
        $tableName = $class->getTableName();
1760 30
        $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 30
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1763
1764 30
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1765
1766 30
        return $sql;
1767
    }
1768
1769
    /**
1770
     * {@inheritdoc}
1771
     */
1772 20
    public function walkUpdateClause($updateClause)
1773
    {
1774 20
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1775 20
        $tableName = $class->getTableName();
1776 20
        $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 20
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1779 20
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1780
1781 20
        return $sql . ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1782
    }
1783
1784
    /**
1785
     * {@inheritdoc}
1786
     */
1787 20
    public function walkUpdateItem($updateItem)
1788
    {
1789 20
        $useTableAliasesBefore    = $this->useSqlTableAliases;
1790 20
        $this->useSqlTableAliases = false;
1791
1792 20
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1793 20
        $newValue = $updateItem->newValue;
1794
1795
        switch (true) {
1796 20
            case $newValue instanceof AST\Node:
1797 19
                $sql .= $newValue->dispatch($this);
1798 19
                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 20
        $this->useSqlTableAliases = $useTableAliasesBefore;
1810
1811 20
        return $sql;
1812
    }
1813
1814
    /**
1815
     * {@inheritdoc}
1816
     */
1817 331
    public function walkWhereClause($whereClause)
1818
    {
1819 331
        $condSql  = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1820 329
        $discrSql = $this->generateDiscriminatorColumnConditionSQL($this->rootAliases);
1821
1822 329
        if ($this->em->hasFilters()) {
1823 1
            $filterClauses = [];
1824 1
            foreach ($this->rootAliases as $dqlAlias) {
1825 1
                $class      = $this->queryComponents[$dqlAlias]['metadata'];
1826 1
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1827 1
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1828
1829 1
                if ($filterExpr) {
1830 1
                    $filterClauses[] = $filterExpr;
1831
                }
1832
            }
1833
1834 1
            if ($filterClauses) {
1835 1
                if ($condSql) {
1836
                    $condSql = '(' . $condSql . ') AND ';
1837
                }
1838
1839 1
                $condSql .= implode(' AND ', $filterClauses);
1840
            }
1841
        }
1842
1843 329
        if ($condSql) {
1844 174
            return ' WHERE ' . (! $discrSql ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1845
        }
1846
1847 177
        if ($discrSql) {
1848 10
            return ' WHERE ' . $discrSql;
1849
        }
1850
1851 170
        return '';
1852
    }
1853
1854
    /**
1855
     * {@inheritdoc}
1856
     */
1857 201
    public function walkConditionalExpression($condExpr)
1858
    {
1859
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1860
        // if only one ConditionalTerm is defined
1861 201
        if (! ($condExpr instanceof AST\ConditionalExpression)) {
1862 190
            return $this->walkConditionalTerm($condExpr);
1863
        }
1864
1865 21
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1866
    }
1867
1868
    /**
1869
     * {@inheritdoc}
1870
     */
1871 201
    public function walkConditionalTerm($condTerm)
1872
    {
1873
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1874
        // if only one ConditionalFactor is defined
1875 201
        if (! ($condTerm instanceof AST\ConditionalTerm)) {
1876 185
            return $this->walkConditionalFactor($condTerm);
1877
        }
1878
1879 32
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1880
    }
1881
1882
    /**
1883
     * {@inheritdoc}
1884
     */
1885 201
    public function walkConditionalFactor($factor)
1886
    {
1887
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1888
        // if only one ConditionalPrimary is defined
1889 201
        return ! ($factor instanceof AST\ConditionalFactor)
1890 198
            ? $this->walkConditionalPrimary($factor)
1891 199
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1892
    }
1893
1894
    /**
1895
     * {@inheritdoc}
1896
     */
1897 201
    public function walkConditionalPrimary($primary)
1898
    {
1899 201
        if ($primary->isSimpleConditionalExpression()) {
1900 201
            return $primary->simpleConditionalExpression->dispatch($this);
1901
        }
1902
1903 20
        if ($primary->isConditionalExpression()) {
1904 20
            $condExpr = $primary->conditionalExpression;
1905
1906 20
            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 6
    public function walkCollectionMemberExpression($collMemberExpr)
1928
    {
1929 6
        $sql  = $collMemberExpr->not ? 'NOT ' : '';
1930 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1931
1932 6
        $entityExpr   = $collMemberExpr->entityExpression;
1933 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1934
1935 6
        $fieldName = $collPathExpr->field;
1936 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1937
1938 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1939
1940
        switch (true) {
1941
            // InputParameter
1942 6
            case $entityExpr instanceof AST\InputParameter:
1943 4
                $dqlParamKey = $entityExpr->name;
1944 4
                $entitySql   = '?';
1945 4
                break;
1946
1947
            // SingleValuedAssociationPathExpression | IdentificationVariable
1948 2
            case $entityExpr instanceof AST\PathExpression:
1949 2
                $entitySql = $this->walkPathExpression($entityExpr);
1950 2
                break;
1951
1952
            default:
1953
                throw new BadMethodCallException('Not implemented');
1954
        }
1955
1956 6
        $association       = $class->getProperty($fieldName);
1957 6
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
1958 6
        $owningAssociation = $association->isOwningSide()
1959 5
            ? $association
1960 6
            : $targetClass->getProperty($association->getMappedBy());
1961
1962 6
        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 5
            $joinTable        = $owningAssociation->getJoinTable();
1995 5
            $joinTableName    = $joinTable->getQuotedQualifiedName($this->platform);
1996 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable->getName());
1997 5
            $targetTableName  = $targetClass->table->getQuotedQualifiedName($this->platform);
1998 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1999 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
2000
2001
            // join to target table
2002 5
            $sql .= $joinTableName . ' ' . $joinTableAlias . ' INNER JOIN ' . $targetTableName . ' ' . $targetTableAlias . ' ON ';
2003
2004
            // join conditions
2005 5
            $joinSqlParts = [];
2006 5
            $joinColumns  = $association->isOwningSide()
2007 5
                ? $joinTable->getInverseJoinColumns()
2008 5
                : $joinTable->getJoinColumns();
2009
2010 5
            foreach ($joinColumns as $joinColumn) {
2011 5
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
2012 5
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
2013
2014 5
                $joinSqlParts[] = sprintf(
2015 5
                    '%s.%s = %s.%s',
2016 5
                    $joinTableAlias,
2017 5
                    $quotedColumnName,
2018 5
                    $targetTableAlias,
2019 5
                    $quotedReferencedColumnName
2020
                );
2021
            }
2022
2023 5
            $sql .= implode(' AND ', $joinSqlParts);
2024 5
            $sql .= ' WHERE ';
2025
2026 5
            $sqlParts    = [];
2027 5
            $joinColumns = $association->isOwningSide()
2028 5
                ? $joinTable->getJoinColumns()
2029 5
                : $joinTable->getInverseJoinColumns();
2030
2031 5
            foreach ($joinColumns as $joinColumn) {
2032 5
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
2033 5
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
2034
2035 5
                $sqlParts[] = sprintf(
2036 5
                    '%s.%s = %s.%s',
2037 5
                    $joinTableAlias,
2038 5
                    $quotedColumnName,
2039 5
                    $sourceTableAlias,
2040 5
                    $quotedReferencedColumnName
2041
                );
2042
            }
2043
2044 5
            foreach ($targetClass->getIdentifierColumns($this->em) as $targetColumn) {
2045 5
                $quotedTargetColumnName = $this->platform->quoteIdentifier($targetColumn->getColumnName());
2046
2047 5
                if (isset($dqlParamKey)) {
2048 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
2049
                }
2050
2051 5
                $sqlParts[] = $targetTableAlias . '.' . $quotedTargetColumnName . ' = ' . $entitySql;
2052
            }
2053
2054 5
            $sql .= implode(' AND ', $sqlParts);
2055
        }
2056
2057 6
        return $sql . ')';
2058
    }
2059
2060
    /**
2061
     * {@inheritdoc}
2062
     */
2063 1
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
2064
    {
2065 1
        $sizeFunc                           = new AST\Functions\SizeFunction('size');
2066 1
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
2067
2068 1
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
2069
    }
2070
2071
    /**
2072
     * {@inheritdoc}
2073
     */
2074 10
    public function walkNullComparisonExpression($nullCompExpr)
2075
    {
2076 10
        $expression = $nullCompExpr->expression;
2077 10
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2078
2079
        // Handle ResultVariable
2080 10
        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 8
        if ($expression instanceof AST\InputParameter) {
2086
            return $this->walkInputParameter($expression) . $comparison;
2087
        }
2088
2089 8
        return $expression->dispatch($this) . $comparison;
2090
    }
2091
2092
    /**
2093
     * {@inheritdoc}
2094
     */
2095 31
    public function walkInExpression($inExpr)
2096
    {
2097 31
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2098
2099 30
        $sql .= $inExpr->subselect
2100 6
            ? $this->walkSubselect($inExpr->subselect)
2101 30
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2102
2103 30
        $sql .= ')';
2104
2105 30
        return $sql;
2106
    }
2107
2108
    /**
2109
     * {@inheritdoc}
2110
     *
2111
     * @throws QueryException
2112
     */
2113 7
    public function walkInstanceOfExpression($instanceOfExpr)
2114
    {
2115 7
        $dqlAlias         = $instanceOfExpr->identificationVariable;
2116 7
        $class            = $this->queryComponents[$dqlAlias]['metadata'];
2117 7
        $discrClass       = $this->em->getClassMetadata($class->getRootClassName());
2118 7
        $discrColumn      = $class->discriminatorColumn;
2119 7
        $discrColumnType  = $discrColumn->getType();
2120 7
        $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
2121 7
        $sqlTableAlias    = $this->useSqlTableAliases
2122 7
            ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
2123 7
            : '';
2124
2125 7
        return sprintf(
2126 7
            '%s %sIN %s',
2127 7
            $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
2128 7
            ($instanceOfExpr->not ? 'NOT ' : ''),
2129 7
            $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr)
2130
        );
2131
    }
2132
2133
    /**
2134
     * {@inheritdoc}
2135
     */
2136 24
    public function walkInParameter($inParam)
2137
    {
2138 24
        return $inParam instanceof AST\InputParameter
2139 17
            ? $this->walkInputParameter($inParam)
2140 24
            : $this->walkLiteral($inParam);
2141
    }
2142
2143
    /**
2144
     * {@inheritdoc}
2145
     */
2146 102
    public function walkLiteral($literal)
2147
    {
2148 102
        switch ($literal->type) {
2149 102
            case AST\Literal::STRING:
2150 37
                return $this->conn->quote($literal->value);
2151 74
            case AST\Literal::BOOLEAN:
2152 6
                return $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true');
2153 68
            case AST\Literal::NUMERIC:
2154 68
                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 150
    public function walkComparisonExpression($compExpr)
2218
    {
2219 150
        $leftExpr  = $compExpr->leftExpression;
2220 150
        $rightExpr = $compExpr->rightExpression;
2221 150
        $sql       = '';
2222
2223 150
        $sql .= $leftExpr instanceof AST\Node
2224 150
            ? $leftExpr->dispatch($this)
2225 150
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2226
2227 150
        $sql .= ' ' . $compExpr->operator . ' ';
2228
2229 150
        $sql .= $rightExpr instanceof AST\Node
2230 148
            ? $rightExpr->dispatch($this)
2231 150
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2232
2233 150
        return $sql;
2234
    }
2235
2236
    /**
2237
     * {@inheritdoc}
2238
     */
2239 90
    public function walkInputParameter($inputParam)
2240
    {
2241 90
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2242
2243 90
        $parameter = $this->query->getParameter($inputParam->name);
2244
2245 90
        if ($parameter) {
2246 12
            $type = $parameter->getType();
2247
2248 12
            if (Type::hasType($type)) {
2249 11
                return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2250
            }
2251
        }
2252
2253 80
        return '?';
2254
    }
2255
2256
    /**
2257
     * {@inheritdoc}
2258
     */
2259 169
    public function walkArithmeticExpression($arithmeticExpr)
2260
    {
2261 169
        return $arithmeticExpr->isSimpleArithmeticExpression()
2262 169
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
2263 168
            : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
2264
    }
2265
2266
    /**
2267
     * {@inheritdoc}
2268
     */
2269 206
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2270
    {
2271 206
        if (! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2272 201
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2273
        }
2274
2275 25
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2276
    }
2277
2278
    /**
2279
     * {@inheritdoc}
2280
     */
2281 210
    public function walkArithmeticTerm($term)
2282
    {
2283 210
        if (is_string($term)) {
2284 17
            return isset($this->queryComponents[$term])
2285 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2286 17
                : $term;
2287
        }
2288
2289
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2290
        // if only one ArithmeticFactor is defined
2291 209
        if (! ($term instanceof AST\ArithmeticTerm)) {
2292 205
            return $this->walkArithmeticFactor($term);
2293
        }
2294
2295 22
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2296
    }
2297
2298
    /**
2299
     * {@inheritdoc}
2300
     */
2301 210
    public function walkArithmeticFactor($factor)
2302
    {
2303 210
        if (is_string($factor)) {
2304 22
            return isset($this->queryComponents[$factor])
2305 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2306 22
                : $factor;
2307
        }
2308
2309
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2310
        // if only one ArithmeticPrimary is defined
2311 210
        if (! ($factor instanceof AST\ArithmeticFactor)) {
2312 209
            return $this->walkArithmeticPrimary($factor);
2313
        }
2314
2315 2
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2316
2317 2
        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 210
    public function walkArithmeticPrimary($primary)
2328
    {
2329 210
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2330
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2331
        }
2332
2333 210
        if ($primary instanceof AST\Node) {
2334 210
            return $primary->dispatch($this);
2335
        }
2336
2337
        return $this->walkEntityIdentificationVariable($primary);
2338
    }
2339
2340
    /**
2341
     * {@inheritdoc}
2342
     */
2343 18
    public function walkStringPrimary($stringPrimary)
2344
    {
2345 18
        return is_string($stringPrimary)
2346
            ? $this->conn->quote($stringPrimary)
2347 18
            : $stringPrimary->dispatch($this);
2348
    }
2349
2350
    /**
2351
     * {@inheritdoc}
2352
     */
2353 27
    public function walkResultVariable($resultVariable)
2354
    {
2355 27
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2356
2357 27
        if (is_array($resultAlias)) {
2358 1
            return implode(', ', $resultAlias);
2359
        }
2360
2361 26
        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 7
    private function getChildDiscriminatorsFromClassMetadata(ClassMetadata $rootClass, AST\InstanceOfExpression $instanceOfExpr) : string
2370
    {
2371 7
        $sqlParameterList = [];
2372 7
        $discriminators   = [];
2373
2374 7
        foreach ($instanceOfExpr->value as $parameter) {
2375 7
            if ($parameter instanceof AST\InputParameter) {
2376 1
                $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
2377
2378 1
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2379
2380 1
                continue;
2381
            }
2382
2383
            // Get name from ClassMetadata to resolve aliases.
2384 6
            $entityClass     = $this->em->getClassMetadata($parameter);
2385 6
            $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 6
            if ($entityClassName !== $rootClass->getClassName()) {
2388 6
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2389 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2390
                }
2391
            }
2392
2393 5
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2394
        }
2395
2396 6
        foreach (array_keys($discriminators) as $discriminator) {
2397 5
            $sqlParameterList[] = $this->conn->quote($discriminator);
2398
        }
2399
2400 6
        return '(' . implode(', ', $sqlParameterList) . ')';
2401
    }
2402
}
2403