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

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