Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

lib/Doctrine/ORM/Query/SqlWalker.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\DBAL\Types\Type;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use Doctrine\ORM\OptimisticLockException;
27
use Doctrine\ORM\Query;
28
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
29
use Doctrine\ORM\Utility\PersisterHelper;
30
31
/**
32
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
33
 * the corresponding SQL.
34
 *
35
 * @author Guilherme Blanco <[email protected]>
36
 * @author Roman Borschel <[email protected]>
37
 * @author Benjamin Eberlei <[email protected]>
38
 * @author Alexander <[email protected]>
39
 * @author Fabio B. Silva <[email protected]>
40
 * @since  2.0
41
 */
42
class SqlWalker implements TreeWalker
43
{
44
    /**
45
     * @var string
46
     */
47
    const HINT_DISTINCT = 'doctrine.distinct';
48
49
    /**
50
     * @var ResultSetMapping
51
     */
52
    private $rsm;
53
54
    /**
55
     * Counter for generating unique column aliases.
56
     *
57
     * @var integer
58
     */
59
    private $aliasCounter = 0;
60
61
    /**
62
     * Counter for generating unique table aliases.
63
     *
64
     * @var integer
65
     */
66
    private $tableAliasCounter = 0;
67
68
    /**
69
     * Counter for generating unique scalar result.
70
     *
71
     * @var integer
72
     */
73
    private $scalarResultCounter = 1;
74
75
    /**
76
     * Counter for generating unique parameter indexes.
77
     *
78
     * @var integer
79
     */
80
    private $sqlParamIndex = 0;
81
82
    /**
83
     * Counter for generating indexes.
84
     *
85
     * @var integer
86
     */
87
    private $newObjectCounter = 0;
88
89
    /**
90
     * @var ParserResult
91
     */
92
    private $parserResult;
93
94
    /**
95
     * @var \Doctrine\ORM\EntityManager
96
     */
97
    private $em;
98
99
    /**
100
     * @var \Doctrine\DBAL\Connection
101
     */
102
    private $conn;
103
104
    /**
105
     * @var \Doctrine\ORM\AbstractQuery
106
     */
107
    private $query;
108
109
    /**
110
     * @var array
111
     */
112
    private $tableAliasMap = [];
113
114
    /**
115
     * Map from result variable names to their SQL column alias names.
116
     *
117
     * @var array
118
     */
119
    private $scalarResultAliasMap = [];
120
121
    /**
122
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
123
     *
124
     * @var array
125
     */
126
    private $orderedColumnsMap = [];
127
128
    /**
129
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
130
     *
131
     * @var array
132
     */
133
    private $scalarFields = [];
134
135
    /**
136
     * Map of all components/classes that appear in the DQL query.
137
     *
138
     * @var array
139
     */
140
    private $queryComponents;
141
142
    /**
143
     * A list of classes that appear in non-scalar SelectExpressions.
144
     *
145
     * @var array
146
     */
147
    private $selectedClasses = [];
148
149
    /**
150
     * The DQL alias of the root class of the currently traversed query.
151
     *
152
     * @var array
153
     */
154
    private $rootAliases = [];
155
156
    /**
157
     * Flag that indicates whether to generate SQL table aliases in the SQL.
158
     * These should only be generated for SELECT queries, not for UPDATE/DELETE.
159
     *
160
     * @var boolean
161
     */
162
    private $useSqlTableAliases = true;
163
164
    /**
165
     * The database platform abstraction.
166
     *
167
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
168
     */
169
    private $platform;
170
171
    /**
172
     * The quote strategy.
173
     *
174
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
175
     */
176
    private $quoteStrategy;
177
178
    /**
179
     * {@inheritDoc}
180
     */
181 699
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 699
        $this->query            = $query;
184 699
        $this->parserResult     = $parserResult;
185 699
        $this->queryComponents  = $queryComponents;
186 699
        $this->rsm              = $parserResult->getResultSetMapping();
187 699
        $this->em               = $query->getEntityManager();
188 699
        $this->conn             = $this->em->getConnection();
189 699
        $this->platform         = $this->conn->getDatabasePlatform();
190 699
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 699
    }
192
193
    /**
194
     * Gets the Query instance used by the walker.
195
     *
196
     * @return Query.
197
     */
198
    public function getQuery()
199
    {
200
        return $this->query;
201
    }
202
203
    /**
204
     * Gets the Connection used by the walker.
205
     *
206
     * @return \Doctrine\DBAL\Connection
207
     */
208 35
    public function getConnection()
209
    {
210 35
        return $this->conn;
211
    }
212
213
    /**
214
     * Gets the EntityManager used by the walker.
215
     *
216
     * @return \Doctrine\ORM\EntityManager
217
     */
218 22
    public function getEntityManager()
219
    {
220 22
        return $this->em;
221
    }
222
223
    /**
224
     * Gets the information about a single query component.
225
     *
226
     * @param string $dqlAlias The DQL alias.
227
     *
228
     * @return array
229
     */
230 17
    public function getQueryComponent($dqlAlias)
231
    {
232 17
        return $this->queryComponents[$dqlAlias];
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getQueryComponents()
239
    {
240
        return $this->queryComponents;
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 1 View Code Duplication
    public function setQueryComponent($dqlAlias, array $queryComponent)
247
    {
248 1
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
249
250 1
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
251 1
            throw QueryException::invalidQueryComponent($dqlAlias);
252
        }
253
254
        $this->queryComponents[$dqlAlias] = $queryComponent;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 693
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 693
            case ($AST instanceof AST\DeleteStatement):
264 38
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 38
                return ($primaryClass->isInheritanceTypeJoined())
267 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 38
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 659
            case ($AST instanceof AST\UpdateStatement):
271 29
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 29
                return ($primaryClass->isInheritanceTypeJoined())
274 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 29
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 634
                return new Exec\SingleSelectExecutor($AST, $this);
279
        }
280
    }
281
282
    /**
283
     * Generates a unique, short SQL table alias.
284
     *
285
     * @param string $tableName Table name
286
     * @param string $dqlAlias  The DQL alias.
287
     *
288
     * @return string Generated table alias.
289
     */
290 645
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 645
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 645
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 645
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 645
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 645
        return $this->tableAliasMap[$tableName];
300
    }
301
302
    /**
303
     * Forces the SqlWalker to use a specific alias for a table name, rather than
304
     * generating an alias on its own.
305
     *
306
     * @param string $tableName
307
     * @param string $alias
308
     * @param string $dqlAlias
309
     *
310
     * @return string
311
     */
312 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 65
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 65
        $this->tableAliasMap[$tableName] = $alias;
317
318 65
        return $alias;
319
    }
320
321
    /**
322
     * Gets an SQL column alias for a column name.
323
     *
324
     * @param string $columnName
325
     *
326
     * @return string
327
     */
328 634
    public function getSQLColumnAlias($columnName)
329
    {
330 634
        return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
331
    }
332
333
    /**
334
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
335
     * for the given class.
336
     *
337
     * @param ClassMetadata $class    The class for which to generate the joins.
338
     * @param string        $dqlAlias The DQL alias of the class.
339
     *
340
     * @return string The SQL.
341
     */
342 100
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 100
        $sql = '';
