PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / vendor-prefixed / doctrine / orm / src / Query / SqlWalker.php
mailpoet / vendor-prefixed / doctrine / orm / src / Query Last commit date
AST 9 months ago Exec 9 months ago Expr 9 months ago Filter 9 months ago Expr.php 9 months ago FilterCollection.php 9 months ago Lexer.php 9 months ago OutputWalker.php 9 months ago Parameter.php 9 months ago ParameterTypeInferer.php 9 months ago Parser.php 9 months ago ParserResult.php 9 months ago Printer.php 9 months ago QueryException.php 9 months ago QueryExpressionVisitor.php 9 months ago ResultSetMapping.php 9 months ago ResultSetMappingBuilder.php 9 months ago SqlOutputWalker.php 9 months ago SqlWalker.php 9 months ago TokenType.php 9 months ago TreeWalker.php 9 months ago TreeWalkerAdapter.php 9 months ago TreeWalkerChain.php 9 months ago TreeWalkerChainIterator.php 9 months ago index.php 9 months ago
SqlWalker.php
1466 lines
1 <?php
2 declare (strict_types=1);
3 namespace MailPoetVendor\Doctrine\ORM\Query;
4 if (!defined('ABSPATH')) exit;
5 use BadMethodCallException;
6 use MailPoetVendor\Doctrine\DBAL\Connection;
7 use MailPoetVendor\Doctrine\DBAL\LockMode;
8 use MailPoetVendor\Doctrine\DBAL\Platforms\AbstractPlatform;
9 use MailPoetVendor\Doctrine\DBAL\Types\Type;
10 use MailPoetVendor\Doctrine\Deprecations\Deprecation;
11 use MailPoetVendor\Doctrine\ORM\EntityManagerInterface;
12 use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata;
13 use MailPoetVendor\Doctrine\ORM\Mapping\QuoteStrategy;
14 use MailPoetVendor\Doctrine\ORM\OptimisticLockException;
15 use MailPoetVendor\Doctrine\ORM\Query;
16 use MailPoetVendor\Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
17 use MailPoetVendor\Doctrine\ORM\Utility\PersisterHelper;
18 use InvalidArgumentException;
19 use LogicException;
20 use function array_diff;
21 use function array_filter;
22 use function array_keys;
23 use function array_map;
24 use function array_merge;
25 use function assert;
26 use function count;
27 use function implode;
28 use function in_array;
29 use function is_array;
30 use function is_float;
31 use function is_numeric;
32 use function is_string;
33 use function preg_match;
34 use function reset;
35 use function sprintf;
36 use function strtolower;
37 use function strtoupper;
38 use function trim;
39 class SqlWalker implements TreeWalker
40 {
41 public const HINT_DISTINCT = 'doctrine.distinct';
42 public const HINT_PARTIAL = 'doctrine.partial';
43 private $rsm;
44 private $aliasCounter = 0;
45 private $tableAliasCounter = 0;
46 private $scalarResultCounter = 1;
47 private $sqlParamIndex = 0;
48 private $newObjectCounter = 0;
49 private $parserResult;
50 private $em;
51 private $conn;
52 private $query;
53 private $tableAliasMap = [];
54 private $scalarResultAliasMap = [];
55 private $orderedColumnsMap = [];
56 private $scalarFields = [];
57 private $queryComponents;
58 private $selectedClasses = [];
59 private $rootAliases = [];
60 private $useSqlTableAliases = \true;
61 private $platform;
62 private $quoteStrategy;
63 public function __construct($query, $parserResult, array $queryComponents)
64 {
65 $this->query = $query;
66 $this->parserResult = $parserResult;
67 $this->queryComponents = $queryComponents;
68 $this->rsm = $parserResult->getResultSetMapping();
69 $this->em = $query->getEntityManager();
70 $this->conn = $this->em->getConnection();
71 $this->platform = $this->conn->getDatabasePlatform();
72 $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
73 }
74 public function getQuery()
75 {
76 return $this->query;
77 }
78 public function getConnection()
79 {
80 return $this->conn;
81 }
82 public function getEntityManager()
83 {
84 return $this->em;
85 }
86 public function getQueryComponent($dqlAlias)
87 {
88 return $this->queryComponents[$dqlAlias];
89 }
90 public function getMetadataForDqlAlias(string $dqlAlias) : ClassMetadata
91 {
92 if (!isset($this->queryComponents[$dqlAlias]['metadata'])) {
93 throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
94 }
95 return $this->queryComponents[$dqlAlias]['metadata'];
96 }
97 public function getQueryComponents()
98 {
99 return $this->queryComponents;
100 }
101 public function setQueryComponent($dqlAlias, array $queryComponent)
102 {
103 $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
104 if (array_diff($requiredKeys, array_keys($queryComponent))) {
105 throw QueryException::invalidQueryComponent($dqlAlias);
106 }
107 $this->queryComponents[$dqlAlias] = $queryComponent;
108 }
109 public function getExecutor($AST)
110 {
111 switch (\true) {
112 case $AST instanceof AST\DeleteStatement:
113 return $this->createDeleteStatementExecutor($AST);
114 case $AST instanceof AST\UpdateStatement:
115 return $this->createUpdateStatementExecutor($AST);
116 default:
117 return new Exec\SingleSelectExecutor($AST, $this);
118 }
119 }
120 protected function createUpdateStatementExecutor(AST\UpdateStatement $AST) : Exec\AbstractSqlExecutor
121 {
122 $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
123 return $primaryClass->isInheritanceTypeJoined() ? new Exec\MultiTableUpdateExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
124 }
125 protected function createDeleteStatementExecutor(AST\DeleteStatement $AST) : Exec\AbstractSqlExecutor
126 {
127 $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
128 return $primaryClass->isInheritanceTypeJoined() ? new Exec\MultiTableDeleteExecutor($AST, $this) : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
129 }
130 public function getSQLTableAlias($tableName, $dqlAlias = '')
131 {
132 $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
133 if (!isset($this->tableAliasMap[$tableName])) {
134 $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't') . $this->tableAliasCounter++ . '_';
135 }
136 return $this->tableAliasMap[$tableName];
137 }
138 public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
139 {
140 $tableName .= $dqlAlias ? '@[' . $dqlAlias . ']' : '';
141 $this->tableAliasMap[$tableName] = $alias;
142 return $alias;
143 }
144 public function getSQLColumnAlias($columnName)
145 {
146 return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
147 }
148 private function generateClassTableInheritanceJoins(ClassMetadata $class, string $dqlAlias) : string
149 {
150 $sql = '';
151 $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
152 // INNER JOIN parent class tables
153 foreach ($class->parentClasses as $parentClassName) {
154 $parentClass = $this->em->getClassMetadata($parentClassName);
155 $tableAlias = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
156 // If this is a joined association we must use left joins to preserve the correct result.
157 $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
158 $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
159 $sqlParts = [];
160 foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
161 $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
162 }
163 // Add filters on the root class
164 $sqlParts[] = $this->generateFilterConditionSQL($parentClass, $tableAlias);
165 $sql .= implode(' AND ', array_filter($sqlParts));
166 }
167 // Ignore subclassing inclusion if partial objects is disallowed
168 if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
169 return $sql;
170 }
171 // LEFT JOIN child class tables
172 foreach ($class->subClasses as $subClassName) {
173 $subClass = $this->em->getClassMetadata($subClassName);
174 $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
175 $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
176 $sqlParts = [];
177 foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
178 $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
179 }
180 $sql .= implode(' AND ', $sqlParts);
181 }
182 return $sql;
183 }
184 private function generateOrderedCollectionOrderByItems() : string
185 {
186 $orderedColumns = [];
187 foreach ($this->selectedClasses as $selectedClass) {
188 $dqlAlias = $selectedClass['dqlAlias'];
189 $qComp = $this->queryComponents[$dqlAlias];
190 if (!isset($qComp['relation']['orderBy'])) {
191 continue;
192 }
193 assert(isset($qComp['metadata']));
194 $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
195 foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
196 $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
197 $tableName = $qComp['metadata']->isInheritanceTypeJoined() ? $persister->getOwningTable($fieldName) : $qComp['metadata']->getTableName();
198 $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
199 // OrderByClause should replace an ordered relation. see - DDC-2475
200 if (isset($this->orderedColumnsMap[$orderedColumn])) {
201 continue;
202 }
203 $this->orderedColumnsMap[$orderedColumn] = $orientation;
204 $orderedColumns[] = $orderedColumn . ' ' . $orientation;
205 }
206 }
207 return implode(', ', $orderedColumns);
208 }
209 private function generateDiscriminatorColumnConditionSQL(array $dqlAliases) : string
210 {
211 $sqlParts = [];
212 foreach ($dqlAliases as $dqlAlias) {
213 $class = $this->getMetadataForDqlAlias($dqlAlias);
214 if (!$class->isInheritanceTypeSingleTable()) {
215 continue;
216 }
217 $sqlTableAlias = $this->useSqlTableAliases ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.' : '';
218 $conn = $this->em->getConnection();
219 $values = [];
220 if ($class->discriminatorValue !== null) {
221 // discriminators can be 0
222 $values[] = $conn->quote($class->discriminatorValue);
223 }
224 foreach ($class->subClasses as $subclassName) {
225 $subclassMetadata = $this->em->getClassMetadata($subclassName);
226 // Abstract entity classes show up in the list of subClasses, but may be omitted
227 // from the discriminator map. In that case, they have a null discriminator value.
228 if ($subclassMetadata->discriminatorValue === null) {
229 continue;
230 }
231 $values[] = $conn->quote($subclassMetadata->discriminatorValue);
232 }
233 if ($values !== []) {
234 $sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()['name'] . ' IN (' . implode(', ', $values) . ')';
235 } else {
236 $sqlParts[] = '1=0';
237 // impossible condition
238 }
239 }
240 $sql = implode(' AND ', $sqlParts);
241 return count($sqlParts) > 1 ? '(' . $sql . ')' : $sql;
242 }
243 private function generateFilterConditionSQL(ClassMetadata $targetEntity, string $targetTableAlias) : string
244 {
245 if (!$this->em->hasFilters()) {
246 return '';
247 }
248 switch ($targetEntity->inheritanceType) {
249 case ClassMetadata::INHERITANCE_TYPE_NONE:
250 break;
251 case ClassMetadata::INHERITANCE_TYPE_JOINED:
252 // The classes in the inheritance will be added to the query one by one,
253 // but only the root node is getting filtered
254 if ($targetEntity->name !== $targetEntity->rootEntityName) {
255 return '';
256 }
257 break;
258 case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
259 // With STI the table will only be queried once, make sure that the filters
260 // are added to the root entity
261 $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
262 break;
263 default:
264 //@todo: throw exception?
265 return '';
266 }
267 $filterClauses = [];
268 foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
269 $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias);
270 if ($filterExpr !== '') {
271 $filterClauses[] = '(' . $filterExpr . ')';
272 }
273 }
274 return implode(' AND ', $filterClauses);
275 }
276 public function walkSelectStatement(AST\SelectStatement $AST)
277 {
278 $sql = $this->createSqlForFinalizer($AST);
279 $finalizer = new Exec\SingleSelectSqlFinalizer($sql);
280 return $finalizer->finalizeSql($this->query);
281 }
282 protected function createSqlForFinalizer(AST\SelectStatement $AST) : string
283 {
284 $sql = $this->walkSelectClause($AST->selectClause) . $this->walkFromClause($AST->fromClause) . $this->walkWhereClause($AST->whereClause);
285 if ($AST->groupByClause) {
286 $sql .= $this->walkGroupByClause($AST->groupByClause);
287 }
288 if ($AST->havingClause) {
289 $sql .= $this->walkHavingClause($AST->havingClause);
290 }
291 if ($AST->orderByClause) {
292 $sql .= $this->walkOrderByClause($AST->orderByClause);
293 }
294 $orderBySql = $this->generateOrderedCollectionOrderByItems();
295 if (!$AST->orderByClause && $orderBySql) {
296 $sql .= ' ORDER BY ' . $orderBySql;
297 }
298 $this->assertOptimisticLockingHasAllClassesVersioned();
299 return $sql;
300 }
301 private function assertOptimisticLockingHasAllClassesVersioned() : void
302 {
303 $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE;
304 if ($lockMode === LockMode::OPTIMISTIC) {
305 foreach ($this->selectedClasses as $selectedClass) {
306 if (!$selectedClass['class']->isVersioned) {
307 throw OptimisticLockException::lockFailed($selectedClass['class']->name);
308 }
309 }
310 }
311 }
312 public function walkUpdateStatement(AST\UpdateStatement $AST)
313 {
314 $this->useSqlTableAliases = \false;
315 $this->rsm->isSelect = \false;
316 return $this->walkUpdateClause($AST->updateClause) . $this->walkWhereClause($AST->whereClause);
317 }
318 public function walkDeleteStatement(AST\DeleteStatement $AST)
319 {
320 $this->useSqlTableAliases = \false;
321 $this->rsm->isSelect = \false;
322 return $this->walkDeleteClause($AST->deleteClause) . $this->walkWhereClause($AST->whereClause);
323 }
324 public function walkEntityIdentificationVariable($identVariable)
325 {
326 $class = $this->getMetadataForDqlAlias($identVariable);
327 $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
328 $sqlParts = [];
329 foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
330 $sqlParts[] = $tableAlias . '.' . $columnName;
331 }
332 return implode(', ', $sqlParts);
333 }
334 public function walkIdentificationVariable($identificationVariable, $fieldName = null)
335 {
336 $class = $this->getMetadataForDqlAlias($identificationVariable);
337 if ($fieldName !== null && $class->isInheritanceTypeJoined() && isset($class->fieldMappings[$fieldName]['inherited'])) {
338 $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
339 }
340 return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
341 }
342 public function walkPathExpression($pathExpr)
343 {
344 $sql = '';
345 assert($pathExpr->field !== null);
346 switch ($pathExpr->type) {
347 case AST\PathExpression::TYPE_STATE_FIELD:
348 $fieldName = $pathExpr->field;
349 $dqlAlias = $pathExpr->identificationVariable;
350 $class = $this->getMetadataForDqlAlias($dqlAlias);
351 if ($this->useSqlTableAliases) {
352 $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
353 }
354 $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
355 break;
356 case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
357 // 1- the owning side:
358 // Just use the foreign key, i.e. u.group_id
359 $fieldName = $pathExpr->field;
360 $dqlAlias = $pathExpr->identificationVariable;
361 $class = $this->getMetadataForDqlAlias($dqlAlias);
362 if (isset($class->associationMappings[$fieldName]['inherited'])) {
363 $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
364 }
365 $assoc = $class->associationMappings[$fieldName];
366 if (!$assoc['isOwningSide']) {
367 throw QueryException::associationPathInverseSideNotSupported($pathExpr);
368 }
369 // COMPOSITE KEYS NOT (YET?) SUPPORTED
370 if (count($assoc['sourceToTargetKeyColumns']) > 1) {
371 throw QueryException::associationPathCompositeKeyNotSupported();
372 }
373 if ($this->useSqlTableAliases) {
374 $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
375 }
376 $sql .= reset($assoc['targetToSourceKeyColumns']);
377 break;
378 default:
379 throw QueryException::invalidPathExpression($pathExpr);
380 }
381 return $sql;
382 }
383 public function walkSelectClause($selectClause)
384 {
385 $sql = 'SELECT ' . ($selectClause->isDistinct ? 'DISTINCT ' : '');
386 $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
387 if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === \true && $selectClause->isDistinct) {
388 $this->query->setHint(self::HINT_DISTINCT, \true);
389 }
390 $addMetaColumns = !$this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && $this->query->getHydrationMode() === Query::HYDRATE_OBJECT || $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
391 foreach ($this->selectedClasses as $selectedClass) {
392 $class = $selectedClass['class'];
393 $dqlAlias = $selectedClass['dqlAlias'];
394 $resultAlias = $selectedClass['resultAlias'];
395 // Register as entity or joined entity result
396 if (!isset($this->queryComponents[$dqlAlias]['relation'])) {
397 $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
398 } else {
399 assert(isset($this->queryComponents[$dqlAlias]['parent']));
400 $this->rsm->addJoinedEntityResult($class->name, $dqlAlias, $this->queryComponents[$dqlAlias]['parent'], $this->queryComponents[$dqlAlias]['relation']['fieldName']);
401 }
402 if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
403 // Add discriminator columns to SQL
404 $rootClass = $this->em->getClassMetadata($class->rootEntityName);
405 $tblAlias = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
406 $discrColumn = $rootClass->getDiscriminatorColumn();
407 $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
408 $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
409 $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
410 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], \false, $discrColumn['type']);
411 if (!empty($discrColumn['enumType'])) {
412 $this->rsm->addEnumResult($columnAlias, $discrColumn['enumType']);
413 }
414 }
415 // Add foreign key columns to SQL, if necessary
416 if (!$addMetaColumns && !$class->containsForeignIdentifier) {
417 continue;
418 }
419 // Add foreign key columns of class and also parent classes
420 foreach ($class->associationMappings as $assoc) {
421 if (!($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) || !$addMetaColumns && !isset($assoc['id'])) {
422 continue;
423 }
424 $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
425 $isIdentifier = isset($assoc['id']) && $assoc['id'] === \true;
426 $owningClass = isset($assoc['inherited']) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
427 $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
428 foreach ($assoc['joinColumns'] as $joinColumn) {
429 $columnName = $joinColumn['name'];
430 $columnAlias = $this->getSQLColumnAlias($columnName);
431 $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
432 $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
433 $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
434 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
435 }
436 }
437 // Add foreign key columns to SQL, if necessary
438 if (!$addMetaColumns) {
439 continue;
440 }
441 // Add foreign key columns of subclasses
442 foreach ($class->subClasses as $subClassName) {
443 $subClass = $this->em->getClassMetadata($subClassName);
444 $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
445 foreach ($subClass->associationMappings as $assoc) {
446 // Skip if association is inherited
447 if (isset($assoc['inherited'])) {
448 continue;
449 }
450 if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
451 $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
452 foreach ($assoc['joinColumns'] as $joinColumn) {
453 $columnName = $joinColumn['name'];
454 $columnAlias = $this->getSQLColumnAlias($columnName);
455 $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
456 $quotedColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
457 $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
458 $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
459 }
460 }
461 }
462 }
463 }
464 return $sql . implode(', ', $sqlSelectExpressions);
465 }
466 public function walkFromClause($fromClause)
467 {
468 $identificationVarDecls = $fromClause->identificationVariableDeclarations;
469 $sqlParts = [];
470 foreach ($identificationVarDecls as $identificationVariableDecl) {
471 $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
472 }
473 return ' FROM ' . implode(', ', $sqlParts);
474 }
475 public function walkIdentificationVariableDeclaration($identificationVariableDecl)
476 {
477 $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
478 if ($identificationVariableDecl->indexBy) {
479 $this->walkIndexBy($identificationVariableDecl->indexBy);
480 }
481 foreach ($identificationVariableDecl->joins as $join) {
482 $sql .= $this->walkJoin($join);
483 }
484 return $sql;
485 }
486 public function walkIndexBy($indexBy)
487 {
488 $pathExpression = $indexBy->singleValuedPathExpression;
489 $alias = $pathExpression->identificationVariable;
490 assert($pathExpression->field !== null);
491 switch ($pathExpression->type) {
492 case AST\PathExpression::TYPE_STATE_FIELD:
493 $field = $pathExpression->field;
494 break;
495 case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
496 // Just use the foreign key, i.e. u.group_id
497 $fieldName = $pathExpression->field;
498 $class = $this->getMetadataForDqlAlias($alias);
499 if (isset($class->associationMappings[$fieldName]['inherited'])) {
500 $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
501 }
502 $association = $class->associationMappings[$fieldName];
503 if (!$association['isOwningSide']) {
504 throw QueryException::associationPathInverseSideNotSupported($pathExpression);
505 }
506 if (count($association['sourceToTargetKeyColumns']) > 1) {
507 throw QueryException::associationPathCompositeKeyNotSupported();
508 }
509 $field = reset($association['targetToSourceKeyColumns']);
510 break;
511 default:
512 throw QueryException::invalidPathExpression($pathExpression);
513 }
514 if (isset($this->scalarFields[$alias][$field])) {
515 $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
516 return;
517 }
518 $this->rsm->addIndexBy($alias, $field);
519 }
520 public function walkRangeVariableDeclaration($rangeVariableDeclaration)
521 {
522 return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, \false);
523 }
524 private function generateRangeVariableDeclarationSQL(AST\RangeVariableDeclaration $rangeVariableDeclaration, bool $buildNestedJoins) : string
525 {
526 $class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
527 $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
528 if ($rangeVariableDeclaration->isRoot) {
529 $this->rootAliases[] = $dqlAlias;
530 }
531 $sql = $this->platform->appendLockHint($this->quoteStrategy->getTableName($class, $this->platform) . ' ' . $this->getSQLTableAlias($class->getTableName(), $dqlAlias), $this->query->getHint(Query::HINT_LOCK_MODE) ?: LockMode::NONE);
532 if (!$class->isInheritanceTypeJoined()) {
533 return $sql;
534 }
535 $classTableInheritanceJoins = $this->generateClassTableInheritanceJoins($class, $dqlAlias);
536 if (!$buildNestedJoins) {
537 return $sql . $classTableInheritanceJoins;
538 }
539 return $classTableInheritanceJoins === '' ? $sql : '(' . $sql . $classTableInheritanceJoins . ')';
540 }
541 public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
542 {
543 $sql = '';
544 $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
545 $joinedDqlAlias = $joinAssociationDeclaration->aliasIdentificationVariable;
546 $indexBy = $joinAssociationDeclaration->indexBy;
547 $relation = $this->queryComponents[$joinedDqlAlias]['relation'] ?? null;
548 assert($relation !== null);
549 $targetClass = $this->em->getClassMetadata($relation['targetEntity']);
550 $sourceClass = $this->em->getClassMetadata($relation['sourceEntity']);
551 $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
552 $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
553 $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
554 // Ensure we got the owning side, since it has all mapping info
555 $assoc = !$relation['isOwningSide'] ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
556 if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) === \true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
557 if ($relation['type'] === ClassMetadata::ONE_TO_MANY || $relation['type'] === ClassMetadata::MANY_TO_MANY) {
558 throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
559 }
560 }
561 $fetchMode = $this->query->getHint('fetchMode')[$assoc['sourceEntity']][$assoc['fieldName']] ?? $relation['fetch'];
562 if ($fetchMode === ClassMetadata::FETCH_EAGER && $condExpr !== null) {
563 throw QueryException::eagerFetchJoinWithNotAllowed($assoc['sourceEntity'], $assoc['fieldName']);
564 }
565 // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
566 // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
567 // The owning side is necessary at this point because only it contains the JoinColumn information.
568 switch (\true) {
569 case $assoc['type'] & ClassMetadata::TO_ONE:
570 $conditions = [];
571 foreach ($assoc['joinColumns'] as $joinColumn) {
572 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
573 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
574 if ($relation['isOwningSide']) {
575 $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
576 continue;
577 }
578 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
579 }
580 // Apply remaining inheritance restrictions
581 $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
582 if ($discrSql) {
583 $conditions[] = $discrSql;
584 }
585 // Apply the filters
586 $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
587 if ($filterExpr) {
588 $conditions[] = $filterExpr;
589 }
590 $targetTableJoin = ['table' => $targetTableName . ' ' . $targetTableAlias, 'condition' => implode(' AND ', $conditions)];
591 break;
592 case $assoc['type'] === ClassMetadata::MANY_TO_MANY:
593 // Join relation table
594 $joinTable = $assoc['joinTable'];
595 $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
596 $joinTableName = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
597 $conditions = [];
598 $relationColumns = $relation['isOwningSide'] ? $assoc['joinTable']['joinColumns'] : $assoc['joinTable']['inverseJoinColumns'];
599 foreach ($relationColumns as $joinColumn) {
600 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
601 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
602 $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
603 }
604 $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
605 // Join target table
606 $sql .= $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN ';
607 $conditions = [];
608 $relationColumns = $relation['isOwningSide'] ? $assoc['joinTable']['inverseJoinColumns'] : $assoc['joinTable']['joinColumns'];
609 foreach ($relationColumns as $joinColumn) {
610 $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
611 $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
612 $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
613 }
614 // Apply remaining inheritance restrictions
615 $discrSql = $this->generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
616 if ($discrSql) {
617 $conditions[] = $discrSql;
618 }
619 // Apply the filters
620 $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
621 if ($filterExpr) {
622 $conditions[] = $filterExpr;
623 }
624 $targetTableJoin = ['table' => $targetTableName . ' ' . $targetTableAlias, 'condition' => implode(' AND ', $conditions)];
625 break;
626 default:
627 throw new BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
628 }
629 // Handle WITH clause
630 $withCondition = $condExpr === null ? '' : '(' . $this->walkConditionalExpression($condExpr) . ')';
631 if ($targetClass->isInheritanceTypeJoined()) {
632 $ctiJoins = $this->generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
633 // If we have WITH condition, we need to build nested joins for target class table and cti joins
634 if ($withCondition && $ctiJoins) {
635 $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
636 } else {
637 $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
638 }
639 } else {
640 $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
641 }
642 if ($withCondition) {
643 $sql .= ' AND ' . $withCondition;
644 }
645 // Apply the indexes
646 if ($indexBy) {
647 // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
648 $this->walkIndexBy($indexBy);
649 } elseif (isset($relation['indexBy'])) {
650 $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
651 }
652 return $sql;
653 }
654 public function walkFunction($function)
655 {
656 return $function->getSql($this);
657 }
658 public function walkOrderByClause($orderByClause)
659 {
660 $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
661 $collectionOrderByItems = $this->generateOrderedCollectionOrderByItems();
662 if ($collectionOrderByItems !== '') {
663 $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
664 }
665 return ' ORDER BY ' . implode(', ', $orderByItems);
666 }
667 public function walkOrderByItem($orderByItem)
668 {
669 $type = strtoupper($orderByItem->type);
670 $expr = $orderByItem->expression;
671 $sql = $expr instanceof AST\Node ? $expr->dispatch($this) : $this->walkResultVariable($this->queryComponents[$expr]['token']->value);
672 $this->orderedColumnsMap[$sql] = $type;
673 if ($expr instanceof AST\Subselect) {
674 return '(' . $sql . ') ' . $type;
675 }
676 return $sql . ' ' . $type;
677 }
678 public function walkHavingClause($havingClause)
679 {
680 return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
681 }
682 public function walkJoin($join)
683 {
684 $joinType = $join->joinType;
685 $joinDeclaration = $join->joinAssociationDeclaration;
686 $sql = $joinType === AST\Join::JOIN_TYPE_LEFT || $joinType === AST\Join::JOIN_TYPE_LEFTOUTER ? ' LEFT JOIN ' : ' INNER JOIN ';
687 switch (\true) {
688 case $joinDeclaration instanceof AST\RangeVariableDeclaration:
689 $class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
690 $dqlAlias = $joinDeclaration->aliasIdentificationVariable;
691 $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
692 $conditions = [];
693 if ($join->conditionalExpression) {
694 $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
695 }
696 $isUnconditionalJoin = $conditions === [];
697 $condExprConjunction = $class->isInheritanceTypeJoined() && $joinType !== AST\Join::JOIN_TYPE_LEFT && $joinType !== AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin ? ' AND ' : ' ON ';
698 $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, !$isUnconditionalJoin);
699 // Apply remaining inheritance restrictions
700 $discrSql = $this->generateDiscriminatorColumnConditionSQL([$dqlAlias]);
701 if ($discrSql) {
702 $conditions[] = $discrSql;
703 }
704 // Apply the filters
705 $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
706 if ($filterExpr) {
707 $conditions[] = $filterExpr;
708 }
709 if ($conditions) {
710 $sql .= $condExprConjunction . implode(' AND ', $conditions);
711 }
712 break;
713 case $joinDeclaration instanceof AST\JoinAssociationDeclaration:
714 $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
715 break;
716 }
717 return $sql;
718 }
719 public function walkCoalesceExpression($coalesceExpression)
720 {
721 $sql = 'COALESCE(';
722 $scalarExpressions = [];
723 foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
724 $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
725 }
726 return $sql . implode(', ', $scalarExpressions) . ')';
727 }
728 public function walkNullIfExpression($nullIfExpression)
729 {
730 $firstExpression = is_string($nullIfExpression->firstExpression) ? $this->conn->quote($nullIfExpression->firstExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
731 $secondExpression = is_string($nullIfExpression->secondExpression) ? $this->conn->quote($nullIfExpression->secondExpression) : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
732 return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
733 }
734 public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
735 {
736 $sql = 'CASE';
737 foreach ($generalCaseExpression->whenClauses as $whenClause) {
738 $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
739 $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
740 }
741 $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
742 return $sql;
743 }
744 public function walkSimpleCaseExpression($simpleCaseExpression)
745 {
746 $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
747 foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
748 $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
749 $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
750 }
751 $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
752 return $sql;
753 }
754 public function walkSelectExpression($selectExpression)
755 {
756 $sql = '';
757 $expr = $selectExpression->expression;
758 $hidden = $selectExpression->hiddenAliasResultVariable;
759 switch (\true) {
760 case $expr instanceof AST\PathExpression:
761 if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
762 throw QueryException::invalidPathExpression($expr);
763 }
764 assert($expr->field !== null);
765 $fieldName = $expr->field;
766 $dqlAlias = $expr->identificationVariable;
767 $class = $this->getMetadataForDqlAlias($dqlAlias);
768 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
769 $tableName = $class->isInheritanceTypeJoined() ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName) : $class->getTableName();
770 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
771 $fieldMapping = $class->fieldMappings[$fieldName];
772 $columnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
773 $columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']);
774 $col = $sqlTableAlias . '.' . $columnName;
775 if (isset($fieldMapping['requireSQLConversion'])) {
776 $type = Type::getType($fieldMapping['type']);
777 $col = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
778 }
779 $sql .= $col . ' AS ' . $columnAlias;
780 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
781 if (!$hidden) {
782 $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
783 $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
784 if (!empty($fieldMapping['enumType'])) {
785 $this->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']);
786 }
787 }
788 break;
789 case $expr instanceof AST\AggregateExpression:
790 case $expr instanceof AST\Functions\FunctionNode:
791 case $expr instanceof AST\SimpleArithmeticExpression:
792 case $expr instanceof AST\ArithmeticTerm:
793 case $expr instanceof AST\ArithmeticFactor:
794 case $expr instanceof AST\ParenthesisExpression:
795 case $expr instanceof AST\Literal:
796 case $expr instanceof AST\NullIfExpression:
797 case $expr instanceof AST\CoalesceExpression:
798 case $expr instanceof AST\GeneralCaseExpression:
799 case $expr instanceof AST\SimpleCaseExpression:
800 $columnAlias = $this->getSQLColumnAlias('sclr');
801 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
802 $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
803 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
804 if ($hidden) {
805 break;
806 }
807 if (!$expr instanceof Query\AST\TypedExpression) {
808 // Conceptually we could resolve field type here by traverse through AST to retrieve field type,
809 // but this is not a feasible solution; assume 'string'.
810 $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
811 break;
812 }
813 $this->rsm->addScalarResult($columnAlias, $resultAlias, Type::getTypeRegistry()->lookupName($expr->getReturnType()));
814 break;
815 case $expr instanceof AST\Subselect:
816 $columnAlias = $this->getSQLColumnAlias('sclr');
817 $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
818 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
819 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
820 if (!$hidden) {
821 // We cannot resolve field type here; assume 'string'.
822 $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
823 }
824 break;
825 case $expr instanceof AST\NewObjectExpression:
826 $sql .= $this->walkNewObject($expr, $selectExpression->fieldIdentificationVariable);
827 break;
828 default:
829 // IdentificationVariable or PartialObjectExpression
830 if ($expr instanceof AST\PartialObjectExpression) {
831 $this->query->setHint(self::HINT_PARTIAL, \true);
832 $dqlAlias = $expr->identificationVariable;
833 $partialFieldSet = $expr->partialFieldSet;
834 } else {
835 $dqlAlias = $expr;
836 $partialFieldSet = [];
837 }
838 $class = $this->getMetadataForDqlAlias($dqlAlias);
839 $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
840 if (!isset($this->selectedClasses[$dqlAlias])) {
841 $this->selectedClasses[$dqlAlias] = ['class' => $class, 'dqlAlias' => $dqlAlias, 'resultAlias' => $resultAlias];
842 }
843 $sqlParts = [];
844 // Select all fields from the queried class
845 foreach ($class->fieldMappings as $fieldName => $mapping) {
846 if ($partialFieldSet && !in_array($fieldName, $partialFieldSet, \true)) {
847 continue;
848 }
849 $tableName = isset($mapping['inherited']) ? $this->em->getClassMetadata($mapping['inherited'])->getTableName() : $class->getTableName();
850 $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
851 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
852 $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
853 $col = $sqlTableAlias . '.' . $quotedColumnName;
854 if (isset($mapping['requireSQLConversion'])) {
855 $type = Type::getType($mapping['type']);
856 $col = $type->convertToPHPValueSQL($col, $this->platform);
857 }
858 $sqlParts[] = $col . ' AS ' . $columnAlias;
859 if ($resultAlias !== null) {
860 $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
861 }
862 $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
863 if (!empty($mapping['enumType'])) {
864 $this->rsm->addEnumResult($columnAlias, $mapping['enumType']);
865 }
866 }
867 // Add any additional fields of subclasses (excluding inherited fields)
868 // 1) on Single Table Inheritance: always, since its marginal overhead
869 // 2) on Class Table Inheritance only if partial objects are disallowed,
870 // since it requires outer joining subtables.
871 if ($class->isInheritanceTypeSingleTable() || !$this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
872 foreach ($class->subClasses as $subClassName) {
873 $subClass = $this->em->getClassMetadata($subClassName);
874 $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
875 foreach ($subClass->fieldMappings as $fieldName => $mapping) {
876 if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet, \true)) {
877 continue;
878 }
879 $columnAlias = $this->getSQLColumnAlias($mapping['columnName']);
880 $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
881 $col = $sqlTableAlias . '.' . $quotedColumnName;
882 if (isset($mapping['requireSQLConversion'])) {
883 $type = Type::getType($mapping['type']);
884 $col = $type->convertToPHPValueSQL($col, $this->platform);
885 }
886 $sqlParts[] = $col . ' AS ' . $columnAlias;
887 if ($resultAlias !== null) {
888 $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
889 }
890 $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
891 }
892 }
893 }
894 $sql .= implode(', ', $sqlParts);
895 }
896 return $sql;
897 }
898 public function walkQuantifiedExpression($qExpr)
899 {
900 return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
901 }
902 public function walkSubselect($subselect)
903 {
904 $useAliasesBefore = $this->useSqlTableAliases;
905 $rootAliasesBefore = $this->rootAliases;
906 $this->rootAliases = [];
907 // reset the rootAliases for the subselect
908 $this->useSqlTableAliases = \true;
909 $sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
910 $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
911 $sql .= $this->walkWhereClause($subselect->whereClause);
912 $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
913 $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
914 $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
915 $this->rootAliases = $rootAliasesBefore;
916 // put the main aliases back
917 $this->useSqlTableAliases = $useAliasesBefore;
918 return $sql;
919 }
920 public function walkSubselectFromClause($subselectFromClause)
921 {
922 $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
923 $sqlParts = [];
924 foreach ($identificationVarDecls as $subselectIdVarDecl) {
925 $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
926 }
927 return ' FROM ' . implode(', ', $sqlParts);
928 }
929 public function walkSimpleSelectClause($simpleSelectClause)
930 {
931 return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
932 }
933 public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
934 {
935 return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
936 }
937 public function walkNewObject($newObjectExpression, $newObjectResultAlias = null)
938 {
939 $sqlSelectExpressions = [];
940 $objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
941 foreach ($newObjectExpression->args as $argIndex => $e) {
942 $resultAlias = $this->scalarResultCounter++;
943 $columnAlias = $this->getSQLColumnAlias('sclr');
944 $fieldType = 'string';
945 switch (\true) {
946 case $e instanceof AST\NewObjectExpression:
947 $sqlSelectExpressions[] = $e->dispatch($this);
948 break;
949 case $e instanceof AST\Subselect:
950 $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
951 break;
952 case $e instanceof AST\PathExpression:
953 assert($e->field !== null);
954 $dqlAlias = $e->identificationVariable;
955 $class = $this->getMetadataForDqlAlias($dqlAlias);
956 $fieldName = $e->field;
957 $fieldMapping = $class->fieldMappings[$fieldName];
958 $fieldType = $fieldMapping['type'];
959 $col = trim($e->dispatch($this));
960 if (isset($fieldMapping['requireSQLConversion'])) {
961 $type = Type::getType($fieldType);
962 $col = $type->convertToPHPValueSQL($col, $this->platform);
963 }
964 $sqlSelectExpressions[] = $col . ' AS ' . $columnAlias;
965 if (!empty($fieldMapping['enumType'])) {
966 $this->rsm->addEnumResult($columnAlias, $fieldMapping['enumType']);
967 }
968 break;
969 case $e instanceof AST\Literal:
970 switch ($e->type) {
971 case AST\Literal::BOOLEAN:
972 $fieldType = 'boolean';
973 break;
974 case AST\Literal::NUMERIC:
975 $fieldType = is_float($e->value) ? 'float' : 'integer';
976 break;
977 }
978 $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
979 break;
980 default:
981 $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
982 break;
983 }
984 $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
985 $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
986 $this->rsm->newObjectMappings[$columnAlias] = ['className' => $newObjectExpression->className, 'objIndex' => $objIndex, 'argIndex' => $argIndex];
987 }
988 return implode(', ', $sqlSelectExpressions);
989 }
990 public function walkSimpleSelectExpression($simpleSelectExpression)
991 {
992 $expr = $simpleSelectExpression->expression;
993 $sql = ' ';
994 switch (\true) {
995 case $expr instanceof AST\PathExpression:
996 $sql .= $this->walkPathExpression($expr);
997 break;
998 case $expr instanceof AST\Subselect:
999 $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1000 $columnAlias = 'sclr' . $this->aliasCounter++;
1001 $this->scalarResultAliasMap[$alias] = $columnAlias;
1002 $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1003 break;
1004 case $expr instanceof AST\Functions\FunctionNode:
1005 case $expr instanceof AST\SimpleArithmeticExpression:
1006 case $expr instanceof AST\ArithmeticTerm:
1007 case $expr instanceof AST\ArithmeticFactor:
1008 case $expr instanceof AST\Literal:
1009 case $expr instanceof AST\NullIfExpression:
1010 case $expr instanceof AST\CoalesceExpression:
1011 case $expr instanceof AST\GeneralCaseExpression:
1012 case $expr instanceof AST\SimpleCaseExpression:
1013 $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1014 $columnAlias = $this->getSQLColumnAlias('sclr');
1015 $this->scalarResultAliasMap[$alias] = $columnAlias;
1016 $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1017 break;
1018 case $expr instanceof AST\ParenthesisExpression:
1019 $sql .= $this->walkParenthesisExpression($expr);
1020 break;
1021 default:
1022 // IdentificationVariable
1023 $sql .= $this->walkEntityIdentificationVariable($expr);
1024 break;
1025 }
1026 return $sql;
1027 }
1028 public function walkAggregateExpression($aggExpression)
1029 {
1030 return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
1031 }
1032 public function walkGroupByClause($groupByClause)
1033 {
1034 $sqlParts = [];
1035 foreach ($groupByClause->groupByItems as $groupByItem) {
1036 $sqlParts[] = $this->walkGroupByItem($groupByItem);
1037 }
1038 return ' GROUP BY ' . implode(', ', $sqlParts);
1039 }
1040 public function walkGroupByItem($groupByItem)
1041 {
1042 // StateFieldPathExpression
1043 if (!is_string($groupByItem)) {
1044 return $this->walkPathExpression($groupByItem);
1045 }
1046 // ResultVariable
1047 if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1048 $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1049 if ($resultVariable instanceof AST\PathExpression) {
1050 return $this->walkPathExpression($resultVariable);
1051 }
1052 if ($resultVariable instanceof AST\Node && isset($resultVariable->pathExpression)) {
1053 return $this->walkPathExpression($resultVariable->pathExpression);
1054 }
1055 return $this->walkResultVariable($groupByItem);
1056 }
1057 // IdentificationVariable
1058 $sqlParts = [];
1059 foreach ($this->getMetadataForDqlAlias($groupByItem)->fieldNames as $field) {
1060 $item = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1061 $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1062 $sqlParts[] = $this->walkPathExpression($item);
1063 }
1064 foreach ($this->getMetadataForDqlAlias($groupByItem)->associationMappings as $mapping) {
1065 if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadata::TO_ONE) {
1066 $item = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
1067 $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1068 $sqlParts[] = $this->walkPathExpression($item);
1069 }
1070 }
1071 return implode(', ', $sqlParts);
1072 }
1073 public function walkDeleteClause(AST\DeleteClause $deleteClause)
1074 {
1075 $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1076 $tableName = $class->getTableName();
1077 $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1078 $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1079 $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1080 return $sql;
1081 }
1082 public function walkUpdateClause($updateClause)
1083 {
1084 $class = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1085 $tableName = $class->getTableName();
1086 $sql = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1087 $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1088 $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1089 return $sql . ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1090 }
1091 public function walkUpdateItem($updateItem)
1092 {
1093 $useTableAliasesBefore = $this->useSqlTableAliases;
1094 $this->useSqlTableAliases = \false;
1095 $sql = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1096 $newValue = $updateItem->newValue;
1097 switch (\true) {
1098 case $newValue instanceof AST\Node:
1099 $sql .= $newValue->dispatch($this);
1100 break;
1101 case $newValue === null:
1102 $sql .= 'NULL';
1103 break;
1104 default:
1105 $sql .= $this->conn->quote($newValue);
1106 break;
1107 }
1108 $this->useSqlTableAliases = $useTableAliasesBefore;
1109 return $sql;
1110 }
1111 public function walkWhereClause($whereClause)
1112 {
1113 $condSql = $whereClause !== null ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1114 $discrSql = $this->generateDiscriminatorColumnConditionSQL($this->rootAliases);
1115 if ($this->em->hasFilters()) {
1116 $filterClauses = [];
1117 foreach ($this->rootAliases as $dqlAlias) {
1118 $class = $this->getMetadataForDqlAlias($dqlAlias);
1119 $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1120 $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1121 if ($filterExpr) {
1122 $filterClauses[] = $filterExpr;
1123 }
1124 }
1125 if (count($filterClauses)) {
1126 if ($condSql) {
1127 $condSql = '(' . $condSql . ') AND ';
1128 }
1129 $condSql .= implode(' AND ', $filterClauses);
1130 }
1131 }
1132 if ($condSql) {
1133 return ' WHERE ' . (!$discrSql ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1134 }
1135 if ($discrSql) {
1136 return ' WHERE ' . $discrSql;
1137 }
1138 return '';
1139 }
1140 public function walkConditionalExpression($condExpr)
1141 {
1142 // Phase 2 AST optimization: Skip processing of ConditionalExpression
1143 // if only one ConditionalTerm is defined
1144 if (!$condExpr instanceof AST\ConditionalExpression) {
1145 return $this->walkConditionalTerm($condExpr);
1146 }
1147 return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1148 }
1149 public function walkConditionalTerm($condTerm)
1150 {
1151 // Phase 2 AST optimization: Skip processing of ConditionalTerm
1152 // if only one ConditionalFactor is defined
1153 if (!$condTerm instanceof AST\ConditionalTerm) {
1154 return $this->walkConditionalFactor($condTerm);
1155 }
1156 return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1157 }
1158 public function walkConditionalFactor($factor)
1159 {
1160 // Phase 2 AST optimization: Skip processing of ConditionalFactor
1161 // if only one ConditionalPrimary is defined
1162 return !$factor instanceof AST\ConditionalFactor ? $this->walkConditionalPrimary($factor) : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1163 }
1164 public function walkConditionalPrimary($primary)
1165 {
1166 if ($primary->isSimpleConditionalExpression()) {
1167 return $primary->simpleConditionalExpression->dispatch($this);
1168 }
1169 if ($primary->isConditionalExpression()) {
1170 $condExpr = $primary->conditionalExpression;
1171 return '(' . $this->walkConditionalExpression($condExpr) . ')';
1172 }
1173 }
1174 public function walkExistsExpression($existsExpr)
1175 {
1176 $sql = $existsExpr->not ? 'NOT ' : '';
1177 $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1178 return $sql;
1179 }
1180 public function walkCollectionMemberExpression($collMemberExpr)
1181 {
1182 $sql = $collMemberExpr->not ? 'NOT ' : '';
1183 $sql .= 'EXISTS (SELECT 1 FROM ';
1184 $entityExpr = $collMemberExpr->entityExpression;
1185 $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1186 assert($collPathExpr->field !== null);
1187 $fieldName = $collPathExpr->field;
1188 $dqlAlias = $collPathExpr->identificationVariable;
1189 $class = $this->getMetadataForDqlAlias($dqlAlias);
1190 switch (\true) {
1191 // InputParameter
1192 case $entityExpr instanceof AST\InputParameter:
1193 $dqlParamKey = $entityExpr->name;
1194 $entitySql = '?';
1195 break;
1196 // SingleValuedAssociationPathExpression | IdentificationVariable
1197 case $entityExpr instanceof AST\PathExpression:
1198 $entitySql = $this->walkPathExpression($entityExpr);
1199 break;
1200 default:
1201 throw new BadMethodCallException('Not implemented');
1202 }
1203 $assoc = $class->associationMappings[$fieldName];
1204 if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) {
1205 $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1206 $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1207 $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1208 $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1209 $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1210 $sqlParts = [];
1211 foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
1212 $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1213 $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1214 }
1215 foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1216 if (isset($dqlParamKey)) {
1217 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1218 }
1219 $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' = ' . $entitySql;
1220 }
1221 $sql .= implode(' AND ', $sqlParts);
1222 } else {
1223 // many-to-many
1224 $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1225 $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1226 $joinTable = $owningAssoc['joinTable'];
1227 // SQL table aliases
1228 $joinTableAlias = $this->getSQLTableAlias($joinTable['name']);
1229 $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1230 $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias . ' WHERE ';
1231 $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1232 $sqlParts = [];
1233 foreach ($joinColumns as $joinColumn) {
1234 $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1235 $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1236 }
1237 $joinColumns = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1238 foreach ($joinColumns as $joinColumn) {
1239 if (isset($dqlParamKey)) {
1240 $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1241 }
1242 $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' IN (' . $entitySql . ')';
1243 }
1244 $sql .= implode(' AND ', $sqlParts);
1245 }
1246 return $sql . ')';
1247 }
1248 public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1249 {
1250 $sizeFunc = new AST\Functions\SizeFunction('size');
1251 $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1252 return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1253 }
1254 public function walkNullComparisonExpression($nullCompExpr)
1255 {
1256 $expression = $nullCompExpr->expression;
1257 $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
1258 // Handle ResultVariable
1259 if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
1260 return $this->walkResultVariable($expression) . $comparison;
1261 }
1262 // Handle InputParameter mapping inclusion to ParserResult
1263 if ($expression instanceof AST\InputParameter) {
1264 return $this->walkInputParameter($expression) . $comparison;
1265 }
1266 return $expression->dispatch($this) . $comparison;
1267 }
1268 public function walkInExpression($inExpr)
1269 {
1270 Deprecation::triggerIfCalledFromOutside('doctrine/orm', 'https://github.com/doctrine/orm/pull/10267', '%s() is deprecated, call walkInListExpression() or walkInSubselectExpression() instead.', __METHOD__);
1271 if ($inExpr instanceof AST\InListExpression) {
1272 return $this->walkInListExpression($inExpr);
1273 }
1274 if ($inExpr instanceof AST\InSubselectExpression) {
1275 return $this->walkInSubselectExpression($inExpr);
1276 }
1277 $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
1278 $sql .= $inExpr->subselect ? $this->walkSubselect($inExpr->subselect) : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
1279 $sql .= ')';
1280 return $sql;
1281 }
1282 public function walkInListExpression(AST\InListExpression $inExpr) : string
1283 {
1284 return $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (' . implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals)) . ')';
1285 }
1286 public function walkInSubselectExpression(AST\InSubselectExpression $inExpr) : string
1287 {
1288 return $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (' . $this->walkSubselect($inExpr->subselect) . ')';
1289 }
1290 public function walkInstanceOfExpression($instanceOfExpr)
1291 {
1292 $sql = '';
1293 $dqlAlias = $instanceOfExpr->identificationVariable;
1294 $discrClass = $class = $this->getMetadataForDqlAlias($dqlAlias);
1295 if ($class->discriminatorColumn) {
1296 $discrClass = $this->em->getClassMetadata($class->rootEntityName);
1297 }
1298 if ($this->useSqlTableAliases) {
1299 $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
1300 }
1301 $sql .= $class->getDiscriminatorColumn()['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
1302 $sql .= $this->getChildDiscriminatorsFromClassMetadata($discrClass, $instanceOfExpr);
1303 return $sql;
1304 }
1305 public function walkInParameter($inParam)
1306 {
1307 return $inParam instanceof AST\InputParameter ? $this->walkInputParameter($inParam) : $this->walkArithmeticExpression($inParam);
1308 }
1309 public function walkLiteral($literal)
1310 {
1311 switch ($literal->type) {
1312 case AST\Literal::STRING:
1313 return $this->conn->quote($literal->value);
1314 case AST\Literal::BOOLEAN:
1315 return (string) $this->conn->getDatabasePlatform()->convertBooleans(strtolower($literal->value) === 'true');
1316 case AST\Literal::NUMERIC:
1317 return (string) $literal->value;
1318 default:
1319 throw QueryException::invalidLiteral($literal);
1320 }
1321 }
1322 public function walkBetweenExpression($betweenExpr)
1323 {
1324 $sql = $this->walkArithmeticExpression($betweenExpr->expression);
1325 if ($betweenExpr->not) {
1326 $sql .= ' NOT';
1327 }
1328 $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
1329 return $sql;
1330 }
1331 public function walkLikeExpression($likeExpr)
1332 {
1333 $stringExpr = $likeExpr->stringExpression;
1334 if (is_string($stringExpr)) {
1335 if (!isset($this->queryComponents[$stringExpr]['resultVariable'])) {
1336 throw new LogicException(sprintf('No result variable found for string expression "%s".', $stringExpr));
1337 }
1338 $leftExpr = $this->walkResultVariable($stringExpr);
1339 } else {
1340 $leftExpr = $stringExpr->dispatch($this);
1341 }
1342 $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
1343 if ($likeExpr->stringPattern instanceof AST\InputParameter) {
1344 $sql .= $this->walkInputParameter($likeExpr->stringPattern);
1345 } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
1346 $sql .= $this->walkFunction($likeExpr->stringPattern);
1347 } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
1348 $sql .= $this->walkPathExpression($likeExpr->stringPattern);
1349 } else {
1350 $sql .= $this->walkLiteral($likeExpr->stringPattern);
1351 }
1352 if ($likeExpr->escapeChar) {
1353 $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
1354 }
1355 return $sql;
1356 }
1357 public function walkStateFieldPathExpression($stateFieldPathExpression)
1358 {
1359 return $this->walkPathExpression($stateFieldPathExpression);
1360 }
1361 public function walkComparisonExpression($compExpr)
1362 {
1363 $leftExpr = $compExpr->leftExpression;
1364 $rightExpr = $compExpr->rightExpression;
1365 $sql = '';
1366 $sql .= $leftExpr instanceof AST\Node ? $leftExpr->dispatch($this) : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
1367 $sql .= ' ' . $compExpr->operator . ' ';
1368 $sql .= $rightExpr instanceof AST\Node ? $rightExpr->dispatch($this) : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
1369 return $sql;
1370 }
1371 public function walkInputParameter($inputParam)
1372 {
1373 $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
1374 $parameter = $this->query->getParameter($inputParam->name);
1375 if ($parameter) {
1376 $type = $parameter->getType();
1377 if (Type::hasType($type)) {
1378 return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
1379 }
1380 }
1381 return '?';
1382 }
1383 public function walkArithmeticExpression($arithmeticExpr)
1384 {
1385 return $arithmeticExpr->isSimpleArithmeticExpression() ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
1386 }
1387 public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
1388 {
1389 if (!$simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression) {
1390 return $this->walkArithmeticTerm($simpleArithmeticExpr);
1391 }
1392 return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
1393 }
1394 public function walkArithmeticTerm($term)
1395 {
1396 if (is_string($term)) {
1397 return isset($this->queryComponents[$term]) ? $this->walkResultVariable($this->queryComponents[$term]['token']->value) : $term;
1398 }
1399 // Phase 2 AST optimization: Skip processing of ArithmeticTerm
1400 // if only one ArithmeticFactor is defined
1401 if (!$term instanceof AST\ArithmeticTerm) {
1402 return $this->walkArithmeticFactor($term);
1403 }
1404 return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
1405 }
1406 public function walkArithmeticFactor($factor)
1407 {
1408 if (is_string($factor)) {
1409 return isset($this->queryComponents[$factor]) ? $this->walkResultVariable($this->queryComponents[$factor]['token']->value) : $factor;
1410 }
1411 // Phase 2 AST optimization: Skip processing of ArithmeticFactor
1412 // if only one ArithmeticPrimary is defined
1413 if (!$factor instanceof AST\ArithmeticFactor) {
1414 return $this->walkArithmeticPrimary($factor);
1415 }
1416 $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
1417 return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
1418 }
1419 public function walkArithmeticPrimary($primary)
1420 {
1421 if ($primary instanceof AST\SimpleArithmeticExpression) {
1422 return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
1423 }
1424 if ($primary instanceof AST\Node) {
1425 return $primary->dispatch($this);
1426 }
1427 return $this->walkEntityIdentificationVariable($primary);
1428 }
1429 public function walkStringPrimary($stringPrimary)
1430 {
1431 return is_string($stringPrimary) ? $this->conn->quote($stringPrimary) : $stringPrimary->dispatch($this);
1432 }
1433 public function walkResultVariable($resultVariable)
1434 {
1435 if (!isset($this->scalarResultAliasMap[$resultVariable])) {
1436 throw new InvalidArgumentException(sprintf('Unknown result variable: %s', $resultVariable));
1437 }
1438 $resultAlias = $this->scalarResultAliasMap[$resultVariable];
1439 if (is_array($resultAlias)) {
1440 return implode(', ', $resultAlias);
1441 }
1442 return $resultAlias;
1443 }
1444 private function getChildDiscriminatorsFromClassMetadata(ClassMetadata $rootClass, AST\InstanceOfExpression $instanceOfExpr) : string
1445 {
1446 $sqlParameterList = [];
1447 $discriminators = [];
1448 foreach ($instanceOfExpr->value as $parameter) {
1449 if ($parameter instanceof AST\InputParameter) {
1450 $this->rsm->discriminatorParameters[$parameter->name] = $parameter->name;
1451 $sqlParameterList[] = $this->walkInParameter($parameter);
1452 continue;
1453 }
1454 $metadata = $this->em->getClassMetadata($parameter);
1455 if ($metadata->getName() !== $rootClass->name && !$metadata->getReflectionClass()->isSubclassOf($rootClass->name)) {
1456 throw QueryException::instanceOfUnrelatedClass($parameter, $rootClass->name);
1457 }
1458 $discriminators += HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($metadata, $this->em);
1459 }
1460 foreach (array_keys($discriminators) as $dis) {
1461 $sqlParameterList[] = $this->conn->quote($dis);
1462 }
1463 return '(' . implode(', ', $sqlParameterList) . ')';
1464 }
1465 }
1466