Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

SqlWalker::getSQLTableAlias()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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

360
            $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...
361
362 34
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
363
364 34
            $sqlParts = [];
365
366 34
            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

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

501
            $filterExpr = $filter->addFilterConstraint(/** @scrutinizer ignore-type */ $targetEntity, $targetTableAlias);
Loading history...
502
503 10
            if ($filterExpr !== '') {
504 10
                $filterClauses[] = '(' . $filterExpr . ')';
505
            }
506
        }
507
508 42
        return implode(' AND ', $filterClauses);
509
    }
510
511
    /**
512
     * {@inheritdoc}
513
     */
514 636
    public function walkSelectStatement(AST\SelectStatement $AST)
515
    {
516 636
        $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

516
        /** @scrutinizer ignore-call */ 
517
        $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...
517 636
        $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

517
        /** @scrutinizer ignore-call */ 
518
        $offset   = $this->query->getFirstResult();
Loading history...
518 636
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
519 636
        $sql      = $this->walkSelectClause($AST->selectClause)
520 636
            . $this->walkFromClause($AST->fromClause)
521 634
            . $this->walkWhereClause($AST->whereClause);
522
523 631
        if ($AST->groupByClause) {
524 25
            $sql .= $this->walkGroupByClause($AST->groupByClause);
525
        }
526
527 631
        if ($AST->havingClause) {
528 14
            $sql .= $this->walkHavingClause($AST->havingClause);
529
        }
530
531 631
        if ($AST->orderByClause) {
532 145
            $sql .= $this->walkOrderByClause($AST->orderByClause);
533
        }
534
535 630
        if (! $AST->orderByClause) {
536 516
            $orderBySql = $this->generateOrderedCollectionOrderByItems();
537
538 516
            if ($orderBySql) {
539 6
                $sql .= ' ORDER BY ' . $orderBySql;
540
            }
541
        }
542
543 630
        if ($limit !== null || $offset !== null) {
544 50
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
545
        }
546
547 630
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
548 625
            return $sql;
549
        }
550
551 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
552 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
553
        }
554
555 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
556 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
557
        }
558
559 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
560
            throw QueryException::invalidLockMode();
561
        }
562
563 1
        foreach ($this->selectedClasses as $selectedClass) {
564 1
            if (! $selectedClass['class']->isVersioned()) {
565 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->getClassName());
566
            }
567
        }
568
569
        return $sql;
570
    }
571
572
    /**
573
     * {@inheritdoc}
574
     */
575
    public function walkUpdateStatement(AST\UpdateStatement $AST)
576
    {
577
        $this->useSqlTableAliases = false;
578
        $this->rsm->isSelect      = false;
579
580
        return $this->walkUpdateClause($AST->updateClause)
581
            . $this->walkWhereClause($AST->whereClause);
582
    }
583
584
    /**
585
     * {@inheritdoc}
586
     */
587
    public function walkDeleteStatement(AST\DeleteStatement $AST)
588
    {
589
        $this->useSqlTableAliases = false;
590
        $this->rsm->isSelect      = false;
591
592
        return $this->walkDeleteClause($AST->deleteClause)
593
            . $this->walkWhereClause($AST->whereClause);
594
    }
595
596
    /**
597
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
598
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
599
     *
600
     * @param string $identVariable
601
     *
602
     * @return string
603
     */
604 2
    public function walkEntityIdentificationVariable($identVariable)
605
    {
606 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
607 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
608 2
        $sqlParts   = [];
609
610 2
        foreach ($class->getIdentifierColumns($this->em) as $column) {
611 2
            $quotedColumnName = $this->platform->quoteIdentifier($column->getColumnName());
612
613 2
            $sqlParts[] = $tableAlias . '.' . $quotedColumnName;
614
        }
615
616 2
        return implode(', ', $sqlParts);
617
    }
618
619
    /**
620
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
621
     *
622
     * @param string $identificationVariable
623
     * @param string $fieldName
624
     *
625
     * @return string The SQL.
626
     */
627 421
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
628
    {
629 421
        $class = $this->queryComponents[$identificationVariable]['metadata'];
630
631 421
        if (! $fieldName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldName of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
632
            return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
633
        }
634
635 421
        $property = $class->getProperty($fieldName);
636
637 421
        if ($class->inheritanceType === InheritanceType::JOINED && $class->isInheritedProperty($fieldName)) {
638 34
            $class = $property->getDeclaringClass();
639
        }
640
641 421
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
642
    }
643
644
    /**
645
     * {@inheritdoc}
646
     */
647 438
    public function walkPathExpression($pathExpr)
648
    {
649 438
        $sql = '';
650
651
        /* @var $pathExpr Query\AST\PathExpression */
652 438
        switch ($pathExpr->type) {
653 438
            case AST\PathExpression::TYPE_STATE_FIELD:
654 421
                $fieldName = $pathExpr->field;
655 421
                $dqlAlias  = $pathExpr->identificationVariable;
656 421
                $class     = $this->queryComponents[$dqlAlias]['metadata'];
657 421
                $property  = $class->getProperty($fieldName);
658
659 421
                if ($this->useSqlTableAliases) {
660 421
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
661
                }
662
663 421
                $sql .= $this->platform->quoteIdentifier($property->getColumnName());
664 421
                break;
665
666 57
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
667
                // 1- the owning side:
668
                //    Just use the foreign key, i.e. u.group_id
669 57
                $fieldName   = $pathExpr->field;
670 57
                $dqlAlias    = $pathExpr->identificationVariable;
671 57
                $class       = $this->queryComponents[$dqlAlias]['metadata'];
672 57
                $association = $class->getProperty($fieldName);
673
674 57
                if (! $association->isOwningSide()) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
676
                }
677
678 55
                $joinColumns = $association->getJoinColumns();
679
680
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
681 55
                if (count($joinColumns) > 1) {
682 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
683
                }
684
685 54
                $joinColumn = reset($joinColumns);
686
687 54
                if ($this->useSqlTableAliases) {
688 54
                    $sql .= $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias) . '.';
689
                }
690
691 54
                $sql .= $this->platform->quoteIdentifier($joinColumn->getColumnName());
692 54
                break;
693
694
            default:
695
                throw QueryException::invalidPathExpression($pathExpr);
696
        }
697
698 435
        return $sql;
699
    }
700
701
    /**
702
     * {@inheritdoc}
703
     */
704 636
    public function walkSelectClause($selectClause)
705
    {
706 636
        $sql                  = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
707 636
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
708
709 636
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true && $selectClause->isDistinct) {
710 1
            $this->query->setHint(self::HINT_DISTINCT, true);
711
        }
712
713
        $addMetaColumns = (
714 636
            ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
715 467
            $this->query->getHydrationMode() === Query::HYDRATE_OBJECT
716
        ) || (
717 299
            $this->query->getHydrationMode() !== Query::HYDRATE_OBJECT &&
718 636
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS)
719
        );
720
721 636
        foreach ($this->selectedClasses as $selectedClass) {
722 487
            $class       = $selectedClass['class'];
723 487
            $dqlAlias    = $selectedClass['dqlAlias'];
724 487
            $resultAlias = $selectedClass['resultAlias'];
725
726
            // Register as entity or joined entity result
727 487
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
728 487
                $this->rsm->addEntityResult($class->getClassName(), $dqlAlias, $resultAlias);
729
            } else {
730 157
                $this->rsm->addJoinedEntityResult(
731 157
                    $class->getClassName(),
732 157
                    $dqlAlias,
733 157
                    $this->queryComponents[$dqlAlias]['parent'],
734 157
                    $this->queryComponents[$dqlAlias]['relation']->getName()
735
                );
736
            }