345
346 100
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 100
        foreach ($class->parentClasses as $parentClassName) {
350 67
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 67
            $tableAlias  = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
352
353
            // If this is a joined association we must use left joins to preserve the correct result.
354 67
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 67
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 67
            $sqlParts = [];
358
359 67 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
360 67
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 67
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 67
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 100
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 79
        foreach ($class->subClasses as $subClassName) {
378 39
            $subClass   = $this->em->getClassMetadata($subClassName);
379 39
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 39
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 39
            $sqlParts = [];
384
385 39 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
386 39
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 39
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 79
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 628
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 628
        $orderedColumns = [];
401
402 628
        foreach ($this->selectedClasses as $selectedClass) {
403 490
            $dqlAlias  = $selectedClass['dqlAlias'];
404 490
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 490
            if ( ! isset($qComp['relation']['orderBy'])) {
407 490
                continue;
408
            }
409
410 6
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 6
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 6
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 6
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415 1
                    ? $persister->getOwningTable($fieldName)
416 6
                    : $qComp['metadata']->getTableName();
417
418 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 6
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 628
        return implode(', ', $orderedColumns);
431
    }
432
433
    /**
434
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
435
     *
436
     * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
437
     *
438
     * @return string
439
     */
440 688
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 688
        $sqlParts = [];
443
444 688
        foreach ($dqlAliases as $dqlAlias) {
445 688
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 688
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 40
            $conn   = $this->em->getConnection();
450 40
            $values = [];
451
452 40
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 21
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 40
            foreach ($class->subClasses as $subclassName) {
457 29
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 40
            $sqlTableAlias = ($this->useSqlTableAliases)
461 35
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 40
                : '';
463
464 40
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 688
        $sql = implode(' AND ', $sqlParts);
468
469 688
        return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
470
    }
471
472
    /**
473
     * Generates the filter SQL for a given entity and table alias.
474
     *
475
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
476
     * @param string        $targetTableAlias The table alias of the joined/selected table.
477
     *
478
     * @return string The SQL query part to add to a query.
479
     */
480 322
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 322
        if (!$this->em->hasFilters()) {
483 284
            return '';
484
        }
485
486 43
        switch($targetEntity->inheritanceType) {
487 43
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 33
                break;
489 10
            case ClassMetadata::INHERITANCE_TYPE_JOINED:
490
                // The classes in the inheritance will be added to the query one by one,
491
                // but only the root node is getting filtered
492 6
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493 4
                    return '';
494
                }
495 6
                break;
496 4
            case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
497
                // With STI the table will only be queried once, make sure that the filters
498
                // are added to the root entity
499 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500 4
                break;
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
        }
505
506 43
        $filterClauses = [];
507 43
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
508 10
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
509 10
                $filterClauses[] = '(' . $filterExpr . ')';
510
            }
511
        }
512
513 43
        return implode(' AND ', $filterClauses);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 634
    public function walkSelectStatement(AST\SelectStatement $AST)
520
    {
521 634
        $limit    = $this->query->getMaxResults();
522 634
        $offset   = $this->query->getFirstResult();
523 634
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
524 634
        $sql      = $this->walkSelectClause($AST->selectClause)
525 634
            . $this->walkFromClause($AST->fromClause)
526 632
            . $this->walkWhereClause($AST->whereClause);
527
528 629
        if ($AST->groupByClause) {
529 24
            $sql .= $this->walkGroupByClause($AST->groupByClause);
530
        }
531
532 629
        if ($AST->havingClause) {
533 14
            $sql .= $this->walkHavingClause($AST->havingClause);
534
        }
535
536 629
        if ($AST->orderByClause) {
537 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
538
        }
539
540 628
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
541 6
            $sql .= ' ORDER BY ' . $orderBySql;
542
        }
543
544 628 View Code Duplication
        if ($limit !== null || $offset !== null) {
545 39
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
546
        }
547
548 628
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
549 623
            return $sql;
550
        }
551
552 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
553 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
554
        }
555
556 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
557 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
558
        }
559
560 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
561
            throw QueryException::invalidLockMode();
562
        }
563
564 1
        foreach ($this->selectedClasses as $selectedClass) {
565 1
            if ( ! $selectedClass['class']->isVersioned) {
566 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->name);
567
            }
568
        }
569
570
        return $sql;
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576 25
    public function walkUpdateStatement(AST\UpdateStatement $AST)
577
    {
578 25
        $this->useSqlTableAliases = false;
579 25
        $this->rsm->isSelect      = false;
580
581 25
        return $this->walkUpdateClause($AST->updateClause)
582 25
            . $this->walkWhereClause($AST->whereClause);
583
    }
584
585
    /**
586
     * {@inheritdoc}
587
     */
588 36
    public function walkDeleteStatement(AST\DeleteStatement $AST)
589
    {
590 36
        $this->useSqlTableAliases = false;
591 36
        $this->rsm->isSelect      = false;
592
593 36
        return $this->walkDeleteClause($AST->deleteClause)
594 36
            . $this->walkWhereClause($AST->whereClause);
595
    }
596
597
    /**
598
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
599
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
600
     *
601
     * @param string $identVariable
602
     *
603
     * @return string
604
     */
605 2
    public function walkEntityIdentificationVariable($identVariable)
606
    {
607 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
608 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
609 2
        $sqlParts   = [];
610
611 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
612 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
613
        }
614
615 2
        return implode(', ', $sqlParts);
616
    }
617
618
    /**
619
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
620
     *
621
     * @param string $identificationVariable
622
     * @param string $fieldName
623
     *
624
     * @return string The SQL.
625
     */
626 427
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 427
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630
        if (
631 427
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
632 427
            isset($class->fieldMappings[$fieldName]['inherited'])
633
        ) {
634 38
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
635
        }
636
637 427
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
638
    }
639
640
    /**
641
     * {@inheritdoc}
642
     */
643 496
    public function walkPathExpression($pathExpr)
