Failed Conditions
Pull Request — master (#7242)
by Gabriel
08:46
created

SqlWalker::generateClassTableInheritanceJoins()   B

Complexity

Conditions 8
Paths 36

Size

Total Lines 60
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8.0023

Importance

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

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

335
            $tableAlias = $this->getSQLTableAlias($parentClass->/** @scrutinizer ignore-call */ getTableName(), $dqlAlias);
Loading history...
336
337
            // If this is a joined association we must use left joins to preserve the correct result.
338 27
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
339 27
            $sql .= 'JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
340
341 27
            $sqlParts = [];
342
343 27
            foreach ($class->getIdentifierColumns($this->em) as $column) {
344 27
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
345
346 27
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
347
            }
348
349 27
            $filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias);
350
351
            // Add filters on the root class
352 27
            if ($filterSql) {
353
                $sqlParts[] = $filterSql;
354
            }
355
356 27
            $sql .= implode(' AND ', $sqlParts);
357
        }
358
359
        // Ignore subclassing inclusion if partial objects is disallowed
360 45
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
361 21
            return $sql;
362
        }
363
364
        // LEFT JOIN child class tables
365 24
        foreach ($class->getSubClasses() as $subClassName) {
366 16
            $subClass   = $this->em->getClassMetadata($subClassName);
367 16
            $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...
368 16
            $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

368
            $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...
369
370 16
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
371
372 16
            $sqlParts = [];
373
374 16
            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

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

526
        /** @scrutinizer ignore-call */ 
527
        $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...
527 323
        $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

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

808
                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...
809
                    // Skip if association is inherited
810 12
                    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

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

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

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

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

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

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

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

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

2415
            /** @scrutinizer ignore-call */ 
2416
            $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...
2416
2417 10
            if ($entityClassName !== $rootClass->getClassName()) {
2418 7
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2419 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2420
                }
2421
            }
2422
2423 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
2424
        }
2425
2426 11
        foreach (array_keys($discriminators) as $discriminator) {
2427 9
            $sqlParameterList[] = $this->conn->quote($discriminator);
2428
        }
2429
2430 11
        return '(' . implode(', ', $sqlParameterList) . ')';
2431
    }
2432
}
2433