737
738 487
            if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || $class->inheritanceType === InheritanceType::JOINED) {
739
                // Add discriminator columns to SQL
740 98
                $discrColumn      = $class->discriminatorColumn;
741 98
                $discrColumnName  = $discrColumn->getColumnName();
742 98
                $discrColumnType  = $discrColumn->getType();
743 98
                $quotedColumnName = $this->platform->quoteIdentifier($discrColumnName);
744 98
                $sqlTableAlias    = $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias);
745 98
                $sqlColumnAlias   = $this->getSQLColumnAlias();
746
747 98
                $sqlSelectExpressions[] = sprintf(
748 98
                    '%s AS %s',
749 98
                    $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . '.' . $quotedColumnName, $this->platform),
750 98
                    $sqlColumnAlias
751
                );
752
753 98
                $this->rsm->setDiscriminatorColumn($dqlAlias, $sqlColumnAlias);
754 98
                $this->rsm->addMetaResult($dqlAlias, $sqlColumnAlias, $discrColumnName, false, $discrColumnType);
755
            }
756
757
            // Add foreign key columns of class and also parent classes
758 487
            foreach ($class->getDeclaredPropertiesIterator() as $association) {
759 487
                if (! ($association instanceof ToOneAssociationMetadata && $association->isOwningSide())
760 487
                    || ( ! $addMetaColumns && ! $association->isPrimaryKey())) {
761 486
                    continue;
762
                }
763
764 279
                $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
765
766 279
                foreach ($association->getJoinColumns() as $joinColumn) {
767
                    /** @var JoinColumnMetadata $joinColumn */
768 279
                    $columnName           = $joinColumn->getColumnName();
769 279
                    $referencedColumnName = $joinColumn->getReferencedColumnName();
770 279
                    $quotedColumnName     = $this->platform->quoteIdentifier($columnName);
771 279
                    $columnAlias          = $this->getSQLColumnAlias();
772 279
                    $sqlTableAlias        = $this->getSQLTableAlias($joinColumn->getTableName(), $dqlAlias);
773
774 279
                    if (! $joinColumn->getType()) {
775 20
                        $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

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

775
                        $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, /** @scrutinizer ignore-type */ $targetClass, $this->em));
Loading history...
776
                    }
777
778 279
                    $sqlSelectExpressions[] = sprintf(
779 279
                        '%s.%s AS %s',
780 279
                        $sqlTableAlias,
781 279
                        $quotedColumnName,
782 279
                        $columnAlias
783
                    );
784
785 279
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $association->isPrimaryKey(), $joinColumn->getType());
786
                }
787
            }
788
789
            // Add foreign key columns to SQL, if necessary
790 487
            if (! $addMetaColumns) {
791 182
                continue;
792
            }
793
794
            // Add foreign key columns of subclasses
795 350
            foreach ($class->getSubClasses() as $subClassName) {
796 37
                $subClass = $this->em->getClassMetadata($subClassName);
797
798 37
                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

798
                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...
799
                    // Skip if association is inherited
800 37
                    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

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

928
            /** @scrutinizer ignore-type */ $this->query->getHint(Query::HINT_LOCK_MODE)
Loading history...
929
        );
930
931 636
        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...
932 543
            return $sql;
933
        }
934
935 101
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Query\SqlWa...TableInheritanceJoins(). ( Ignorable by Annotation )

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

935
        $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins(/** @scrutinizer ignore-type */ $class, $dqlAlias);
Loading history...
936
937 101
        if (! $buildNestedJoins) {
938 99
            return $sql . $classTableInheritanceJoins;
939
        }
940
941 3
        return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
942
    }
943
944
    /**
945
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
946
     *
947
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
948
     * @param int                            $joinType
949
     * @param AST\ConditionalExpression      $condExpr
950
     *
951
     * @return string
952
     *
953
     * @throws QueryException
954
     */
