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