vendor/doctrine/orm/lib/Doctrine/ORM/Query/SqlWalker.php line 531

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