644
    {
645 496
        $sql = '';
646
647
        /* @var $pathExpr Query\AST\PathExpression */
648 496
        switch ($pathExpr->type) {
649 496
            case AST\PathExpression::TYPE_STATE_FIELD:
650 475
                $fieldName = $pathExpr->field;
651 475
                $dqlAlias = $pathExpr->identificationVariable;
652 475
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 475
                if ($this->useSqlTableAliases) {
655 427
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 475
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 475
                break;
660
661 62
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 62
                $fieldName = $pathExpr->field;
665 62
                $dqlAlias = $pathExpr->identificationVariable;
666 62
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 62
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 62
                $assoc = $class->associationMappings[$fieldName];
673
674 62
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 60
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 59
                if ($this->useSqlTableAliases) {
684 56
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 59
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 59
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 493
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 634
    public function walkSelectClause($selectClause)
701
    {
702 634
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 634
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
704
705 634
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 634
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 471
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 290
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 634
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 634
        foreach ($this->selectedClasses as $selectedClass) {
716 496
            $class       = $selectedClass['class'];
717 496
            $dqlAlias    = $selectedClass['dqlAlias'];
718 496
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 496
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 496
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 159
                $this->rsm->addJoinedEntityResult(
725 159
                    $class->name,
726 159
                    $dqlAlias,
727 159
                    $this->queryComponents[$dqlAlias]['parent'],
728 159
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 496
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 97
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 97
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 97
                $discrColumn = $rootClass->discriminatorColumn;
737 97
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 97
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 97
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 97
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 496
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 181
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 365
            foreach ($class->associationMappings as $assoc) {
752 319
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)
753 319
                    || ( ! $addMetaColumns && !isset($assoc['id']))) {
754 268
                    continue;
755
                }
756
757 286
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
758 286
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
759 286
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
760 286
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
761
762 286 View Code Duplication
                foreach ($assoc['joinColumns'] as $joinColumn) {
763 286
                    $columnName  = $joinColumn['name'];
764 286
                    $columnAlias = $this->getSQLColumnAlias($columnName);
765 286
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
766
767 286
                    $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
768 286
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
769
770 286
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 365
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 360
            foreach ($class->subClasses as $subClassName) {
781 37
                $subClass      = $this->em->getClassMetadata($subClassName);
782 37
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 37
                foreach ($subClass->associationMappings as $assoc) {
785
                    // Skip if association is inherited
786 27
                    if (isset($assoc['inherited'])) continue;
787
788 16
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
789 14
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
790
791 14 View Code Duplication
                        foreach ($assoc['joinColumns'] as $joinColumn) {
792 14
                            $columnName  = $joinColumn['name'];
793 14
                            $columnAlias = $this->getSQLColumnAlias($columnName);
794 14
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
795
796 14
                            $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
797 14
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
798
799 360
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
800
                        }
801
                    }
802
                }
803
            }
804
        }
805
806 634
        $sql .= implode(', ', $sqlSelectExpressions);
807
808 634
        return $sql;
809
    }
810
811
    /**
812
     * {@inheritdoc}
813
     */
814 636 View Code Duplication
    public function walkFromClause($fromClause)
815
    {
816 636
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
817 636
        $sqlParts = [];
818
819 636
        foreach ($identificationVarDecls as $identificationVariableDecl) {
820 636
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
821
        }
822
823 634
        return ' FROM ' . implode(', ', $sqlParts);
824
    }
825
826
    /**
827
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
828
     *
829
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
830
     *
831
     * @return string
832
     */
833 637
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
834
    {
835 637
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
836
837 637
        if ($identificationVariableDecl->indexBy) {
838 6
            $this->walkIndexBy($identificationVariableDecl->indexBy);
839
        }
840
841 637
        foreach ($identificationVariableDecl->joins as $join) {
842 247
            $sql .= $this->walkJoin($join);
843
        }
844
845 635
        return $sql;
846
    }
847
848
    /**
849
     * Walks down a IndexBy AST node.
850
     *
851
     * @param AST\IndexBy $indexBy
852
     *
853
     * @return void
854
     */
855 9
    public function walkIndexBy($indexBy)
856
    {
857 9
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
858 9
        $alias          = $pathExpression->identificationVariable;
859 9
        $field          = $pathExpression->field;
860
861 9
        if (isset($this->scalarFields[$alias][$field])) {
862
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
863
864
            return;
865
        }
866
867 9
        $this->rsm->addIndexBy($alias, $field);
868 9
    }
869
870
    /**
871
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
872
     *
873
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
874
     *
875
     * @return string
876
     */
877 637
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
878
    {
879 637
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
880
    }
881
882
    /**
883
     * Generate appropriate SQL for RangeVariableDeclaration AST node
884
     *
885
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
886
     * @param bool $buildNestedJoins
887
     *
888
     * @return string
889
     */
890 637
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
891
    {
892 637
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
893 637
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
894
895 637
        if ($rangeVariableDeclaration->isRoot) {
896 637
            $this->rootAliases[] = $dqlAlias;
897
        }
898
899 637
        $sql = $this->platform->appendLockHint(
900 637
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
901 637
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
902 637
            $this->query->getHint(Query::HINT_LOCK_MODE)
903
        );
904
905 637
        if ($class->isInheritanceTypeJoined()) {
906 96
            if ($buildNestedJoins) {
907 3
                $sql = '(' . $sql . $this->_generateClassTableInheritanceJoins($class, $dqlAlias) . ')';
908
            } else {
909 94
                $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
910
            }
911
        }
912
913 637
        return $sql;
914
    }
915
916
    /**
917
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
918
     *
919
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
920
     * @param int                            $joinType
921
     * @param AST\ConditionalExpression      $condExpr
922
     *
923
     * @return string
924
     *
925
     * @throws QueryException
926
     */
927 229
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
928
    {
929 229
        $sql = '';
930
931 229
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
932 229
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
933 229
        $indexBy                   = $joinAssociationDeclaration->indexBy;
934
935 229
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
936 229
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
937 229
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
938 229
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
939
940 229
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
941 229
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
942
943
        // Ensure we got the owning side, since it has all mapping info
944 229
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
945
946 229
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
947 3
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
948 2
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
949
            }
950
        }
951
952 227
        $targetTableJoin = null;
953
954
        // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
955
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
956
        // The owning side is necessary at this point because only it contains the JoinColumn information.
957
        switch (true) {
958 227
            case ($assoc['type'] & ClassMetadata::TO_ONE):
959 180
                $conditions = [];
960
961 180
                foreach ($assoc['joinColumns'] as $joinColumn) {
962 180
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
963 180
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
964
965 180
                    if ($relation['isOwningSide']) {
966 104
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
967
968 104
                        continue;
969
                    }
970
971 110
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
972
                }
973
974
                // Apply remaining inheritance restrictions
975 180
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
976
977 180
                if ($discrSql) {
978 3
                    $conditions[] = $discrSql;
979
                }
980
981
                // Apply the filters
982 180
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
983
984 180
                if ($filterExpr) {
985 1
                    $conditions[] = $filterExpr;
986
                }
987
988
                $targetTableJoin = [
989 180
                    'table' => $targetTableName . ' ' . $targetTableAlias,
990 180
                    'condition' => implode(' AND ', $conditions),
991
                ];
992 180
                break;
993
994 57
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
995
                // Join relation table
996 57
                $joinTable      = $assoc['joinTable'];
997 57
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
998 57
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
999
1000 57
                $conditions      = [];
1001 57
                $relationColumns = ($relation['isOwningSide'])
1002 48
                    ? $assoc['joinTable']['joinColumns']
1003 57
                    : $assoc['joinTable']['inverseJoinColumns'];
1004
1005 57 View Code Duplication
                foreach ($relationColumns as $joinColumn) {
1006 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1007 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1008
1009 57
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1010
                }
1011
1012 57
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
1013
1014
                // Join target table
1015 57
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
1016
1017 57
                $conditions      = [];
1018 57
                $relationColumns = ($relation['isOwningSide'])
1019 48
                    ? $assoc['joinTable']['inverseJoinColumns']
1020 57
                    : $assoc['joinTable']['joinColumns'];
1021
1022 57 View Code Duplication
                foreach ($relationColumns as $joinColumn) {
1023 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1024 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1025
1026 57
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1027
                }
1028
1029
                // Apply remaining inheritance restrictions
1030 57
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1031
1032 57
                if ($discrSql) {
1033 1
                    $conditions[] = $discrSql;
1034
                }
1035
1036
                // Apply the filters
1037 57
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1038
1039 57
                if ($filterExpr) {
1040 1
                    $conditions[] = $filterExpr;
1041
                }
1042
1043
                $targetTableJoin = [
1044 57
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1045 57
                    'condition' => implode(' AND ', $conditions),
1046
                ];
1047 57
                break;
1048
1049
            default:
1050
                throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1051
        }
1052
1053
        // Handle WITH clause
1054 227
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1055
1056 227
        if ($targetClass->isInheritanceTypeJoined()) {
1057 9
            $ctiJoins = $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1058
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1059 9
            if ($withCondition) {
1060 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1061
            } else {
1062 9
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1063
            }
1064
        } else {
1065 218
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1066
        }
1067
1068 227
        if ($withCondition) {
1069 5
            $sql .= ' AND ' . $withCondition;
1070
        }
1071
1072
        // Apply the indexes
1073 227
        if ($indexBy) {
1074
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1075 6
            $this->walkIndexBy($indexBy);
1076 222
        } else if (isset($relation['indexBy'])) {
1077 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1078
        }
1079
1080 227
        return $sql;
1081
    }
1082
1083
    /**
1084
     * {@inheritdoc}
1085
     */
1086 118
    public function walkFunction($function)
1087
    {
1088 118
        return $function->getSql($this);
1089
    }
1090
1091
    /**
1092
     * {@inheritdoc}
1093
     */
1094 153
    public function walkOrderByClause($orderByClause)
1095
    {
1096 153
        $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1097
1098 152
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1099
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1100
        }
1101
1102 152
        return ' ORDER BY ' . implode(', ', $orderByItems);
1103
    }
1104
1105
    /**
1106
     * {@inheritdoc}
1107
     */
1108 171
    public function walkOrderByItem($orderByItem)
1109
    {
1110 171
        $type = strtoupper($orderByItem->type);
1111 171
        $expr = $orderByItem->expression;
1112 171
        $sql  = ($expr instanceof AST\Node)
1113 164
            ? $expr->dispatch($this)
1114 170
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1115
1116 170
        $this->orderedColumnsMap[$sql] = $type;
1117
1118 170
        if ($expr instanceof AST\Subselect) {
1119 2
            return '(' . $sql . ') ' . $type;
1120
        }
1121
1122 168
        return $sql . ' ' . $type;
1123
    }
1124
1125
    /**
1126
     * {@inheritdoc}
1127
     */
1128 14
    public function walkHavingClause($havingClause)
1129
    {
1130 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1131
    }
1132
1133
    /**
1134
     * {@inheritdoc}
1135
     */
1136 247
    public function walkJoin($join)
1137
    {
1138 247
        $joinType        = $join->joinType;
1139 247
        $joinDeclaration = $join->joinAssociationDeclaration;
1140
1141 247
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1142 57
            ? ' LEFT JOIN '
1143 247
            : ' INNER JOIN ';
1144
1145
        switch (true) {
1146 247
            case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
1147 18
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1148 18
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1149 18
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1150 18
                $conditions = [];
1151
1152 18
                if ($join->conditionalExpression) {
1153 16
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1154
                }
1155
1156 18
                $isUnconditionalJoin = empty($conditions);
1157 18
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin)
1158 2
                    ? ' AND '
1159 18
                    : ' ON ';
1160
1161 18
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, !$isUnconditionalJoin);
1162
1163
                // Apply remaining inheritance restrictions
1164 18
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1165
1166 18
                if ($discrSql) {
1167 3
                    $conditions[] = $discrSql;
1168
                }
1169
1170
                // Apply the filters
1171 18
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1172
1173 18
                if ($filterExpr) {
1174
                    $conditions[] = $filterExpr;
1175
                }
1176
1177 18
                if ($conditions) {
1178 16
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1179
                }
1180
1181 18
                break;
1182
1183 229
            case ($joinDeclaration instanceof AST\JoinAssociationDeclaration):
1184 229
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1185 227
                break;
1186
        }