955 230
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
956
    {
957 230
        $sql = '';
958
959 230
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
960 230
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
961 230
        $indexBy                   = $joinAssociationDeclaration->indexBy;
962
963 230
        $association     = $this->queryComponents[$joinedDqlAlias]['relation'];
964 230
        $targetClass     = $this->em->getClassMetadata($association->getTargetEntity());
965 230
        $sourceClass     = $this->em->getClassMetadata($association->getSourceEntity());
966 230
        $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...
967
968 230
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
969 230
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
970
971
        // Ensure we got the owning side, since it has all mapping info
972 230
        $owningAssociation = ! $association->isOwningSide()
973 120
            ? $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

973
            ? $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...
974 230
            : $association
975
        ;
976
977 230
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === true &&
978 230
            (! $this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
979 3
            if ($association instanceof ToManyAssociationMetadata) {
980 2
                throw QueryException::iterateWithFetchJoinNotAllowed($owningAssociation);
981
            }
982
        }
983
984 228
        $targetTableJoin = null;
985
986
        // This condition is not checking ManyToOneAssociationMetadata, because by definition it cannot
987
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
988
        // The owning side is necessary at this point because only it contains the JoinColumn information.
989 228
        if ($owningAssociation instanceof ToOneAssociationMetadata) {
990 181
            $conditions = [];
991
992 181
            foreach ($owningAssociation->getJoinColumns() as $joinColumn) {
993 181
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
994 181
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
995
996 181
                if ($association->isOwningSide()) {
997 105
                    $conditions[] = sprintf(
998 105
                        '%s.%s = %s.%s',
999 105
                        $sourceTableAlias,
1000 105
                        $quotedColumnName,
1001 105
                        $targetTableAlias,
1002 105
                        $quotedReferencedColumnName
1003
                    );
1004
1005 105
                    continue;
1006
                }
1007
1008 109
                $conditions[] = sprintf(
1009 109
                    '%s.%s = %s.%s',
1010 109
                    $sourceTableAlias,
1011 109
                    $quotedReferencedColumnName,
1012 109
                    $targetTableAlias,
1013 109
                    $quotedColumnName
1014
                );
1015
            }
1016
1017
            // Apply remaining inheritance restrictions
1018 181
            $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1019
1020 181
            if ($discrSql) {
1021 3
                $conditions[] = $discrSql;
1022
            }
1023
1024
            // Apply the filters
1025 181
            $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $targetEntity of Doctrine\ORM\Query\SqlWa...ateFilterConditionSQL(). ( Ignorable by Annotation )

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

1025
            $filterExpr = $this->generateFilterConditionSQL(/** @scrutinizer ignore-type */ $targetClass, $targetTableAlias);
Loading history...
1026
1027 181
            if ($filterExpr) {
1028 1
                $conditions[] = $filterExpr;
1029
            }
1030
1031
            $targetTableJoin = [
1032 181
                'table' => $targetTableName . ' ' . $targetTableAlias,
1033 181
                'condition' => implode(' AND ', $conditions),
1034
            ];
1035 57
        } elseif ($owningAssociation instanceof ManyToManyAssociationMetadata) {
1036
            // Join relation table
1037 57
            $joinTable      = $owningAssociation->getJoinTable();
1038 57
            $joinTableName  = $joinTable->getQuotedQualifiedName($this->platform);
1039 57
            $joinTableAlias = $this->getSQLTableAlias($joinTable->getName(), $joinedDqlAlias);
1040
1041 57
            $conditions  = [];
1042 57
            $joinColumns = $association->isOwningSide()
1043 48
                ? $joinTable->getJoinColumns()
1044 57
                : $joinTable->getInverseJoinColumns()
1045
            ;
1046
1047 57
            foreach ($joinColumns as $joinColumn) {
1048 57
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
1049 57
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
1050
1051 57
                $conditions[] = sprintf(
1052 57
                    '%s.%s = %s.%s',
1053 57
                    $sourceTableAlias,
1054 57
                    $quotedReferencedColumnName,
1055 57
                    $joinTableAlias,
1056 57
                    $quotedColumnName
1057
                );
1058
            }
1059
1060 57
            $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
1061
1062
            // Join target table
1063 57
            $sql .= ($joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
1064
1065 57
            $conditions  = [];
1066 57
            $joinColumns = $association->isOwningSide()
1067 48
                ? $joinTable->getInverseJoinColumns()
1068 57
                : $joinTable->getJoinColumns()
1069
            ;
1070
1071 57
            foreach ($joinColumns as $joinColumn) {
1072 57
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
1073 57
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
1074
1075 57
                $conditions[] = sprintf(
1076 57
                    '%s.%s = %s.%s',
1077 57
                    $targetTableAlias,
1078 57
                    $quotedReferencedColumnName,
1079 57
                    $joinTableAlias,
1080 57
                    $quotedColumnName
1081
                );
1082
            }
1083
1084
            // Apply remaining inheritance restrictions
1085 57
            $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1086
1087 57
            if ($discrSql) {
1088 1
                $conditions[] = $discrSql;
1089
            }
1090
1091
            // Apply the filters
1092 57
            $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1093
1094 57
            if ($filterExpr) {
1095 1
                $conditions[] = $filterExpr;
1096
            }
1097
1098
            $targetTableJoin = [
1099 57
                'table' => $targetTableName . ' ' . $targetTableAlias,
1100 57
                'condition' => implode(' AND ', $conditions),
1101
            ];
1102
        } else {
1103
            throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1104
        }
1105
1106
        // Handle WITH clause
1107 228
        $withCondition = ($condExpr === null) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1108
1109 228
        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...
1110 10
            $ctiJoins = $this->generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Query\SqlWa...TableInheritanceJoins(). ( Ignorable by Annotation )

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

1110
            $ctiJoins = $this->generateClassTableInheritanceJoins(/** @scrutinizer ignore-type */ $targetClass, $joinedDqlAlias);
Loading history...
1111
1112
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1113 10
            if ($withCondition) {
1114 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1115
            } else {
1116 10
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1117
            }
1118
        } else {
1119 218
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1120
        }
1121
1122 228
        if ($withCondition) {
1123 6
            $sql .= ' AND ' . $withCondition;
1124
        }
1125
1126
        // Apply the indexes
1127 228
        if ($indexBy) {
1128
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1129 5
            $this->walkIndexBy($indexBy);
1130 223
        } elseif ($association instanceof ToManyAssociationMetadata && $association->getIndexedBy()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $association->getIndexedBy() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1131 3
            $this->rsm->addIndexBy($joinedDqlAlias, $association->getIndexedBy());
1132
        }
1133
1134 228
        return $sql;
1135
    }
1136
1137
    /**
1138
     * {@inheritdoc}
1139
     */
1140 127
    public function walkFunction($function)
1141
    {
1142 127
        return $function->getSql($this);
1143
    }
1144
1145
    /**
1146
     * {@inheritdoc}
1147
     */
1148 156
    public function walkOrderByClause($orderByClause)
1149
    {
1150 156
        $orderByItems           = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1151 155
        $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems();
1152
1153 155
        if ($collectionOrderByItems !== '') {
1154
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1155
        }
1156
1157 155
        return ' ORDER BY ' . implode(', ', $orderByItems);
1158
    }
1159
1160
    /**
1161
     * {@inheritdoc}
1162
     */
1163 174
    public function walkOrderByItem($orderByItem)
1164
    {
1165 174
        $type = strtoupper($orderByItem->type);
1166 174
        $expr = $orderByItem->expression;
1167 174
        $sql  = ($expr instanceof AST\Node)
1168 165
            ? $expr->dispatch($this)
1169 173
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1170
1171 173
        $this->orderedColumnsMap[$sql] = $type;
1172
1173 173
        if ($expr instanceof AST\Subselect) {
1174 2
            return '(' . $sql . ') ' . $type;
1175
        }
1176
1177 171
        return $sql . ' ' . $type;
1178
    }
1179
1180
    /**
1181
     * {@inheritdoc}
1182
     */
1183 14
    public function walkHavingClause($havingClause)
1184
    {
1185 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1186
    }
1187
1188
    /**
1189
     * {@inheritdoc}
1190
     */
1191 247
    public function walkJoin($join)
1192
    {
1193 247
        $joinType        = $join->joinType;
1194 247
        $joinDeclaration = $join->joinAssociationDeclaration;
1195
1196 247
        $sql = ($joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER)
1197 57
            ? ' LEFT JOIN '
1198 247
            : ' INNER JOIN ';
1199
1200
        switch (true) {
1201 247
            case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
1202 18
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1203 18
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1204 18
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1205 18
                $conditions = [];
1206
1207 18
                if ($join->conditionalExpression) {
1208 16
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1209
                }
1210
1211 18
                $isUnconditionalJoin = empty($conditions);
1212 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...
1213 2
                    ? ' AND '
1214 18
                    : ' ON ';
1215
1216 18
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, ! $isUnconditionalJoin);
1217
1218
                // Apply remaining inheritance restrictions
1219 18
                $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1220
1221 18
                if ($discrSql) {
1222 3
                    $conditions[] = $discrSql;
1223
                }
1224
1225
                // Apply the filters
1226 18
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $targetEntity of Doctrine\ORM\Query\SqlWa...ateFilterConditionSQL(). ( Ignorable by Annotation )

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

1226
                $filterExpr = $this->generateFilterConditionSQL(/** @scrutinizer ignore-type */ $class, $tableAlias);
Loading history...
1227
1228 18
                if ($filterExpr) {
1229
                    $conditions[] = $filterExpr;
1230
                }
1231
1232 18
                if ($conditions) {
1233 16
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1234
                }
1235
1236 18
                break;
1237
1238 230
            case ($joinDeclaration instanceof AST\JoinAssociationDeclaration):
1239 230
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1240 228
                break;
1241
        }
1242
1243 245
        return $sql;
1244
    }
1245
1246
    /**
1247
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1248
     *
1249
     * @param AST\CoalesceExpression $coalesceExpression
1250
     *
1251
     * @return string The SQL.
1252
     */
1253 2
    public function walkCoalesceExpression($coalesceExpression)
1254
    {
1255 2
        $sql = 'COALESCE(';
1256
1257 2
        $scalarExpressions = [];
1258
1259 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1260 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1261
        }
1262
1263 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1264
1265 2
        return $sql;
1266
    }
1267
1268
    /**
1269
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1270
     *
1271
     * @param AST\NullIfExpression $nullIfExpression
1272
     *
1273
     * @return string The SQL.
1274
     */
1275 3
    public function walkNullIfExpression($nullIfExpression)
