Failed Conditions
Pull Request — develop (#6947)
by Filippo
10:01
created

SqlWalker::generateClassTableInheritanceJoins()   B

Complexity

Conditions 8
Paths 36

Size

Total Lines 58
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 28
nc 36
nop 2
dl 0
loc 58
ccs 29
cts 29
cp 1
crap 8
rs 7.1977
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

364
            $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...
365
366 39
            $sql .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
367
368 39
            $sqlParts = [];
369
370 39
            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

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

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

518
        /** @scrutinizer ignore-call */ 
519
        $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...
519 645
        $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

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

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

796
                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...
797
                    // Skip if association is inherited
798 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

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

931
            /** @scrutinizer ignore-type */ $this->query->getHint(Query::HINT_LOCK_MODE)
Loading history...
932
        );
933
934 648
        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...
935 550
            return $sql;
936
        }
937
938 106
        $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

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

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

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

1113
            $ctiJoins = $this->generateClassTableInheritanceJoins(/** @scrutinizer ignore-type */ $targetClass, $joinedDqlAlias);
Loading history...
1114
1115
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1116 10
            if ($withCondition) {
1117 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1118
            } else {
1119 10
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1120
            }
1121
        } else {
1122 218
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1123
        }
1124
1125 228
        if ($withCondition) {
1126 6
            $sql .= ' AND ' . $withCondition;
1127
        }
1128
1129
        // Apply the indexes
1130 228
        if ($indexBy) {
1131
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1132 5
            $this->walkIndexBy($indexBy);
1133 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...
1134 3
            $this->rsm->addIndexBy($joinedDqlAlias, $association->getIndexedBy());
1135
        }
1136
1137 228
        return $sql;
1138
    }
1139
1140
    /**
1141
     * {@inheritdoc}
1142
     */
1143 132
    public function walkFunction($function)
1144
    {
1145 132
        return $function->getSql($this);
1146
    }
1147
1148
    /**
1149
     * {@inheritdoc}
1150
     */
1151 156
    public function walkOrderByClause($orderByClause)
1152
    {
1153 156
        $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1154
1155 155
        if (($collectionOrderByItems = $this->generateOrderedCollectionOrderByItems()) !== '') {
1156
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1157
        }
1158
1159 155
        return ' ORDER BY ' . implode(', ', $orderByItems);
1160
    }
1161
1162
    /**
1163
     * {@inheritdoc}
1164
     */
1165 174
    public function walkOrderByItem($orderByItem)
1166
    {
1167 174
        $type = strtoupper($orderByItem->type);
1168 174
        $expr = $orderByItem->expression;
1169 174
        $sql  = ($expr instanceof AST\Node)
1170 165
            ? $expr->dispatch($this)
1171 173
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1172
1173 173
        $this->orderedColumnsMap[$sql] = $type;
1174
1175 173
        if ($expr instanceof AST\Subselect) {
1176 2
            return '(' . $sql . ') ' . $type;
1177
        }
1178
1179 171
        return $sql . ' ' . $type;
1180
    }
1181
1182
    /**
1183
     * {@inheritdoc}
1184
     */
1185 14
    public function walkHavingClause($havingClause)
1186
    {
1187 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1188
    }
1189
1190
    /**
1191
     * {@inheritdoc}
1192
     */
1193 247
    public function walkJoin($join)
1194
    {
1195 247
        $joinType        = $join->joinType;
1196 247
        $joinDeclaration = $join->joinAssociationDeclaration;
1197
1198 247
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1199 57
            ? ' LEFT JOIN '
1200 247
            : ' INNER JOIN ';
1201
1202
        switch (true) {
1203 247
            case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
1204 18
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1205 18
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1206 18
                $tableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1207 18
                $conditions = [];
1208
1209 18
                if ($join->conditionalExpression) {
1210 16
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1211
                }
1212
1213 18
                $isUnconditionalJoin = empty($conditions);
1214 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...
1215 2
                    ? ' AND '
1216 18
                    : ' ON ';
1217
1218 18
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, !$isUnconditionalJoin);
1219
1220
                // Apply remaining inheritance restrictions
1221 18
                $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1222
1223 18
                if ($discrSql) {
1224 3
                    $conditions[] = $discrSql;
1225
                }
1226
1227
                // Apply the filters
1228 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

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

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

2407
            /** @scrutinizer ignore-call */ 
2408
            $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...
2408
2409 10
            if ($entityClassName !== $rootClass->getClassName()) {
2410 7
                if (! $entityClass->getReflectionClass()->isSubclassOf($rootClass->getClassName())) {
2411 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $rootClass->getClassName());
2412
                }
2413
            }
2414
2415 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

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