Passed
Pull Request — 2.7 (#8050)
by Benjamin
06:49
created

SqlWalker::walkArithmeticTerm()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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

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

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

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