Skip to content

Commit

Permalink
Add handling of index hints on join clauses too. Fixes #593 and #497.
Browse files Browse the repository at this point in the history
  • Loading branch information
niconoe- committed Feb 3, 2025
1 parent 25ad961 commit d4e2ea2
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 4 deletions.
20 changes: 16 additions & 4 deletions src/Components/JoinKeyword.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PhpMyAdmin\SqlParser\Component;
use PhpMyAdmin\SqlParser\Parsers\Conditions;
use PhpMyAdmin\SqlParser\Parsers\IndexHints;

use function array_search;

Expand Down Expand Up @@ -59,29 +60,40 @@ final class JoinKeyword implements Component
*/
public ArrayObj|null $using = null;

/**
* Index hints
*
* @var IndexHint[]
*/
public array $indexHints = [];

/**
* @see JoinKeyword::JOINS
*
* @param string|null $type Join type
* @param Expression|null $expr join expression
* @param Condition[]|null $on join conditions
* @param ArrayObj|null $using columns joined
* @param string|null $type Join type
* @param Expression|null $expr join expression
* @param Condition[]|null $on join conditions
* @param ArrayObj|null $using columns joined
* @param IndexHint[] $indexHints index hints
*/
public function __construct(
string|null $type = null,
Expression|null $expr = null,
array|null $on = null,
ArrayObj|null $using = null,
array $indexHints = [],
) {
$this->type = $type;
$this->expr = $expr;
$this->on = $on;
$this->using = $using;
$this->indexHints = $indexHints;
}

public function build(): string
{
return array_search($this->type, self::JOINS) . ' ' . $this->expr

Check warning on line 95 in src/Components/JoinKeyword.php

View workflow job for this annotation

GitHub Actions / Mutation tests with PHP 8.2

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ } public function build() : string { - return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? ' USING ' . $this->using->build() : ''); + return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->using) ? ' USING ' . $this->using->build() : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : ''); } public function __toString() : string {

Check warning on line 95 in src/Components/JoinKeyword.php

View workflow job for this annotation

GitHub Actions / Mutation tests with PHP 8.2

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ } public function build() : string { - return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? ' USING ' . $this->using->build() : ''); + return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : ''); } public function __toString() : string {
. ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '')
. (! empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '')
. (! empty($this->using) ? ' USING ' . $this->using->build() : '');

Check warning on line 98 in src/Components/JoinKeyword.php

View workflow job for this annotation

GitHub Actions / Mutation tests with PHP 8.2

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ } public function build() : string { - return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? ' USING ' . $this->using->build() : ''); + return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? $this->using->build() . ' USING ' : ''); } public function __toString() : string {

Check warning on line 98 in src/Components/JoinKeyword.php

View workflow job for this annotation

GitHub Actions / Mutation tests with PHP 8.2

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ } public function build() : string { - return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? ' USING ' . $this->using->build() : ''); + return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? $this->using->build() : ''); } public function __toString() : string {

Check warning on line 98 in src/Components/JoinKeyword.php

View workflow job for this annotation

GitHub Actions / Mutation tests with PHP 8.2

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ } public function build() : string { - return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? ' USING ' . $this->using->build() : ''); + return array_search($this->type, self::JOINS) . ' ' . $this->expr . ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '') . (!empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '') . (!empty($this->using) ? ' USING ' : ''); } public function __toString() : string {
}
Expand Down
25 changes: 25 additions & 0 deletions src/Parsers/JoinKeywords.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public static function parse(Parser $parser, TokensList $list, array $options =
$expr = new JoinKeyword();

/**
* TODO: OLD
* The state of the parser.
*
* Below are the states of the parser.
Expand All @@ -46,6 +47,24 @@ public static function parse(Parser $parser, TokensList $list, array $options =
*
* 4 ----------------------[ columns ]--------------------> 0
*/
/**
* TODO: NEW
* The state of the parser.
*
* Below are the states of the parser.
*
* 0 -----------------------[ JOIN ]----------------------> 1
*
* 1 -----------------------[ expr ]----------------------> 2
*
* 2 -------------------[ index_hints ]-------------------> 2
* 2 ------------------------[ ON ]-----------------------> 3
* 2 -----------------------[ USING ]---------------------> 4
*
* 3 --------------------[ conditions ]-------------------> 0
*
* 4 ----------------------[ columns ]--------------------> 0
*/
$state = 0;

// By design, the parser will parse first token after the keyword.
Expand Down Expand Up @@ -90,6 +109,12 @@ public static function parse(Parser $parser, TokensList $list, array $options =
case 'USING':
$state = 4;

Check warning on line 110 in src/Parsers/JoinKeywords.php

View workflow job for this annotation

GitHub Actions / Mutation tests with PHP 8.2

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ $state = 3; break; case 'USING': - $state = 4; + $state = 5; break; case 'USE': case 'IGNORE':
break;
case 'USE':
case 'IGNORE':
case 'FORCE':
// Adding index hint on the JOIN clause.
$expr->indexHints = IndexHints::parse($parser, $list);
break;
default:
if (empty(JoinKeyword::JOINS[$token->keyword])) {
/* Next clause is starting */
Expand Down
61 changes: 61 additions & 0 deletions tests/Builder/SelectStatementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -353,4 +353,65 @@ public function testBuilderSurroundedByParanthesisWithLimit(): void
$stmt->build(),
);
}

public function testBuilderSelectFromWithForceIndex(): void
{
$query = 'SELECT *'
. ' FROM uno FORCE INDEX (id)';
$parser = new Parser($query);
$stmt = $parser->statements[0];

self::assertSame($query, $stmt->build());
}

/**
* Ensures issue #497 is fixed.
*/
public function testBuilderSelectFromJoinWithForceIndex(): void
{
$query = 'SELECT *'
. ' FROM uno'
. ' JOIN dos FORCE INDEX (two_id) ON dos.id = uno.id';
$parser = new Parser($query);
$stmt = $parser->statements[0];

self::assertSame($query, $stmt->build());
}

/**
* Ensures issue #593 is fixed.
*/
public function testBuilderSelectFromInnerJoinWithForceIndex(): void
{
$query = 'SELECT a.id, a.name, b.order_id, b.total'
. ' FROM customers a'
. ' INNER JOIN orders b FORCE INDEX (idx_customer_id)'
. ' ON a.id = b.customer_id'
. " WHERE a.status = 'active'";

$parser = new Parser($query);
$stmt = $parser->statements[0];

$expectedQuery = 'SELECT a.id, a.name, b.order_id, b.total'
. ' FROM customers AS `a`'
. ' INNER JOIN orders AS `b` FORCE INDEX (idx_customer_id)'
. ' ON a.id = b.customer_id'
. " WHERE a.status = 'active'";

self::assertSame($expectedQuery, $stmt->build());
}

public function testBuilderSelectAllFormsOfIndexHints(): void
{
$query = 'SELECT *'
. ' FROM one USE INDEX (col1) IGNORE INDEX (col1, col2) FORCE INDEX (col1, col2, col3)'
. ' INNER JOIN two USE INDEX (col3) IGNORE INDEX (col2, col3) FORCE INDEX (col1, col2, col3)'
. ' ON one.col1 = two.col2'
. ' WHERE 1 = 1';

$parser = new Parser($query);
$stmt = $parser->statements[0];

self::assertSame($query, $stmt->build());
}
}

0 comments on commit d4e2ea2

Please sign in to comment.