Failed Conditions
Pull Request — 2.8.x (#7918)
by
unknown
17:09
created

SqlWalker   F

Complexity

Total Complexity 379

Size/Duplication

Total Lines 2313
Duplicated Lines 0 %

Test Coverage

Coverage 95.66%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 379
eloc 960
c 3
b 0
f 0
dl 0
loc 2313
ccs 948
cts 991
cp 0.9566
rs 1.64

77 Methods

Rating   Name   Duplication   Size   Complexity  
A walkExistsExpression() 0 7 2
A walkSimpleCaseExpression() 0 12 2
A walkOrderByClause() 0 9 2
A getExecutor() 0 19 5
A getSQLColumnAlias() 0 3 1
A generateRangeVariableDeclarationSQL() 0 26 5
A walkConditionalTerm() 0 9 2
A walkStateFieldPathExpression() 0 3 1
B _generateDiscriminatorColumnConditionSQL() 0 30 7
A walkIdentificationVariable() 0 12 4
A walkStringPrimary() 0 5 2
A walkConditionalExpression() 0 9 2
A walkUpdateStatement() 0 7 1
A walkDeleteClause() 0 10 1
A walkUpdateClause() 0 12 1
A __construct() 0 10 1
A walkComparisonExpression() 0 17 5
A walkArithmeticFactor() 0 17 6
A walkFunction() 0 3 1
A walkOrderByItem() 0 15 3
A walkNullComparisonExpression() 0 16 5
B walkIndexBy() 0 45 7
D walkSelectStatement() 0 52 16
A walkEntityIdentificationVariable() 0 11 2
A walkGeneralCaseExpression() 0 12 2
A walkArithmeticPrimary() 0 11 3
A walkCoalesceExpression() 0 13 2
A walkLiteral() 0 14 4
B walkPathExpression() 0 52 8
A getQueryComponents() 0 3 1
A walkNullIfExpression() 0 11 3
A walkAggregateExpression() 0 4 2
F walkJoin() 0 53 13
F walkJoinAssociationDeclaration() 0 154 27
B _generateClassTableInheritanceJoins() 0 51 8
A walkConditionalFactor() 0 7 3
C walkCollectionMemberExpression() 0 105 15
A walkFromClause() 0 10 2
A walkBetweenExpression() 0 12 2
B walkGroupByItem() 0 42 9
A walkInputParameter() 0 11 3
A walkInstanceOfExpression() 0 19 4
A walkSubselectFromClause() 0 10 2
A walkInExpression() 0 11 3
B walkLikeExpression() 0 24 8
A getSQLTableAlias() 0 10 4
A getConnection() 0 3 1
A walkParenthesisExpression() 0 3 1
A getChildDiscriminatorsFromClassMetadata() 0 25 6
B walkWhereClause() 0 34 10
A walkArithmeticTerm() 0 15 4
F walkSelectExpression() 0 175 40
A walkIdentificationVariableDeclaration() 0 13 3
A walkSimpleSelectClause() 0 4 2
A walkGroupByClause() 0 9 2
A walkDeleteStatement() 0 7 1
A walkInParameter() 0 5 2
A walkRangeVariableDeclaration() 0 3 1
A walkSubselect() 0 20 4
A _generateOrderedCollectionOrderByItems() 0 33 6
B walkNewObject() 0 58 10
A walkResultVariable() 0 9 2
B generateFilterConditionSQL() 0 34 8
F walkSelectClause() 0 109 28
A walkSimpleArithmeticExpression() 0 7 2
A setQueryComponent() 0 9 2
A walkHavingClause() 0 3 1
A getQueryComponent() 0 3 1
A walkEmptyCollectionComparisonExpression() 0 6 2
A walkUpdateItem() 0 25 3
A setSQLTableAlias() 0 7 2
A getQuery() 0 3 1
C walkSimpleSelectExpression() 0 46 15
A walkQuantifiedExpression() 0 3 1
A walkArithmeticExpression() 0 5 2
A walkConditionalPrimary() 0 10 3
A getEntityManager() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SqlWalker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SqlWalker, and based on these observations, apply Extract Interface, too.

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

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

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

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

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

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

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

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

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

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