1276
    {
1277 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1278
            ? $this->conn->quote($nullIfExpression->firstExpression)
1279 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1280
1281 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1282
            ? $this->conn->quote($nullIfExpression->secondExpression)
1283 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1284
1285 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1286
    }
1287
1288
    /**
1289
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1290
     *
1291
     * @return string The SQL.
1292
     */
1293 9
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1294
    {
1295 9
        $sql = 'CASE';
1296
1297 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1298 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1299 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1300
        }
1301
1302 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1303
1304 9
        return $sql;
1305
    }
1306
1307
    /**
1308
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1309
     *
1310
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1311
     *
1312
     * @return string The SQL.
1313
     */
1314 5
    public function walkSimpleCaseExpression($simpleCaseExpression)
1315
    {
1316 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1317
1318 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1319 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1320 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1321
        }
1322
1323 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1324
1325 5
        return $sql;
1326
    }
1327
1328
    /**
1329
     * {@inheritdoc}
1330
     */
1331 636
    public function walkSelectExpression($selectExpression)
1332
    {
1333 636
        $sql    = '';
1334 636
        $expr   = $selectExpression->expression;
1335 636
        $hidden = $selectExpression->hiddenAliasResultVariable;
1336
1337
        switch (true) {
1338 636
            case ($expr instanceof AST\PathExpression):
1339 108
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1340
                    throw QueryException::invalidPathExpression($expr);
1341
                }
1342
1343 108
                $fieldName   = $expr->field;
1344 108
                $dqlAlias    = $expr->identificationVariable;
1345 108
                $qComp       = $this->queryComponents[$dqlAlias];
1346 108
                $class       = $qComp['metadata'];
1347 108
                $property    = $class->getProperty($fieldName);
1348 108
                $columnAlias = $this->getSQLColumnAlias();
1349 108
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1350 108
                $col         = sprintf(
1351 108
                    '%s.%s',
1352 108
                    $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1353 108
                    $this->platform->quoteIdentifier($property->getColumnName())
1354
                );
1355
1356 108
                $sql .= sprintf(
1357 108
                    '%s AS %s',
1358 108
                    $property->getType()->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform()),
1359 108
                    $columnAlias
1360
                );
1361
1362 108
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1363
1364 108
                if (! $hidden) {
1365 108
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $property->getType());
1366 108
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1367
                }
1368
1369 108
                break;
1370
1371 583
            case ($expr instanceof AST\AggregateExpression):
1372 575
            case ($expr instanceof AST\Functions\FunctionNode):
1373 526
            case ($expr instanceof AST\SimpleArithmeticExpression):
1374 526
            case ($expr instanceof AST\ArithmeticTerm):
1375 524
            case ($expr instanceof AST\ArithmeticFactor):
1376 523
            case ($expr instanceof AST\ParenthesisExpression):
1377 522
            case ($expr instanceof AST\Literal):
1378 521
            case ($expr instanceof AST\NullIfExpression):
1379 520
            case ($expr instanceof AST\CoalesceExpression):
1380 519
            case ($expr instanceof AST\GeneralCaseExpression):
1381 515
            case ($expr instanceof AST\SimpleCaseExpression):
1382 116
                $columnAlias = $this->getSQLColumnAlias();
1383 116
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1384
1385 116
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1386
1387 116
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1388
1389 116
                if (! $hidden) {
1390
                    // Conceptually we could resolve field type here by traverse through AST to retrieve field type,
1391
                    // but this is not a feasible solution; assume 'string'.
1392 116
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getType('string'));
1393
                }
1394 116
                break;
1395
1396 514
            case ($expr instanceof AST\Subselect):
1397 16
                $columnAlias = $this->getSQLColumnAlias();
1398 16
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1399
1400 16
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1401
1402 16
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1403
1404 16
                if (! $hidden) {
1405
                    // We cannot resolve field type here; assume 'string'.
1406 14
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getType('string'));
1407
                }
1408 16
                break;
1409
1410 509
            case ($expr instanceof AST\NewObjectExpression):
1411 22
                $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable);
1412 22
                break;
1413
1414
            default:
1415
                // IdentificationVariable or PartialObjectExpression
1416 487
                if ($expr instanceof AST\PartialObjectExpression) {
1417 15
                    $dqlAlias        = $expr->identificationVariable;
1418 15
                    $partialFieldSet = $expr->partialFieldSet;
1419
                } else {
1420 482
                    $dqlAlias        = $expr;
1421 482
                    $partialFieldSet = [];
1422
                }
1423
1424 487
                $queryComp   = $this->queryComponents[$dqlAlias];
1425 487
                $class       = $queryComp['metadata'];
1426 487
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1427
1428 487
                if (! isset($this->selectedClasses[$dqlAlias])) {
1429 487
                    $this->selectedClasses[$dqlAlias] = [
1430 487
                        'class'       => $class,
1431 487
                        'dqlAlias'    => $dqlAlias,
1432 487
                        'resultAlias' => $resultAlias,
1433
                    ];
1434
                }
1435
1436 487
                $sqlParts = [];
1437
1438
                // Select all fields from the queried class
1439 487
                foreach ($class->getDeclaredPropertiesIterator() as $fieldName => $property) {
1440 487
                    if (! ($property instanceof FieldMetadata)) {
1441 437
                        continue;
1442
                    }
1443
1444 486
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
1445 13
                        continue;
1446
                    }
1447
1448 485
                    $columnAlias = $this->getSQLColumnAlias();
1449 485
                    $col         = sprintf(
1450 485
                        '%s.%s',
1451 485
                        $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1452 485
                        $this->platform->quoteIdentifier($property->getColumnName())
1453
                    );
1454
1455 485
                    $sqlParts[] = sprintf(
1456 485
                        '%s AS %s',
1457 485
                        $property->getType()->convertToPHPValueSQL($col, $this->platform),
1458 485
                        $columnAlias
1459
                    );
1460
1461 485
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1462
1463 485
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->getClassName());
1464
                }
1465
1466
                // Add any additional fields of subclasses (excluding inherited fields)
1467
                // 1) on Single Table Inheritance: always, since its marginal overhead
1468
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1469
                //    since it requires outer joining subtables.
1470 487
                if ($class->inheritanceType === InheritanceType::SINGLE_TABLE || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1471 394
                    foreach ($class->getSubClasses() as $subClassName) {
1472 49
                        $subClass = $this->em->getClassMetadata($subClassName);
1473
1474 49
                        foreach ($subClass->getDeclaredPropertiesIterator() as $fieldName => $property) {
1475 49
                            if (! ($property instanceof FieldMetadata)) {
1476 39
                                continue;
1477
                            }
1478
1479 49
                            if ($subClass->isInheritedProperty($fieldName) || ($partialFieldSet && ! in_array($fieldName, $partialFieldSet))) {
1480 49
                                continue;
1481
                            }
1482
1483 36
                            $columnAlias = $this->getSQLColumnAlias();
1484 36
                            $col         = sprintf(
1485 36
                                '%s.%s',
1486 36
                                $this->getSQLTableAlias($property->getTableName(), $dqlAlias),
1487 36
                                $this->platform->quoteIdentifier($property->getColumnName())
1488
                            );
1489
1490 36
                            $sqlParts[] = sprintf(
1491 36
                                '%s AS %s',
1492 36
                                $property->getType()->convertToPHPValueSQL($col, $this->platform),
1493 36
                                $columnAlias
1494
                            );
1495
1496 36
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1497
1498 49
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1499
                        }
1500
                    }
1501
                }