1187
1188 245
        return $sql;
1189
    }
1190
1191
    /**
1192
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1193
     *
1194
     * @param AST\CoalesceExpression $coalesceExpression
1195
     *
1196
     * @return string The SQL.
1197
     */
1198 2
    public function walkCoalesceExpression($coalesceExpression)
1199
    {
1200 2
        $sql = 'COALESCE(';
1201
1202 2
        $scalarExpressions = [];
1203
1204 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1205 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1206
        }
1207
1208 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1209
1210 2
        return $sql;
1211
    }
1212
1213
    /**
1214
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1215
     *
1216
     * @param AST\NullIfExpression $nullIfExpression
1217
     *
1218
     * @return string The SQL.
1219
     */
1220 3
    public function walkNullIfExpression($nullIfExpression)
1221
    {
1222 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1223
            ? $this->conn->quote($nullIfExpression->firstExpression)
1224 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1225
1226 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1227
            ? $this->conn->quote($nullIfExpression->secondExpression)
1228 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1229
1230 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1231
    }
1232
1233
    /**
1234
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1235
     *
1236
     * @param AST\GeneralCaseExpression $generalCaseExpression
1237
     *
1238
     * @return string The SQL.
1239
     */
1240 9 View Code Duplication
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1241
    {
1242 9
        $sql = 'CASE';
1243
1244 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1245 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1246 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1247
        }
1248
1249 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1250
1251 9
        return $sql;
1252
    }
1253
1254
    /**
1255
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1256
     *
1257
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1258
     *
1259
     * @return string The SQL.
1260
     */
1261 5 View Code Duplication
    public function walkSimpleCaseExpression($simpleCaseExpression)
1262
    {
1263 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1264
1265 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1266 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1267 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1268
        }
1269
1270 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1271
1272 5
        return $sql;
1273
    }
1274
1275
    /**
1276
     * {@inheritdoc}
1277
     */
1278 634
    public function walkSelectExpression($selectExpression)
