Failed Conditions
Pull Request — 2.6 (#7882)
by
unknown
08:05
created

SqlWalker::walkGroupByItem()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 42
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 9.0086

Importance

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

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

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

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

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

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

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

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

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

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