Passed
Push — 2.7 ( 8d67ee...73ec48 )
by Gabriel
06:49
created

SqlWalker::walkNewObject()   B

Complexity

Conditions 11
Paths 10

Size

Total Lines 66
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 11.0105

Importance

Changes 0
Metric Value
eloc 46
c 0
b 0
f 0
dl 0
loc 66
ccs 43
cts 45
cp 0.9556
rs 7.3166
cc 11
nc 10
nop 2
crap 11.0105

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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