1279
    {
1280 634
        $sql    = '';
1281 634
        $expr   = $selectExpression->expression;
1282 634
        $hidden = $selectExpression->hiddenAliasResultVariable;
1283
1284
        switch (true) {
1285 634
            case ($expr instanceof AST\PathExpression):
1286 102
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1287
                    throw QueryException::invalidPathExpression($expr);
1288
                }
1289
1290 102
                $fieldName = $expr->field;
1291 102
                $dqlAlias  = $expr->identificationVariable;
1292 102
                $qComp     = $this->queryComponents[$dqlAlias];
1293 102
                $class     = $qComp['metadata'];
1294
1295 102
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1296 102
                $tableName   = ($class->isInheritanceTypeJoined())
1297 11
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1298 102
                    : $class->getTableName();
1299
1300 102
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1301 102
                $fieldMapping  = $class->fieldMappings[$fieldName];
1302 102
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1303 102
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1304 102
                $col           = $sqlTableAlias . '.' . $columnName;
1305
1306 102
                if (isset($fieldMapping['requireSQLConversion'])) {
1307 2
                    $type = Type::getType($fieldMapping['type']);
1308 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1309
                }
1310
1311 102
                $sql .= $col . ' AS ' . $columnAlias;
1312
1313 102
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1314
1315 102
                if ( ! $hidden) {
1316 102
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1317 102
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1318
                }
1319
1320 102
                break;
1321
1322 583
            case ($expr instanceof AST\AggregateExpression):
1323 574
            case ($expr instanceof AST\Functions\FunctionNode):
1324 534
            case ($expr instanceof AST\SimpleArithmeticExpression):
1325 534
            case ($expr instanceof AST\ArithmeticTerm):
1326 532
            case ($expr instanceof AST\ArithmeticFactor):
1327 531
            case ($expr instanceof AST\ParenthesisExpression):
1328 530
            case ($expr instanceof AST\Literal):
1329 529
            case ($expr instanceof AST\NullIfExpression):
1330 528
            case ($expr instanceof AST\CoalesceExpression):
1331 527
            case ($expr instanceof AST\GeneralCaseExpression):
1332 523 View Code Duplication
            case ($expr instanceof AST\SimpleCaseExpression):
1333 107
                $columnAlias = $this->getSQLColumnAlias('sclr');
1334 107
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1335
1336 107
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1337
1338 107
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1339
1340 107
                if ( ! $hidden) {
1341
                    // We cannot resolve field type here; assume 'string'.
1342 107
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1343
                }
1344 107
                break;
1345
1346 522 View Code Duplication
            case ($expr instanceof AST\Subselect):
1347 15
                $columnAlias = $this->getSQLColumnAlias('sclr');
1348 15
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1349
1350 15
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1351
1352 15
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1353
1354 15
                if ( ! $hidden) {
1355
                    // We cannot resolve field type here; assume 'string'.
1356 13
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1357
                }
1358 15
                break;
1359
1360 518
            case ($expr instanceof AST\NewObjectExpression):
1361 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1362 22
                break;
1363
1364
            default:
1365
                // IdentificationVariable or PartialObjectExpression
1366 496
                if ($expr instanceof AST\PartialObjectExpression) {
1367 16
                    $dqlAlias = $expr->identificationVariable;
1368 16
                    $partialFieldSet = $expr->partialFieldSet;
1369
                } else {
1370 491
                    $dqlAlias = $expr;
1371 491
                    $partialFieldSet = [];
1372
                }
1373
1374 496
                $queryComp   = $this->queryComponents[$dqlAlias];
1375 496
                $class       = $queryComp['metadata'];
1376 496
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1377
1378 496
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1379 496
                    $this->selectedClasses[$dqlAlias] = [
1380 496
                        'class'       => $class,
1381 496
                        'dqlAlias'    => $dqlAlias,
1382 496
                        'resultAlias' => $resultAlias
1383
                    ];
1384
                }
1385
1386 496
                $sqlParts = [];
1387
1388
                // Select all fields from the queried class
1389 496 View Code Duplication
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1390 495
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
1391 14
                        continue;
1392
                    }
1393
1394 494
                    $tableName = (isset($mapping['inherited']))
1395 52
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1396 494
                        : $class->getTableName();
1397
1398 494
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1399 494
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1400 494
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1401
1402 494
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1403
1404 494
                    if (isset($mapping['requireSQLConversion'])) {
1405 5
                        $type = Type::getType($mapping['type']);
1406 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1407
                    }
1408
1409 494
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1410
1411 494
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1412
1413 494
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
1414
                }
1415
1416
                // Add any additional fields of subclasses (excluding inherited fields)
1417
                // 1) on Single Table Inheritance: always, since its marginal overhead
1418
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1419
                //    since it requires outer joining subtables.
1420 496
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1421 403 View Code Duplication
                    foreach ($class->subClasses as $subClassName) {
1422 48
                        $subClass      = $this->em->getClassMetadata($subClassName);
1423 48
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1424
1425 48
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1426 48
                            if (isset($mapping['inherited']) || ($partialFieldSet && !in_array($fieldName, $partialFieldSet))) {
1427 48
                                continue;
1428
                            }
1429
1430 35
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1431 35
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1432
1433 35
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1434
1435 35
                            if (isset($mapping['requireSQLConversion'])) {
1436
                                $type = Type::getType($mapping['type']);
1437
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1438
                            }
1439
1440 35
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1441
1442 35
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1443
1444 48
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1445
                        }
1446
                    }
1447
                }
1448
1449 496
                $sql .= implode(', ', $sqlParts);
1450
        }
1451
1452 634
        return $sql;
1453
    }
1454
1455
    /**
1456
     * {@inheritdoc}
1457
     */
1458
    public function walkQuantifiedExpression($qExpr)
1459
    {
1460
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1461
    }
1462
1463
    /**
1464
     * {@inheritdoc}
1465
     */
1466 33
    public function walkSubselect($subselect)
1467
    {
1468 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1469 33
        $rootAliasesBefore = $this->rootAliases;
1470
1471 33
        $this->rootAliases = []; // reset the rootAliases for the subselect
1472 33
        $this->useSqlTableAliases = true;
1473
1474 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1475 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1476 33
        $sql .= $this->walkWhereClause($subselect->whereClause);
1477
1478 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1479 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1480 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1481
1482 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1483 33
        $this->useSqlTableAliases = $useAliasesBefore;
1484
1485 33
        return $sql;
1486
    }
1487
1488
    /**
1489
     * {@inheritdoc}
1490
     */
1491 33 View Code Duplication
    public function walkSubselectFromClause($subselectFromClause)
1492
    {
1493 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1494 33
        $sqlParts               = [];
1495
1496 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1497 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1498
        }
1499
1500 33
        return ' FROM ' . implode(', ', $sqlParts);
1501
    }
1502
1503
    /**
1504
     * {@inheritdoc}
1505
     */
1506 33
    public function walkSimpleSelectClause($simpleSelectClause)
1507
    {
1508 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1509 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1510
    }
1511
1512
    /**
1513
     * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression
1514
     *
1515
     * @return string.
1516
     */
1517 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1518
    {
1519 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1520
    }
1521
1522
    /**
1523
     * @param AST\NewObjectExpression   $newObjectExpression
1524
     * @param null|string               $newObjectResultAlias
1525
     * @return string The SQL.
1526
     */
1527 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1528
    {
1529 22
        $sqlSelectExpressions = [];
1530 22
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1531
1532 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1533 22
            $resultAlias = $this->scalarResultCounter++;
1534 22
            $columnAlias = $this->getSQLColumnAlias('sclr');
1535 22
            $fieldType   = 'string';
1536
1537
            switch (true) {
1538 22
                case ($e instanceof AST\NewObjectExpression):
1539
                    $sqlSelectExpressions[] = $e->dispatch($this);
1540
                    break;
1541
1542 22
                case ($e instanceof AST\Subselect):
1543 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1544 1
                    break;
1545
1546 22
                case ($e instanceof AST\PathExpression):
1547 22
                    $dqlAlias  = $e->identificationVariable;
1548 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1549 22
                    $class     = $qComp['metadata'];
1550 22
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1551
1552 22
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1553 22
                    break;
1554
1555 6
                case ($e instanceof AST\Literal):
1556 1
                    switch ($e->type) {
1557 1
                        case AST\Literal::BOOLEAN:
1558 1
                            $fieldType = 'boolean';
1559 1
                            break;
1560
1561 1
                        case AST\Literal::NUMERIC:
1562 1
                            $fieldType = is_float($e->value) ? 'float' : 'integer';
1563 1
                            break;
1564
                    }
1565
1566 1
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1567 1
                    break;
1568
1569
                default:
1570 5
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1571 5
                    break;
1572
            }
1573
1574 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1575 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1576
1577 22
            $this->rsm->newObjectMappings[$columnAlias] = [
1578 22
                'className' => $newObjectExpression->className,
1579 22
                'objIndex'  => $objIndex,
1580 22
                'argIndex'  => $argIndex
1581
            ];
1582
        }
