Skip to content

Commit 51b3e47

Browse files
authored
Support subselects (#103)
1 parent 232c5a7 commit 51b3e47

File tree

2 files changed

+26
-3
lines changed

2 files changed

+26
-3
lines changed

src/Doctrine/MySql/UseIndexHintHandler.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace ShipMonk\Doctrine\MySql;
44

5+
use Doctrine\ORM\Query;
56
use Doctrine\ORM\Query\AST\SelectStatement;
7+
use Doctrine\ORM\Query\Parser;
68
use LogicException;
79
use ShipMonk\Doctrine\Walker\HintHandler;
810
use ShipMonk\Doctrine\Walker\SqlNode;
@@ -25,7 +27,7 @@ class UseIndexHintHandler extends HintHandler
2527
*/
2628
public function getNodes(): array
2729
{
28-
return [SqlNode::FromClause];
30+
return [SqlNode::FromClause, SqlNode::SubselectFromClause];
2931
}
3032

3133
public function processNode(SqlNode $sqlNode, string $sql): string
@@ -34,6 +36,18 @@ public function processNode(SqlNode $sqlNode, string $sql): string
3436
$sqlWalker = $this->getDoctrineSqlWalker();
3537
$query = $sqlWalker->getQuery();
3638
$platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
39+
$dql = $query->getDQL();
40+
41+
if ($dql === null) {
42+
throw new LogicException('Empty DQL query encountered');
43+
}
44+
45+
$queryCopy = new Query($this->getDoctrineSqlWalker()->getEntityManager());
46+
$queryCopy->setDQL($dql);
47+
$parser = new Parser($queryCopy);
48+
49+
/** @var string $wholeSql */
50+
$wholeSql = $parser->parse()->getSqlExecutor()->getSqlStatements();
3751

3852
if (!is_a($platform, 'Doctrine\DBAL\Platforms\MySqlPlatform')) { // bypass platform MySqlPlatform => MySQLPlatform rename in dbal
3953
throw new LogicException("Only MySQL platform is supported, {$platform->getName()} given");
@@ -66,12 +80,12 @@ public function processNode(SqlNode $sqlNode, string $sql): string
6680
: '\S+'; // doctrine always adds some alias
6781
$tableWithAliasRegex = "{$delimiter}{$tableName}\s+{$tableAlias}{$delimiter}i";
6882

69-
if (preg_match($tableWithAliasRegex, $sql) === 0) {
83+
if (preg_match($tableWithAliasRegex, $wholeSql) === 0) {
7084
$aliasInfo = $hint->getDqlAlias() !== null ? " with DQL alias {$hint->getDqlAlias()}" : '';
7185
throw new LogicException("Invalid hint for index {$hint->getIndexName()}, table {$tableName}{$aliasInfo} is not present in the query.");
7286
}
7387

74-
if ($hint->getDqlAlias() === null && preg_match_all($tableWithAliasRegex, $sql) !== 1) {
88+
if ($hint->getDqlAlias() === null && preg_match_all($tableWithAliasRegex, $wholeSql) !== 1) {
7589
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.");
7690
}
7791

tests/Doctrine/MySql/UseIndexSqlWalkerTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function testWalker(string $dql, callable $configureQueryCallback, ?strin
4848
public static function walksProvider(): iterable
4949
{
5050
$userSelectDql = sprintf('SELECT u FROM %s u', User::class);
51+
$userSubselectDql = sprintf('SELECT u FROM %s u WHERE u.id = (SELECT u2.id FROM %s u2 WHERE u2.id = 1)', User::class, User::class);
5152

5253
yield 'FROM - use single index' => [
5354
$userSelectDql,
@@ -181,6 +182,14 @@ static function (Query $query): void {
181182
. ' INNER JOIN account a2_ ON u0_.id = a2_.manager_id',
182183
];
183184

185+
yield 'FROM in subselect' => [
186+
$userSubselectDql,
187+
static function (Query $query): void {
188+
$query->setHint(UseIndexHintHandler::class, [IndexHint::use('IDX_FOO', User::TABLE_NAME, 'u2')]);
189+
},
190+
'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)',
191+
];
192+
184193
yield 'no hint' => [
185194
$userSelectDql,
186195
static function (Query $query): void {

0 commit comments

Comments
 (0)