1502
1503 487
                $sql .= implode(', ', $sqlParts);
1504
        }
1505
1506 636
        return $sql;
1507
    }
1508
1509
    /**
1510
     * {@inheritdoc}
1511
     */
1512
    public function walkQuantifiedExpression($qExpr)
1513
    {
1514
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1515
    }
1516
1517
    /**
1518
     * {@inheritdoc}
1519
     */
1520 33
    public function walkSubselect($subselect)
1521
    {
1522 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1523 33
        $rootAliasesBefore = $this->rootAliases;
1524
1525 33
        $this->rootAliases        = []; // reset the rootAliases for the subselect
1526 33
        $this->useSqlTableAliases = true;
1527
1528 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1529 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1530 33
        $sql .= $this->walkWhereClause($subselect->whereClause);
1531
1532 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1533 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1534 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1535
1536 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1537 33
        $this->useSqlTableAliases = $useAliasesBefore;
1538
1539 33
        return $sql;
1540
    }
1541
1542
    /**
1543
     * {@inheritdoc}
1544
     */
1545 33
    public function walkSubselectFromClause($subselectFromClause)
1546
    {
1547 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1548 33
        $sqlParts               = [];
1549
1550 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1551 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1552
        }
1553
1554 33
        return ' FROM ' . implode(', ', $sqlParts);
1555
    }
1556
1557
    /**
1558
     * {@inheritdoc}
1559
     */
1560 33
    public function walkSimpleSelectClause($simpleSelectClause)
1561
    {
1562 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1563 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1564
    }
1565
1566
    /**
1567
     * @return string.
1568
     */
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...
1569 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1570
    {
1571 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1572
    }
1573
1574
    /**
1575
     * @param AST\NewObjectExpression $newObjectExpression
1576
     * @param string|null             $newObjectResultAlias
1577
     * @return string The SQL.
1578
     */
1579 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias = null)
1580
    {
1581 22
        $sqlSelectExpressions = [];
1582 22
        $objIndex             = $newObjectResultAlias ?: $this->newObjectCounter++;
1583
1584 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1585 22
            $resultAlias = $this->scalarResultCounter++;
1586 22
            $columnAlias = $this->getSQLColumnAlias();
1587 22
            $fieldType   = Type::getType('string');
1588
1589
            switch (true) {
1590 22
                case ($e instanceof AST\NewObjectExpression):
1591
                    $sqlSelectExpressions[] = $e->dispatch($this);
1592
                    break;
1593
1594 22
                case ($e instanceof AST\Subselect):
1595 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1596 1
                    break;
1597
1598 22
                case ($e instanceof AST\PathExpression):
1599 22
                    $dqlAlias  = $e->identificationVariable;
1600 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1601 22
                    $class     = $qComp['metadata'];
1602 22
                    $fieldType = $class->getProperty($e->field)->getType();
1603
1604 22
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1605 22
                    break;
1606
1607 6
                case ($e instanceof AST\Literal):
1608 1
                    switch ($e->type) {
1609 1
                        case AST\Literal::BOOLEAN:
1610 1
                            $fieldType = Type::getType('boolean');
1611 1
                            break;
1612
1613 1
                        case AST\Literal::NUMERIC:
1614 1
                            $fieldType = Type::getType(is_float($e->value) ? 'float' : 'integer');
1615 1
                            break;
1616
                    }
1617
1618 1
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1619 1
                    break;
1620
1621
                default:
1622 5
                    $sqlSelectExpressions[] = trim((string) $e->dispatch($this)) . ' AS ' . $columnAlias;
1623 5
                    break;
1624
            }
1625
1626 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1627 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1628
1629 22
            $this->rsm->newObjectMappings[$columnAlias] = [
1630 22
                'className' => $newObjectExpression->className,
1631 22
                'objIndex'  => $objIndex,
1632 22
                'argIndex'  => $argIndex,
1633
            ];
1634
        }
1635
1636 22
        return implode(', ', $sqlSelectExpressions);
1637
    }
1638
1639
    /**
1640
     * {@inheritdoc}
1641
     */
1642 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1643
    {
1644 33
        $expr = $simpleSelectExpression->expression;
1645 33
        $sql  = ' ';
1646
1647
        switch (true) {
1648 33
            case ($expr instanceof AST\PathExpression):
1649 8
                $sql .= $this->walkPathExpression($expr);
1650 8
                break;
1651
1652 25
            case ($expr instanceof AST\Subselect):
1653
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1654
1655
                $columnAlias                        = 'sclr' . $this->aliasCounter++;
1656
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1657
1658
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1659
                break;
1660
1661 25
            case ($expr instanceof AST\Functions\FunctionNode):
1662 11
            case ($expr instanceof AST\SimpleArithmeticExpression):
1663 10
            case ($expr instanceof AST\ArithmeticTerm):
1664 9
            case ($expr instanceof AST\ArithmeticFactor):
1665 9
            case ($expr instanceof AST\Literal):
1666 7
            case ($expr instanceof AST\NullIfExpression):
1667 7
            case ($expr instanceof AST\CoalesceExpression):
1668 7
            case ($expr instanceof AST\GeneralCaseExpression):
1669 5
            case ($expr instanceof AST\SimpleCaseExpression):
1670 22
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1671
1672 22
                $columnAlias                        = $this->getSQLColumnAlias();
1673 22
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1674
1675 22
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1676 22
                break;
1677
1678 3
            case ($expr instanceof AST\ParenthesisExpression):
1679 1
                $sql .= $this->walkParenthesisExpression($expr);
1680 1
                break;
1681
1682
            default: // IdentificationVariable
1683 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1684 2
                break;
1685
        }
1686
1687 33
        return $sql;
1688
    }
1689
1690
    /**
1691
     * {@inheritdoc}
1692
     */
1693 74
    public function walkAggregateExpression($aggExpression)
1694
    {
1695 74
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1696 74
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1697
    }
1698
1699
    /**
1700
     * {@inheritdoc}
1701
     */
1702 25
    public function walkGroupByClause($groupByClause)
1703
    {
1704 25
        $sqlParts = [];
1705
1706 25
        foreach ($groupByClause->groupByItems as $groupByItem) {
1707 25
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1708
        }
1709
1710 25
        return ' GROUP BY ' . implode(', ', $sqlParts);
1711
    }
1712
1713
    /**
1714
     * {@inheritdoc}
1715
     */
1716 25
    public function walkGroupByItem($groupByItem)
1717
    {
1718
        // StateFieldPathExpression
1719 25
        if (! is_string($groupByItem)) {
1720 12
            return $this->walkPathExpression($groupByItem);
1721
        }
1722
1723
        // ResultVariable
1724 13
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1725 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1726
1727 2
            if ($resultVariable instanceof AST\PathExpression) {
1728 1
                return $this->walkPathExpression($resultVariable);
1729
            }
1730
1731 1
            if (isset($resultVariable->pathExpression)) {
1732
                return $this->walkPathExpression($resultVariable->pathExpression);
1733
            }
1734
1735 1
            return $this->walkResultVariable($groupByItem);
1736
        }
1737
1738
        // IdentificationVariable
1739
        /** @var ClassMetadata $classMetadata */
1740 11
        $classMetadata = $this->queryComponents[$groupByItem]['metadata'];