1583
1584 22
        return implode(', ', $sqlSelectExpressions);
1585
    }
1586
1587
    /**
1588
     * {@inheritdoc}
1589
     */
1590 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1591
    {
1592 33
        $expr = $simpleSelectExpression->expression;
1593 33
        $sql  = ' ';
1594
1595
        switch (true) {
1596 33
            case ($expr instanceof AST\PathExpression):
1597 9
                $sql .= $this->walkPathExpression($expr);
1598 9
                break;
1599
1600 24 View Code Duplication
            case ($expr instanceof AST\Subselect):
1601
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1602
1603
                $columnAlias = 'sclr' . $this->aliasCounter++;
1604
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1605
1606
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1607
                break;
1608
1609 24
            case ($expr instanceof AST\Functions\FunctionNode):
1610 11
            case ($expr instanceof AST\SimpleArithmeticExpression):
1611 10
            case ($expr instanceof AST\ArithmeticTerm):
1612 9
            case ($expr instanceof AST\ArithmeticFactor):
1613 9
            case ($expr instanceof AST\Literal):
1614 7
            case ($expr instanceof AST\NullIfExpression):
1615 7
            case ($expr instanceof AST\CoalesceExpression):
1616 7
            case ($expr instanceof AST\GeneralCaseExpression):
1617 5 View Code Duplication
            case ($expr instanceof AST\SimpleCaseExpression):
1618 21
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1619
1620 21
                $columnAlias = $this->getSQLColumnAlias('sclr');
1621 21
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1622
1623 21
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1624 21
                break;
1625
1626 3
            case ($expr instanceof AST\ParenthesisExpression):
1627 1
                $sql .= $this->walkParenthesisExpression($expr);
1628 1
                break;
1629
1630
            default: // IdentificationVariable
1631 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1632 2
                break;
1633
        }
1634
1635 33
        return $sql;
1636
    }
1637
1638
    /**
1639
     * {@inheritdoc}
1640
     */
1641 76
    public function walkAggregateExpression($aggExpression)
1642
    {
1643 76
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1644 76
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1645
    }
1646
1647
    /**
1648
     * {@inheritdoc}
1649
     */
1650 24
    public function walkGroupByClause($groupByClause)
1651
    {
1652 24
        $sqlParts = [];
1653
1654 24
        foreach ($groupByClause->groupByItems as $groupByItem) {
1655 24
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1656
        }
1657
1658 24
        return ' GROUP BY ' . implode(', ', $sqlParts);
1659
    }
1660
1661
    /**
1662
     * {@inheritdoc}
1663
     */
1664 24
    public function walkGroupByItem($groupByItem)
1665
    {
1666
        // StateFieldPathExpression
1667 24
        if ( ! is_string($groupByItem)) {
1668 11
            return $this->walkPathExpression($groupByItem);
1669
        }
1670
1671
        // ResultVariable
1672 13
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1673 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1674
1675 2
            if ($resultVariable instanceof AST\PathExpression) {
1676 1
                return $this->walkPathExpression($resultVariable);
1677
            }
1678
1679 1
            if (isset($resultVariable->pathExpression)) {
1680
                return $this->walkPathExpression($resultVariable->pathExpression);
1681
            }
1682
1683 1
            return $this->walkResultVariable($groupByItem);
1684
        }
1685
1686
        // IdentificationVariable
1687 11
        $sqlParts = [];
1688
1689 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) {
1690 11
            $item       = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1691 11
            $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1692
1693 11
            $sqlParts[] = $this->walkPathExpression($item);
1694
        }
1695
1696 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) {
1697 11
            if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
1698 7
                $item       = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
1699 7
                $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1700
1701 11
                $sqlParts[] = $this->walkPathExpression($item);
1702
            }
1703
        }
1704
1705 11
        return implode(', ', $sqlParts);
1706
    }
1707
1708
    /**
1709
     * {@inheritdoc}
1710
     */
1711 36
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1712
    {
1713 36
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1714 36
        $tableName = $class->getTableName();
1715 36
        $sql       = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1716
1717 36
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1718 36
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1719
1720 36
        return $sql;
1721
    }
1722
1723
    /**
1724
     * {@inheritdoc}
1725
     */
1726 25
    public function walkUpdateClause($updateClause)
1727
    {
1728 25
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1729 25
        $tableName = $class->getTableName();
1730 25
        $sql       = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1731
1732 25
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1733 25
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1734
1735 25
        $sql .= ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1736
1737 25
        return $sql;
1738
    }
1739
1740
    /**
1741
     * {@inheritdoc}
1742
     */
1743 29
    public function walkUpdateItem($updateItem)
1744
    {
1745 29
        $useTableAliasesBefore = $this->useSqlTableAliases;
1746 29
        $this->useSqlTableAliases = false;
1747
1748 29
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1749 29
        $newValue = $updateItem->newValue;
1750
1751
        switch (true) {
1752 29
            case ($newValue instanceof AST\Node):
1753 28
                $sql .= $newValue->dispatch($this);
1754 28
                break;
1755
1756 1
            case ($newValue === null):
1757 1
                $sql .= 'NULL';
1758 1
                break;
1759
1760
            default:
1761
                $sql .= $this->conn->quote($newValue);
1762
                break;
1763
        }
1764
1765 29
        $this->useSqlTableAliases = $useTableAliasesBefore;
1766
1767 29
        return $sql;
1768
    }
1769
1770
    /**
1771
     * {@inheritdoc}
1772
     */
1773 691
    public function walkWhereClause($whereClause)
1774
    {
1775 691
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1776 688
        $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->rootAliases);
1777
1778 688
        if ($this->em->hasFilters()) {
1779 43
            $filterClauses = [];
1780 43
            foreach ($this->rootAliases as $dqlAlias) {
1781 43
                $class = $this->queryComponents[$dqlAlias]['metadata'];
1782 43
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1783
1784 43
                if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1785 43
                    $filterClauses[] = $filterExpr;
1786
                }
1787
            }
1788
1789 43
            if (count($filterClauses)) {
1790 6
                if ($condSql) {
1791 2
                    $condSql = '(' . $condSql . ') AND ';
1792
                }
1793
1794 6
                $condSql .= implode(' AND ', $filterClauses);
1795
            }
1796
        }
1797
1798 688
        if ($condSql) {
1799 340
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1800
        }
1801
1802 424
        if ($discrSql) {
1803 23
            return ' WHERE ' . $discrSql;
1804
        }
1805
1806 404
        return '';
1807
    }
1808
1809
    /**
1810
     * {@inheritdoc}
1811
     */
