Failed Conditions
Pull Request — 2.8.x (#7918)
by
unknown
73:08 queued 61:09
created

SqlWalker::walkSelectExpression()   F

Complexity

Conditions 40
Paths 42

Size

Total Lines 175
Code Lines 108

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 103
CRAP Score 40.0362

Importance

Changes 0
Metric Value
cc 40
eloc 108
nc 42
nop 1
dl 0
loc 175
rs 3.3333
c 0
b 0
f 0
ccs 103
cts 106
cp 0.9717
crap 40.0362

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\DBAL\Types\Type;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use Doctrine\ORM\OptimisticLockException;
27
use Doctrine\ORM\Query;
28
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
29
use Doctrine\ORM\Utility\PersisterHelper;
30
31
/**
32
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
33
 * the corresponding SQL.
34
 *
35
 * @author Guilherme Blanco <[email protected]>
36
 * @author Roman Borschel <[email protected]>
37
 * @author Benjamin Eberlei <[email protected]>
38
 * @author Alexander <[email protected]>
39
 * @author Fabio B. Silva <[email protected]>
40
 * @since  2.0
41
 */
42
class SqlWalker implements TreeWalker
43
{
44
    /**
45
     * @var string
46
     */
47
    const HINT_DISTINCT = 'doctrine.distinct';
48
49
    /**
50
     * @var ResultSetMapping
51
     */
52
    private $rsm;
53
54
    /**
55
     * Counter for generating unique column aliases.
56
     *
57
     * @var integer
58
     */
59
    private $aliasCounter = 0;
60
61
    /**
62
     * Counter for generating unique table aliases.
63
     *
64
     * @var integer
65
     */
66
    private $tableAliasCounter = 0;
67
68
    /**
69
     * Counter for generating unique scalar result.
70
     *
71
     * @var integer
72
     */
73
    private $scalarResultCounter = 1;
74
75
    /**
76
     * Counter for generating unique parameter indexes.
77
     *
78
     * @var integer
79
     */
80
    private $sqlParamIndex = 0;
81
82
    /**
83
     * Counter for generating indexes.
84
     *
85
     * @var integer
86
     */
87
    private $newObjectCounter = 0;
88
89
    /**
90
     * @var ParserResult
91
     */
92
    private $parserResult;
93
94
    /**
95
     * @var \Doctrine\ORM\EntityManager
96
     */
97
    private $em;
98
99
    /**
100
     * @var \Doctrine\DBAL\Connection
101
     */
102
    private $conn;
103
104
    /**
105
     * @var \Doctrine\ORM\AbstractQuery
106
     */
107
    private $query;
108
109
    /**
110
     * @var array
111
     */
112
    private $tableAliasMap = [];
113
114
    /**
115
     * Map from result variable names to their SQL column alias names.
116
     *
117
     * @var array
118
     */
119
    private $scalarResultAliasMap = [];
120
121
    /**
122
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
123
     *
124
     * @var array
125
     */
126
    private $orderedColumnsMap = [];
127
128
    /**
129
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
130
     *
131
     * @var array
132
     */
133
    private $scalarFields = [];
134
135
    /**
136
     * Map of all components/classes that appear in the DQL query.
137
     *
138
     * @var array
139
     */
140
    private $queryComponents;
141
142
    /**
143
     * A list of classes that appear in non-scalar SelectExpressions.
144
     *
145
     * @var array
146
     */
147
    private $selectedClasses = [];
148
149
    /**
150
     * The DQL alias of the root class of the currently traversed query.
151
     *
152
     * @var array
153
     */
154
    private $rootAliases = [];
155
156
    /**
157
     * Flag that indicates whether to generate SQL table aliases in the SQL.
158
     * These should only be generated for SELECT queries, not for UPDATE/DELETE.
159
     *
160
     * @var boolean
161
     */
162
    private $useSqlTableAliases = true;
163
164
    /**
165
     * The database platform abstraction.
166
     *
167
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
168
     */
169
    private $platform;
170
171
    /**
172
     * The quote strategy.
173
     *
174
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
175
     */
176
    private $quoteStrategy;
177
178
    /**
179
     * {@inheritDoc}
180
     */
181 719
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 719
        $this->query            = $query;
184 719
        $this->parserResult     = $parserResult;
185 719
        $this->queryComponents  = $queryComponents;
186 719
        $this->rsm              = $parserResult->getResultSetMapping();
187 719
        $this->em               = $query->getEntityManager();
188 719
        $this->conn             = $this->em->getConnection();
189 719
        $this->platform         = $this->conn->getDatabasePlatform();
190 719
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 719
    }
192
193
    /**
194
     * Gets the Query instance used by the walker.
195
     *
196
     * @return Query.
0 ignored issues
show
Documentation Bug introduced by
The doc comment Query. at position 0 could not be parsed: Unknown type name 'Query.' at position 0 in Query..
Loading history...
197
     */
198
    public function getQuery()
199
    {
200
        return $this->query;
201
    }
202
203
    /**
204
     * Gets the Connection used by the walker.
205
     *
206
     * @return \Doctrine\DBAL\Connection
207
     */
208 48
    public function getConnection()
209
    {
210 48
        return $this->conn;
211
    }
212
213
    /**
214
     * Gets the EntityManager used by the walker.
215
     *
216
     * @return \Doctrine\ORM\EntityManager
217
     */
218 22
    public function getEntityManager()
219
    {
220 22
        return $this->em;
221
    }
222
223
    /**
224
     * Gets the information about a single query component.
225
     *
226
     * @param string $dqlAlias The DQL alias.
227
     *
228
     * @return array
229
     */
230 17
    public function getQueryComponent($dqlAlias)
231
    {
232 17
        return $this->queryComponents[$dqlAlias];
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getQueryComponents()
239
    {
240
        return $this->queryComponents;
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 1
    public function setQueryComponent($dqlAlias, array $queryComponent)
247
    {
248 1
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
249
250 1
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
251 1
            throw QueryException::invalidQueryComponent($dqlAlias);
252
        }
253
254
        $this->queryComponents[$dqlAlias] = $queryComponent;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 713
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 713
            case ($AST instanceof AST\DeleteStatement):
264 40
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 40
                return ($primaryClass->isInheritanceTypeJoined())
267 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 40
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 678
            case ($AST instanceof AST\UpdateStatement):
271 29
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 29
                return ($primaryClass->isInheritanceTypeJoined())
274 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 29
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 653
                return new Exec\SingleSelectExecutor($AST, $this);
279
        }
280
    }
281
282
    /**
283
     * Generates a unique, short SQL table alias.
284
     *
285
     * @param string $tableName Table name
286
     * @param string $dqlAlias  The DQL alias.
287
     *
288
     * @return string Generated table alias.
289
     */
290 664
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 664
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 664
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 664
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 664
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 664
        return $this->tableAliasMap[$tableName];
300
    }
