From c05f18565331daad951c49d2cab835602e7373e2 Mon Sep 17 00:00:00 2001 From: Daniele Barbaro Date: Mon, 19 Oct 2026 14:38:41 +0200 Subject: [PATCH] feat(migrations): add the typed column indexes for existing installs --- ...d_indexes_to_custom_field_values_table.php | 32 +++++++ docs/installation.md | 11 ++- docs/upgrading.md | 37 +++----- src/CustomFieldsServiceProvider.php | 1 + tests/Feature/IndexMigrationTest.php | 88 +++++++++++++++++++ tests/Feature/ServiceProviderTest.php | 5 +- tests/TestCase.php | 4 +- 7 files changed, 147 insertions(+), 31 deletions(-) create mode 100644 database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php create mode 100644 tests/Feature/IndexMigrationTest.php diff --git a/database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php b/database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php new file mode 100644 index 0000000..bb0387b --- /dev/null +++ b/database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php @@ -0,0 +1,32 @@ + 'value_integer', + 'cfv_field_decimal_index' => 'value_decimal', + 'cfv_field_date_index' => 'value_date', + 'cfv_field_datetime_index' => 'value_datetime', + ]; + + public function up(): void + { + $tableName = config('laravel-custom-fields.tables.values'); + + foreach (self::INDEXES as $name => $column) { + if (Schema::hasIndex($tableName, $name)) { + continue; + } + + Schema::table($tableName, function (Blueprint $table) use ($name, $column): void { + $table->index(['custom_field_id', $column], $name); + }); + } + } +}; diff --git a/docs/installation.md b/docs/installation.md index 7ed0c17..5924edd 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -49,10 +49,13 @@ php artisan vendor:publish --tag="laravel-custom-fields-migrations" php artisan migrate ``` -Two migrations are published, `create_custom_fields_table` and -`create_custom_field_values_table`. They are published one file at a time and receive a -fresh timestamp, so they run after the migrations already in your application. Publish -them after the tables of the models that will own custom fields exist. +Three migrations are published, `create_custom_fields_table`, +`create_custom_field_values_table` and `add_typed_indexes_to_custom_field_values_table`. +The third one is for installs that ran the first two before 0.7.0 and does nothing on a +fresh install, where the values table is created with every index. They are published one +file at a time and receive a fresh timestamp, so they run after the migrations already in +your application. Publish them after the tables of the models that will own custom fields +exist. > [!IMPORTANT] > Set `key_type` and `morph_key_type` in the published configuration **before** you run diff --git a/docs/upgrading.md b/docs/upgrading.md index 83698db..8872700 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -96,33 +96,20 @@ fields run as a subquery on the field and the typed column, and no longer scan e of the field. An application that ran the migration before 0.7.0 does not get them, because a migration -that already ran is not run again. Create a migration of your own with -`php artisan make:migration add_typed_value_indexes` and paste this in. It reads the same -table name the package reads, so it follows a changed `tables.values` configuration. - -```php -index(['custom_field_id', 'value_integer'], 'cfv_field_integer_index'); - $table->index(['custom_field_id', 'value_decimal'], 'cfv_field_decimal_index'); - $table->index(['custom_field_id', 'value_date'], 'cfv_field_date_index'); - $table->index(['custom_field_id', 'value_datetime'], 'cfv_field_datetime_index'); - }); - } -}; +that already ran is not run again. A third migration, +`add_typed_indexes_to_custom_field_values_table`, adds them. Publish it and run it: + +```bash +php artisan vendor:publish --tag="laravel-custom-fields-migrations" +php artisan migrate ``` -A new install needs nothing. If you already added an index on one of these columns -yourself, drop it first or remove its line from the migration above. +Publishing copies only the file your application does not have yet, so the two migrations +you already published are left alone. The migration reads the values table name from the +`tables.values` configuration and creates each index only when it is missing. An index you +added by hand under the same name is skipped, and an index you added under a different name +is left in place, so the column then carries both. A new install needs nothing, the first +migration already creates all six indexes and the third one does nothing. ## Data notes diff --git a/src/CustomFieldsServiceProvider.php b/src/CustomFieldsServiceProvider.php index 8f49219..99e9d5a 100644 --- a/src/CustomFieldsServiceProvider.php +++ b/src/CustomFieldsServiceProvider.php @@ -55,6 +55,7 @@ public function configurePackage(Package $package): void ->hasMigrations([ '2026_01_01_000000_create_custom_fields_table', '2026_01_01_000001_create_custom_field_values_table', + '2026_01_01_000002_add_typed_indexes_to_custom_field_values_table', ]); } diff --git a/tests/Feature/IndexMigrationTest.php b/tests/Feature/IndexMigrationTest.php new file mode 100644 index 0000000..67f698b --- /dev/null +++ b/tests/Feature/IndexMigrationTest.php @@ -0,0 +1,88 @@ +toBeInstanceOf(Migration::class); + + return $migration; +} + +/** @return array */ +function valueIndexNames(): array +{ + $names = array_map( + static fn (array $index): string => strtolower($index['name']), + Schema::getIndexes(config('laravel-custom-fields.tables.values')), + ); + + sort($names); + + return $names; +} + +/** @return array */ +function typedIndexNames(): array +{ + return [ + 'cfv_field_date_index', + 'cfv_field_datetime_index', + 'cfv_field_decimal_index', + 'cfv_field_integer_index', + ]; +} + +it('publishes the index migration under the migrations tag', function (): void { + $published = array_map( + 'basename', + array_keys(ServiceProvider::pathsToPublish(CustomFieldsServiceProvider::class, 'laravel-custom-fields-migrations')), + ); + + expect($published)->toHaveCount(3) + ->and($published[2])->toBe('2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php'); +}); + +it('leaves the six indexes of a fresh install untouched', function (): void { + typedIndexMigration()->up(); + + expect(array_values(array_filter( + valueIndexNames(), + static fn (string $name): bool => str_starts_with($name, 'cfv_field_') && ! str_ends_with($name, '_unique'), + )))->toBe([ + 'cfv_field_boolean_index', + 'cfv_field_date_index', + 'cfv_field_datetime_index', + 'cfv_field_decimal_index', + 'cfv_field_integer_index', + 'cfv_field_string_index', + ]); +}); + +it('recreates the typed indexes an older install lacks', function (): void { + Schema::table(config('laravel-custom-fields.tables.values'), function ($table): void { + foreach (typedIndexNames() as $name) { + $table->dropIndex($name); + } + }); + + expect(valueIndexNames())->not->toContain(...typedIndexNames()); + + typedIndexMigration()->up(); + + expect(valueIndexNames())->toContain(...typedIndexNames()); +}); + +it('can run twice without failing', function (): void { + typedIndexMigration()->up(); + typedIndexMigration()->up(); + + expect(valueIndexNames())->toContain(...typedIndexNames()); +}); diff --git a/tests/Feature/ServiceProviderTest.php b/tests/Feature/ServiceProviderTest.php index 8db84b1..6e0edd3 100644 --- a/tests/Feature/ServiceProviderTest.php +++ b/tests/Feature/ServiceProviderTest.php @@ -79,10 +79,11 @@ function publishedTargets(string $tag): array ->each->toEndWith('lang/vendor/laravel-custom-fields'); }); -it('publishes both migrations under the migrations tag', function (): void { +it('publishes the three migrations under the migrations tag', function (): void { expect(publishedPaths('laravel-custom-fields-migrations'))->toBe([ 'database/migrations/2026_01_01_000000_create_custom_fields_table.php', 'database/migrations/2026_01_01_000001_create_custom_field_values_table.php', + 'database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php', ]); }); @@ -92,6 +93,7 @@ function publishedTargets(string $tag): array 'lang', 'database/migrations/2026_01_01_000000_create_custom_fields_table.php', 'database/migrations/2026_01_01_000001_create_custom_field_values_table.php', + 'database/migrations/2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php', ]); }); @@ -101,6 +103,7 @@ function publishedTargets(string $tag): array expect($files)->toBe([ '2026_01_01_000000_create_custom_fields_table.php', '2026_01_01_000001_create_custom_field_values_table.php', + '2026_01_01_000002_add_typed_indexes_to_custom_field_values_table.php', ]); }); diff --git a/tests/TestCase.php b/tests/TestCase.php index dd63961..afd98ee 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -167,7 +167,9 @@ protected function rebuildPackageTables(): void } foreach (array_reverse($migrations) as $migration) { - $migration->down(); + if (method_exists($migration, 'down')) { + $migration->down(); + } } foreach ($migrations as $migration) {