SqlWalker::walkSelectStatement()   F
last analyzed

Complexity

Conditions 16
Paths 336

Size

Total Lines 56
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 16.0686

Importance

Changes 0
Metric Value
cc 16
eloc 30
nc 336
nop 1
dl 0
loc 56
ccs 29
cts 31
cp 0.9355
crap 16.0686
rs 3.0333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use 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 711
    public function __construct(AbstractQuery $query, ParserResult $parserResult, array $queryComponents)
168
    {
169 711
        $this->query           = $query;
170 711
        $this->parserResult    = $parserResult;
171 711
        $this->queryComponents = $queryComponents;
172 711
        $this->rsm             = $parserResult->getResultSetMapping();
173 711
        $this->em              = $query->getEntityManager();
174 711
        $this->conn            = $this->em->getConnection();
175 711
        $this->platform        = $this->conn->getDatabasePlatform();
176 711
    }
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 49
    public function getConnection()
194
    {
195 49
        return $this->conn;
196
    }
197
198
    /**
199
     * Gets the EntityManager used by the walker.
200
     *
201
     * @return EntityManagerInterface
202
     */
203 23
    public function getEntityManager()
204
    {
205 23
        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 18
    public function getQueryComponent($dqlAlias)
216
    {
217 18
        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 705
    public function getExecutor($AST)
246
    {
247
        switch (true) {
248 705
            case ($AST instanceof AST\DeleteStatement):
249 39
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
250
251 39
                return ($primaryClass->inheritanceType === InheritanceType::JOINED)
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
252 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
253 39
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
254
255 670
            case ($AST instanceof AST\UpdateStatement):
256 28
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
257
258 28
                return ($primaryClass->inheritanceType === InheritanceType::JOINED)
259 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
260 28
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
261
262
            default:
263 646
                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 657
    public function getSQLTableAlias($tableName, $dqlAlias = '')
276
    {
277 657
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
278
279 657
        if (! isset($this->tableAliasMap[$tableName])) {
280 657
            $this->tableAliasMap[$tableName] = 't' . $this->tableAliasCounter++;
281
        }
282
283 657
        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 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
297
    {
298 65
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
299
300 65
        $this->tableAliasMap[$tableName] = $alias;
301
302 65
        return $alias;
303
    }
304
305
    /**
306
     * Gets an SQL column alias for a column name.
307
     *
308
     * @return string
309
     */
310 646
    public function getSQLColumnAlias()
311
    {
312 646
        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 113
    private function generateClassTableInheritanceJoins($class, $dqlAlias)
325
    {
326 113
        $sql = '';
327
328 113
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
329
330
        // INNER JOIN parent class tables
331 113
        $parentClass = $class;
332
333 113
        while (($parentClass = $parentClass->getParent()) !== null) {
334 78
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
335 78
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\ComponentMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\ComponentMetadata such as Doctrine\ORM\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

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

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

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

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

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 41
                $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
376
377 41
                $sqlParts[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
378
            }
379
380 41
            $sql .= implode(' AND ', $sqlParts);
381
        }
382
383 92
        return $sql;
384
    }
385
386
    /**
387
     * @return string
388
     */
389 640
    private function generateOrderedCollectionOrderByItems()
390
    {
391 640
        $orderedColumns = [];
392
393 640
        foreach ($this->selectedClasses as $selectedClass) {
394 487
            $dqlAlias    = $selectedClass['dqlAlias'];
395 487
            $qComp       = $this->queryComponents[$dqlAlias];
396 487
            $association = $qComp['relation'];
397
398 487
            if (! ($association instanceof ToManyAssociationMetadata)) {
399 487
                continue;
400
            }
401
402 98
            foreach ($association->getOrderBy() as $fieldName => $orientation) {
403 6
                $property      = $qComp['metadata']->getProperty($fieldName);
404 6
                $tableName     = $property->getTableName();
405 6
                $columnName    = $this->platform->quoteIdentifier($property->getColumnName());
406 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
407
408
                // OrderByClause should replace an ordered relation. see - DDC-2475
409 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
410 1
                    continue;
411
                }
412
413 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
414 98
                $orderedColumns[]                        = $orderedColumn . ' ' . $orientation;
415
            }
416
        }
417
418 640
        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 700
    private function generateDiscriminatorColumnConditionSQL(array $dqlAliases)
429
    {
430 700
        $sqlParts = [];
431
432 700
        foreach ($dqlAliases as $dqlAlias) {
433 700
            $class = $this->queryComponents[$dqlAlias]['metadata'];
434
435 700
            if ($class->inheritanceType !== InheritanceType::SINGLE_TABLE) {
436 674
                continue;
437
            }
438
439 41
            $conn   = $this->em->getConnection();
440 41
            $values = [];
441
442 41
            if ($class->discriminatorValue !== null) { // discriminators can be 0
443 21
                $values[] = $conn->quote($class->discriminatorValue);
444
            }
445
446 41
            foreach ($class->getSubClasses() as $subclassName) {
447 30
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
0 ignored issues
show
Bug introduced by
Accessing discriminatorValue on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
448
            }
449
450 41
            $discrColumn      = $class->discriminatorColumn;
451 41
            $discrColumnType  = $discrColumn->getType();
452 41
            $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
453 41
            $sqlTableAlias    = ($this->useSqlTableAliases)
454 36
                ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
455 41
                : '';
456
457 41
            $sqlParts[] = sprintf(
458 41
                '%s IN (%s)',
459 41
                $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
460 41
                implode(', ', $values)
461
            );
462
        }
463
464 700
        $sql = implode(' AND ', $sqlParts);
465
466 700
        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 330
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
478
    {
479 330
        if (! $this->em->hasFilters()) {
480 294
            return '';
481
        }
482
483 41
        switch ($targetEntity->inheritanceType) {
484 41
            case InheritanceType::NONE:
485 31
                break;
486
487 10
            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 6
                if ($targetEntity->getClassName() !== $targetEntity->getRootClassName()) {
491 4
                    return '';
492
                }
493 6
                break;
494
495 4
            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 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->getRootClassName());
499 4
                break;
500
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
        }
505
506 41
        $filterClauses = [];
507
508 41
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
509 10
            $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
510
511 10
            if ($filterExpr !== '') {
512 10
                $filterClauses[] = '(' . $filterExpr . ')';
513
            }
514
        }
515
516 41
        return implode(' AND ', $filterClauses);
517
    }
518
519
    /**
520
     * {@inheritdoc}
521
     */
522 646
    public function walkSelectStatement(AST\SelectStatement $AST)
523
    {
524 646
        $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

524
        /** @scrutinizer ignore-call */ 
525
        $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...
525 646
        $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

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

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

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

}

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

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

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

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

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

806
                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...
807
                    // Skip if association is inherited
808 38
                    if ($subClass->isInheritedProperty($association->getName())) {
0 ignored issues
show
Bug introduced by
The method isInheritedProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

808
                    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...
809 38
                        continue;
810
                    }
811
812 28
                    if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())) {
813 27
                        continue;
814
                    }
815
816 14
                    $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
817
818 14
                    foreach ($association->getJoinColumns() as $joinColumn) {
819
                        /** @var JoinColumnMetadata $joinColumn */
820 14
                        $columnName           = $joinColumn->getColumnName();
821 14
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
822 14
                        $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
823 14
                        $columnAlias          = $this->getSQLColumnAlias();
824 14
                        $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
825
826 14
                        if (! $joinColumn->getType()) {
827 1
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
828
                        }
829
830 14
                        $sqlSelectExpressions[] = sprintf(
831 14
                            '%s.%s AS %s',
832 14
                            $sqlTableAlias,
833 14
                            $quotedColumnName,
834 14
                            $columnAlias
835
                        );
836
837 355
                        $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
838
                    }
839
                }
840
            }
841
        }