1741 11
        $sqlParts      = [];
1742
1743 11
        foreach ($classMetadata->getDeclaredPropertiesIterator() as $property) {
1744
            switch (true) {
1745 11
                case ($property instanceof FieldMetadata):
1746 11
                    $type       = AST\PathExpression::TYPE_STATE_FIELD;
1747 11
                    $item       = new AST\PathExpression($type, $groupByItem, $property->getName());
1748 11
                    $item->type = $type;
1749
1750 11
                    $sqlParts[] = $this->walkPathExpression($item);
1751 11
                    break;
1752
1753 11
                case ($property instanceof ToOneAssociationMetadata && $property->isOwningSide()):
1754 7
                    $type       = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1755 7
                    $item       = new AST\PathExpression($type, $groupByItem, $property->getName());
1756 7
                    $item->type = $type;
1757
1758 7
                    $sqlParts[] = $this->walkPathExpression($item);
1759 11
                    break;
1760
            }
1761
        }
1762
1763 11
        return implode(', ', $sqlParts);
1764
    }
1765
1766
    /**
1767
     * {@inheritdoc}
1768
     */
1769
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1770
    {
1771
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1772
        $tableName = $class->getTableName();
1773
        $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...
1774
1775
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1776
1777
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1778
1779
        return $sql;
1780
    }
1781
1782
    /**
1783
     * {@inheritdoc}
1784
     */
1785
    public function walkUpdateClause($updateClause)
1786
    {
1787
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1788
        $tableName = $class->getTableName();
1789
        $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...
1790
1791
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1792
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1793
1794
        $sql .= ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1795
1796
        return $sql;
1797
    }
1798
1799
    /**
1800
     * {@inheritdoc}
1801
     */
1802
    public function walkUpdateItem($updateItem)
1803
    {
1804
        $useTableAliasesBefore    = $this->useSqlTableAliases;
1805
        $this->useSqlTableAliases = false;
1806
1807
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1808
        $newValue = $updateItem->newValue;
1809
1810
        switch (true) {
1811
            case ($newValue instanceof AST\Node):
1812
                $sql .= $newValue->dispatch($this);
1813
                break;
1814
1815
            case ($newValue === null):
1816
                $sql .= 'NULL';
1817
                break;
1818
1819
            default:
1820
                $sql .= $this->conn->quote($newValue);
1821
                break;
1822
        }
1823
1824
        $this->useSqlTableAliases = $useTableAliasesBefore;
1825
1826
        return $sql;
1827
    }
1828
1829
    /**
1830
     * {@inheritdoc}
1831
     */
1832 634
    public function walkWhereClause($whereClause)
1833
    {
1834 634
        $condSql  = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1835 631
        $discrSql = $this->generateDiscriminatorColumnConditionSql($this->rootAliases);
1836
1837 631
        if ($this->em->hasFilters()) {
1838 42
            $filterClauses = [];
1839 42
            foreach ($this->rootAliases as $dqlAlias) {
1840 42
                $class      = $this->queryComponents[$dqlAlias]['metadata'];
1841 42
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1842 42
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1843
1844 42
                if ($filterExpr) {
1845 42
                    $filterClauses[] = $filterExpr;
1846
                }
1847
            }
1848
1849 42
            if ($filterClauses) {
1850 6
                if ($condSql) {
1851 2
                    $condSql = '(' . $condSql . ') AND ';
1852
                }
1853
1854 6
                $condSql .= implode(' AND ', $filterClauses);
1855
            }
1856
        }
1857
1858 631
        if ($condSql) {
1859 282
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1860
        }
1861
1862 422
        if ($discrSql) {
1863 22
            return ' WHERE ' . $discrSql;
1864
        }
1865
1866 404
        return '';
1867
    }
1868
1869
    /**
1870
     * {@inheritdoc}
1871
     */
1872 315
    public function walkConditionalExpression($condExpr)
1873
    {
1874
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1875
        // if only one ConditionalTerm is defined
1876 315
        if (! ($condExpr instanceof AST\ConditionalExpression)) {
1877 263
            return $this->walkConditionalTerm($condExpr);
1878
        }
1879
1880 69
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1881
    }
1882
1883
    /**
1884
     * {@inheritdoc}
1885
     */
1886 315
    public function walkConditionalTerm($condTerm)
1887
    {
1888
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1889
        // if only one ConditionalFactor is defined
1890 315
        if (! ($condTerm instanceof AST\ConditionalTerm)) {
1891 253
            return $this->walkConditionalFactor($condTerm);
1892
        }
1893
1894 83
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1895
    }
1896
1897
    /**
1898
     * {@inheritdoc}
1899
     */
1900 315
    public function walkConditionalFactor($factor)
1901
    {
1902
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1903
        // if only one ConditionalPrimary is defined
1904 315
        return ( ! ($factor instanceof AST\ConditionalFactor))
1905 313
            ? $this->walkConditionalPrimary($factor)
1906 312
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1907
    }
1908
1909
    /**
1910
     * {@inheritdoc}
1911
     */
1912 315
    public function walkConditionalPrimary($primary)
1913
    {
1914 315
        if ($primary->isSimpleConditionalExpression()) {
1915 315
            return $primary->simpleConditionalExpression->dispatch($this);
1916
        }
1917
1918 22
        if ($primary->isConditionalExpression()) {
1919 22
            $condExpr = $primary->conditionalExpression;
1920
1921 22
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
1922
        }
1923
    }
1924
1925
    /**
1926
     * {@inheritdoc}
1927
     */
1928 5
    public function walkExistsExpression($existsExpr)
1929
    {
1930 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1931
1932 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1933
1934 5
        return $sql;
1935
    }
1936
1937
    /**
1938
     * {@inheritdoc}
1939
     */
1940 7
    public function walkCollectionMemberExpression($collMemberExpr)
