Passed
Pull Request — master (#7641)
by
unknown
12:21
created

SqlWalker::walkCoalesceExpression()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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

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

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

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

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

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

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

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

522
        /** @scrutinizer ignore-call */ 
523
        $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...
523 652
        $offset   = $this->query->getFirstResult();
0 ignored issues
show
Bug introduced by
The method getFirstResult() does not exist on Doctrine\ORM\AbstractQuery. It seems like you code against a sub-type of Doctrine\ORM\AbstractQuery such as Doctrine\ORM\Query. ( Ignorable by Annotation )

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

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

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

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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