842
843 646
        $sql .= implode(', ', $sqlSelectExpressions);
844
845 646
        return $sql;
846
    }
847
848
    /**
849
     * {@inheritdoc}
850
     */
851 648
    public function walkFromClause($fromClause)
852
    {
853 648
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
854 648
        $sqlParts               = [];
855
856 648
        foreach ($identificationVarDecls as $identificationVariableDecl) {
857 648
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
858
        }
859
860 646
        return ' FROM ' . implode(', ', $sqlParts);
861
    }
862
863
    /**
864
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
865
     *
866
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
867
     *
868
     * @return string
869
     */
870 649
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
871
    {
872 649
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
873
874 649
        if ($identificationVariableDecl->indexBy) {
875 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
876
        }
877
878 649
        foreach ($identificationVariableDecl->joins as $join) {
879 245
            $sql .= $this->walkJoin($join);
880
        }
881
882 647
        return $sql;
883
    }
884
885
    /**
886
     * Walks down a IndexBy AST node.
887
     *
888
     * @param AST\IndexBy $indexBy
889
     */
890 8
    public function walkIndexBy($indexBy)
891
    {
892 8
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
893 8
        $alias          = $pathExpression->identificationVariable;
894 8
        $field          = $pathExpression->field;
895
896 8
        if (isset($this->scalarFields[$alias][$field])) {
897
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
898
899
            return;
900
        }
901
902 8
        $this->rsm->addIndexBy($alias, $field);
903 8
    }
904
905
    /**
906
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
907
     *
908
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
909
     *
910
     * @return string
911
     */
912 649
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
913
    {
914 649
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
915
    }
916
917
    /**
918
     * Generate appropriate SQL for RangeVariableDeclaration AST node
919
     *
920
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
921
     */
922 649
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
923
    {
924 649
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
925 649
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
926
927 649
        if ($rangeVariableDeclaration->isRoot) {
928 649
            $this->rootAliases[] = $dqlAlias;
929
        }
930
931 649
        $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...
932 649
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
933
934 649
        $sql = $this->platform->appendLockHint(
935 649
            $tableName . ' ' . $tableAlias,
936 649
            $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

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

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

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

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