301
302
    /**
303
     * Forces the SqlWalker to use a specific alias for a table name, rather than
304
     * generating an alias on its own.
305
     *
306
     * @param string $tableName
307
     * @param string $alias
308
     * @param string $dqlAlias
309
     *
310
     * @return string
311
     */
312 67
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 67
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 67
        $this->tableAliasMap[$tableName] = $alias;
317
318 67
        return $alias;
319
    }
320
321
    /**
322
     * Gets an SQL column alias for a column name.
323
     *
324
     * @param string $columnName
325
     *
326
     * @return string
327
     */
328 653
    public function getSQLColumnAlias($columnName)
329
    {
330 653
        return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
331
    }
332
333
    /**
334
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
335
     * for the given class.
336
     *
337
     * @param ClassMetadata $class    The class for which to generate the joins.
338
     * @param string        $dqlAlias The DQL alias of the class.
339
     *
340
     * @return string The SQL.
341
     */
342 113
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 113
        $sql = '';
345
346 113
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 113
        foreach ($class->parentClasses as $parentClassName) {
350 78
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 78
            $tableAlias  = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
352
353
            // If this is a joined association we must use left joins to preserve the correct result.
354 78
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 78
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 78
            $sqlParts = [];
358
359 78
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
360 78
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 78
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 78
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 113
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 92
        foreach ($class->subClasses as $subClassName) {
378 41
            $subClass   = $this->em->getClassMetadata($subClassName);
379 41
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 41
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 41
            $sqlParts = [];
384
385 41
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
386 41
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 41
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 92
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 647
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 647
        $orderedColumns = [];
401
402 647
        foreach ($this->selectedClasses as $selectedClass) {
403 496
            $dqlAlias  = $selectedClass['dqlAlias'];
404 496
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 496
            if ( ! isset($qComp['relation']['orderBy'])) {
407 496
                continue;
408
            }
409
410 6
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 6
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 6
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 6
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415 1
                    ? $persister->getOwningTable($fieldName)
416 6
                    : $qComp['metadata']->getTableName();
417
418 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 6
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 647
        return implode(', ', $orderedColumns);
431
    }
432
433
    /**
434
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
435
     *
436
     * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
437
     *
438
     * @return string
439
     */
440 708
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 708
        $sqlParts = [];
443
444 708
        foreach ($dqlAliases as $dqlAlias) {
445 708
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 708
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 41
            $conn   = $this->em->getConnection();
450 41
            $values = [];
451
452 41
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 21
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 41
            foreach ($class->subClasses as $subclassName) {
457 30
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 41
            $sqlTableAlias = ($this->useSqlTableAliases)
461 36
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 41
                : '';
463
464 41
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 708
        $sql = implode(' AND ', $sqlParts);
468
469 708
        return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
470
    }
471
472
    /**
473
     * Generates the filter SQL for a given entity and table alias.
474
     *
475
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
476
     * @param string        $targetTableAlias The table alias of the joined/selected table.
477
     *
478
     * @return string The SQL query part to add to a query.
479
     */
480 326
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 326
        if (!$this->em->hasFilters()) {
483 286
            return '';
484
        }
485
486 45
        switch($targetEntity->inheritanceType) {
487 45
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 35
                break;
489 10
            case ClassMetadata::INHERITANCE_TYPE_JOINED:
490
                // The classes in the inheritance will be added to the query one by one,
491
                // but only the root node is getting filtered
492 6
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493 4
                    return '';
494
                }
495 6
                break;
496 4
            case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
497
                // With STI the table will only be queried once, make sure that the filters
498
                // are added to the root entity
499 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500 4
                break;
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
        }
505
506 45
        $filterClauses = [];
507 45
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
508 10
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
509 10
                $filterClauses[] = '(' . $filterExpr . ')';
510
            }
511
        }
