From 8f02b3adca0a1b654a24dac46138a846649e83e2 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 17 Jun 2024 11:56:45 +0200 Subject: [PATCH] Support subselects --- src/Doctrine/MySql/UseIndexHintHandler.php | 20 ++++++++++++++++--- .../Doctrine/MySql/UseIndexSqlWalkerTest.php | 9 +++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Doctrine/MySql/UseIndexHintHandler.php b/src/Doctrine/MySql/UseIndexHintHandler.php index b8289ac..c65bc1e 100644 --- a/src/Doctrine/MySql/UseIndexHintHandler.php +++ b/src/Doctrine/MySql/UseIndexHintHandler.php @@ -2,7 +2,9 @@ namespace ShipMonk\Doctrine\MySql; +use Doctrine\ORM\Query; use Doctrine\ORM\Query\AST\SelectStatement; +use Doctrine\ORM\Query\Parser; use LogicException; use ShipMonk\Doctrine\Walker\HintHandler; use ShipMonk\Doctrine\Walker\SqlNode; @@ -25,7 +27,7 @@ class UseIndexHintHandler extends HintHandler */ public function getNodes(): array { - return [SqlNode::FromClause]; + return [SqlNode::FromClause, SqlNode::SubselectFromClause]; } public function processNode(SqlNode $sqlNode, string $sql): string @@ -34,6 +36,18 @@ public function processNode(SqlNode $sqlNode, string $sql): string $sqlWalker = $this->getDoctrineSqlWalker(); $query = $sqlWalker->getQuery(); $platform = $query->getEntityManager()->getConnection()->getDatabasePlatform(); + $dql = $query->getDQL(); + + if ($dql === null) { + throw new LogicException('Empty DQL query encountered'); + } + + $queryCopy = new Query($this->getDoctrineSqlWalker()->getEntityManager()); + $queryCopy->setDQL($dql); + $parser = new Parser($queryCopy); + + /** @var string $wholeSql */ + $wholeSql = $parser->parse()->getSqlExecutor()->getSqlStatements(); if (!is_a($platform, 'Doctrine\DBAL\Platforms\MySqlPlatform')) { // bypass platform MySqlPlatform => MySQLPlatform rename in dbal throw new LogicException("Only MySQL platform is supported, {$platform->getName()} given"); @@ -66,12 +80,12 @@ public function processNode(SqlNode $sqlNode, string $sql): string : '\S+'; // doctrine always adds some alias $tableWithAliasRegex = "{$delimiter}{$tableName}\s+{$tableAlias}{$delimiter}i"; - if (preg_match($tableWithAliasRegex, $sql) === 0) { + if (preg_match($tableWithAliasRegex, $wholeSql) === 0) { $aliasInfo = $hint->getDqlAlias() !== null ? " with DQL alias {$hint->getDqlAlias()}" : ''; throw new LogicException("Invalid hint for index {$hint->getIndexName()}, table {$tableName}{$aliasInfo} is not present in the query."); } - if ($hint->getDqlAlias() === null && preg_match_all($tableWithAliasRegex, $sql) !== 1) { + if ($hint->getDqlAlias() === null && preg_match_all($tableWithAliasRegex, $wholeSql) !== 1) { throw new LogicException("Invalid hint for index {$hint->getIndexName()}, table {$tableName} is present multiple times in the query, please specify DQL alias to apply index on a proper place."); } diff --git a/tests/Doctrine/MySql/UseIndexSqlWalkerTest.php b/tests/Doctrine/MySql/UseIndexSqlWalkerTest.php index 46f3506..bcb8f87 100644 --- a/tests/Doctrine/MySql/UseIndexSqlWalkerTest.php +++ b/tests/Doctrine/MySql/UseIndexSqlWalkerTest.php @@ -48,6 +48,7 @@ public function testWalker(string $dql, callable $configureQueryCallback, ?strin public static function walksProvider(): iterable { $userSelectDql = sprintf('SELECT u FROM %s u', User::class); + $userSubselectDql = sprintf('SELECT u FROM %s u WHERE u.id = (SELECT u2.id FROM %s u2 WHERE u2.id = 1)', User::class, User::class); yield 'FROM - use single index' => [ $userSelectDql, @@ -181,6 +182,14 @@ static function (Query $query): void { . ' INNER JOIN account a2_ ON u0_.id = a2_.manager_id', ]; + yield 'FROM in subselect' => [ + $userSubselectDql, + static function (Query $query): void { + $query->setHint(UseIndexHintHandler::class, [IndexHint::use('IDX_FOO', User::TABLE_NAME, 'u2')]); + }, + 'SELECT u0_.id AS id_0, u0_.account_id AS account_id_1 FROM user u0_ WHERE u0_.id = (SELECT u1_.id FROM user u1_ USE INDEX (IDX_FOO) WHERE u1_.id = 1)', + ]; + yield 'no hint' => [ $userSelectDql, static function (Query $query): void {