Skip to content

Commit d4e2ea2

Browse files
committed
Add handling of index hints on join clauses too. Fixes phpmyadmin#593 and phpmyadmin#497.
1 parent 25ad961 commit d4e2ea2

File tree

3 files changed

+102
-4
lines changed

3 files changed

+102
-4
lines changed

src/Components/JoinKeyword.php

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PhpMyAdmin\SqlParser\Component;
88
use PhpMyAdmin\SqlParser\Parsers\Conditions;
9+
use PhpMyAdmin\SqlParser\Parsers\IndexHints;
910

1011
use function array_search;
1112

@@ -59,29 +60,40 @@ final class JoinKeyword implements Component
5960
*/
6061
public ArrayObj|null $using = null;
6162

63+
/**
64+
* Index hints
65+
*
66+
* @var IndexHint[]
67+
*/
68+
public array $indexHints = [];
69+
6270
/**
6371
* @see JoinKeyword::JOINS
6472
*
65-
* @param string|null $type Join type
66-
* @param Expression|null $expr join expression
67-
* @param Condition[]|null $on join conditions
68-
* @param ArrayObj|null $using columns joined
73+
* @param string|null $type Join type
74+
* @param Expression|null $expr join expression
75+
* @param Condition[]|null $on join conditions
76+
* @param ArrayObj|null $using columns joined
77+
* @param IndexHint[] $indexHints index hints
6978
*/
7079
public function __construct(
7180
string|null $type = null,
7281
Expression|null $expr = null,
7382
array|null $on = null,
7483
ArrayObj|null $using = null,
84+
array $indexHints = [],
7585
) {
7686
$this->type = $type;
7787
$this->expr = $expr;
7888
$this->on = $on;
7989
$this->using = $using;
90+
$this->indexHints = $indexHints;
8091
}
8192

8293
public function build(): string
8394
{
8495
return array_search($this->type, self::JOINS) . ' ' . $this->expr
96+
. ($this->indexHints !== [] ? ' ' . IndexHints::buildAll($this->indexHints) : '')
8597
. (! empty($this->on) ? ' ON ' . Conditions::buildAll($this->on) : '')
8698
. (! empty($this->using) ? ' USING ' . $this->using->build() : '');
8799
}

src/Parsers/JoinKeywords.php

+25
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public static function parse(Parser $parser, TokensList $list, array $options =
3131
$expr = new JoinKeyword();
3232

3333
/**
34+
* TODO: OLD
3435
* The state of the parser.
3536
*
3637
* Below are the states of the parser.
@@ -46,6 +47,24 @@ public static function parse(Parser $parser, TokensList $list, array $options =
4647
*
4748
* 4 ----------------------[ columns ]--------------------> 0
4849
*/
50+
/**
51+
* TODO: NEW
52+
* The state of the parser.
53+
*
54+
* Below are the states of the parser.
55+
*
56+
* 0 -----------------------[ JOIN ]----------------------> 1
57+
*
58+
* 1 -----------------------[ expr ]----------------------> 2
59+
*
60+
* 2 -------------------[ index_hints ]-------------------> 2
61+
* 2 ------------------------[ ON ]-----------------------> 3
62+
* 2 -----------------------[ USING ]---------------------> 4
63+
*
64+
* 3 --------------------[ conditions ]-------------------> 0
65+
*
66+
* 4 ----------------------[ columns ]--------------------> 0
67+
*/
4968
$state = 0;
5069

5170
// By design, the parser will parse first token after the keyword.
@@ -90,6 +109,12 @@ public static function parse(Parser $parser, TokensList $list, array $options =
90109
case 'USING':
91110
$state = 4;
92111
break;
112+
case 'USE':
113+
case 'IGNORE':
114+
case 'FORCE':
115+
// Adding index hint on the JOIN clause.
116+
$expr->indexHints = IndexHints::parse($parser, $list);
117+
break;
93118
default:
94119
if (empty(JoinKeyword::JOINS[$token->keyword])) {
95120
/* Next clause is starting */

tests/Builder/SelectStatementTest.php

+61
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,65 @@ public function testBuilderSurroundedByParanthesisWithLimit(): void
353353
$stmt->build(),
354354
);
355355
}
356+
357+
public function testBuilderSelectFromWithForceIndex(): void
358+
{
359+
$query = 'SELECT *'
360+
. ' FROM uno FORCE INDEX (id)';
361+
$parser = new Parser($query);
362+
$stmt = $parser->statements[0];
363+
364+
self::assertSame($query, $stmt->build());
365+
}
366+
367+
/**
368+
* Ensures issue #497 is fixed.
369+
*/
370+
public function testBuilderSelectFromJoinWithForceIndex(): void
371+
{
372+
$query = 'SELECT *'
373+
. ' FROM uno'
374+
. ' JOIN dos FORCE INDEX (two_id) ON dos.id = uno.id';
375+
$parser = new Parser($query);
376+
$stmt = $parser->statements[0];
377+
378+
self::assertSame($query, $stmt->build());
379+
}
380+
381+
/**
382+
* Ensures issue #593 is fixed.
383+
*/
384+
public function testBuilderSelectFromInnerJoinWithForceIndex(): void
385+
{
386+
$query = 'SELECT a.id, a.name, b.order_id, b.total'
387+
. ' FROM customers a'
388+
. ' INNER JOIN orders b FORCE INDEX (idx_customer_id)'
389+
. ' ON a.id = b.customer_id'
390+
. " WHERE a.status = 'active'";
391+
392+
$parser = new Parser($query);
393+
$stmt = $parser->statements[0];
394+
395+
$expectedQuery = 'SELECT a.id, a.name, b.order_id, b.total'
396+
. ' FROM customers AS `a`'
397+
. ' INNER JOIN orders AS `b` FORCE INDEX (idx_customer_id)'
398+
. ' ON a.id = b.customer_id'
399+
. " WHERE a.status = 'active'";
400+
401+
self::assertSame($expectedQuery, $stmt->build());
402+
}
403+
404+
public function testBuilderSelectAllFormsOfIndexHints(): void
405+
{
406+
$query = 'SELECT *'
407+
. ' FROM one USE INDEX (col1) IGNORE INDEX (col1, col2) FORCE INDEX (col1, col2, col3)'
408+
. ' INNER JOIN two USE INDEX (col3) IGNORE INDEX (col2, col3) FORCE INDEX (col1, col2, col3)'
409+
. ' ON one.col1 = two.col2'
410+
. ' WHERE 1 = 1';
411+
412+
$parser = new Parser($query);
413+
$stmt = $parser->statements[0];
414+
415+
self::assertSame($query, $stmt->build());
416+
}
356417
}

0 commit comments

Comments
 (0)