1941
    {
1942 7
        $sql  = $collMemberExpr->not ? 'NOT ' : '';
1943 7
        $sql .= 'EXISTS (SELECT 1 FROM ';
1944
1945 7
        $entityExpr   = $collMemberExpr->entityExpression;
1946 7
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1947
1948 7
        $fieldName = $collPathExpr->field;
1949 7
        $dqlAlias  = $collPathExpr->identificationVariable;
1950
1951 7
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1952
1953
        switch (true) {
1954
            // InputParameter
1955 7
            case ($entityExpr instanceof AST\InputParameter):
1956 4
                $dqlParamKey = $entityExpr->name;
1957 4
                $entitySql   = '?';
1958 4
                break;
1959
1960
            // SingleValuedAssociationPathExpression | IdentificationVariable
1961 3
            case ($entityExpr instanceof AST\PathExpression):
1962 3
                $entitySql = $this->walkPathExpression($entityExpr);
1963 3
                break;
1964
1965
            default:
1966
                throw new \BadMethodCallException('Not implemented');
1967
        }
1968
1969 7
        $association       = $class->getProperty($fieldName);
1970 7
        $targetClass       = $this->em->getClassMetadata($association->getTargetEntity());
1971 7
        $owningAssociation = $association->isOwningSide()
1972 6
            ? $association
1973 7
            : $targetClass->getProperty($association->getMappedBy())
1974
        ;
1975
1976 7
        if ($association instanceof OneToManyAssociationMetadata) {
1977 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...
1978 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1979 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1980
1981 1
            $sql .= $targetTableName . ' ' . $targetTableAlias . ' WHERE ';
1982
1983 1
            $sqlParts = [];
1984
1985 1
            foreach ($owningAssociation->getJoinColumns() as $joinColumn) {
1986 1
                $sqlParts[] = sprintf(
1987 1
                    '%s.%s = %s.%s',
1988 1
                    $sourceTableAlias,
1989 1
                    $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName()),
1990 1
                    $targetTableAlias,
1991 1
                    $this->platform->quoteIdentifier($joinColumn->getColumnName())
1992
                );
1993
            }
1994
1995 1
            foreach ($targetClass->getIdentifierColumns($this->em) as $targetColumn) {
1996 1
                $quotedTargetColumnName = $this->platform->quoteIdentifier($targetColumn->getColumnName());
1997
1998 1
                if (isset($dqlParamKey)) {
1999 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
2000
                }
2001
2002 1
                $sqlParts[] = $targetTableAlias . '.' . $quotedTargetColumnName . ' = ' . $entitySql;
2003
            }
2004
2005 1
            $sql .= implode(' AND ', $sqlParts);
2006
        } else { // many-to-many
2007
            // SQL table aliases
2008 6
            $joinTable        = $owningAssociation->getJoinTable();
2009 6
            $joinTableName    = $joinTable->getQuotedQualifiedName($this->platform);
2010 6
            $joinTableAlias   = $this->getSQLTableAlias($joinTable->getName());
2011 6
            $targetTableName  = $targetClass->table->getQuotedQualifiedName($this->platform);
2012 6
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
2013 6
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
2014
2015
            // join to target table
2016 6
            $sql .= $joinTableName . ' ' . $joinTableAlias . ' INNER JOIN ' . $targetTableName . ' ' . $targetTableAlias . ' ON ';
2017
2018
            // join conditions
2019 6
            $joinSqlParts = [];
2020 6
            $joinColumns  = $association->isOwningSide()
2021 6
                ? $joinTable->getInverseJoinColumns()
2022 6
                : $joinTable->getJoinColumns()
2023
            ;
2024
2025 6
            foreach ($joinColumns as $joinColumn) {
2026 6
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
2027 6
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
2028
2029 6
                $joinSqlParts[] = sprintf(
2030 6
                    '%s.%s = %s.%s',
2031 6
                    $joinTableAlias,
2032 6
                    $quotedColumnName,
2033 6
                    $targetTableAlias,
2034 6
                    $quotedReferencedColumnName
2035
                );
2036
            }
2037
2038 6
            $sql .= implode(' AND ', $joinSqlParts);
2039 6
            $sql .= ' WHERE ';
2040
2041 6
            $sqlParts    = [];
2042 6
            $joinColumns = $association->isOwningSide()
2043 6
                ? $joinTable->getJoinColumns()
2044 6
                : $joinTable->getInverseJoinColumns()
2045
            ;
2046
2047 6
            foreach ($joinColumns as $joinColumn) {
2048 6
                $quotedColumnName           = $this->platform->quoteIdentifier($joinColumn->getColumnName());
2049 6
                $quotedReferencedColumnName = $this->platform->quoteIdentifier($joinColumn->getReferencedColumnName());
2050
2051 6
                $sqlParts[] = sprintf(
2052 6
                    '%s.%s = %s.%s',
2053 6
                    $joinTableAlias,
2054 6
                    $quotedColumnName,
2055 6
                    $sourceTableAlias,
2056 6
                    $quotedReferencedColumnName
2057
                );
2058
            }
2059
2060 6
            foreach ($targetClass->getIdentifierColumns($this->em) as $targetColumn) {
2061 6
                $quotedTargetColumnName = $this->platform->quoteIdentifier($targetColumn->getColumnName());
2062
2063 6
                if (isset($dqlParamKey)) {
2064 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
2065
                }
2066
2067 6
                $sqlParts[] = $targetTableAlias . '.' . $quotedTargetColumnName . ' = ' . $entitySql;
2068
            }
2069
2070 6
            $sql .= implode(' AND ', $sqlParts);
2071
        }
2072
2073 7
        return $sql . ')';
2074
    }
2075
2076
    /**
2077
     * {@inheritdoc}
2078
     */
2079 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
2080
    {
2081 3
        $sizeFunc                           = new AST\Functions\SizeFunction('size');
2082 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
2083
2084 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
2085
    }
2086
2087
    /**
2088
     * {@inheritdoc}
2089
     */
2090 9
    public function walkNullComparisonExpression($nullCompExpr)
2091
    {
2092 9
        $expression = $nullCompExpr->expression;
2093 9
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2094
2095
        // Handle ResultVariable
2096 9
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2097 2
            return $this->walkResultVariable($expression) . $comparison;
2098
        }
2099
2100
        // Handle InputParameter mapping inclusion to ParserResult
2101 7
        if ($expression instanceof AST\InputParameter) {
2102
            return $this->walkInputParameter($expression) . $comparison;
2103
        }
2104
2105 7
        return $expression->dispatch($this) . $comparison;
2106
    }
2107
2108
    /**
2109
     * {@inheritdoc}
2110
     */
2111 80
    public function walkInExpression($inExpr)
2112
    {
2113 80
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2114
2115 79
        $sql .= ($inExpr->subselect)
2116 7
            ? $this->walkSubselect($inExpr->subselect)
2117 79
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2118
2119 79
        $sql .= ')';
2120
2121 79
        return $sql;
2122
    }
2123
2124
    /**
2125
     * {@inheritdoc}
2126
     * @throws \Doctrine\ORM\Query\QueryException
2127
     */
2128 14
    public function walkInstanceOfExpression($instanceOfExpr)
2129
    {
2130 14
        $dqlAlias         = $instanceOfExpr->identificationVariable;
2131 14
        $class            = $this->queryComponents[$dqlAlias]['metadata'];
2132 14
        $discrClass       = $this->em->getClassMetadata($class->getRootClassName());
2133 14
        $discrColumn      = $class->discriminatorColumn;
2134 14
        $discrColumnType  = $discrColumn->getType();
2135 14
        $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
2136 14
        $sqlTableAlias    = $this->useSqlTableAliases
2137 14
            ? $this->getSQLTableAlias($discrColumn->getTableName(), $dqlAlias) . '.'
2138 14
            : '';
2139
2140 14
        return sprintf(
2141 14
            '%s %sIN %s',
2142 14
            $discrColumnType->convertToDatabaseValueSQL($sqlTableAlias . $quotedColumnName, $this->platform),
2143 14
            ($instanceOfExpr->not ? 'NOT ' : ''),
2144 14
            $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr)
0 ignored issues
show
Bug introduced by
$discrClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $rootClass of Doctrine\ORM\Query\SqlWa...torsFromClassMetadata(). ( Ignorable by Annotation )

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

2144
            $this->getChildDiscriminatorsFromClassMetadata(/** @scrutinizer ignore-type */ $discrClass, $instanceOfExpr)
Loading history...
2145
        );
2146
    }
2147
2148
    /**
2149
     * {@inheritdoc}
2150
     */
2151 72
    public function walkInParameter($inParam)
2152
    {
2153 72
        return $inParam instanceof AST\InputParameter
2154 65
            ? $this->walkInputParameter($inParam)
2155 72
            : $this->walkLiteral($inParam);
2156
    }
2157
2158
    /**
2159
     * {@inheritdoc}
2160
     */
2161 140
    public function walkLiteral($literal)
