Skip to content

Table aliases might not be resolved when SubselectFromClause is being processed and wrong alias is generated #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions src/Doctrine/MySql/UseIndexHintHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

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;
Expand All @@ -27,7 +25,7 @@ class UseIndexHintHandler extends HintHandler
*/
public function getNodes(): array
{
return [SqlNode::FromClause, SqlNode::SubselectFromClause];
return [SqlNode::SelectStatement, SqlNode::UpdateStatement, SqlNode::DeleteStatement];
}

public function processNode(SqlNode $sqlNode, string $sql): string
Expand All @@ -36,18 +34,6 @@ 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");
Expand Down Expand Up @@ -80,12 +66,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, $wholeSql) === 0) {
if (preg_match($tableWithAliasRegex, $sql) === 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, $wholeSql) !== 1) {
if ($hint->getDqlAlias() === null && preg_match_all($tableWithAliasRegex, $sql) !== 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.");
}

Expand Down
26 changes: 21 additions & 5 deletions tests/Doctrine/MySql/UseIndexSqlWalkerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ 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);
$userSelectWithJoinsDql = sprintf('SELECT u FROM %s u JOIN u.account a JOIN u.managedAccounts ma', User::class);
$userSubselectInWhereDql = sprintf('SELECT u FROM %s u WHERE u.id = (SELECT u2.id FROM %s u2 WHERE u2.id = 1)', User::class, User::class);
$userSubselectInSelectDql = sprintf('SELECT u, (SELECT u2.id FROM %s u2 WHERE u2.id = 1) as uu FROM %s u', User::class, User::class);
$userSubselectInSelectDql2 = sprintf('SELECT (SELECT u2.id FROM %s u2 WHERE u2.id = 1) as uu FROM %s u', User::class, User::class);

yield 'FROM - use single index' => [
$userSelectDql,
Expand Down Expand Up @@ -101,9 +104,6 @@ static function (Query $query): void {
},
'SELECT u0_.id AS id_0, u0_.account_id AS account_id_1 FROM user u0_ IGNORE INDEX (IDX_FOO, IDX_BAR)',
];

$userSelectWithJoinsDql = sprintf('SELECT u FROM %s u JOIN u.account a JOIN u.managedAccounts ma', User::class);

yield 'JOIN - one single use index' => [
$userSelectWithJoinsDql,
static function (Query $query): void {
Expand Down Expand Up @@ -183,13 +183,29 @@ static function (Query $query): void {
];

yield 'FROM in subselect' => [
$userSubselectDql,
$userSubselectInWhereDql,
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 'subselect and hint in main query' => [
$userSubselectInSelectDql,
static function (Query $query): void {
$query->setHint(UseIndexHintHandler::class, [IndexHint::use('IDX_FOO', User::TABLE_NAME, 'u')]);
},
'SELECT u0_.id AS id_0, (SELECT u1_.id FROM user u1_ WHERE u1_.id = 1) AS sclr_1, u0_.account_id AS account_id_2 FROM user u0_ USE INDEX (IDX_FOO)',
];

yield 'subselect and hint in main query 2' => [
$userSubselectInSelectDql2,
static function (Query $query): void {
$query->setHint(UseIndexHintHandler::class, [IndexHint::use('IDX_FOO', User::TABLE_NAME, 'u')]);
},
'SELECT (SELECT u0_.id FROM user u0_ WHERE u0_.id = 1) AS sclr_0 FROM user u1_ USE INDEX (IDX_FOO)',
];

yield 'no hint' => [
$userSelectDql,
static function (Query $query): void {
Expand Down
Loading