From 11756e6bf0e55322966d037f731b7dd9192360c1 Mon Sep 17 00:00:00 2001 From: Nicolas Giraud Date: Mon, 3 Feb 2025 15:51:10 +0100 Subject: [PATCH 1/2] Add handling of index hints on join clauses too. Fixes #593 and #497. --- src/Components/JoinKeyword.php | 27 +++++++++--- tests/Builder/SelectStatementTest.php | 61 +++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/Components/JoinKeyword.php b/src/Components/JoinKeyword.php index 6f964993a..73c51f0a3 100644 --- a/src/Components/JoinKeyword.php +++ b/src/Components/JoinKeyword.php @@ -72,20 +72,29 @@ class JoinKeyword extends Component */ public $using; + /** + * Index hints + * + * @var IndexHint[] + */ + public $indexHints = []; + /** * @see JoinKeyword::$JOINS * - * @param string $type Join type - * @param Expression $expr join expression - * @param Condition[] $on join conditions - * @param ArrayObj $using columns joined + * @param string $type Join type + * @param Expression $expr join expression + * @param Condition[] $on join conditions + * @param ArrayObj $using columns joined + * @param IndexHint[] $indexHints index hints */ - public function __construct($type = null, $expr = null, $on = null, $using = null) + public function __construct($type = null, $expr = null, $on = null, $using = null, $indexHints = []) { $this->type = $type; $this->expr = $expr; $this->on = $on; $this->using = $using; + $this->indexHints = $indexHints; } /** @@ -110,6 +119,7 @@ public static function parse(Parser $parser, TokensList $list, array $options = * * 1 -----------------------[ expr ]----------------------> 2 * + * 2 -------------------[ index_hints ]-------------------> 2 * 2 ------------------------[ ON ]-----------------------> 3 * 2 -----------------------[ USING ]---------------------> 4 * @@ -163,6 +173,12 @@ public static function parse(Parser $parser, TokensList $list, array $options = case 'USING': $state = 4; break; + case 'USE': + case 'IGNORE': + case 'FORCE': + // Adding index hint on the JOIN clause. + $expr->indexHints = IndexHint::parse($parser, $list); + break; default: if (empty(static::$JOINS[$token->keyword])) { /* Next clause is starting */ @@ -210,6 +226,7 @@ public static function build($component, array $options = []) $ret = []; foreach ($component as $c) { $ret[] = array_search($c->type, static::$JOINS) . ' ' . $c->expr + . ($c->indexHints !== [] ? ' ' . IndexHint::build($c->indexHints) : '') . (! empty($c->on) ? ' ON ' . Condition::build($c->on) : '') . (! empty($c->using) diff --git a/tests/Builder/SelectStatementTest.php b/tests/Builder/SelectStatementTest.php index cd9168e62..f90f95b9c 100644 --- a/tests/Builder/SelectStatementTest.php +++ b/tests/Builder/SelectStatementTest.php @@ -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()); + } } From 4e763f1a4b216f188087788389a808d019088639 Mon Sep 17 00:00:00 2001 From: Nicolas Giraud Date: Mon, 3 Feb 2025 16:12:07 +0100 Subject: [PATCH 2/2] Fix QA. --- src/Components/IndexHint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/IndexHint.php b/src/Components/IndexHint.php index 5ff53862c..ea01c1aa0 100644 --- a/src/Components/IndexHint.php +++ b/src/Components/IndexHint.php @@ -70,7 +70,7 @@ public function __construct( * @param TokensList $list the list of tokens that are being parsed * @param array $options parameters for parsing * - * @return IndexHint|Component[] + * @return IndexHint[] */ public static function parse(Parser $parser, TokensList $list, array $options = []) {