2162
    {
2163 140
        switch ($literal->type) {
2164 140
            case AST\Literal::STRING:
2165 47
                return $this->conn->quote($literal->value);
2166
2167 103
            case AST\Literal::BOOLEAN:
2168 6
                return $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true');
2169
2170 98
            case AST\Literal::NUMERIC:
2171 98
                return $literal->value;
2172
2173
            default:
2174
                throw QueryException::invalidLiteral($literal);
2175
        }
2176
    }
2177
2178
    /**
2179
     * {@inheritdoc}
2180
     */
2181 3
    public function walkBetweenExpression($betweenExpr)
2182
    {
2183 3
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2184
2185 3
        if ($betweenExpr->not) {
2186 1
            $sql .= ' NOT';
2187
        }
2188
2189 3
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2190 3
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2191
2192 3
        return $sql;
2193
    }
2194
2195
    /**
2196
     * {@inheritdoc}
2197
     */
2198 7
    public function walkLikeExpression($likeExpr)
2199
    {
2200 7
        $stringExpr = $likeExpr->stringExpression;
2201 7
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2202 1
            ? $this->walkResultVariable($stringExpr)
2203 7
            : $stringExpr->dispatch($this);
2204
2205 7
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2206
2207 7
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2208 1
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2209 7
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2210 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2211 7
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2212 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2213
        } else {
2214 7
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2215
        }
2216
2217 7
        if ($likeExpr->escapeChar) {
2218
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2219
        }
2220
2221 7
        return $sql;
2222
    }
2223
2224
    /**
2225
     * {@inheritdoc}
2226
     */
2227 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2228
    {
2229 5
        return $this->walkPathExpression($stateFieldPathExpression);
2230
    }
2231
2232
    /**
2233
     * {@inheritdoc}
2234
     */
2235 217
    public function walkComparisonExpression($compExpr)
2236
    {
2237 217
        $leftExpr  = $compExpr->leftExpression;
2238 217
        $rightExpr = $compExpr->rightExpression;
2239 217
        $sql       = '';
2240
2241 217
        $sql .= ($leftExpr instanceof AST\Node)
2242 217
            ? $leftExpr->dispatch($this)
2243 216
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2244
2245 216
        $sql .= ' ' . $compExpr->operator . ' ';
2246
2247 216
        $sql .= ($rightExpr instanceof AST\Node)
2248 214
            ? $rightExpr->dispatch($this)
2249 216
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2250
2251 216
        return $sql;
2252
    }
2253
2254
    /**
2255
     * {@inheritdoc}
2256
     */
2257 173
    public function walkInputParameter($inputParam)
2258
    {
2259 173
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2260
2261 173
        $parameter = $this->query->getParameter($inputParam->name);
2262
2263 173
        if ($parameter) {
2264 132
            $type = $parameter->getType();
2265
2266 132
            if (Type::hasType($type)) {
2267 50
                return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2268
            }
2269
        }
2270
2271 127
        return '?';
2272
    }
2273
2274
    /**
2275
     * {@inheritdoc}
2276
     */
2277 276
    public function walkArithmeticExpression($arithmeticExpr)
2278
    {
2279 276
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2280 276
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
2281 274
            : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
2282
    }
2283
2284
    /**
2285
     * {@inheritdoc}
2286
     */
2287 337
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2288
    {
2289 337
        if (! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2290 288
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2291
        }
2292
2293 74
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2294
    }
2295
2296
    /**
2297
     * {@inheritdoc}
2298
     */
2299 358
    public function walkArithmeticTerm($term)
2300
    {
2301 358
        if (is_string($term)) {
2302 21
            return (isset($this->queryComponents[$term]))
2303 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2304 21
                : $term;
2305
        }
2306
2307
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2308
        // if only one ArithmeticFactor is defined
2309 357
        if (! ($term instanceof AST\ArithmeticTerm)) {
2310 335
            return $this->walkArithmeticFactor($term);
2311
        }
2312
2313 46
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2314
    }
2315
2316
    /**
2317
     * {@inheritdoc}
2318
     */
2319 358
    public function walkArithmeticFactor($factor)
2320
    {
2321 358
        if (is_string($factor)) {
2322 46
            return (isset($this->queryComponents[$factor]))
2323 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2324 46
                : $factor;
2325
        }
2326
2327
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2328
        // if only one ArithmeticPrimary is defined
2329 358
        if (! ($factor instanceof AST\ArithmeticFactor)) {
2330 357
            return $this->walkArithmeticPrimary($factor);
2331
        }
2332
2333 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2334
2335 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2336
    }
2337
2338
    /**
2339
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2340
     *
2341
     * @param mixed $primary
2342
     *
2343
     * @return string The SQL.
2344
     */
2345 358
    public function walkArithmeticPrimary($primary)
2346
    {
2347 358
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2348
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2349
        }
2350
2351 358
        if ($primary instanceof AST\Node) {
2352 358
            return $primary->dispatch($this);
2353
        }
2354
2355
        return $this->walkEntityIdentificationVariable($primary);
2356
    }
2357
2358
    /**
2359
     * {@inheritdoc}
2360
     */
2361 21
    public function walkStringPrimary($stringPrimary)
2362
    {
2363 21
        return (is_string($stringPrimary))
2364
            ? $this->conn->quote($stringPrimary)
2365 21
            : $stringPrimary->dispatch($this);
2366
    }
2367
2368
    /**
2369
     * {@inheritdoc}
2370
     */
2371 32
    public function walkResultVariable($resultVariable)
2372
    {
2373 32
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2374
2375 32
        if (is_array($resultAlias)) {
2376 1
            return implode(', ', $resultAlias);
2377
        }
2378
2379 31
        return $resultAlias;
2380
    }
2381
2382
    /**
2383
     *
2384
     * @return string The list in parentheses of valid child discriminators from the given class
2385
     *
2386
     * @throws QueryException
2387
     */
2388 14
    private function getChildDiscriminatorsFromClassMetadata(ClassMetadata $rootClass, AST\InstanceOfExpression $instanceOfExpr) : string
2389
    {
2390 14
        $sqlParameterList = [];
2391 14
        $discriminators   = [];
2392
2393 14
        foreach ($instanceOfExpr->value as $parameter) {
2394 14
            if ($parameter instanceof AST\InputParameter) {
2395 4
                $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
2396
2397 4
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2398
2399 4
                continue;
2400
            }
2401
2402
            // Get name from ClassMetadata to resolve aliases.
2403 10
            $entityClass     = $this->em->getClassMetadata($parameter);
2404 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

2404
            /** @scrutinizer ignore-call */ 
2405
            $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...
2405
2406 10
            if ($entityClassName !== $rootClass->getClassName()) {
2407 7
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2408 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2409
                }
2410
            }
2411
2412 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($entityClass, $this->em);
0 ignored issues
show
Bug introduced by
$entityClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $rootClassMetadata of Doctrine\ORM\Utility\Hie...iscriminatorsForClass(). ( Ignorable by Annotation )

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

2412
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass(/** @scrutinizer ignore-type */ $entityClass, $this->em);
Loading history...
2413
        }
2414
2415 13
        foreach (array_keys($discriminators) as $discriminator) {
2416 9
            $sqlParameterList[] = $this->conn->quote($discriminator);
2417
        }
2418
2419 13
        return '(' . implode(', ', $sqlParameterList) . ')';
2420
    }
2421
}
2422