|
0
|
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 LGPL. For more information, see |
|
|
17 |
* <http://www.doctrine-project.org>. |
|
|
18 |
*/ |
|
|
19 |
|
|
|
20 |
namespace Doctrine\ORM\Query; |
|
|
21 |
|
|
|
22 |
use Doctrine\DBAL\LockMode, |
|
|
23 |
Doctrine\ORM\Mapping\ClassMetadata, |
|
|
24 |
Doctrine\ORM\Query, |
|
|
25 |
Doctrine\ORM\Query\QueryException; |
|
|
26 |
|
|
|
27 |
/** |
|
|
28 |
* The SqlWalker is a TreeWalker that walks over a DQL AST and constructs |
|
|
29 |
* the corresponding SQL. |
|
|
30 |
* |
|
|
31 |
* @author Roman Borschel <roman@code-factory.org> |
|
|
32 |
* @author Benjamin Eberlei <kontakt@beberlei.de> |
|
|
33 |
* @since 2.0 |
|
|
34 |
* @todo Rename: SQLWalker |
|
|
35 |
*/ |
|
|
36 |
class SqlWalker implements TreeWalker |
|
|
37 |
{ |
|
|
38 |
/** |
|
|
39 |
* @var ResultSetMapping |
|
|
40 |
*/ |
|
|
41 |
private $_rsm; |
|
|
42 |
|
|
|
43 |
/** Counters for generating unique column aliases, table aliases and parameter indexes. */ |
|
|
44 |
private $_aliasCounter = 0; |
|
|
45 |
private $_tableAliasCounter = 0; |
|
|
46 |
private $_scalarResultCounter = 1; |
|
|
47 |
private $_sqlParamIndex = 0; |
|
|
48 |
|
|
|
49 |
/** |
|
|
50 |
* @var ParserResult |
|
|
51 |
*/ |
|
|
52 |
private $_parserResult; |
|
|
53 |
|
|
|
54 |
/** |
|
|
55 |
* @var EntityManager |
|
|
56 |
*/ |
|
|
57 |
private $_em; |
|
|
58 |
|
|
|
59 |
/** |
|
|
60 |
* @var Doctrine\DBAL\Connection |
|
|
61 |
*/ |
|
|
62 |
private $_conn; |
|
|
63 |
|
|
|
64 |
/** |
|
|
65 |
* @var AbstractQuery |
|
|
66 |
*/ |
|
|
67 |
private $_query; |
|
|
68 |
|
|
|
69 |
private $_tableAliasMap = array(); |
|
|
70 |
|
|
|
71 |
/** Map from result variable names to their SQL column alias names. */ |
|
|
72 |
private $_scalarResultAliasMap = array(); |
|
|
73 |
|
|
|
74 |
/** Map of all components/classes that appear in the DQL query. */ |
|
|
75 |
private $_queryComponents; |
|
|
76 |
|
|
|
77 |
/** A list of classes that appear in non-scalar SelectExpressions. */ |
|
|
78 |
private $_selectedClasses = array(); |
|
|
79 |
|
|
|
80 |
/** |
|
|
81 |
* The DQL alias of the root class of the currently traversed query. |
|
|
82 |
*/ |
|
|
83 |
private $_rootAliases = array(); |
|
|
84 |
|
|
|
85 |
/** |
|
|
86 |
* Flag that indicates whether to generate SQL table aliases in the SQL. |
|
|
87 |
* These should only be generated for SELECT queries, not for UPDATE/DELETE. |
|
|
88 |
*/ |
|
|
89 |
private $_useSqlTableAliases = true; |
|
|
90 |
|
|
|
91 |
/** |
|
|
92 |
* The database platform abstraction. |
|
|
93 |
* |
|
|
94 |
* @var AbstractPlatform |
|
|
95 |
*/ |
|
|
96 |
private $_platform; |
|
|
97 |
|
|
|
98 |
/** |
|
|
99 |
* {@inheritDoc} |
|
|
100 |
*/ |
|
|
101 |
public function __construct($query, $parserResult, array $queryComponents) |
|
|
102 |
{ |
|
|
103 |
$this->_query = $query; |
|
|
104 |
$this->_parserResult = $parserResult; |
|
|
105 |
$this->_queryComponents = $queryComponents; |
|
|
106 |
$this->_rsm = $parserResult->getResultSetMapping(); |
|
|
107 |
$this->_em = $query->getEntityManager(); |
|
|
108 |
$this->_conn = $this->_em->getConnection(); |
|
|
109 |
$this->_platform = $this->_conn->getDatabasePlatform(); |
|
|
110 |
} |
|
|
111 |
|
|
|
112 |
/** |
|
|
113 |
* Gets the Query instance used by the walker. |
|
|
114 |
* |
|
|
115 |
* @return Query. |
|
|
116 |
*/ |
|
|
117 |
public function getQuery() |
|
|
118 |
{ |
|
|
119 |
return $this->_query; |
|
|
120 |
} |
|
|
121 |
|
|
|
122 |
/** |
|
|
123 |
* Gets the Connection used by the walker. |
|
|
124 |
* |
|
|
125 |
* @return Connection |
|
|
126 |
*/ |
|
|
127 |
public function getConnection() |
|
|
128 |
{ |
|
|
129 |
return $this->_conn; |
|
|
130 |
} |
|
|
131 |
|
|
|
132 |
/** |
|
|
133 |
* Gets the EntityManager used by the walker. |
|
|
134 |
* |
|
|
135 |
* @return EntityManager |
|
|
136 |
*/ |
|
|
137 |
public function getEntityManager() |
|
|
138 |
{ |
|
|
139 |
return $this->_em; |
|
|
140 |
} |
|
|
141 |
|
|
|
142 |
/** |
|
|
143 |
* Gets the information about a single query component. |
|
|
144 |
* |
|
|
145 |
* @param string $dqlAlias The DQL alias. |
|
|
146 |
* @return array |
|
|
147 |
*/ |
|
|
148 |
public function getQueryComponent($dqlAlias) |
|
|
149 |
{ |
|
|
150 |
return $this->_queryComponents[$dqlAlias]; |
|
|
151 |
} |
|
|
152 |
|
|
|
153 |
/** |
|
|
154 |
* Gets an executor that can be used to execute the result of this walker. |
|
|
155 |
* |
|
|
156 |
* @return AbstractExecutor |
|
|
157 |
*/ |
|
|
158 |
public function getExecutor($AST) |
|
|
159 |
{ |
|
|
160 |
$isDeleteStatement = $AST instanceof AST\DeleteStatement; |
|
|
161 |
$isUpdateStatement = $AST instanceof AST\UpdateStatement; |
|
|
162 |
|
|
|
163 |
if ($isDeleteStatement) { |
|
|
164 |
$primaryClass = $this->_em->getClassMetadata( |
|
|
165 |
$AST->deleteClause->abstractSchemaName |
|
|
166 |
); |
|
|
167 |
|
|
|
168 |
if ($primaryClass->isInheritanceTypeJoined()) { |
|
|
169 |
return new Exec\MultiTableDeleteExecutor($AST, $this); |
|
|
170 |
} else { |
|
|
171 |
return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); |
|
|
172 |
} |
|
|
173 |
} else if ($isUpdateStatement) { |
|
|
174 |
$primaryClass = $this->_em->getClassMetadata( |
|
|
175 |
$AST->updateClause->abstractSchemaName |
|
|
176 |
); |
|
|
177 |
|
|
|
178 |
if ($primaryClass->isInheritanceTypeJoined()) { |
|
|
179 |
return new Exec\MultiTableUpdateExecutor($AST, $this); |
|
|
180 |
} else { |
|
|
181 |
return new Exec\SingleTableDeleteUpdateExecutor($AST, $this); |
|
|
182 |
} |
|
|
183 |
} |
|
|
184 |
|
|
|
185 |
return new Exec\SingleSelectExecutor($AST, $this); |
|
|
186 |
} |
|
|
187 |
|
|
|
188 |
/** |
|
|
189 |
* Generates a unique, short SQL table alias. |
|
|
190 |
* |
|
|
191 |
* @param string $tableName Table name |
|
|
192 |
* @param string $dqlAlias The DQL alias. |
|
|
193 |
* @return string Generated table alias. |
|
|
194 |
*/ |
|
|
195 |
public function getSQLTableAlias($tableName, $dqlAlias = '') |
|
|
196 |
{ |
|
|
197 |
$tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; |
|
|
198 |
|
|
|
199 |
if ( ! isset($this->_tableAliasMap[$tableName])) { |
|
|
200 |
$this->_tableAliasMap[$tableName] = strtolower(substr($tableName, 0, 1)) . $this->_tableAliasCounter++ . '_'; |
|
|
201 |
} |
|
|
202 |
|
|
|
203 |
return $this->_tableAliasMap[$tableName]; |
|
|
204 |
} |
|
|
205 |
|
|
|
206 |
/** |
|
|
207 |
* Forces the SqlWalker to use a specific alias for a table name, rather than |
|
|
208 |
* generating an alias on its own. |
|
|
209 |
* |
|
|
210 |
* @param string $tableName |
|
|
211 |
* @param string $alias |
|
|
212 |
* @param string $dqlAlias |
|
|
213 |
* @return string |
|
|
214 |
*/ |
|
|
215 |
public function setSQLTableAlias($tableName, $alias, $dqlAlias = '') |
|
|
216 |
{ |
|
|
217 |
$tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : ''; |
|
|
218 |
|
|
|
219 |
$this->_tableAliasMap[$tableName] = $alias; |
|
|
220 |
|
|
|
221 |
return $alias; |
|
|
222 |
} |
|
|
223 |
|
|
|
224 |
/** |
|
|
225 |
* Gets an SQL column alias for a column name. |
|
|
226 |
* |
|
|
227 |
* @param string $columnName |
|
|
228 |
* @return string |
|
|
229 |
*/ |
|
|
230 |
public function getSQLColumnAlias($columnName) |
|
|
231 |
{ |
|
|
232 |
return $columnName . $this->_aliasCounter++; |
|
|
233 |
} |
|
|
234 |
|
|
|
235 |
/** |
|
|
236 |
* Generates the SQL JOINs that are necessary for Class Table Inheritance |
|
|
237 |
* for the given class. |
|
|
238 |
* |
|
|
239 |
* @param ClassMetadata $class The class for which to generate the joins. |
|
|
240 |
* @param string $dqlAlias The DQL alias of the class. |
|
|
241 |
* @return string The SQL. |
|
|
242 |
*/ |
|
|
243 |
private function _generateClassTableInheritanceJoins($class, $dqlAlias) |
|
|
244 |
{ |
|
|
245 |
$sql = ''; |
|
|
246 |
|
|
|
247 |
$baseTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
248 |
|
|
|
249 |
// INNER JOIN parent class tables |
|
|
250 |
foreach ($class->parentClasses as $parentClassName) { |
|
|
251 |
$parentClass = $this->_em->getClassMetadata($parentClassName); |
|
|
252 |
$tableAlias = $this->getSQLTableAlias($parentClass->table['name'], $dqlAlias); |
|
|
253 |
// If this is a joined association we must use left joins to preserve the correct result. |
|
|
254 |
$sql .= isset($this->_queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER '; |
|
|
255 |
$sql .= 'JOIN ' . $parentClass->getQuotedTableName($this->_platform) |
|
|
256 |
. ' ' . $tableAlias . ' ON '; |
|
|
257 |
$first = true; |
|
|
258 |
foreach ($class->identifier as $idField) { |
|
|
259 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
260 |
|
|
|
261 |
$columnName = $class->getQuotedColumnName($idField, $this->_platform); |
|
|
262 |
$sql .= $baseTableAlias . '.' . $columnName |
|
|
263 |
. ' = ' |
|
|
264 |
. $tableAlias . '.' . $columnName; |
|
|
265 |
} |
|
|
266 |
} |
|
|
267 |
|
|
|
268 |
// LEFT JOIN subclass tables, if partial objects disallowed. |
|
|
269 |
if ( ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { |
|
|
270 |
foreach ($class->subClasses as $subClassName) { |
|
|
271 |
$subClass = $this->_em->getClassMetadata($subClassName); |
|
|
272 |
$tableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); |
|
|
273 |
$sql .= ' LEFT JOIN ' . $subClass->getQuotedTableName($this->_platform) |
|
|
274 |
. ' ' . $tableAlias . ' ON '; |
|
|
275 |
$first = true; |
|
|
276 |
foreach ($class->identifier as $idField) { |
|
|
277 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
278 |
|
|
|
279 |
$columnName = $class->getQuotedColumnName($idField, $this->_platform); |
|
|
280 |
$sql .= $baseTableAlias . '.' . $columnName |
|
|
281 |
. ' = ' |
|
|
282 |
. $tableAlias . '.' . $columnName; |
|
|
283 |
} |
|
|
284 |
} |
|
|
285 |
} |
|
|
286 |
|
|
|
287 |
return $sql; |
|
|
288 |
} |
|
|
289 |
|
|
|
290 |
private function _generateOrderedCollectionOrderByItems() |
|
|
291 |
{ |
|
|
292 |
$sql = ''; |
|
|
293 |
foreach ($this->_selectedClasses AS $dqlAlias => $class) { |
|
|
294 |
$qComp = $this->_queryComponents[$dqlAlias]; |
|
|
295 |
if (isset($qComp['relation']['orderBy'])) { |
|
|
296 |
foreach ($qComp['relation']['orderBy'] AS $fieldName => $orientation) { |
|
|
297 |
if ($qComp['metadata']->isInheritanceTypeJoined()) { |
|
|
298 |
$tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); |
|
|
299 |
} else { |
|
|
300 |
$tableName = $qComp['metadata']->table['name']; |
|
|
301 |
} |
|
|
302 |
|
|
|
303 |
if ($sql != '') { |
|
|
304 |
$sql .= ', '; |
|
|
305 |
} |
|
|
306 |
$sql .= $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . |
|
|
307 |
$qComp['metadata']->getQuotedColumnName($fieldName, $this->_platform) . " $orientation"; |
|
|
308 |
} |
|
|
309 |
} |
|
|
310 |
} |
|
|
311 |
return $sql; |
|
|
312 |
} |
|
|
313 |
|
|
|
314 |
/** |
|
|
315 |
* Generates a discriminator column SQL condition for the class with the given DQL alias. |
|
|
316 |
* |
|
|
317 |
* @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions. |
|
|
318 |
* @return string |
|
|
319 |
*/ |
|
|
320 |
private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases) |
|
|
321 |
{ |
|
|
322 |
$encapsulate = false; |
|
|
323 |
$sql = ''; |
|
|
324 |
|
|
|
325 |
foreach ($dqlAliases as $dqlAlias) { |
|
|
326 |
$class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
|
327 |
|
|
|
328 |
if ($class->isInheritanceTypeSingleTable()) { |
|
|
329 |
$conn = $this->_em->getConnection(); |
|
|
330 |
$values = array(); |
|
|
331 |
if ($class->discriminatorValue !== null) { // discrimnators can be 0 |
|
|
332 |
$values[] = $conn->quote($class->discriminatorValue); |
|
|
333 |
} |
|
|
334 |
|
|
|
335 |
foreach ($class->subClasses as $subclassName) { |
|
|
336 |
$values[] = $conn->quote($this->_em->getClassMetadata($subclassName)->discriminatorValue); |
|
|
337 |
} |
|
|
338 |
|
|
|
339 |
if ($sql != '') { |
|
|
340 |
$sql .= ' AND '; |
|
|
341 |
$encapsulate = true; |
|
|
342 |
} |
|
|
343 |
|
|
|
344 |
$sql .= ($sql != '' ? ' AND ' : '') |
|
|
345 |
. (($this->_useSqlTableAliases) ? $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.' : '') |
|
|
346 |
. $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')'; |
|
|
347 |
} |
|
|
348 |
} |
|
|
349 |
|
|
|
350 |
return ($encapsulate) ? '(' . $sql . ')' : $sql; |
|
|
351 |
} |
|
|
352 |
|
|
|
353 |
/** |
|
|
354 |
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL. |
|
|
355 |
* |
|
|
356 |
* @return string The SQL. |
|
|
357 |
*/ |
|
|
358 |
public function walkSelectStatement(AST\SelectStatement $AST) |
|
|
359 |
{ |
|
|
360 |
$sql = $this->walkSelectClause($AST->selectClause); |
|
|
361 |
$sql .= $this->walkFromClause($AST->fromClause); |
|
|
362 |
|
|
|
363 |
if (($whereClause = $AST->whereClause) !== null) { |
|
|
364 |
$sql .= $this->walkWhereClause($whereClause); |
|
|
365 |
} else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { |
|
|
366 |
$sql .= ' WHERE ' . $discSql; |
|
|
367 |
} |
|
|
368 |
|
|
|
369 |
$sql .= $AST->groupByClause ? $this->walkGroupByClause($AST->groupByClause) : ''; |
|
|
370 |
$sql .= $AST->havingClause ? $this->walkHavingClause($AST->havingClause) : ''; |
|
|
371 |
|
|
|
372 |
if (($orderByClause = $AST->orderByClause) !== null) { |
|
|
373 |
$sql .= $AST->orderByClause ? $this->walkOrderByClause($AST->orderByClause) : ''; |
|
|
374 |
} else if (($orderBySql = $this->_generateOrderedCollectionOrderByItems()) !== '') { |
|
|
375 |
$sql .= ' ORDER BY '.$orderBySql; |
|
|
376 |
} |
|
|
377 |
|
|
|
378 |
|
|
|
379 |
$sql = $this->_platform->modifyLimitQuery( |
|
|
380 |
$sql, $this->_query->getMaxResults(), $this->_query->getFirstResult() |
|
|
381 |
); |
|
|
382 |
|
|
|
383 |
if (($lockMode = $this->_query->getHint(Query::HINT_LOCK_MODE)) !== false) { |
|
|
384 |
if ($lockMode == LockMode::PESSIMISTIC_READ) { |
|
|
385 |
$sql .= " " . $this->_platform->getReadLockSQL(); |
|
|
386 |
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) { |
|
|
387 |
$sql .= " " . $this->_platform->getWriteLockSQL(); |
|
|
388 |
} else if ($lockMode == LockMode::OPTIMISTIC) { |
|
|
389 |
foreach ($this->_selectedClasses AS $class) { |
|
|
390 |
if ( ! $class->isVersioned) { |
|
|
391 |
throw \Doctrine\ORM\OptimisticLockException::lockFailed(); |
|
|
392 |
} |
|
|
393 |
} |
|
|
394 |
} |
|
|
395 |
} |
|
|
396 |
|
|
|
397 |
return $sql; |
|
|
398 |
} |
|
|
399 |
|
|
|
400 |
/** |
|
|
401 |
* Walks down an UpdateStatement AST node, thereby generating the appropriate SQL. |
|
|
402 |
* |
|
|
403 |
* @param UpdateStatement |
|
|
404 |
* @return string The SQL. |
|
|
405 |
*/ |
|
|
406 |
public function walkUpdateStatement(AST\UpdateStatement $AST) |
|
|
407 |
{ |
|
|
408 |
$this->_useSqlTableAliases = false; |
|
|
409 |
$sql = $this->walkUpdateClause($AST->updateClause); |
|
|
410 |
|
|
|
411 |
if (($whereClause = $AST->whereClause) !== null) { |
|
|
412 |
$sql .= $this->walkWhereClause($whereClause); |
|
|
413 |
} else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { |
|
|
414 |
$sql .= ' WHERE ' . $discSql; |
|
|
415 |
} |
|
|
416 |
|
|
|
417 |
return $sql; |
|
|
418 |
} |
|
|
419 |
|
|
|
420 |
/** |
|
|
421 |
* Walks down a DeleteStatement AST node, thereby generating the appropriate SQL. |
|
|
422 |
* |
|
|
423 |
* @param DeleteStatement |
|
|
424 |
* @return string The SQL. |
|
|
425 |
*/ |
|
|
426 |
public function walkDeleteStatement(AST\DeleteStatement $AST) |
|
|
427 |
{ |
|
|
428 |
$this->_useSqlTableAliases = false; |
|
|
429 |
$sql = $this->walkDeleteClause($AST->deleteClause); |
|
|
430 |
|
|
|
431 |
if (($whereClause = $AST->whereClause) !== null) { |
|
|
432 |
$sql .= $this->walkWhereClause($whereClause); |
|
|
433 |
} else if (($discSql = $this->_generateDiscriminatorColumnConditionSQL($this->_rootAliases)) !== '') { |
|
|
434 |
$sql .= ' WHERE ' . $discSql; |
|
|
435 |
} |
|
|
436 |
|
|
|
437 |
return $sql; |
|
|
438 |
} |
|
|
439 |
|
|
|
440 |
|
|
|
441 |
/** |
|
|
442 |
* Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL. |
|
|
443 |
* |
|
|
444 |
* @param string $identificationVariable |
|
|
445 |
* @param string $fieldName |
|
|
446 |
* @return string The SQL. |
|
|
447 |
*/ |
|
|
448 |
public function walkIdentificationVariable($identificationVariable, $fieldName = null) |
|
|
449 |
{ |
|
|
450 |
$class = $this->_queryComponents[$identificationVariable]['metadata']; |
|
|
451 |
|
|
|
452 |
if ( |
|
|
453 |
$fieldName !== null && $class->isInheritanceTypeJoined() && |
|
|
454 |
isset($class->fieldMappings[$fieldName]['inherited']) |
|
|
455 |
) { |
|
|
456 |
$class = $this->_em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']); |
|
|
457 |
} |
|
|
458 |
|
|
|
459 |
return $this->getSQLTableAlias($class->table['name'], $identificationVariable); |
|
|
460 |
} |
|
|
461 |
|
|
|
462 |
/** |
|
|
463 |
* Walks down a PathExpression AST node, thereby generating the appropriate SQL. |
|
|
464 |
* |
|
|
465 |
* @param mixed |
|
|
466 |
* @return string The SQL. |
|
|
467 |
*/ |
|
|
468 |
public function walkPathExpression($pathExpr) |
|
|
469 |
{ |
|
|
470 |
$sql = ''; |
|
|
471 |
|
|
|
472 |
switch ($pathExpr->type) { |
|
|
473 |
case AST\PathExpression::TYPE_STATE_FIELD: |
|
|
474 |
$fieldName = $pathExpr->field; |
|
|
475 |
$dqlAlias = $pathExpr->identificationVariable; |
|
|
476 |
$class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
|
477 |
|
|
|
478 |
if ($this->_useSqlTableAliases) { |
|
|
479 |
$sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.'; |
|
|
480 |
} |
|
|
481 |
|
|
|
482 |
$sql .= $class->getQuotedColumnName($fieldName, $this->_platform); |
|
|
483 |
break; |
|
|
484 |
|
|
|
485 |
case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION: |
|
|
486 |
// 1- the owning side: |
|
|
487 |
// Just use the foreign key, i.e. u.group_id |
|
|
488 |
$fieldName = $pathExpr->field; |
|
|
489 |
$dqlAlias = $pathExpr->identificationVariable; |
|
|
490 |
$class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
|
491 |
|
|
|
492 |
if (isset($class->associationMappings[$fieldName]['inherited'])) { |
|
|
493 |
$class = $this->_em->getClassMetadata($class->associationMappings[$fieldName]['inherited']); |
|
|
494 |
} |
|
|
495 |
|
|
|
496 |
$assoc = $class->associationMappings[$fieldName]; |
|
|
497 |
|
|
|
498 |
if ($assoc['isOwningSide']) { |
|
|
499 |
// COMPOSITE KEYS NOT (YET?) SUPPORTED |
|
|
500 |
if (count($assoc['sourceToTargetKeyColumns']) > 1) { |
|
|
501 |
throw QueryException::associationPathCompositeKeyNotSupported(); |
|
|
502 |
} |
|
|
503 |
|
|
|
504 |
if ($this->_useSqlTableAliases) { |
|
|
505 |
$sql .= $this->getSQLTableAlias($class->table['name'], $dqlAlias) . '.'; |
|
|
506 |
} |
|
|
507 |
|
|
|
508 |
$sql .= reset($assoc['targetToSourceKeyColumns']); |
|
|
509 |
} else { |
|
|
510 |
throw QueryException::associationPathInverseSideNotSupported(); |
|
|
511 |
} |
|
|
512 |
break; |
|
|
513 |
|
|
|
514 |
default: |
|
|
515 |
throw QueryException::invalidPathExpression($pathExpr); |
|
|
516 |
} |
|
|
517 |
|
|
|
518 |
return $sql; |
|
|
519 |
} |
|
|
520 |
|
|
|
521 |
/** |
|
|
522 |
* Walks down a SelectClause AST node, thereby generating the appropriate SQL. |
|
|
523 |
* |
|
|
524 |
* @param $selectClause |
|
|
525 |
* @return string The SQL. |
|
|
526 |
*/ |
|
|
527 |
public function walkSelectClause($selectClause) |
|
|
528 |
{ |
|
|
529 |
$sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '') . implode( |
|
|
530 |
', ', array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)) |
|
|
531 |
); |
|
|
532 |
|
|
|
533 |
$addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && |
|
|
534 |
$this->_query->getHydrationMode() == Query::HYDRATE_OBJECT |
|
|
535 |
|| |
|
|
536 |
$this->_query->getHydrationMode() != Query::HYDRATE_OBJECT && |
|
|
537 |
$this->_query->getHint(Query::HINT_INCLUDE_META_COLUMNS); |
|
|
538 |
|
|
|
539 |
foreach ($this->_selectedClasses as $dqlAlias => $class) { |
|
|
540 |
// Register as entity or joined entity result |
|
|
541 |
if ($this->_queryComponents[$dqlAlias]['relation'] === null) { |
|
|
542 |
$this->_rsm->addEntityResult($class->name, $dqlAlias); |
|
|
543 |
} else { |
|
|
544 |
$this->_rsm->addJoinedEntityResult( |
|
|
545 |
$class->name, $dqlAlias, |
|
|
546 |
$this->_queryComponents[$dqlAlias]['parent'], |
|
|
547 |
$this->_queryComponents[$dqlAlias]['relation']['fieldName'] |
|
|
548 |
); |
|
|
549 |
} |
|
|
550 |
|
|
|
551 |
if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) { |
|
|
552 |
// Add discriminator columns to SQL |
|
|
553 |
$rootClass = $this->_em->getClassMetadata($class->rootEntityName); |
|
|
554 |
$tblAlias = $this->getSQLTableAlias($rootClass->table['name'], $dqlAlias); |
|
|
555 |
$discrColumn = $rootClass->discriminatorColumn; |
|
|
556 |
$columnAlias = $this->getSQLColumnAlias($discrColumn['name']); |
|
|
557 |
$sql .= ", $tblAlias." . $discrColumn['name'] . ' AS ' . $columnAlias; |
|
|
558 |
|
|
|
559 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
560 |
$this->_rsm->setDiscriminatorColumn($dqlAlias, $columnAlias); |
|
|
561 |
$this->_rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName']); |
|
|
562 |
|
|
|
563 |
// Add foreign key columns to SQL, if necessary |
|
|
564 |
if ($addMetaColumns) { |
|
|
565 |
//FIXME: Include foreign key columns of child classes also!!?? |
|
|
566 |
foreach ($class->associationMappings as $assoc) { |
|
|
567 |
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { |
|
|
568 |
if (isset($assoc['inherited'])) { |
|
|
569 |
$owningClass = $this->_em->getClassMetadata($assoc['inherited']); |
|
|
570 |
$sqlTableAlias = $this->getSQLTableAlias($owningClass->table['name'], $dqlAlias); |
|
|
571 |
} else { |
|
|
572 |
$sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
573 |
} |
|
|
574 |
|
|
|
575 |
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { |
|
|
576 |
$columnAlias = $this->getSQLColumnAlias($srcColumn); |
|
|
577 |
$sql .= ", $sqlTableAlias." . $srcColumn . ' AS ' . $columnAlias; |
|
|
578 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
579 |
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); |
|
|
580 |
} |
|
|
581 |
} |
|
|
582 |
} |
|
|
583 |
} |
|
|
584 |
} else { |
|
|
585 |
// Add foreign key columns to SQL, if necessary |
|
|
586 |
if ($addMetaColumns) { |
|
|
587 |
$sqlTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
588 |
foreach ($class->associationMappings as $assoc) { |
|
|
589 |
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { |
|
|
590 |
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { |
|
|
591 |
$columnAlias = $this->getSQLColumnAlias($srcColumn); |
|
|
592 |
$sql .= ', ' . $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; |
|
|
593 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
594 |
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn, (isset($assoc['id']) && $assoc['id'] === true)); |
|
|
595 |
} |
|
|
596 |
} |
|
|
597 |
} |
|
|
598 |
} |
|
|
599 |
} |
|
|
600 |
} |
|
|
601 |
|
|
|
602 |
return $sql; |
|
|
603 |
} |
|
|
604 |
|
|
|
605 |
/** |
|
|
606 |
* Walks down a FromClause AST node, thereby generating the appropriate SQL. |
|
|
607 |
* |
|
|
608 |
* @return string The SQL. |
|
|
609 |
*/ |
|
|
610 |
public function walkFromClause($fromClause) |
|
|
611 |
{ |
|
|
612 |
$identificationVarDecls = $fromClause->identificationVariableDeclarations; |
|
|
613 |
$sqlParts = array(); |
|
|
614 |
|
|
|
615 |
foreach ($identificationVarDecls as $identificationVariableDecl) { |
|
|
616 |
$sql = ''; |
|
|
617 |
|
|
|
618 |
$rangeDecl = $identificationVariableDecl->rangeVariableDeclaration; |
|
|
619 |
$dqlAlias = $rangeDecl->aliasIdentificationVariable; |
|
|
620 |
|
|
|
621 |
$this->_rootAliases[] = $dqlAlias; |
|
|
622 |
|
|
|
623 |
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); |
|
|
624 |
$sql .= $class->getQuotedTableName($this->_platform) . ' ' |
|
|
625 |
. $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
626 |
|
|
|
627 |
if ($class->isInheritanceTypeJoined()) { |
|
|
628 |
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); |
|
|
629 |
} |
|
|
630 |
|
|
|
631 |
foreach ($identificationVariableDecl->joinVariableDeclarations as $joinVarDecl) { |
|
|
632 |
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl); |
|
|
633 |
} |
|
|
634 |
|
|
|
635 |
if ($identificationVariableDecl->indexBy) { |
|
|
636 |
$this->_rsm->addIndexBy( |
|
|
637 |
$identificationVariableDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, |
|
|
638 |
$identificationVariableDecl->indexBy->simpleStateFieldPathExpression->field |
|
|
639 |
); |
|
|
640 |
} |
|
|
641 |
|
|
|
642 |
$sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); |
|
|
643 |
} |
|
|
644 |
|
|
|
645 |
return ' FROM ' . implode(', ', $sqlParts); |
|
|
646 |
} |
|
|
647 |
|
|
|
648 |
/** |
|
|
649 |
* Walks down a FunctionNode AST node, thereby generating the appropriate SQL. |
|
|
650 |
* |
|
|
651 |
* @return string The SQL. |
|
|
652 |
*/ |
|
|
653 |
public function walkFunction($function) |
|
|
654 |
{ |
|
|
655 |
return $function->getSql($this); |
|
|
656 |
} |
|
|
657 |
|
|
|
658 |
/** |
|
|
659 |
* Walks down an OrderByClause AST node, thereby generating the appropriate SQL. |
|
|
660 |
* |
|
|
661 |
* @param OrderByClause |
|
|
662 |
* @return string The SQL. |
|
|
663 |
*/ |
|
|
664 |
public function walkOrderByClause($orderByClause) |
|
|
665 |
{ |
|
|
666 |
$colSql = $this->_generateOrderedCollectionOrderByItems(); |
|
|
667 |
if ($colSql != '') { |
|
|
668 |
$colSql = ", ".$colSql; |
|
|
669 |
} |
|
|
670 |
|
|
|
671 |
// OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* |
|
|
672 |
return ' ORDER BY ' . implode( |
|
|
673 |
', ', array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems) |
|
|
674 |
) . $colSql; |
|
|
675 |
} |
|
|
676 |
|
|
|
677 |
/** |
|
|
678 |
* Walks down an OrderByItem AST node, thereby generating the appropriate SQL. |
|
|
679 |
* |
|
|
680 |
* @param OrderByItem |
|
|
681 |
* @return string The SQL. |
|
|
682 |
*/ |
|
|
683 |
public function walkOrderByItem($orderByItem) |
|
|
684 |
{ |
|
|
685 |
$sql = ''; |
|
|
686 |
$expr = $orderByItem->expression; |
|
|
687 |
|
|
|
688 |
if ($expr instanceof AST\PathExpression) { |
|
|
689 |
$sql = $this->walkPathExpression($expr); |
|
|
690 |
} else { |
|
|
691 |
$columnName = $this->_queryComponents[$expr]['token']['value']; |
|
|
692 |
|
|
|
693 |
$sql = $this->_scalarResultAliasMap[$columnName]; |
|
|
694 |
} |
|
|
695 |
|
|
|
696 |
return $sql . ' ' . strtoupper($orderByItem->type); |
|
|
697 |
} |
|
|
698 |
|
|
|
699 |
/** |
|
|
700 |
* Walks down a HavingClause AST node, thereby generating the appropriate SQL. |
|
|
701 |
* |
|
|
702 |
* @param HavingClause |
|
|
703 |
* @return string The SQL. |
|
|
704 |
*/ |
|
|
705 |
public function walkHavingClause($havingClause) |
|
|
706 |
{ |
|
|
707 |
return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression); |
|
|
708 |
} |
|
|
709 |
|
|
|
710 |
/** |
|
|
711 |
* Walks down a JoinVariableDeclaration AST node and creates the corresponding SQL. |
|
|
712 |
* |
|
|
713 |
* @param JoinVariableDeclaration $joinVarDecl |
|
|
714 |
* @return string The SQL. |
|
|
715 |
*/ |
|
|
716 |
public function walkJoinVariableDeclaration($joinVarDecl) |
|
|
717 |
{ |
|
|
718 |
$join = $joinVarDecl->join; |
|
|
719 |
$joinType = $join->joinType; |
|
|
720 |
|
|
|
721 |
if ($joinVarDecl->indexBy) { |
|
|
722 |
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. |
|
|
723 |
$this->_rsm->addIndexBy( |
|
|
724 |
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, |
|
|
725 |
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field |
|
|
726 |
); |
|
|
727 |
} |
|
|
728 |
|
|
|
729 |
if ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) { |
|
|
730 |
$sql = ' LEFT JOIN '; |
|
|
731 |
} else { |
|
|
732 |
$sql = ' INNER JOIN '; |
|
|
733 |
} |
|
|
734 |
|
|
|
735 |
$joinAssocPathExpr = $join->joinAssociationPathExpression; |
|
|
736 |
$joinedDqlAlias = $join->aliasIdentificationVariable; |
|
|
737 |
$relation = $this->_queryComponents[$joinedDqlAlias]['relation']; |
|
|
738 |
$targetClass = $this->_em->getClassMetadata($relation['targetEntity']); |
|
|
739 |
$sourceClass = $this->_em->getClassMetadata($relation['sourceEntity']); |
|
|
740 |
$targetTableName = $targetClass->getQuotedTableName($this->_platform); |
|
|
741 |
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name'], $joinedDqlAlias); |
|
|
742 |
$sourceTableAlias = $this->getSQLTableAlias($sourceClass->table['name'], $joinAssocPathExpr->identificationVariable); |
|
|
743 |
|
|
|
744 |
// Ensure we got the owning side, since it has all mapping info |
|
|
745 |
$assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; |
|
|
746 |
|
|
|
747 |
if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true) { |
|
|
748 |
if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { |
|
|
749 |
throw QueryException::iterateWithFetchJoinNotAllowed($assoc); |
|
|
750 |
} |
|
|
751 |
} |
|
|
752 |
|
|
|
753 |
if ($joinVarDecl->indexBy) { |
|
|
754 |
// For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently. |
|
|
755 |
$this->_rsm->addIndexBy( |
|
|
756 |
$joinVarDecl->indexBy->simpleStateFieldPathExpression->identificationVariable, |
|
|
757 |
$joinVarDecl->indexBy->simpleStateFieldPathExpression->field |
|
|
758 |
); |
|
|
759 |
} else if (isset($relation['indexBy'])) { |
|
|
760 |
$this->_rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']); |
|
|
761 |
} |
|
|
762 |
|
|
|
763 |
// This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot |
|
|
764 |
// be the owning side and previously we ensured that $assoc is always the owning side of the associations. |
|
|
765 |
// The owning side is necessary at this point because only it contains the JoinColumn information. |
|
|
766 |
if ($assoc['type'] & ClassMetadata::TO_ONE) { |
|
|
767 |
|
|
|
768 |
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; |
|
|
769 |
$first = true; |
|
|
770 |
|
|
|
771 |
foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) { |
|
|
772 |
if ( ! $first) $sql .= ' AND '; else $first = false; |
|
|
773 |
|
|
|
774 |
if ($relation['isOwningSide']) { |
|
|
775 |
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) { |
|
|
776 |
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
|
777 |
} else { |
|
|
778 |
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); |
|
|
779 |
} |
|
|
780 |
$sql .= $sourceTableAlias . '.' . $sourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn; |
|
|
781 |
} else { |
|
|
782 |
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) { |
|
|
783 |
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
|
784 |
} else { |
|
|
785 |
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); |
|
|
786 |
} |
|
|
787 |
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn; |
|
|
788 |
} |
|
|
789 |
} |
|
|
790 |
} else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) { |
|
|
791 |
// Join relation table |
|
|
792 |
$joinTable = $assoc['joinTable']; |
|
|
793 |
$joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias); |
|
|
794 |
$sql .= $sourceClass->getQuotedJoinTableName($assoc, $this->_platform) . ' ' . $joinTableAlias . ' ON '; |
|
|
795 |
|
|
|
796 |
$first = true; |
|
|
797 |
if ($relation['isOwningSide']) { |
|
|
798 |
foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { |
|
|
799 |
if ( ! $first) $sql .= ' AND '; else $first = false; |
|
|
800 |
|
|
|
801 |
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$sourceColumn])) { |
|
|
802 |
$quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted. |
|
|
803 |
} else { |
|
|
804 |
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$sourceColumn], $this->_platform); |
|
|
805 |
} |
|
|
806 |
|
|
|
807 |
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
|
808 |
} |
|
|
809 |
} else { |
|
|
810 |
foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { |
|
|
811 |
if ( ! $first) $sql .= ' AND '; else $first = false; |
|
|
812 |
|
|
|
813 |
if ($sourceClass->containsForeignIdentifier && !isset($sourceClass->fieldNames[$targetColumn])) { |
|
|
814 |
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
|
815 |
} else { |
|
|
816 |
$quotedTargetColumn = $sourceClass->getQuotedColumnName($sourceClass->fieldNames[$targetColumn], $this->_platform); |
|
|
817 |
} |
|
|
818 |
|
|
|
819 |
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
|
820 |
} |
|
|
821 |
} |
|
|
822 |
|
|
|
823 |
// Join target table |
|
|
824 |
$sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) |
|
|
825 |
? ' LEFT JOIN ' : ' INNER JOIN '; |
|
|
826 |
$sql .= $targetTableName . ' ' . $targetTableAlias . ' ON '; |
|
|
827 |
|
|
|
828 |
$first = true; |
|
|
829 |
if ($relation['isOwningSide']) { |
|
|
830 |
foreach ($assoc['relationToTargetKeyColumns'] as $relationColumn => $targetColumn) { |
|
|
831 |
if ( ! $first) $sql .= ' AND '; else $first = false; |
|
|
832 |
|
|
|
833 |
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$targetColumn])) { |
|
|
834 |
$quotedTargetColumn = $targetColumn; // Join columns cannot be quoted. |
|
|
835 |
} else { |
|
|
836 |
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$targetColumn], $this->_platform); |
|
|
837 |
} |
|
|
838 |
|
|
|
839 |
$sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
|
840 |
} |
|
|
841 |
} else { |
|
|
842 |
foreach ($assoc['relationToSourceKeyColumns'] as $relationColumn => $sourceColumn) { |
|
|
843 |
if ( ! $first) $sql .= ' AND '; else $first = false; |
|
|
844 |
|
|
|
845 |
if ($targetClass->containsForeignIdentifier && !isset($targetClass->fieldNames[$sourceColumn])) { |
|
|
846 |
$quotedTargetColumn = $sourceColumn; // Join columns cannot be quoted. |
|
|
847 |
} else { |
|
|
848 |
$quotedTargetColumn = $targetClass->getQuotedColumnName($targetClass->fieldNames[$sourceColumn], $this->_platform); |
|
|
849 |
} |
|
|
850 |
|
|
|
851 |
$sql .= $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $relationColumn; |
|
|
852 |
} |
|
|
853 |
} |
|
|
854 |
} |
|
|
855 |
|
|
|
856 |
// Handle WITH clause |
|
|
857 |
if (($condExpr = $join->conditionalExpression) !== null) { |
|
|
858 |
// Phase 2 AST optimization: Skip processment of ConditionalExpression |
|
|
859 |
// if only one ConditionalTerm is defined |
|
|
860 |
$sql .= ' AND (' . $this->walkConditionalExpression($condExpr) . ')'; |
|
|
861 |
} |
|
|
862 |
|
|
|
863 |
$discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias)); |
|
|
864 |
|
|
|
865 |
if ($discrSql) { |
|
|
866 |
$sql .= ' AND ' . $discrSql; |
|
|
867 |
} |
|
|
868 |
|
|
|
869 |
// FIXME: these should either be nested or all forced to be left joins (DDC-XXX) |
|
|
870 |
if ($targetClass->isInheritanceTypeJoined()) { |
|
|
871 |
$sql .= $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias); |
|
|
872 |
} |
|
|
873 |
|
|
|
874 |
return $sql; |
|
|
875 |
} |
|
|
876 |
|
|
|
877 |
/** |
|
|
878 |
* Walks down a CoalesceExpression AST node and generates the corresponding SQL. |
|
|
879 |
* |
|
|
880 |
* @param CoalesceExpression $coalesceExpression |
|
|
881 |
* @return string The SQL. |
|
|
882 |
*/ |
|
|
883 |
public function walkCoalesceExpression($coalesceExpression) |
|
|
884 |
{ |
|
|
885 |
$sql = 'COALESCE('; |
|
|
886 |
|
|
|
887 |
$scalarExpressions = array(); |
|
|
888 |
|
|
|
889 |
foreach ($coalesceExpression->scalarExpressions as $scalarExpression) { |
|
|
890 |
$scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression); |
|
|
891 |
} |
|
|
892 |
|
|
|
893 |
$sql .= implode(', ', $scalarExpressions) . ')'; |
|
|
894 |
|
|
|
895 |
return $sql; |
|
|
896 |
} |
|
|
897 |
|
|
|
898 |
public function walkCaseExpression($expression) |
|
|
899 |
{ |
|
|
900 |
switch (true) { |
|
|
901 |
case ($expression instanceof AST\CoalesceExpression): |
|
|
902 |
return $this->walkCoalesceExpression($expression); |
|
|
903 |
|
|
|
904 |
case ($expression instanceof AST\NullIfExpression): |
|
|
905 |
return $this->walkNullIfExpression($expression); |
|
|
906 |
|
|
|
907 |
default: |
|
|
908 |
return ''; |
|
|
909 |
} |
|
|
910 |
} |
|
|
911 |
|
|
|
912 |
/** |
|
|
913 |
* Walks down a NullIfExpression AST node and generates the corresponding SQL. |
|
|
914 |
* |
|
|
915 |
* @param NullIfExpression $nullIfExpression |
|
|
916 |
* @return string The SQL. |
|
|
917 |
*/ |
|
|
918 |
public function walkNullIfExpression($nullIfExpression) |
|
|
919 |
{ |
|
|
920 |
$firstExpression = is_string($nullIfExpression->firstExpression) |
|
|
921 |
? $this->_conn->quote($nullIfExpression->firstExpression) |
|
|
922 |
: $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression); |
|
|
923 |
|
|
|
924 |
$secondExpression = is_string($nullIfExpression->secondExpression) |
|
|
925 |
? $this->_conn->quote($nullIfExpression->secondExpression) |
|
|
926 |
: $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression); |
|
|
927 |
|
|
|
928 |
return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')'; |
|
|
929 |
} |
|
|
930 |
|
|
|
931 |
/** |
|
|
932 |
* Walks down a SelectExpression AST node and generates the corresponding SQL. |
|
|
933 |
* |
|
|
934 |
* @param SelectExpression $selectExpression |
|
|
935 |
* @return string The SQL. |
|
|
936 |
*/ |
|
|
937 |
public function walkSelectExpression($selectExpression) |
|
|
938 |
{ |
|
|
939 |
$sql = ''; |
|
|
940 |
$expr = $selectExpression->expression; |
|
|
941 |
|
|
|
942 |
if ($expr instanceof AST\PathExpression) { |
|
|
943 |
if ($expr->type == AST\PathExpression::TYPE_STATE_FIELD) { |
|
|
944 |
$fieldName = $expr->field; |
|
|
945 |
$dqlAlias = $expr->identificationVariable; |
|
|
946 |
$qComp = $this->_queryComponents[$dqlAlias]; |
|
|
947 |
$class = $qComp['metadata']; |
|
|
948 |
|
|
|
949 |
if ( ! $selectExpression->fieldIdentificationVariable) { |
|
|
950 |
$resultAlias = $fieldName; |
|
|
951 |
} else { |
|
|
952 |
$resultAlias = $selectExpression->fieldIdentificationVariable; |
|
|
953 |
} |
|
|
954 |
|
|
|
955 |
if ($class->isInheritanceTypeJoined()) { |
|
|
956 |
$tableName = $this->_em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName); |
|
|
957 |
} else { |
|
|
958 |
$tableName = $class->getTableName(); |
|
|
959 |
} |
|
|
960 |
|
|
|
961 |
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); |
|
|
962 |
$columnName = $class->getQuotedColumnName($fieldName, $this->_platform); |
|
|
963 |
|
|
|
964 |
$columnAlias = $this->getSQLColumnAlias($columnName); |
|
|
965 |
$sql .= $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias; |
|
|
966 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
967 |
$this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
|
968 |
} else { |
|
|
969 |
throw QueryException::invalidPathExpression($expr->type); |
|
|
970 |
} |
|
|
971 |
} |
|
|
972 |
else if ($expr instanceof AST\AggregateExpression) { |
|
|
973 |
if ( ! $selectExpression->fieldIdentificationVariable) { |
|
|
974 |
$resultAlias = $this->_scalarResultCounter++; |
|
|
975 |
} else { |
|
|
976 |
$resultAlias = $selectExpression->fieldIdentificationVariable; |
|
|
977 |
} |
|
|
978 |
|
|
|
979 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
980 |
$sql .= $this->walkAggregateExpression($expr) . ' AS ' . $columnAlias; |
|
|
981 |
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
|
982 |
|
|
|
983 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
984 |
$this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
|
985 |
} |
|
|
986 |
else if ($expr instanceof AST\Subselect) { |
|
|
987 |
if ( ! $selectExpression->fieldIdentificationVariable) { |
|
|
988 |
$resultAlias = $this->_scalarResultCounter++; |
|
|
989 |
} else { |
|
|
990 |
$resultAlias = $selectExpression->fieldIdentificationVariable; |
|
|
991 |
} |
|
|
992 |
|
|
|
993 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
994 |
$sql .= '(' . $this->walkSubselect($expr) . ') AS '.$columnAlias; |
|
|
995 |
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
|
996 |
|
|
|
997 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
998 |
$this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
|
999 |
} |
|
|
1000 |
else if ($expr instanceof AST\Functions\FunctionNode) { |
|
|
1001 |
if ( ! $selectExpression->fieldIdentificationVariable) { |
|
|
1002 |
$resultAlias = $this->_scalarResultCounter++; |
|
|
1003 |
} else { |
|
|
1004 |
$resultAlias = $selectExpression->fieldIdentificationVariable; |
|
|
1005 |
} |
|
|
1006 |
|
|
|
1007 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
1008 |
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; |
|
|
1009 |
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
|
1010 |
|
|
|
1011 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
1012 |
$this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
|
1013 |
} else if ( |
|
|
1014 |
$expr instanceof AST\SimpleArithmeticExpression || |
|
|
1015 |
$expr instanceof AST\ArithmeticTerm || |
|
|
1016 |
$expr instanceof AST\ArithmeticFactor || |
|
|
1017 |
$expr instanceof AST\ArithmeticPrimary || |
|
|
1018 |
$expr instanceof AST\Literal |
|
|
1019 |
) { |
|
|
1020 |
if ( ! $selectExpression->fieldIdentificationVariable) { |
|
|
1021 |
$resultAlias = $this->_scalarResultCounter++; |
|
|
1022 |
} else { |
|
|
1023 |
$resultAlias = $selectExpression->fieldIdentificationVariable; |
|
|
1024 |
} |
|
|
1025 |
|
|
|
1026 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
1027 |
|
|
|
1028 |
if ($expr instanceof AST\Literal) { |
|
|
1029 |
$sql .= $this->walkLiteral($expr) . ' AS ' .$columnAlias; |
|
|
1030 |
} else { |
|
|
1031 |
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; |
|
|
1032 |
} |
|
|
1033 |
|
|
|
1034 |
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
|
1035 |
|
|
|
1036 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
1037 |
$this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
|
1038 |
} else if ( |
|
|
1039 |
$expr instanceof AST\NullIfExpression || |
|
|
1040 |
$expr instanceof AST\CoalesceExpression || |
|
|
1041 |
$expr instanceof AST\CaseExpression |
|
|
1042 |
) { |
|
|
1043 |
if ( ! $selectExpression->fieldIdentificationVariable) { |
|
|
1044 |
$resultAlias = $this->_scalarResultCounter++; |
|
|
1045 |
} else { |
|
|
1046 |
$resultAlias = $selectExpression->fieldIdentificationVariable; |
|
|
1047 |
} |
|
|
1048 |
|
|
|
1049 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
1050 |
|
|
|
1051 |
$sql .= $this->walkCaseExpression($expr) . ' AS ' . $columnAlias; |
|
|
1052 |
|
|
|
1053 |
$this->_scalarResultAliasMap[$resultAlias] = $columnAlias; |
|
|
1054 |
|
|
|
1055 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
1056 |
$this->_rsm->addScalarResult($columnAlias, $resultAlias); |
|
|
1057 |
} else { |
|
|
1058 |
// IdentificationVariable or PartialObjectExpression |
|
|
1059 |
if ($expr instanceof AST\PartialObjectExpression) { |
|
|
1060 |
$dqlAlias = $expr->identificationVariable; |
|
|
1061 |
$partialFieldSet = $expr->partialFieldSet; |
|
|
1062 |
} else { |
|
|
1063 |
$dqlAlias = $expr; |
|
|
1064 |
$partialFieldSet = array(); |
|
|
1065 |
} |
|
|
1066 |
|
|
|
1067 |
$queryComp = $this->_queryComponents[$dqlAlias]; |
|
|
1068 |
$class = $queryComp['metadata']; |
|
|
1069 |
|
|
|
1070 |
if ( ! isset($this->_selectedClasses[$dqlAlias])) { |
|
|
1071 |
$this->_selectedClasses[$dqlAlias] = $class; |
|
|
1072 |
} |
|
|
1073 |
|
|
|
1074 |
$beginning = true; |
|
|
1075 |
// Select all fields from the queried class |
|
|
1076 |
foreach ($class->fieldMappings as $fieldName => $mapping) { |
|
|
1077 |
if ($partialFieldSet && !in_array($fieldName, $partialFieldSet)) { |
|
|
1078 |
continue; |
|
|
1079 |
} |
|
|
1080 |
|
|
|
1081 |
if (isset($mapping['inherited'])) { |
|
|
1082 |
$tableName = $this->_em->getClassMetadata($mapping['inherited'])->table['name']; |
|
|
1083 |
} else { |
|
|
1084 |
$tableName = $class->table['name']; |
|
|
1085 |
} |
|
|
1086 |
|
|
|
1087 |
if ($beginning) $beginning = false; else $sql .= ', '; |
|
|
1088 |
|
|
|
1089 |
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias); |
|
|
1090 |
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']); |
|
|
1091 |
$sql .= $sqlTableAlias . '.' . $class->getQuotedColumnName($fieldName, $this->_platform) |
|
|
1092 |
. ' AS ' . $columnAlias; |
|
|
1093 |
|
|
|
1094 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
1095 |
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name); |
|
|
1096 |
} |
|
|
1097 |
|
|
|
1098 |
// Add any additional fields of subclasses (excluding inherited fields) |
|
|
1099 |
// 1) on Single Table Inheritance: always, since its marginal overhead |
|
|
1100 |
// 2) on Class Table Inheritance only if partial objects are disallowed, |
|
|
1101 |
// since it requires outer joining subtables. |
|
|
1102 |
if ($class->isInheritanceTypeSingleTable() || ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) { |
|
|
1103 |
foreach ($class->subClasses as $subClassName) { |
|
|
1104 |
$subClass = $this->_em->getClassMetadata($subClassName); |
|
|
1105 |
$sqlTableAlias = $this->getSQLTableAlias($subClass->table['name'], $dqlAlias); |
|
|
1106 |
foreach ($subClass->fieldMappings as $fieldName => $mapping) { |
|
|
1107 |
if (isset($mapping['inherited']) || $partialFieldSet && !in_array($fieldName, $partialFieldSet)) { |
|
|
1108 |
continue; |
|
|
1109 |
} |
|
|
1110 |
|
|
|
1111 |
if ($beginning) $beginning = false; else $sql .= ', '; |
|
|
1112 |
|
|
|
1113 |
$columnAlias = $this->getSQLColumnAlias($mapping['columnName']); |
|
|
1114 |
$sql .= $sqlTableAlias . '.' . $subClass->getQuotedColumnName($fieldName, $this->_platform) |
|
|
1115 |
. ' AS ' . $columnAlias; |
|
|
1116 |
|
|
|
1117 |
$columnAlias = $this->_platform->getSQLResultCasing($columnAlias); |
|
|
1118 |
$this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName); |
|
|
1119 |
} |
|
|
1120 |
|
|
|
1121 |
// Add join columns (foreign keys) of the subclass |
|
|
1122 |
//TODO: Probably better do this in walkSelectClause to honor the INCLUDE_META_COLUMNS hint |
|
|
1123 |
foreach ($subClass->associationMappings as $fieldName => $assoc) { |
|
|
1124 |
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) { |
|
|
1125 |
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) { |
|
|
1126 |
if ($beginning) $beginning = false; else $sql .= ', '; |
|
|
1127 |
$columnAlias = $this->getSQLColumnAlias($srcColumn); |
|
|
1128 |
$sql .= $sqlTableAlias . '.' . $srcColumn . ' AS ' . $columnAlias; |
|
|
1129 |
$this->_rsm->addMetaResult($dqlAlias, $this->_platform->getSQLResultCasing($columnAlias), $srcColumn); |
|
|
1130 |
} |
|
|
1131 |
} |
|
|
1132 |
} |
|
|
1133 |
} |
|
|
1134 |
} |
|
|
1135 |
} |
|
|
1136 |
|
|
|
1137 |
return $sql; |
|
|
1138 |
} |
|
|
1139 |
|
|
|
1140 |
/** |
|
|
1141 |
* Walks down a QuantifiedExpression AST node, thereby generating the appropriate SQL. |
|
|
1142 |
* |
|
|
1143 |
* @param QuantifiedExpression |
|
|
1144 |
* @return string The SQL. |
|
|
1145 |
*/ |
|
|
1146 |
public function walkQuantifiedExpression($qExpr) |
|
|
1147 |
{ |
|
|
1148 |
return ' ' . strtoupper($qExpr->type) |
|
|
1149 |
. '(' . $this->walkSubselect($qExpr->subselect) . ')'; |
|
|
1150 |
} |
|
|
1151 |
|
|
|
1152 |
/** |
|
|
1153 |
* Walks down a Subselect AST node, thereby generating the appropriate SQL. |
|
|
1154 |
* |
|
|
1155 |
* @param Subselect |
|
|
1156 |
* @return string The SQL. |
|
|
1157 |
*/ |
|
|
1158 |
public function walkSubselect($subselect) |
|
|
1159 |
{ |
|
|
1160 |
$useAliasesBefore = $this->_useSqlTableAliases; |
|
|
1161 |
$this->_useSqlTableAliases = true; |
|
|
1162 |
|
|
|
1163 |
$sql = $this->walkSimpleSelectClause($subselect->simpleSelectClause); |
|
|
1164 |
$sql .= $this->walkSubselectFromClause($subselect->subselectFromClause); |
|
|
1165 |
$sql .= $subselect->whereClause ? $this->walkWhereClause($subselect->whereClause) : ''; |
|
|
1166 |
$sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : ''; |
|
|
1167 |
$sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : ''; |
|
|
1168 |
$sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : ''; |
|
|
1169 |
|
|
|
1170 |
$this->_useSqlTableAliases = $useAliasesBefore; |
|
|
1171 |
|
|
|
1172 |
return $sql; |
|
|
1173 |
} |
|
|
1174 |
|
|
|
1175 |
/** |
|
|
1176 |
* Walks down a SubselectFromClause AST node, thereby generating the appropriate SQL. |
|
|
1177 |
* |
|
|
1178 |
* @param SubselectFromClause |
|
|
1179 |
* @return string The SQL. |
|
|
1180 |
*/ |
|
|
1181 |
public function walkSubselectFromClause($subselectFromClause) |
|
|
1182 |
{ |
|
|
1183 |
$identificationVarDecls = $subselectFromClause->identificationVariableDeclarations; |
|
|
1184 |
$sqlParts = array (); |
|
|
1185 |
|
|
|
1186 |
foreach ($identificationVarDecls as $subselectIdVarDecl) { |
|
|
1187 |
$sql = ''; |
|
|
1188 |
|
|
|
1189 |
$rangeDecl = $subselectIdVarDecl->rangeVariableDeclaration; |
|
|
1190 |
$dqlAlias = $rangeDecl->aliasIdentificationVariable; |
|
|
1191 |
|
|
|
1192 |
$class = $this->_em->getClassMetadata($rangeDecl->abstractSchemaName); |
|
|
1193 |
$sql .= $class->getQuotedTableName($this->_platform) . ' ' |
|
|
1194 |
. $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
1195 |
|
|
|
1196 |
if ($class->isInheritanceTypeJoined()) { |
|
|
1197 |
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias); |
|
|
1198 |
} |
|
|
1199 |
|
|
|
1200 |
foreach ($subselectIdVarDecl->joinVariableDeclarations as $joinVarDecl) { |
|
|
1201 |
$sql .= $this->walkJoinVariableDeclaration($joinVarDecl); |
|
|
1202 |
} |
|
|
1203 |
|
|
|
1204 |
$sqlParts[] = $this->_platform->appendLockHint($sql, $this->_query->getHint(Query::HINT_LOCK_MODE)); |
|
|
1205 |
} |
|
|
1206 |
|
|
|
1207 |
return ' FROM ' . implode(', ', $sqlParts); |
|
|
1208 |
} |
|
|
1209 |
|
|
|
1210 |
/** |
|
|
1211 |
* Walks down a SimpleSelectClause AST node, thereby generating the appropriate SQL. |
|
|
1212 |
* |
|
|
1213 |
* @param SimpleSelectClause |
|
|
1214 |
* @return string The SQL. |
|
|
1215 |
*/ |
|
|
1216 |
public function walkSimpleSelectClause($simpleSelectClause) |
|
|
1217 |
{ |
|
|
1218 |
return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '') |
|
|
1219 |
. $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression); |
|
|
1220 |
} |
|
|
1221 |
|
|
|
1222 |
/** |
|
|
1223 |
* Walks down a SimpleSelectExpression AST node, thereby generating the appropriate SQL. |
|
|
1224 |
* |
|
|
1225 |
* @param SimpleSelectExpression |
|
|
1226 |
* @return string The SQL. |
|
|
1227 |
*/ |
|
|
1228 |
public function walkSimpleSelectExpression($simpleSelectExpression) |
|
|
1229 |
{ |
|
|
1230 |
$sql = ''; |
|
|
1231 |
$expr = $simpleSelectExpression->expression; |
|
|
1232 |
|
|
|
1233 |
if ($expr instanceof AST\PathExpression) { |
|
|
1234 |
$sql .= $this->walkPathExpression($expr); |
|
|
1235 |
} else if ($expr instanceof AST\AggregateExpression) { |
|
|
1236 |
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
|
1237 |
$alias = $this->_scalarResultCounter++; |
|
|
1238 |
} else { |
|
|
1239 |
$alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
|
1240 |
} |
|
|
1241 |
|
|
|
1242 |
$sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias; |
|
|
1243 |
} else if ($expr instanceof AST\Subselect) { |
|
|
1244 |
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
|
1245 |
$alias = $this->_scalarResultCounter++; |
|
|
1246 |
} else { |
|
|
1247 |
$alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
|
1248 |
} |
|
|
1249 |
|
|
|
1250 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
1251 |
$sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias; |
|
|
1252 |
$this->_scalarResultAliasMap[$alias] = $columnAlias; |
|
|
1253 |
} else if ($expr instanceof AST\Functions\FunctionNode) { |
|
|
1254 |
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
|
1255 |
$alias = $this->_scalarResultCounter++; |
|
|
1256 |
} else { |
|
|
1257 |
$alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
|
1258 |
} |
|
|
1259 |
|
|
|
1260 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
1261 |
$sql .= $this->walkFunction($expr) . ' AS ' . $columnAlias; |
|
|
1262 |
$this->_scalarResultAliasMap[$alias] = $columnAlias; |
|
|
1263 |
} else if ( |
|
|
1264 |
$expr instanceof AST\SimpleArithmeticExpression || |
|
|
1265 |
$expr instanceof AST\ArithmeticTerm || |
|
|
1266 |
$expr instanceof AST\ArithmeticFactor || |
|
|
1267 |
$expr instanceof AST\ArithmeticPrimary |
|
|
1268 |
) { |
|
|
1269 |
if ( ! $simpleSelectExpression->fieldIdentificationVariable) { |
|
|
1270 |
$alias = $this->_scalarResultCounter++; |
|
|
1271 |
} else { |
|
|
1272 |
$alias = $simpleSelectExpression->fieldIdentificationVariable; |
|
|
1273 |
} |
|
|
1274 |
|
|
|
1275 |
$columnAlias = 'sclr' . $this->_aliasCounter++; |
|
|
1276 |
$sql .= $this->walkSimpleArithmeticExpression($expr) . ' AS ' . $columnAlias; |
|
|
1277 |
$this->_scalarResultAliasMap[$alias] = $columnAlias; |
|
|
1278 |
} else { |
|
|
1279 |
// IdentificationVariable |
|
|
1280 |
$class = $this->_queryComponents[$expr]['metadata']; |
|
|
1281 |
$tableAlias = $this->getSQLTableAlias($class->getTableName(), $expr); |
|
|
1282 |
$first = true; |
|
|
1283 |
|
|
|
1284 |
foreach ($class->identifier as $identifier) { |
|
|
1285 |
if ($first) $first = false; else $sql .= ', '; |
|
|
1286 |
$sql .= $tableAlias . '.' . $class->getQuotedColumnName($identifier, $this->_platform); |
|
|
1287 |
} |
|
|
1288 |
} |
|
|
1289 |
|
|
|
1290 |
return ' ' . $sql; |
|
|
1291 |
} |
|
|
1292 |
|
|
|
1293 |
/** |
|
|
1294 |
* Walks down an AggregateExpression AST node, thereby generating the appropriate SQL. |
|
|
1295 |
* |
|
|
1296 |
* @param AggregateExpression |
|
|
1297 |
* @return string The SQL. |
|
|
1298 |
*/ |
|
|
1299 |
public function walkAggregateExpression($aggExpression) |
|
|
1300 |
{ |
|
|
1301 |
return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '') |
|
|
1302 |
. $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')'; |
|
|
1303 |
} |
|
|
1304 |
|
|
|
1305 |
/** |
|
|
1306 |
* Walks down a GroupByClause AST node, thereby generating the appropriate SQL. |
|
|
1307 |
* |
|
|
1308 |
* @param GroupByClause |
|
|
1309 |
* @return string The SQL. |
|
|
1310 |
*/ |
|
|
1311 |
public function walkGroupByClause($groupByClause) |
|
|
1312 |
{ |
|
|
1313 |
$sql = ''; |
|
|
1314 |
foreach ($groupByClause->groupByItems AS $groupByItem) { |
|
|
1315 |
if (is_string($groupByItem)) { |
|
|
1316 |
foreach ($this->_queryComponents[$groupByItem]['metadata']->identifier AS $idField) { |
|
|
1317 |
if ($sql != '') { |
|
|
1318 |
$sql .= ', '; |
|
|
1319 |
} |
|
|
1320 |
$groupByItem = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $idField); |
|
|
1321 |
$groupByItem->type = AST\PathExpression::TYPE_STATE_FIELD; |
|
|
1322 |
$sql .= $this->walkGroupByItem($groupByItem); |
|
|
1323 |
} |
|
|
1324 |
} else { |
|
|
1325 |
if ($sql != '') { |
|
|
1326 |
$sql .= ', '; |
|
|
1327 |
} |
|
|
1328 |
$sql .= $this->walkGroupByItem($groupByItem); |
|
|
1329 |
} |
|
|
1330 |
} |
|
|
1331 |
return ' GROUP BY ' . $sql; |
|
|
1332 |
} |
|
|
1333 |
|
|
|
1334 |
/** |
|
|
1335 |
* Walks down a GroupByItem AST node, thereby generating the appropriate SQL. |
|
|
1336 |
* |
|
|
1337 |
* @param GroupByItem |
|
|
1338 |
* @return string The SQL. |
|
|
1339 |
*/ |
|
|
1340 |
public function walkGroupByItem(AST\PathExpression $pathExpr) |
|
|
1341 |
{ |
|
|
1342 |
return $this->walkPathExpression($pathExpr); |
|
|
1343 |
} |
|
|
1344 |
|
|
|
1345 |
/** |
|
|
1346 |
* Walks down a DeleteClause AST node, thereby generating the appropriate SQL. |
|
|
1347 |
* |
|
|
1348 |
* @param DeleteClause |
|
|
1349 |
* @return string The SQL. |
|
|
1350 |
*/ |
|
|
1351 |
public function walkDeleteClause(AST\DeleteClause $deleteClause) |
|
|
1352 |
{ |
|
|
1353 |
$sql = 'DELETE FROM '; |
|
|
1354 |
$class = $this->_em->getClassMetadata($deleteClause->abstractSchemaName); |
|
|
1355 |
$sql .= $class->getQuotedTableName($this->_platform); |
|
|
1356 |
|
|
|
1357 |
$this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $deleteClause->aliasIdentificationVariable); |
|
|
1358 |
|
|
|
1359 |
$this->_rootAliases[] = $deleteClause->aliasIdentificationVariable; |
|
|
1360 |
|
|
|
1361 |
return $sql; |
|
|
1362 |
} |
|
|
1363 |
|
|
|
1364 |
/** |
|
|
1365 |
* Walks down an UpdateClause AST node, thereby generating the appropriate SQL. |
|
|
1366 |
* |
|
|
1367 |
* @param UpdateClause |
|
|
1368 |
* @return string The SQL. |
|
|
1369 |
*/ |
|
|
1370 |
public function walkUpdateClause($updateClause) |
|
|
1371 |
{ |
|
|
1372 |
$sql = 'UPDATE '; |
|
|
1373 |
$class = $this->_em->getClassMetadata($updateClause->abstractSchemaName); |
|
|
1374 |
$sql .= $class->getQuotedTableName($this->_platform); |
|
|
1375 |
|
|
|
1376 |
$this->setSQLTableAlias($class->getTableName(), $class->getTableName(), $updateClause->aliasIdentificationVariable); |
|
|
1377 |
|
|
|
1378 |
$this->_rootAliases[] = $updateClause->aliasIdentificationVariable; |
|
|
1379 |
|
|
|
1380 |
$sql .= ' SET ' . implode( |
|
|
1381 |
', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems) |
|
|
1382 |
); |
|
|
1383 |
|
|
|
1384 |
return $sql; |
|
|
1385 |
} |
|
|
1386 |
|
|
|
1387 |
/** |
|
|
1388 |
* Walks down an UpdateItem AST node, thereby generating the appropriate SQL. |
|
|
1389 |
* |
|
|
1390 |
* @param UpdateItem |
|
|
1391 |
* @return string The SQL. |
|
|
1392 |
*/ |
|
|
1393 |
public function walkUpdateItem($updateItem) |
|
|
1394 |
{ |
|
|
1395 |
$useTableAliasesBefore = $this->_useSqlTableAliases; |
|
|
1396 |
$this->_useSqlTableAliases = false; |
|
|
1397 |
|
|
|
1398 |
$sql = $this->walkPathExpression($updateItem->pathExpression) . ' = '; |
|
|
1399 |
|
|
|
1400 |
$newValue = $updateItem->newValue; |
|
|
1401 |
|
|
|
1402 |
if ($newValue === null) { |
|
|
1403 |
$sql .= 'NULL'; |
|
|
1404 |
} else if ($newValue instanceof AST\Node) { |
|
|
1405 |
$sql .= $newValue->dispatch($this); |
|
|
1406 |
} else { |
|
|
1407 |
$sql .= $this->_conn->quote($newValue); |
|
|
1408 |
} |
|
|
1409 |
|
|
|
1410 |
$this->_useSqlTableAliases = $useTableAliasesBefore; |
|
|
1411 |
|
|
|
1412 |
return $sql; |
|
|
1413 |
} |
|
|
1414 |
|
|
|
1415 |
/** |
|
|
1416 |
* Walks down a WhereClause AST node, thereby generating the appropriate SQL. |
|
|
1417 |
* |
|
|
1418 |
* @param WhereClause |
|
|
1419 |
* @return string The SQL. |
|
|
1420 |
*/ |
|
|
1421 |
public function walkWhereClause($whereClause) |
|
|
1422 |
{ |
|
|
1423 |
$discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases); |
|
|
1424 |
$condSql = $this->walkConditionalExpression($whereClause->conditionalExpression); |
|
|
1425 |
|
|
|
1426 |
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql); |
|
|
1427 |
} |
|
|
1428 |
|
|
|
1429 |
/** |
|
|
1430 |
* Walk down a ConditionalExpression AST node, thereby generating the appropriate SQL. |
|
|
1431 |
* |
|
|
1432 |
* @param ConditionalExpression |
|
|
1433 |
* @return string The SQL. |
|
|
1434 |
*/ |
|
|
1435 |
public function walkConditionalExpression($condExpr) |
|
|
1436 |
{ |
|
|
1437 |
// Phase 2 AST optimization: Skip processment of ConditionalExpression |
|
|
1438 |
// if only one ConditionalTerm is defined |
|
|
1439 |
return ( ! ($condExpr instanceof AST\ConditionalExpression)) |
|
|
1440 |
? $this->walkConditionalTerm($condExpr) |
|
|
1441 |
: implode( |
|
|
1442 |
' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms) |
|
|
1443 |
); |
|
|
1444 |
} |
|
|
1445 |
|
|
|
1446 |
/** |
|
|
1447 |
* Walks down a ConditionalTerm AST node, thereby generating the appropriate SQL. |
|
|
1448 |
* |
|
|
1449 |
* @param ConditionalTerm |
|
|
1450 |
* @return string The SQL. |
|
|
1451 |
*/ |
|
|
1452 |
public function walkConditionalTerm($condTerm) |
|
|
1453 |
{ |
|
|
1454 |
// Phase 2 AST optimization: Skip processment of ConditionalTerm |
|
|
1455 |
// if only one ConditionalFactor is defined |
|
|
1456 |
return ( ! ($condTerm instanceof AST\ConditionalTerm)) |
|
|
1457 |
? $this->walkConditionalFactor($condTerm) |
|
|
1458 |
: implode( |
|
|
1459 |
' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors) |
|
|
1460 |
); |
|
|
1461 |
} |
|
|
1462 |
|
|
|
1463 |
/** |
|
|
1464 |
* Walks down a ConditionalFactor AST node, thereby generating the appropriate SQL. |
|
|
1465 |
* |
|
|
1466 |
* @param ConditionalFactor |
|
|
1467 |
* @return string The SQL. |
|
|
1468 |
*/ |
|
|
1469 |
public function walkConditionalFactor($factor) |
|
|
1470 |
{ |
|
|
1471 |
// Phase 2 AST optimization: Skip processment of ConditionalFactor |
|
|
1472 |
// if only one ConditionalPrimary is defined |
|
|
1473 |
return ( ! ($factor instanceof AST\ConditionalFactor)) |
|
|
1474 |
? $this->walkConditionalPrimary($factor) |
|
|
1475 |
: ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary); |
|
|
1476 |
} |
|
|
1477 |
|
|
|
1478 |
/** |
|
|
1479 |
* Walks down a ConditionalPrimary AST node, thereby generating the appropriate SQL. |
|
|
1480 |
* |
|
|
1481 |
* @param ConditionalPrimary |
|
|
1482 |
* @return string The SQL. |
|
|
1483 |
*/ |
|
|
1484 |
public function walkConditionalPrimary($primary) |
|
|
1485 |
{ |
|
|
1486 |
if ($primary->isSimpleConditionalExpression()) { |
|
|
1487 |
return $primary->simpleConditionalExpression->dispatch($this); |
|
|
1488 |
} else if ($primary->isConditionalExpression()) { |
|
|
1489 |
$condExpr = $primary->conditionalExpression; |
|
|
1490 |
|
|
|
1491 |
return '(' . $this->walkConditionalExpression($condExpr) . ')'; |
|
|
1492 |
} |
|
|
1493 |
} |
|
|
1494 |
|
|
|
1495 |
/** |
|
|
1496 |
* Walks down an ExistsExpression AST node, thereby generating the appropriate SQL. |
|
|
1497 |
* |
|
|
1498 |
* @param ExistsExpression |
|
|
1499 |
* @return string The SQL. |
|
|
1500 |
*/ |
|
|
1501 |
public function walkExistsExpression($existsExpr) |
|
|
1502 |
{ |
|
|
1503 |
$sql = ($existsExpr->not) ? 'NOT ' : ''; |
|
|
1504 |
|
|
|
1505 |
$sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')'; |
|
|
1506 |
|
|
|
1507 |
return $sql; |
|
|
1508 |
} |
|
|
1509 |
|
|
|
1510 |
/** |
|
|
1511 |
* Walks down a CollectionMemberExpression AST node, thereby generating the appropriate SQL. |
|
|
1512 |
* |
|
|
1513 |
* @param CollectionMemberExpression |
|
|
1514 |
* @return string The SQL. |
|
|
1515 |
*/ |
|
|
1516 |
public function walkCollectionMemberExpression($collMemberExpr) |
|
|
1517 |
{ |
|
|
1518 |
$sql = $collMemberExpr->not ? 'NOT ' : ''; |
|
|
1519 |
$sql .= 'EXISTS (SELECT 1 FROM '; |
|
|
1520 |
$entityExpr = $collMemberExpr->entityExpression; |
|
|
1521 |
$collPathExpr = $collMemberExpr->collectionValuedPathExpression; |
|
|
1522 |
|
|
|
1523 |
$fieldName = $collPathExpr->field; |
|
|
1524 |
$dqlAlias = $collPathExpr->identificationVariable; |
|
|
1525 |
|
|
|
1526 |
$class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
|
1527 |
|
|
|
1528 |
if ($entityExpr instanceof AST\InputParameter) { |
|
|
1529 |
$dqlParamKey = $entityExpr->name; |
|
|
1530 |
$entity = $this->_query->getParameter($dqlParamKey); |
|
|
1531 |
} else { |
|
|
1532 |
//TODO |
|
|
1533 |
throw new \BadMethodCallException("Not implemented"); |
|
|
1534 |
} |
|
|
1535 |
|
|
|
1536 |
$assoc = $class->associationMappings[$fieldName]; |
|
|
1537 |
|
|
|
1538 |
if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) { |
|
|
1539 |
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); |
|
|
1540 |
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']); |
|
|
1541 |
$sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
1542 |
|
|
|
1543 |
$sql .= $targetClass->getQuotedTableName($this->_platform) |
|
|
1544 |
. ' ' . $targetTableAlias . ' WHERE '; |
|
|
1545 |
|
|
|
1546 |
$owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']]; |
|
|
1547 |
|
|
|
1548 |
$first = true; |
|
|
1549 |
|
|
|
1550 |
foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) { |
|
|
1551 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
1552 |
|
|
|
1553 |
$sql .= $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $this->_platform) |
|
|
1554 |
. ' = ' |
|
|
1555 |
. $targetTableAlias . '.' . $sourceColumn; |
|
|
1556 |
} |
|
|
1557 |
|
|
|
1558 |
$sql .= ' AND '; |
|
|
1559 |
$first = true; |
|
|
1560 |
|
|
|
1561 |
foreach ($targetClass->identifier as $idField) { |
|
|
1562 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
1563 |
|
|
|
1564 |
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
|
1565 |
$sql .= $targetTableAlias . '.' |
|
|
1566 |
. $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; |
|
|
1567 |
} |
|
|
1568 |
} else { // many-to-many |
|
|
1569 |
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']); |
|
|
1570 |
|
|
|
1571 |
$owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']]; |
|
|
1572 |
$joinTable = $owningAssoc['joinTable']; |
|
|
1573 |
|
|
|
1574 |
// SQL table aliases |
|
|
1575 |
$joinTableAlias = $this->getSQLTableAlias($joinTable['name']); |
|
|
1576 |
$targetTableAlias = $this->getSQLTableAlias($targetClass->table['name']); |
|
|
1577 |
$sourceTableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias); |
|
|
1578 |
|
|
|
1579 |
// join to target table |
|
|
1580 |
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $this->_platform) |
|
|
1581 |
. ' ' . $joinTableAlias . ' INNER JOIN ' |
|
|
1582 |
. $targetClass->getQuotedTableName($this->_platform) |
|
|
1583 |
. ' ' . $targetTableAlias . ' ON '; |
|
|
1584 |
|
|
|
1585 |
// join conditions |
|
|
1586 |
$joinColumns = $assoc['isOwningSide'] |
|
|
1587 |
? $joinTable['inverseJoinColumns'] |
|
|
1588 |
: $joinTable['joinColumns']; |
|
|
1589 |
|
|
|
1590 |
$first = true; |
|
|
1591 |
foreach ($joinColumns as $joinColumn) { |
|
|
1592 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
1593 |
|
|
|
1594 |
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' |
|
|
1595 |
. $targetTableAlias . '.' . $targetClass->getQuotedColumnName( |
|
|
1596 |
$targetClass->fieldNames[$joinColumn['referencedColumnName']], |
|
|
1597 |
$this->_platform); |
|
|
1598 |
} |
|
|
1599 |
|
|
|
1600 |
$sql .= ' WHERE '; |
|
|
1601 |
|
|
|
1602 |
$joinColumns = $assoc['isOwningSide'] |
|
|
1603 |
? $joinTable['joinColumns'] |
|
|
1604 |
: $joinTable['inverseJoinColumns']; |
|
|
1605 |
|
|
|
1606 |
$first = true; |
|
|
1607 |
foreach ($joinColumns as $joinColumn) { |
|
|
1608 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
1609 |
|
|
|
1610 |
$sql .= $joinTableAlias . '.' . $joinColumn['name'] . ' = ' |
|
|
1611 |
. $sourceTableAlias . '.' . $class->getQuotedColumnName( |
|
|
1612 |
$class->fieldNames[$joinColumn['referencedColumnName']], |
|
|
1613 |
$this->_platform); |
|
|
1614 |
} |
|
|
1615 |
|
|
|
1616 |
$sql .= ' AND '; |
|
|
1617 |
$first = true; |
|
|
1618 |
|
|
|
1619 |
foreach ($targetClass->identifier as $idField) { |
|
|
1620 |
if ($first) $first = false; else $sql .= ' AND '; |
|
|
1621 |
|
|
|
1622 |
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
|
1623 |
$sql .= $targetTableAlias . '.' |
|
|
1624 |
. $targetClass->getQuotedColumnName($idField, $this->_platform) . ' = ?'; |
|
|
1625 |
} |
|
|
1626 |
} |
|
|
1627 |
|
|
|
1628 |
return $sql . ')'; |
|
|
1629 |
} |
|
|
1630 |
|
|
|
1631 |
/** |
|
|
1632 |
* Walks down an EmptyCollectionComparisonExpression AST node, thereby generating the appropriate SQL. |
|
|
1633 |
* |
|
|
1634 |
* @param EmptyCollectionComparisonExpression |
|
|
1635 |
* @return string The SQL. |
|
|
1636 |
*/ |
|
|
1637 |
public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr) |
|
|
1638 |
{ |
|
|
1639 |
$sizeFunc = new AST\Functions\SizeFunction('size'); |
|
|
1640 |
$sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression; |
|
|
1641 |
|
|
|
1642 |
return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0'); |
|
|
1643 |
} |
|
|
1644 |
|
|
|
1645 |
/** |
|
|
1646 |
* Walks down a NullComparisonExpression AST node, thereby generating the appropriate SQL. |
|
|
1647 |
* |
|
|
1648 |
* @param NullComparisonExpression |
|
|
1649 |
* @return string The SQL. |
|
|
1650 |
*/ |
|
|
1651 |
public function walkNullComparisonExpression($nullCompExpr) |
|
|
1652 |
{ |
|
|
1653 |
$sql = ''; |
|
|
1654 |
$innerExpr = $nullCompExpr->expression; |
|
|
1655 |
|
|
|
1656 |
if ($innerExpr instanceof AST\InputParameter) { |
|
|
1657 |
$dqlParamKey = $innerExpr->name; |
|
|
1658 |
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
|
1659 |
$sql .= ' ?'; |
|
|
1660 |
} else { |
|
|
1661 |
$sql .= $this->walkPathExpression($innerExpr); |
|
|
1662 |
} |
|
|
1663 |
|
|
|
1664 |
$sql .= ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL'; |
|
|
1665 |
|
|
|
1666 |
return $sql; |
|
|
1667 |
} |
|
|
1668 |
|
|
|
1669 |
/** |
|
|
1670 |
* Walks down an InExpression AST node, thereby generating the appropriate SQL. |
|
|
1671 |
* |
|
|
1672 |
* @param InExpression |
|
|
1673 |
* @return string The SQL. |
|
|
1674 |
*/ |
|
|
1675 |
public function walkInExpression($inExpr) |
|
|
1676 |
{ |
|
|
1677 |
$sql = $this->walkPathExpression($inExpr->pathExpression) |
|
|
1678 |
. ($inExpr->not ? ' NOT' : '') . ' IN ('; |
|
|
1679 |
|
|
|
1680 |
if ($inExpr->subselect) { |
|
|
1681 |
$sql .= $this->walkSubselect($inExpr->subselect); |
|
|
1682 |
} else { |
|
|
1683 |
$sql .= implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals)); |
|
|
1684 |
} |
|
|
1685 |
|
|
|
1686 |
$sql .= ')'; |
|
|
1687 |
|
|
|
1688 |
return $sql; |
|
|
1689 |
} |
|
|
1690 |
|
|
|
1691 |
/** |
|
|
1692 |
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL. |
|
|
1693 |
* |
|
|
1694 |
* @param InstanceOfExpression |
|
|
1695 |
* @return string The SQL. |
|
|
1696 |
*/ |
|
|
1697 |
public function walkInstanceOfExpression($instanceOfExpr) |
|
|
1698 |
{ |
|
|
1699 |
$sql = ''; |
|
|
1700 |
|
|
|
1701 |
$dqlAlias = $instanceOfExpr->identificationVariable; |
|
|
1702 |
$discrClass = $class = $this->_queryComponents[$dqlAlias]['metadata']; |
|
|
1703 |
$fieldName = null; |
|
|
1704 |
|
|
|
1705 |
if ($class->discriminatorColumn) { |
|
|
1706 |
$discrClass = $this->_em->getClassMetadata($class->rootEntityName); |
|
|
1707 |
} |
|
|
1708 |
|
|
|
1709 |
if ($this->_useSqlTableAliases) { |
|
|
1710 |
$sql .= $this->getSQLTableAlias($discrClass->table['name'], $dqlAlias) . '.'; |
|
|
1711 |
} |
|
|
1712 |
|
|
|
1713 |
$sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' <> ' : ' = '); |
|
|
1714 |
|
|
|
1715 |
if ($instanceOfExpr->value instanceof AST\InputParameter) { |
|
|
1716 |
// We need to modify the parameter value to be its correspondent mapped value |
|
|
1717 |
$dqlParamKey = $instanceOfExpr->value->name; |
|
|
1718 |
$paramValue = $this->_query->getParameter($dqlParamKey); |
|
|
1719 |
|
|
|
1720 |
if ( ! ($paramValue instanceof \Doctrine\ORM\Mapping\ClassMetadata)) { |
|
|
1721 |
throw QueryException::invalidParameterType('ClassMetadata', get_class($paramValue)); |
|
|
1722 |
} |
|
|
1723 |
|
|
|
1724 |
$entityClassName = $paramValue->name; |
|
|
1725 |
} else { |
|
|
1726 |
// Get name from ClassMetadata to resolve aliases. |
|
|
1727 |
$entityClassName = $this->_em->getClassMetadata($instanceOfExpr->value)->name; |
|
|
1728 |
} |
|
|
1729 |
|
|
|
1730 |
if ($entityClassName == $class->name) { |
|
|
1731 |
$sql .= $this->_conn->quote($class->discriminatorValue); |
|
|
1732 |
} else { |
|
|
1733 |
$discrMap = array_flip($class->discriminatorMap); |
|
|
1734 |
if (!isset($discrMap[$entityClassName])) { |
|
|
1735 |
throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName); |
|
|
1736 |
} |
|
|
1737 |
|
|
|
1738 |
$sql .= $this->_conn->quote($discrMap[$entityClassName]); |
|
|
1739 |
} |
|
|
1740 |
|
|
|
1741 |
return $sql; |
|
|
1742 |
} |
|
|
1743 |
|
|
|
1744 |
/** |
|
|
1745 |
* Walks down an InParameter AST node, thereby generating the appropriate SQL. |
|
|
1746 |
* |
|
|
1747 |
* @param InParameter |
|
|
1748 |
* @return string The SQL. |
|
|
1749 |
*/ |
|
|
1750 |
public function walkInParameter($inParam) |
|
|
1751 |
{ |
|
|
1752 |
return $inParam instanceof AST\InputParameter ? |
|
|
1753 |
$this->walkInputParameter($inParam) : |
|
|
1754 |
$this->walkLiteral($inParam); |
|
|
1755 |
} |
|
|
1756 |
|
|
|
1757 |
/** |
|
|
1758 |
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL. |
|
|
1759 |
* |
|
|
1760 |
* @param mixed |
|
|
1761 |
* @return string The SQL. |
|
|
1762 |
*/ |
|
|
1763 |
public function walkLiteral($literal) |
|
|
1764 |
{ |
|
|
1765 |
switch ($literal->type) { |
|
|
1766 |
case AST\Literal::STRING: |
|
|
1767 |
return $this->_conn->quote($literal->value); |
|
|
1768 |
case AST\Literal::BOOLEAN: |
|
|
1769 |
$bool = strtolower($literal->value) == 'true' ? true : false; |
|
|
1770 |
$boolVal = $this->_conn->getDatabasePlatform()->convertBooleans($bool); |
|
|
1771 |
return is_string($boolVal) ? $this->_conn->quote($boolVal) : $boolVal; |
|
|
1772 |
case AST\Literal::NUMERIC: |
|
|
1773 |
return $literal->value; |
|
|
1774 |
default: |
|
|
1775 |
throw QueryException::invalidLiteral($literal); |
|
|
1776 |
} |
|
|
1777 |
} |
|
|
1778 |
|
|
|
1779 |
/** |
|
|
1780 |
* Walks down a BetweenExpression AST node, thereby generating the appropriate SQL. |
|
|
1781 |
* |
|
|
1782 |
* @param BetweenExpression |
|
|
1783 |
* @return string The SQL. |
|
|
1784 |
*/ |
|
|
1785 |
public function walkBetweenExpression($betweenExpr) |
|
|
1786 |
{ |
|
|
1787 |
$sql = $this->walkArithmeticExpression($betweenExpr->expression); |
|
|
1788 |
|
|
|
1789 |
if ($betweenExpr->not) $sql .= ' NOT'; |
|
|
1790 |
|
|
|
1791 |
$sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression) |
|
|
1792 |
. ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression); |
|
|
1793 |
|
|
|
1794 |
return $sql; |
|
|
1795 |
} |
|
|
1796 |
|
|
|
1797 |
/** |
|
|
1798 |
* Walks down a LikeExpression AST node, thereby generating the appropriate SQL. |
|
|
1799 |
* |
|
|
1800 |
* @param LikeExpression |
|
|
1801 |
* @return string The SQL. |
|
|
1802 |
*/ |
|
|
1803 |
public function walkLikeExpression($likeExpr) |
|
|
1804 |
{ |
|
|
1805 |
$stringExpr = $likeExpr->stringExpression; |
|
|
1806 |
$sql = $stringExpr->dispatch($this) . ($likeExpr->not ? ' NOT' : '') . ' LIKE '; |
|
|
1807 |
|
|
|
1808 |
if ($likeExpr->stringPattern instanceof AST\InputParameter) { |
|
|
1809 |
$inputParam = $likeExpr->stringPattern; |
|
|
1810 |
$dqlParamKey = $inputParam->name; |
|
|
1811 |
$this->_parserResult->addParameterMapping($dqlParamKey, $this->_sqlParamIndex++); |
|
|
1812 |
$sql .= '?'; |
|
|
1813 |
} else { |
|
|
1814 |
$sql .= $this->_conn->quote($likeExpr->stringPattern); |
|
|
1815 |
} |
|
|
1816 |
|
|
|
1817 |
if ($likeExpr->escapeChar) { |
|
|
1818 |
$sql .= ' ESCAPE ' . $this->_conn->quote($likeExpr->escapeChar); |
|
|
1819 |
} |
|
|
1820 |
|
|
|
1821 |
return $sql; |
|
|
1822 |
} |
|
|
1823 |
|
|
|
1824 |
/** |
|
|
1825 |
* Walks down a StateFieldPathExpression AST node, thereby generating the appropriate SQL. |
|
|
1826 |
* |
|
|
1827 |
* @param StateFieldPathExpression |
|
|
1828 |
* @return string The SQL. |
|
|
1829 |
*/ |
|
|
1830 |
public function walkStateFieldPathExpression($stateFieldPathExpression) |
|
|
1831 |
{ |
|
|
1832 |
return $this->walkPathExpression($stateFieldPathExpression); |
|
|
1833 |
} |
|
|
1834 |
|
|
|
1835 |
/** |
|
|
1836 |
* Walks down a ComparisonExpression AST node, thereby generating the appropriate SQL. |
|
|
1837 |
* |
|
|
1838 |
* @param ComparisonExpression |
|
|
1839 |
* @return string The SQL. |
|
|
1840 |
*/ |
|
|
1841 |
public function walkComparisonExpression($compExpr) |
|
|
1842 |
{ |
|
|
1843 |
$sql = ''; |
|
|
1844 |
$leftExpr = $compExpr->leftExpression; |
|
|
1845 |
$rightExpr = $compExpr->rightExpression; |
|
|
1846 |
|
|
|
1847 |
if ($leftExpr instanceof AST\Node) { |
|
|
1848 |
$sql .= $leftExpr->dispatch($this); |
|
|
1849 |
} else { |
|
|
1850 |
$sql .= is_numeric($leftExpr) ? $leftExpr : $this->_conn->quote($leftExpr); |
|
|
1851 |
} |
|
|
1852 |
|
|
|
1853 |
$sql .= ' ' . $compExpr->operator . ' '; |
|
|
1854 |
|
|
|
1855 |
if ($rightExpr instanceof AST\Node) { |
|
|
1856 |
$sql .= $rightExpr->dispatch($this); |
|
|
1857 |
} else { |
|
|
1858 |
$sql .= is_numeric($rightExpr) ? $rightExpr : $this->_conn->quote($rightExpr); |
|
|
1859 |
} |
|
|
1860 |
|
|
|
1861 |
return $sql; |
|
|
1862 |
} |
|
|
1863 |
|
|
|
1864 |
/** |
|
|
1865 |
* Walks down an InputParameter AST node, thereby generating the appropriate SQL. |
|
|
1866 |
* |
|
|
1867 |
* @param InputParameter |
|
|
1868 |
* @return string The SQL. |
|
|
1869 |
*/ |
|
|
1870 |
public function walkInputParameter($inputParam) |
|
|
1871 |
{ |
|
|
1872 |
$this->_parserResult->addParameterMapping($inputParam->name, $this->_sqlParamIndex++); |
|
|
1873 |
|
|
|
1874 |
return '?'; |
|
|
1875 |
} |
|
|
1876 |
|
|
|
1877 |
/** |
|
|
1878 |
* Walks down an ArithmeticExpression AST node, thereby generating the appropriate SQL. |
|
|
1879 |
* |
|
|
1880 |
* @param ArithmeticExpression |
|
|
1881 |
* @return string The SQL. |
|
|
1882 |
*/ |
|
|
1883 |
public function walkArithmeticExpression($arithmeticExpr) |
|
|
1884 |
{ |
|
|
1885 |
return ($arithmeticExpr->isSimpleArithmeticExpression()) |
|
|
1886 |
? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression) |
|
|
1887 |
: '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')'; |
|
|
1888 |
} |
|
|
1889 |
|
|
|
1890 |
/** |
|
|
1891 |
* Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL. |
|
|
1892 |
* |
|
|
1893 |
* @param SimpleArithmeticExpression |
|
|
1894 |
* @return string The SQL. |
|
|
1895 |
*/ |
|
|
1896 |
public function walkSimpleArithmeticExpression($simpleArithmeticExpr) |
|
|
1897 |
{ |
|
|
1898 |
return ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) |
|
|
1899 |
? $this->walkArithmeticTerm($simpleArithmeticExpr) |
|
|
1900 |
: implode( |
|
|
1901 |
' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms) |
|
|
1902 |
); |
|
|
1903 |
} |
|
|
1904 |
|
|
|
1905 |
/** |
|
|
1906 |
* Walks down an ArithmeticTerm AST node, thereby generating the appropriate SQL. |
|
|
1907 |
* |
|
|
1908 |
* @param mixed |
|
|
1909 |
* @return string The SQL. |
|
|
1910 |
*/ |
|
|
1911 |
public function walkArithmeticTerm($term) |
|
|
1912 |
{ |
|
|
1913 |
if (is_string($term)) { |
|
|
1914 |
return $term; |
|
|
1915 |
} |
|
|
1916 |
|
|
|
1917 |
// Phase 2 AST optimization: Skip processment of ArithmeticTerm |
|
|
1918 |
// if only one ArithmeticFactor is defined |
|
|
1919 |
return ( ! ($term instanceof AST\ArithmeticTerm)) |
|
|
1920 |
? $this->walkArithmeticFactor($term) |
|
|
1921 |
: implode( |
|
|
1922 |
' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors) |
|
|
1923 |
); |
|
|
1924 |
} |
|
|
1925 |
|
|
|
1926 |
/** |
|
|
1927 |
* Walks down an ArithmeticFactor that represents an AST node, thereby generating the appropriate SQL. |
|
|
1928 |
* |
|
|
1929 |
* @param mixed |
|
|
1930 |
* @return string The SQL. |
|
|
1931 |
*/ |
|
|
1932 |
public function walkArithmeticFactor($factor) |
|
|
1933 |
{ |
|
|
1934 |
if (is_string($factor)) { |
|
|
1935 |
return $factor; |
|
|
1936 |
} |
|
|
1937 |
|
|
|
1938 |
// Phase 2 AST optimization: Skip processment of ArithmeticFactor |
|
|
1939 |
// if only one ArithmeticPrimary is defined |
|
|
1940 |
return ( ! ($factor instanceof AST\ArithmeticFactor)) |
|
|
1941 |
? $this->walkArithmeticPrimary($factor) |
|
|
1942 |
: ($factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '')) |
|
|
1943 |
. $this->walkArithmeticPrimary($factor->arithmeticPrimary); |
|
|
1944 |
} |
|
|
1945 |
|
|
|
1946 |
/** |
|
|
1947 |
* Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL. |
|
|
1948 |
* |
|
|
1949 |
* @param mixed |
|
|
1950 |
* @return string The SQL. |
|
|
1951 |
*/ |
|
|
1952 |
public function walkArithmeticPrimary($primary) |
|
|
1953 |
{ |
|
|
1954 |
if ($primary instanceof AST\SimpleArithmeticExpression) { |
|
|
1955 |
return '(' . $this->walkSimpleArithmeticExpression($primary) . ')'; |
|
|
1956 |
} else if ($primary instanceof AST\Node) { |
|
|
1957 |
return $primary->dispatch($this); |
|
|
1958 |
} |
|
|
1959 |
|
|
|
1960 |
// TODO: We need to deal with IdentificationVariable here |
|
|
1961 |
return ''; |
|
|
1962 |
} |
|
|
1963 |
|
|
|
1964 |
/** |
|
|
1965 |
* Walks down a StringPrimary that represents an AST node, thereby generating the appropriate SQL. |
|
|
1966 |
* |
|
|
1967 |
* @param mixed |
|
|
1968 |
* @return string The SQL. |
|
|
1969 |
*/ |
|
|
1970 |
public function walkStringPrimary($stringPrimary) |
|
|
1971 |
{ |
|
|
1972 |
return (is_string($stringPrimary)) |
|
|
1973 |
? $this->_conn->quote($stringPrimary) |
|
|
1974 |
: $stringPrimary->dispatch($this); |
|
|
1975 |
} |
|
|
1976 |
} |