Skip to content

Commit 8a9123a

Browse files
Chris Whitetaylorotwell
andauthored
[9.x] Add index hinting support to query builder (#46063)
* Add useIndex(), forceIndex(), ignoreIndex() methods to query builder * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent df863c0 commit 8a9123a

File tree

7 files changed

+190
-0
lines changed

7 files changed

+190
-0
lines changed

src/Illuminate/Database/Query/Builder.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ class Builder implements BuilderContract
100100
*/
101101
public $from;
102102

103+
/**
104+
* The index hint for the query.
105+
*
106+
* @var \Illuminate\Database\Query\IndexHint
107+
*/
108+
public $indexHint;
109+
103110
/**
104111
* The table joins for the query.
105112
*
@@ -458,6 +465,45 @@ public function from($table, $as = null)
458465
return $this;
459466
}
460467

468+
/**
469+
* Add an index hint to suggest a query index.
470+
*
471+
* @param string $index
472+
* @return $this
473+
*/
474+
public function useIndex($index)
475+
{
476+
$this->indexHint = new IndexHint('hint', $index);
477+
478+
return $this;
479+
}
480+
481+
/**
482+
* Add an index hint to force a query index.
483+
*
484+
* @param string $index
485+
* @return $this
486+
*/
487+
public function forceIndex($index)
488+
{
489+
$this->indexHint = new IndexHint('force', $index);
490+
491+
return $this;
492+
}
493+
494+
/**
495+
* Add an index hint to ignore a query index.
496+
*
497+
* @param string $index
498+
* @return $this
499+
*/
500+
public function ignoreIndex($index)
501+
{
502+
$this->indexHint = new IndexHint('ignore', $index);
503+
504+
return $this;
505+
}
506+
461507
/**
462508
* Add a join clause to the query.
463509
*

src/Illuminate/Database/Query/Grammars/Grammar.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Grammar extends BaseGrammar
3636
'aggregate',
3737
'columns',
3838
'from',
39+
'indexHint',
3940
'joins',
4041
'wheres',
4142
'groups',

src/Illuminate/Database/Query/Grammars/MySqlGrammar.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Database\Query\Grammars;
44

55
use Illuminate\Database\Query\Builder;
6+
use Illuminate\Database\Query\IndexHint;
67
use Illuminate\Support\Str;
78

89
class MySqlGrammar extends Grammar
@@ -74,6 +75,22 @@ public function whereFullText(Builder $query, $where)
7475
return "match ({$columns}) against (".$value."{$mode}{$expanded})";
7576
}
7677

78+
/**
79+
* Compile the index hints for the query.
80+
*
81+
* @param \Illuminate\Database\Query\Builder $query
82+
* @param \Illuminate\Database\Query\IndexHint $indexHint
83+
* @return string
84+
*/
85+
protected function compileIndexHint(Builder $query, $indexHint)
86+
{
87+
return match ($indexHint->type) {
88+
'hint' => "use index ({$indexHint->index})",
89+
'force' => "force index ({$indexHint->index})",
90+
default => "ignore index ({$indexHint->index})",
91+
};
92+
}
93+
7794
/**
7895
* Compile an insert ignore statement into SQL.
7996
*

src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Database\Query\Grammars;
44

55
use Illuminate\Database\Query\Builder;
6+
use Illuminate\Database\Query\IndexHint;
67
use Illuminate\Support\Arr;
78
use Illuminate\Support\Str;
89

@@ -117,6 +118,20 @@ protected function dateBasedWhere($type, Builder $query, $where)
117118
return "strftime('{$type}', {$this->wrap($where['column'])}) {$where['operator']} cast({$value} as text)";
118119
}
119120

121+
/**
122+
* Compile the index hints for the query.
123+
*
124+
* @param \Illuminate\Database\Query\Builder $query
125+
* @param \Illuminate\Database\Query\IndexHint $indexHint
126+
* @return string
127+
*/
128+
protected function compileIndexHint(Builder $query, $indexHint)
129+
{
130+
return $indexHint->type === 'force'
131+
? "indexed by {$indexHint->index}"
132+
: '';
133+
}
134+
120135
/**
121136
* Compile a "JSON length" statement into SQL.
122137
*

src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Database\Query\Grammars;
44

55
use Illuminate\Database\Query\Builder;
6+
use Illuminate\Database\Query\IndexHint;
67
use Illuminate\Support\Arr;
78
use Illuminate\Support\Str;
89

@@ -96,6 +97,20 @@ protected function compileFrom(Builder $query, $table)
9697
return $from;
9798
}
9899

100+
/**
101+
* Compile the index hints for the query.
102+
*
103+
* @param \Illuminate\Database\Query\Builder $query
104+
* @param \Illuminate\Database\Query\IndexHint $indexHint
105+
* @return string
106+
*/
107+
protected function compileIndexHint(Builder $query, $indexHint)
108+
{
109+
return $indexHint->type === 'force'
110+
? "with (index({$indexHint->index}))"
111+
: '';
112+
}
113+
99114
/**
100115
* {@inheritdoc}
101116
*
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Query;
4+
5+
class IndexHint
6+
{
7+
/**
8+
* The type of query hint.
9+
*
10+
* @var string
11+
*/
12+
public $type;
13+
14+
/**
15+
* The name of the index.
16+
*
17+
* @var string
18+
*/
19+
public $index;
20+
21+
/**
22+
* Create a new index hint instance.
23+
*
24+
* @param string $type
25+
* @param string $index
26+
* @return void
27+
*/
28+
public function __construct($type, $index)
29+
{
30+
$this->type = $type;
31+
$this->index = $index;
32+
}
33+
}

tests/Database/DatabaseQueryBuilderTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5418,6 +5418,69 @@ public function testFromQuestionMarkOperatorOnPostgres()
54185418
$this->assertSame('select * from "users" where "roles" ??& ?', $builder->toSql());
54195419
}
54205420

5421+
public function testUseIndexMySql()
5422+
{
5423+
$builder = $this->getMySqlBuilder();
5424+
$builder->select('foo')->from('users')->useIndex('test_index');
5425+
$this->assertSame('select `foo` from `users` use index (test_index)', $builder->toSql());
5426+
}
5427+
5428+
public function testForceIndexMySql()
5429+
{
5430+
$builder = $this->getMySqlBuilder();
5431+
$builder->select('foo')->from('users')->forceIndex('test_index');
5432+
$this->assertSame('select `foo` from `users` force index (test_index)', $builder->toSql());
5433+
}
5434+
5435+
public function testIgnoreIndexMySql()
5436+
{
5437+
$builder = $this->getMySqlBuilder();
5438+
$builder->select('foo')->from('users')->ignoreIndex('test_index');
5439+
$this->assertSame('select `foo` from `users` ignore index (test_index)', $builder->toSql());
5440+
}
5441+
5442+
public function testUseIndexSqlite()
5443+
{
5444+
$builder = $this->getSQLiteBuilder();
5445+
$builder->select('foo')->from('users')->useIndex('test_index');
5446+
$this->assertSame('select "foo" from "users"', $builder->toSql());
5447+
}
5448+
5449+
public function testForceIndexSqlite()
5450+
{
5451+
$builder = $this->getSQLiteBuilder();
5452+
$builder->select('foo')->from('users')->forceIndex('test_index');
5453+
$this->assertSame('select "foo" from "users" indexed by test_index', $builder->toSql());
5454+
}
5455+
5456+
public function testIgnoreIndexSqlite()
5457+
{
5458+
$builder = $this->getSQLiteBuilder();
5459+
$builder->select('foo')->from('users')->ignoreIndex('test_index');
5460+
$this->assertSame('select "foo" from "users"', $builder->toSql());
5461+
}
5462+
5463+
public function testUseIndexSqlServer()
5464+
{
5465+
$builder = $this->getSqlServerBuilder();
5466+
$builder->select('foo')->from('users')->useIndex('test_index');
5467+
$this->assertSame('select [foo] from [users]', $builder->toSql());
5468+
}
5469+
5470+
public function testForceIndexSqlServer()
5471+
{
5472+
$builder = $this->getSqlServerBuilder();
5473+
$builder->select('foo')->from('users')->forceIndex('test_index');
5474+
$this->assertSame('select [foo] from [users] with (index(test_index))', $builder->toSql());
5475+
}
5476+
5477+
public function testIgnoreIndexSqlServer()
5478+
{
5479+
$builder = $this->getSqlServerBuilder();
5480+
$builder->select('foo')->from('users')->ignoreIndex('test_index');
5481+
$this->assertSame('select [foo] from [users]', $builder->toSql());
5482+
}
5483+
54215484
public function testClone()
54225485
{
54235486
$builder = $this->getBuilder();

0 commit comments

Comments
 (0)