512
513 45
        return implode(' AND ', $filterClauses);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 653
    public function walkSelectStatement(AST\SelectStatement $AST)
520
    {
521 653
        $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

521
        /** @scrutinizer ignore-call */ 
522
        $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...
522 653
        $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

522
        /** @scrutinizer ignore-call */ 
523
        $offset   = $this->query->getFirstResult();
Loading history...
523 653
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
524 653
        $sql      = $this->walkSelectClause($AST->selectClause)
525 653
            . $this->walkFromClause($AST->fromClause)
526 651
            . $this->walkWhereClause($AST->whereClause);
527
528 648
        if ($AST->groupByClause) {
529 27
            $sql .= $this->walkGroupByClause($AST->groupByClause);
530
        }
531
532 648
        if ($AST->havingClause) {
533 14
            $sql .= $this->walkHavingClause($AST->havingClause);
534
        }
535
536 648
        if ($AST->orderByClause) {
537 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
538
        }
539
540 647
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
541 6
            $sql .= ' ORDER BY ' . $orderBySql;
542
        }
543
544 647
        if ($limit !== null || $offset !== null) {
545 57
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
546
        }
547
548 647
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
549 642
            return $sql;
550
        }
551
552 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
553 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
554
        }
555
556 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
557 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
558
        }
559
560 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
561
            throw QueryException::invalidLockMode();
562
        }
563
564 1
        foreach ($this->selectedClasses as $selectedClass) {
565 1
            if ( ! $selectedClass['class']->isVersioned) {
566 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->name);
567
            }
568
        }
569
570
        return $sql;
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576 25
    public function walkUpdateStatement(AST\UpdateStatement $AST)