1812 372
    public function walkConditionalExpression($condExpr)
1813
    {
1814
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1815
        // if only one ConditionalTerm is defined
1816 372
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1817 317
            return $this->walkConditionalTerm($condExpr);
1818
        }
1819
1820 72
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1821
    }
1822
1823
    /**
1824
     * {@inheritdoc}
1825
     */
1826 372
    public function walkConditionalTerm($condTerm)
1827
    {
1828
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1829
        // if only one ConditionalFactor is defined
1830 372
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1831 305
            return $this->walkConditionalFactor($condTerm);
1832
        }
1833
1834 90
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1835
    }
1836
1837
    /**
1838
     * {@inheritdoc}
1839
     */
1840 372
    public function walkConditionalFactor($factor)
1841
    {
1842
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1843
        // if only one ConditionalPrimary is defined
1844 372
        return ( ! ($factor instanceof AST\ConditionalFactor))
1845 369
            ? $this->walkConditionalPrimary($factor)
1846 369
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1847
    }
1848
1849
    /**
1850
     * {@inheritdoc}
1851
     */
1852 372
    public function walkConditionalPrimary($primary)
1853
    {
1854 372
        if ($primary->isSimpleConditionalExpression()) {
1855 372
            return $primary->simpleConditionalExpression->dispatch($this);
1856
        }
1857
1858 25
        if ($primary->isConditionalExpression()) {
1859 25
            $condExpr = $primary->conditionalExpression;
1860
1861 25
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
1862
        }
1863
    }
1864
1865
    /**
1866
     * {@inheritdoc}
1867
     */
1868 5
    public function walkExistsExpression($existsExpr)
1869
    {
1870 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1871
1872 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1873
1874 5
        return $sql;
1875
    }
1876
1877
    /**
1878
     * {@inheritdoc}
1879
     */
1880 6
    public function walkCollectionMemberExpression($collMemberExpr)
1881
    {
1882 6
        $sql = $collMemberExpr->not ? 'NOT ' : '';
1883 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1884
1885 6
        $entityExpr   = $collMemberExpr->entityExpression;
1886 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1887
1888 6
        $fieldName = $collPathExpr->field;
1889 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1890
1891 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1892
1893
        switch (true) {
1894
            // InputParameter
1895 6
            case ($entityExpr instanceof AST\InputParameter):
1896 4
                $dqlParamKey = $entityExpr->name;
1897 4
                $entitySql   = '?';
1898 4
                break;
1899
1900
            // SingleValuedAssociationPathExpression | IdentificationVariable
1901 2
            case ($entityExpr instanceof AST\PathExpression):
1902 2
                $entitySql = $this->walkPathExpression($entityExpr);
1903 2
                break;
1904
1905
            default:
1906
                throw new \BadMethodCallException("Not implemented");
1907
        }
1908
1909 6
        $assoc = $class->associationMappings[$fieldName];
1910
1911 6
        if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
1912 1
            $targetClass      = $this->em->getClassMetadata($assoc['targetEntity']);
1913 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1914 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1915
1916 1
            $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1917
1918 1
            $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1919 1
            $sqlParts    = [];
1920
1921 1 View Code Duplication
            foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
1922 1
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1923
1924 1
                $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1925
            }
1926
1927 1 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1928 1
                if (isset($dqlParamKey)) {
1929 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1930
                }
1931
1932 1
                $sqlParts[] = $targetTableAlias . '.'  . $targetColumnName . ' = ' . $entitySql;
1933
            }
1934
1935 1
            $sql .= implode(' AND ', $sqlParts);
1936
        } else { // many-to-many
1937 5
            $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1938
1939 5
            $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1940 5
            $joinTable = $owningAssoc['joinTable'];
1941
1942
            // SQL table aliases
1943 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable['name']);
1944 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1945 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1946
1947
            // join to target table
1948 5
            $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias
1949 5
                . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON ';
1950
1951
            // join conditions
1952 5
            $joinColumns  = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1953 5
            $joinSqlParts = [];
1954
1955 5 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
1956 5
                $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1957
1958 5
                $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1959
            }
1960
1961 5
            $sql .= implode(' AND ', $joinSqlParts);
1962 5
            $sql .= ' WHERE ';
1963
1964 5
            $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1965 5
            $sqlParts    = [];
1966
1967 5 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
1968 5
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1969
1970 5
                $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1971
            }
1972
1973 5 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1974 5
                if (isset($dqlParamKey)) {
1975 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1976
                }
1977
1978 5
                $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' IN (' . $entitySql . ')';
1979
            }
1980
1981 5
            $sql .= implode(' AND ', $sqlParts);
1982
        }
1983
1984 6
        return $sql . ')';
1985
    }
1986
1987
    /**
1988
     * {@inheritdoc}
1989
     */
1990 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1991
    {
1992 3
        $sizeFunc = new AST\Functions\SizeFunction('size');
1993 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1994
1995 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1996
    }
1997
1998
    /**
1999
     * {@inheritdoc}
2000
     */
2001 11
    public function walkNullComparisonExpression($nullCompExpr)
2002
    {
2003 11
        $expression = $nullCompExpr->expression;
2004 11
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2005
2006
        // Handle ResultVariable
2007 11
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2008 2
            return $this->walkResultVariable($expression) . $comparison;
2009
        }
2010
2011
        // Handle InputParameter mapping inclusion to ParserResult
2012 9
        if ($expression instanceof AST\InputParameter) {
2013
            return $this->walkInputParameter($expression) . $comparison;
2014
        }
2015
2016 9
        return $expression->dispatch($this) . $comparison;
2017
    }
2018
2019
    /**
2020
     * {@inheritdoc}
2021
     */
2022 86
    public function walkInExpression($inExpr)
2023
    {
2024 86
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2025
2026 85
        $sql .= ($inExpr->subselect)
2027 7
            ? $this->walkSubselect($inExpr->subselect)
2028 85
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2029
2030 85
        $sql .= ')';
2031
2032 85
        return $sql;
2033
    }
2034
2035
    /**
2036
     * {@inheritdoc}
2037
     * @throws \Doctrine\ORM\Query\QueryException
2038
     */
2039 14
    public function walkInstanceOfExpression($instanceOfExpr)
2040
    {
2041 14
        $sql = '';
2042
2043 14
        $dqlAlias = $instanceOfExpr->identificationVariable;
2044 14
        $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
2045
2046 14
        if ($class->discriminatorColumn) {
2047 14
            $discrClass = $this->em->getClassMetadata($class->rootEntityName);
2048
        }
2049
2050 14
        if ($this->useSqlTableAliases) {
2051 14
            $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
2052
        }
2053
2054 14
        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
2055 14
        $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr);
2056
2057 13
        return $sql;
2058
    }
2059
2060
    /**
2061
     * {@inheritdoc}
2062
     */
2063 82
    public function walkInParameter($inParam)
2064
    {
2065 82
        return $inParam instanceof AST\InputParameter
2066 73
            ? $this->walkInputParameter($inParam)
2067 82
            : $this->walkLiteral($inParam);
2068
    }
2069
2070
    /**
2071
     * {@inheritdoc}
2072
     */
