vendor/doctrine/orm/src/Query/SqlWalker.php line 824

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