577
    {
578 25
        $this->useSqlTableAliases = false;
579 25
        $this->rsm->isSelect      = false;
580
581 25
        return $this->walkUpdateClause($AST->updateClause)
582 25
            . $this->walkWhereClause($AST->whereClause);
583
    }
584
585
    /**
586
     * {@inheritdoc}
587
     */
588 38
    public function walkDeleteStatement(AST\DeleteStatement $AST)
589
    {
590 38
        $this->useSqlTableAliases = false;
591 38
        $this->rsm->isSelect      = false;
592
593 38
        return $this->walkDeleteClause($AST->deleteClause)
594 38
            . $this->walkWhereClause($AST->whereClause);
595
    }
596
597
    /**
598
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
599
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
600
     *
601
     * @param string $identVariable
602
     *
603
     * @return string
604
     */
605 2
    public function walkEntityIdentificationVariable($identVariable)
606
    {
607 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
608 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
609 2
        $sqlParts   = [];
610
611 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
612 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
613
        }
614
615 2
        return implode(', ', $sqlParts);
616
    }
617
618
    /**
619
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
620
     *
621
     * @param string $identificationVariable
622
     * @param string $fieldName
623
     *
624
     * @return string The SQL.
625
     */
626 428
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 428
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630
        if (
631 428
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
632 428
            isset($class->fieldMappings[$fieldName]['inherited'])
633
        ) {
634 38
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
635
        }
636
637 428
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
638
    }
639
640
    /**
641
     * {@inheritdoc}
642
     */
643 498
    public function walkPathExpression($pathExpr)