2073 148
    public function walkLiteral($literal)
2074
    {
2075 148
        switch ($literal->type) {
2076 148
            case AST\Literal::STRING:
2077 50
                return $this->conn->quote($literal->value);
2078
2079 111
            case AST\Literal::BOOLEAN:
2080 8
                return $this->conn->getDatabasePlatform()->convertBooleans('true' === strtolower($literal->value));
2081
2082 104
            case AST\Literal::NUMERIC:
2083 104
                return $literal->value;
2084
2085
            default:
2086
                throw QueryException::invalidLiteral($literal);
2087
        }
2088
    }
2089
2090
    /**
2091
     * {@inheritdoc}
2092
     */
2093 6
    public function walkBetweenExpression($betweenExpr)
2094
    {
2095 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2096
2097 6
        if ($betweenExpr->not) {
2098 2
            $sql .= ' NOT';
2099
        }
2100
2101 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2102 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2103
2104 6
        return $sql;
2105
    }
2106
2107
    /**
2108
     * {@inheritdoc}
2109
     */
2110 9
    public function walkLikeExpression($likeExpr)
2111
    {
2112 9
        $stringExpr = $likeExpr->stringExpression;
2113 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2114 1
            ? $this->walkResultVariable($stringExpr)
2115 9
            : $stringExpr->dispatch($this);
2116
2117 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2118
2119 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2120 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2121 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2122 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2123 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2124 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2125
        } else {
2126 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2127
        }
2128
2129 9
        if ($likeExpr->escapeChar) {
2130 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2131
        }
2132
2133 9
        return $sql;
2134
    }
2135
2136
    /**
2137
     * {@inheritdoc}
2138
     */
2139 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2140
    {
2141 5
        return $this->walkPathExpression($stateFieldPathExpression);
2142
    }
2143
2144
    /**
2145
     * {@inheritdoc}
2146
     */
2147 263
    public function walkComparisonExpression($compExpr)
2148
    {
2149 263
        $leftExpr  = $compExpr->leftExpression;
2150 263
        $rightExpr = $compExpr->rightExpression;
2151 263
        $sql       = '';
2152
2153 263
        $sql .= ($leftExpr instanceof AST\Node)
2154 263
            ? $leftExpr->dispatch($this)
2155 262
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2156
2157 262
        $sql .= ' ' . $compExpr->operator . ' ';
2158
2159 262
        $sql .= ($rightExpr instanceof AST\Node)
2160 260
            ? $rightExpr->dispatch($this)
2161 262
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2162
2163 262
        return $sql;
2164
    }
2165
2166
    /**
2167
     * {@inheritdoc}
2168
     */
2169 219
    public function walkInputParameter($inputParam)
2170
    {
2171 219
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2172
2173 219
        $parameter = $this->query->getParameter($inputParam->name);
2174
2175 219
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2176 56
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2177
        }
2178
2179 169
        return '?';
2180
    }
2181
2182
    /**
2183
     * {@inheritdoc}
2184
     */
2185 330
    public function walkArithmeticExpression($arithmeticExpr)
2186
    {
2187 330
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2188 330
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
0 ignored issues
show
It seems like $arithmeticExpr->simpleArithmeticExpression can be null; however, walkSimpleArithmeticExpression() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2189 328
            : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
0 ignored issues
show
It seems like $arithmeticExpr->subselect can be null; however, walkSubselect() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2190
    }
2191
2192
    /**
2193
     * {@inheritdoc}
2194
     */
2195 393
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2196
    {
2197 393
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2198 343
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2199
        }
2200
2201 75
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2202
    }
2203
2204
    /**
2205
     * {@inheritdoc}
2206
     */
2207 414
    public function walkArithmeticTerm($term)
2208
    {
2209 414 View Code Duplication
        if (is_string($term)) {
2210 21
            return (isset($this->queryComponents[$term]))
2211 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2212 21
                : $term;
2213
        }
2214
2215
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2216
        // if only one ArithmeticFactor is defined
2217 413
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2218 391
            return $this->walkArithmeticFactor($term);
2219
        }
2220
2221 47
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2222
    }
2223
2224
    /**
2225
     * {@inheritdoc}
2226
     */
2227 414
    public function walkArithmeticFactor($factor)
2228
    {
2229 414 View Code Duplication
        if (is_string($factor)) {
2230 47
            return (isset($this->queryComponents[$factor]))
2231 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2232 47
                : $factor;
2233
        }
2234
2235
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2236
        // if only one ArithmeticPrimary is defined
2237 414
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2238 413
            return $this->walkArithmeticPrimary($factor);
2239
        }
2240
2241 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2242
2243 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2244
    }
2245
2246
    /**
2247
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2248
     *
2249
     * @param mixed $primary
2250
     *
2251
     * @return string The SQL.
2252
     */
2253 414
    public function walkArithmeticPrimary($primary)
2254
    {
2255 414
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2256
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2257
        }
2258
2259 414
        if ($primary instanceof AST\Node) {
2260 414
            return $primary->dispatch($this);
2261
        }
2262
2263
        return $this->walkEntityIdentificationVariable($primary);
2264
    }
2265
2266
    /**
2267
     * {@inheritdoc}
2268
     */
2269 18
    public function walkStringPrimary($stringPrimary)
2270
    {
2271 18
        return (is_string($stringPrimary))
2272
            ? $this->conn->quote($stringPrimary)
2273 18
            : $stringPrimary->dispatch($this);
2274
    }
2275
2276
    /**
2277
     * {@inheritdoc}
2278
     */
2279 30
    public function walkResultVariable($resultVariable)
2280
    {
2281 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2282
2283 30
        if (is_array($resultAlias)) {
2284 1
            return implode(', ', $resultAlias);
2285
        }
2286
2287 29
        return $resultAlias;
2288
    }
2289
2290
    /**
2291
     * @param ClassMetadataInfo $rootClass
2292
     * @param AST\InstanceOfExpression $instanceOfExpr
2293
     * @return string The list in parentheses of valid child discriminators from the given class
2294
     * @throws QueryException
2295
     */
2296 14
    private function getChildDiscriminatorsFromClassMetadata(ClassMetadataInfo $rootClass, AST\InstanceOfExpression $instanceOfExpr): string
2297
    {
2298 14
        $sqlParameterList = [];
2299 14
        $discriminators = [];
2300 14
        foreach ($instanceOfExpr->value as $parameter) {
2301 14
            if ($parameter instanceof AST\InputParameter) {
2302 4
                $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
2303 4
                $sqlParameterList[] = $this->walkInParameter($parameter);
2304 4
                continue;
2305
            }
2306
2307 10
            $metadata = $this->em->getClassMetadata($parameter);
2308
2309 10
            if ($metadata->getName() !== $rootClass->name && ! $metadata->getReflectionClass()->isSubclassOf($rootClass->name)) {
2310 1
                throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name);
2311
            }
2312
2313 9
            $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em);
2314
        }
2315
2316 13
        foreach (array_keys($discriminators) as $dis) {
2317 9
            $sqlParameterList[] = $this->conn->quote($dis);
2318
        }
2319
2320 13
        return '(' . implode(', ', $sqlParameterList) . ')';
2321
    }
2322
}
2323