Skip to content

Commit 6d04cee

Browse files
committed
index ifNotExists() modifier (references #82)
1 parent 2ab4968 commit 6d04cee

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ composer require tpetry/laravel-postgresql-enhanced
3131
- [Nulls Not Distinct](#nulls-not-distinct)
3232
- [Partial Indexes](#partial-indexes)
3333
- [Include Columns](#include-columns)
34+
- [If Not Exists](#if-not-exists)
3435
- [Storage Parameters](#storage-parameters-index)
3536
- [Functional Indexes / Column Options](#functional-indexes--column-options)
3637
- [Fulltext Indexes](#fulltext-indexes)
@@ -452,6 +453,27 @@ Schema::table('users', function(Blueprint $table) {
452453
```
453454
Columns are included in an index with the `include` method on an index created by `index()`, `spatialIndex` or `uniqueIndex`.
454455

456+
#### If Not Exists
457+
458+
Sometimes, you fix performance issues by testing new indexes on the production database rather than pushing a new migration each time.
459+
But when you find the perfect one, you should also make a migration for it.
460+
Dropping the existing index to recreate it by the migration is silly.
461+
Right?
462+
You can now skip the index creation from the migration when the exact index already exists.
463+
464+
```php
465+
use Tpetry\PostgresqlEnhanced\Schema\Blueprint;
466+
use Tpetry\PostgresqlEnhanced\Support\Facades\Schema;
467+
468+
Schema::table('invoices', function(Blueprint $table) {
469+
$table->index(['target', 'division', 'date'])->ifNotExists();
470+
});
471+
```
472+
473+
> [!TIP]
474+
> Indexes are determined to be identical by their (automatically-generated) name.
475+
> Either create the index statement to run in production by temporary migrations from your development machine or use specific index names.
476+
455477
#### Storage Parameters (Index)
456478

457479
In some cases you want to specify the storage parameters of an index. If you are using gin indexes you should read the article [Debugging random slow writes in PostgreSQL](https://iamsafts.com/posts/postgres-gin-performance/) why storage parameters for a gin index are important:

src/Schema/Grammars/GrammarIndex.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ private function genericCompileCreateIndex(Blueprint $blueprint, Fluent $command
167167

168168
$index = [
169169
$unique ? 'create unique index' : 'create index',
170+
$command['ifNotExists'] ? 'if not exists' : '',
170171
$this->wrap($command['index']),
171172
'on',
172173
$this->wrapTable($blueprint),

src/Schema/IndexDefinition.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
*/
1212
class IndexDefinition extends BaseIndexDefinition
1313
{
14+
/**
15+
* Only create index if it does not exist yet (PostgreSQL).
16+
*/
17+
public function ifNotExists(): self
18+
{
19+
return $this;
20+
}
21+
1422
/**
1523
* Include non-key columns in the index (PostgreSQL).
1624
*

tests/Migration/IndexOptionsTest.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,135 @@
1212

1313
class IndexOptionsTest extends TestCase
1414
{
15+
public function testIfNotExistsFulltextByColumn(): void
16+
{
17+
if (Comparator::lessThan($this->app->version(), '8.74.0')) {
18+
$this->markTestSkipped('Fulltext indexes have been added in a later Laraverl version.');
19+
}
20+
21+
Schema::create('test_806712', function (Blueprint $table): void {
22+
$table->string('col_274742');
23+
});
24+
$queries = $this->withQueryLog(function (): void {
25+
Schema::table('test_806712', function (Blueprint $table): void {
26+
$table->fullText(['col_274742'])->ifNotExists();
27+
});
28+
});
29+
$this->assertEquals(['create index if not exists "test_806712_col_274742_fulltext" on "test_806712" using gin ((to_tsvector(\'english\', "col_274742")))'], array_column($queries, 'query'));
30+
}
31+
32+
public function testIfNotExistsFulltextByName(): void
33+
{
34+
if (Comparator::lessThan($this->app->version(), '8.74.0')) {
35+
$this->markTestSkipped('Fulltext indexes have been added in a later Laraverl version.');
36+
}
37+
38+
Schema::create('test_940928', function (Blueprint $table): void {
39+
$table->string('col_563370');
40+
});
41+
$queries = $this->withQueryLog(function (): void {
42+
Schema::table('test_940928', function (Blueprint $table): void {
43+
$table->fullText(['col_563370'], 'index_726055')->ifNotExists();
44+
});
45+
});
46+
$this->assertEquals(['create index if not exists "index_726055" on "test_940928" using gin ((to_tsvector(\'english\', "col_563370")))'], array_column($queries, 'query'));
47+
}
48+
49+
public function testIfNotExistsIndexByColumn(): void
50+
{
51+
Schema::create('test_233704', function (Blueprint $table): void {
52+
$table->string('col_484213');
53+
});
54+
$queries = $this->withQueryLog(function (): void {
55+
Schema::table('test_233704', function (Blueprint $table): void {
56+
$table->index(['col_484213'])->ifNotExists();
57+
});
58+
});
59+
$this->assertEquals(['create index if not exists "test_233704_col_484213_index" on "test_233704" ("col_484213")'], array_column($queries, 'query'));
60+
}
61+
62+
public function testIfNotExistsIndexByName(): void
63+
{
64+
Schema::create('test_101004', function (Blueprint $table): void {
65+
$table->string('col_749919');
66+
});
67+
$queries = $this->withQueryLog(function (): void {
68+
Schema::table('test_101004', function (Blueprint $table): void {
69+
$table->index(['col_749919'], 'index_605253')->ifNotExists();
70+
});
71+
});
72+
$this->assertEquals(['create index if not exists "index_605253" on "test_101004" ("col_749919")'], array_column($queries, 'query'));
73+
}
74+
75+
public function testIfNotExistsRawIndex(): void
76+
{
77+
if (Comparator::lessThan($this->app->version(), '7.7.0')) {
78+
$this->markTestSkipped('Raw indexes have been added in a later Laravel version.');
79+
}
80+
81+
Schema::create('test_302103', function (Blueprint $table): void {
82+
$table->string('col_650398');
83+
});
84+
$queries = $this->withQueryLog(function (): void {
85+
Schema::table('test_302103', function (Blueprint $table): void {
86+
$table->rawIndex('col_650398', 'idx_728687')->ifNotExists();
87+
});
88+
});
89+
$this->assertEquals(['create index if not exists "idx_728687" on "test_302103" (col_650398)'], array_column($queries, 'query'));
90+
}
91+
92+
public function testIfNotExistsSpatialIndexByColumn(): void
93+
{
94+
Schema::create('test_508190', function (Blueprint $table): void {
95+
$table->integerRange('col_402778');
96+
});
97+
$queries = $this->withQueryLog(function (): void {
98+
Schema::table('test_508190', function (Blueprint $table): void {
99+
$table->spatialIndex(['col_402778'])->ifNotExists();
100+
});
101+
});
102+
$this->assertEquals(['create index if not exists "test_508190_col_402778_spatialindex" on "test_508190" using gist ("col_402778")'], array_column($queries, 'query'));
103+
}
104+
105+
public function testIfNotExistsSpatialIndexByName(): void
106+
{
107+
Schema::create('test_521621', function (Blueprint $table): void {
108+
$table->integerRange('col_990392');
109+
});
110+
$queries = $this->withQueryLog(function (): void {
111+
Schema::table('test_521621', function (Blueprint $table): void {
112+
$table->spatialIndex(['col_990392'], 'index_124848')->ifNotExists();
113+
});
114+
});
115+
$this->assertEquals(['create index if not exists "index_124848" on "test_521621" using gist ("col_990392")'], array_column($queries, 'query'));
116+
}
117+
118+
public function testIfNotExistsUniqueIndexByColumn(): void
119+
{
120+
Schema::create('test_868599', function (Blueprint $table): void {
121+
$table->string('col_571721');
122+
});
123+
$queries = $this->withQueryLog(function (): void {
124+
Schema::table('test_868599', function (Blueprint $table): void {
125+
$table->uniqueIndex(['col_571721'])->ifNotExists();
126+
});
127+
});
128+
$this->assertEquals(['create unique index if not exists "test_868599_col_571721_unique" on "test_868599" ("col_571721")'], array_column($queries, 'query'));
129+
}
130+
131+
public function testIfNotExistsUniqueIndexByName(): void
132+
{
133+
Schema::create('test_324400', function (Blueprint $table): void {
134+
$table->string('col_208151');
135+
});
136+
$queries = $this->withQueryLog(function (): void {
137+
Schema::table('test_324400', function (Blueprint $table): void {
138+
$table->uniqueIndex(['col_208151'], 'index_810276')->ifNotExists();
139+
});
140+
});
141+
$this->assertEquals(['create unique index if not exists "index_810276" on "test_324400" ("col_208151")'], array_column($queries, 'query'));
142+
}
143+
15144
public function testIncludeIndexByColumn(): void
16145
{
17146
Schema::create('test_130163', function (Blueprint $table): void {

0 commit comments

Comments
 (0)