644
    {
645 498
        $sql = '';
646
647
        /* @var $pathExpr Query\AST\PathExpression */
648 498
        switch ($pathExpr->type) {
649 498
            case AST\PathExpression::TYPE_STATE_FIELD:
650 476
                $fieldName = $pathExpr->field;
651 476
                $dqlAlias = $pathExpr->identificationVariable;
652 476
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 476
                if ($this->useSqlTableAliases) {
655 428
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 476
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 476
                break;
660
661 62
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 62
                $fieldName = $pathExpr->field;
665 62
                $dqlAlias = $pathExpr->identificationVariable;
666 62
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 62
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 62
                $assoc = $class->associationMappings[$fieldName];
673
674 62
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 60
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 59
                if ($this->useSqlTableAliases) {
684 56
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 59
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 59
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 495
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 653
    public function walkSelectClause($selectClause)
701
    {
702 653
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 653
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
704
705 653
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 653
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $addMetaColumns = (! $th..._INCLUDE_META_COLUMNS)), Probably Intended Meaning: $addMetaColumns = ! $thi..._INCLUDE_META_COLUMNS))
Loading history...
710 490
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 281
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 653
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 653
        foreach ($this->selectedClasses as $selectedClass) {
716 502
            $class       = $selectedClass['class'];
717 502
            $dqlAlias    = $selectedClass['dqlAlias'];
718 502
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 502
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 502
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 153
                $this->rsm->addJoinedEntityResult(
725 153
                    $class->name,
726 153
                    $dqlAlias,
727 153
                    $this->queryComponents[$dqlAlias]['parent'],
728 153
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 502
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 99
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 99
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 99
                $discrColumn = $rootClass->discriminatorColumn;
737 99
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 99
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 99
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 99
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 502
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 166
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 372
            foreach ($class->associationMappings as $assoc) {
752 322
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)
753 322
                    || ( ! $addMetaColumns && !isset($assoc['id']))) {
754 269
                    continue;
755
                }
756
757 287
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
758 287
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
759 287
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
760 287
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
761
762 287
                foreach ($assoc['joinColumns'] as $joinColumn) {
763 287
                    $columnName  = $joinColumn['name'];
764 287
                    $columnAlias = $this->getSQLColumnAlias($columnName);
765 287
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
766
767 287
                    $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
768 287
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
769
770 287
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 372
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 366
            foreach ($class->subClasses as $subClassName) {
781 38
                $subClass      = $this->em->getClassMetadata($subClassName);
782 38
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 38
                foreach ($subClass->associationMappings as $assoc) {
785
                    // Skip if association is inherited
786 27
                    if (isset($assoc['inherited'])) continue;
787
788 16
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
789 14
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
790
791 14
                        foreach ($assoc['joinColumns'] as $joinColumn) {
792 14
                            $columnName  = $joinColumn['name'];
793 14
                            $columnAlias = $this->getSQLColumnAlias($columnName);
794 14
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
795
796 14
                            $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
797 14
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
798
799 366
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
800
                        }
801
                    }
802
                }
803
            }
804
        }
805
806 653
        $sql .= implode(', ', $sqlSelectExpressions);
807
808 653
        return $sql;
809
    }
810
811
    /**
812
     * {@inheritdoc}
813
     */
814 655
    public function walkFromClause($fromClause)
815
    {
816 655
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
817 655
        $sqlParts = [];
818
819 655
        foreach ($identificationVarDecls as $identificationVariableDecl) {
820 655
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
821
        }
822
823 653
        return ' FROM ' . implode(', ', $sqlParts);
824
    }
825
826
    /**
827
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
828
     *
829
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
830
     *
831
     * @return string
832
     */
833 656
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
834
    {
835 656
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
836
837 656
        if ($identificationVariableDecl->indexBy) {
838 7
            $this->walkIndexBy($identificationVariableDecl->indexBy);
839
        }
840
841 656
        foreach ($identificationVariableDecl->joins as $join) {
842 238
            $sql .= $this->walkJoin($join);
843
        }
844
845 654
        return $sql;
846
    }
847
848
    /**
849
     * Walks down a IndexBy AST node.
850
     *
851
     * @param AST\IndexBy $indexBy
852
     *
853
     * @return void
854
     */
855 10
    public function walkIndexBy($indexBy)
856
    {
857 10
        $pathExpr = $indexBy->singleValuedPathExpression;
858 10
        $alias    = $pathExpr->identificationVariable;
859
860
        /** @var Query\AST\PathExpression $pathExpr */
861 10
        switch ($pathExpr->type) {
862 10
            case AST\PathExpression::TYPE_STATE_FIELD:
863 9
                $field = $pathExpr->field;
864 9
                break;
865
866 1
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
867
                // 1- the owning side:
868
                //    Just use the foreign key, i.e. u.group_id
869 1
                $fieldName = $pathExpr->field;
870 1
                $class     = $this->queryComponents[$alias]['metadata'];
871
872 1
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
873
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
874
                }
875
876 1
                $assoc = $class->associationMappings[$fieldName];
877
878 1
                if ( ! $assoc['isOwningSide']) {
879
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
880
                }
881
882 1
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
883
                    throw QueryException::associationPathCompositeKeyNotSupported();
884
                }
885
886 1
                $field = reset($assoc['targetToSourceKeyColumns']);
887 1
                break;
888
889
            default:
890
                throw QueryException::invalidPathExpression($pathExpr);
891
        }
892
893 10
        if (isset($this->scalarFields[$alias][$field])) {
894
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
895
896
            return;
897
        }
898
899 10
        $this->rsm->addIndexBy($alias, $field);
900 10
    }
901
902
    /**
903
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
904
     *
905
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
906
     *
907
     * @return string
908
     */
909 656
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
910
    {
911 656
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
912
    }
913
914
    /**
915
     * Generate appropriate SQL for RangeVariableDeclaration AST node
916
     *
917
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
918
     * @param bool $buildNestedJoins
919
     *
920
     * @return string
921
     */
922 656
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
923
    {
924 656
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
925 656
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
926
927 656
        if ($rangeVariableDeclaration->isRoot) {
928 656
            $this->rootAliases[] = $dqlAlias;
929
        }
930
931 656
        $sql = $this->platform->appendLockHint(
932 656
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
933 656
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
934 656
            $this->query->getHint(Query::HINT_LOCK_MODE)
0 ignored issues
show
Bug introduced by
It seems like $this->query->getHint(Do...\Query::HINT_LOCK_MODE) can also be of type false; however, parameter $lockMode of Doctrine\DBAL\Platforms\...tform::appendLockHint() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

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

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