Failed Conditions
Pull Request — 2.8.x (#8006)
by Maciej
07:24
created

SqlWalker::walkLiteral()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4.25

Importance

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

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

521
        /** @scrutinizer ignore-call */ 
522
        $limit    = $this->query->getMaxResults();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
522 652
        $offset   = $this->query->getFirstResult();
0 ignored issues
show
Bug introduced by
The method getFirstResult() does not exist on Doctrine\ORM\AbstractQuery. It seems like you code against a sub-type of Doctrine\ORM\AbstractQuery such as Doctrine\ORM\Query. ( Ignorable by Annotation )

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

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

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