Failed Conditions
Pull Request — 2.8.x (#7918)
by
unknown
11:13
created

SqlWalker::walkIndexBy()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 44
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7.7656

Importance

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

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

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

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