From a0d0655cdfe367327415849ba8240fdcf3165705 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Fri, 28 Aug 2020 12:26:14 -0400 Subject: [PATCH] Add support for generating indexes (#351) --- src/Builder.php | 5 +- src/Generators/MigrationGenerator.php | 12 +++- src/Lexers/ModelLexer.php | 8 +++ src/Models/Index.php | 25 +++++++++ src/Models/Model.php | 11 ++++ .../Generator/MigrationGeneratorTest.php | 5 +- tests/Unit/BuilderTest.php | 56 ++++++++++++++++++- tests/fixtures/drafts/indexes.yaml | 16 ++++++ tests/fixtures/migrations/indexes.php | 43 ++++++++++++++ 9 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 src/Models/Index.php create mode 100644 tests/fixtures/drafts/indexes.yaml create mode 100644 tests/fixtures/migrations/indexes.php diff --git a/src/Builder.php b/src/Builder.php index 4a1739c0..b73aa708 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -13,7 +13,10 @@ public function execute(Blueprint $blueprint, Filesystem $files, string $draft, $cache = $blueprint->parse($files->get('.blueprint')); } - $tokens = $blueprint->parse($files->get($draft)); + $contents = $files->get($draft); + $using_indexes = preg_match('/^\s+indexes:\R/m', $contents) !== 1; + + $tokens = $blueprint->parse($contents, $using_indexes); $tokens['cache'] = $cache['models'] ?? []; $registry = $blueprint->analyze($tokens); diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php index f93ac779..52d7d35d 100644 --- a/src/Generators/MigrationGenerator.php +++ b/src/Generators/MigrationGenerator.php @@ -200,6 +200,16 @@ protected function buildDefinition(Model $model) $definition .= self::INDENT.sprintf('$table->string(\'%s\');', Str::lower($model->morphTo().'_type')).PHP_EOL; } + foreach ($model->indexes() as $index) { + $index_definition = self::INDENT; + $index_definition .= '$table->'.$index->type(); + if (count($index->columns()) > 1) { + $index_definition .= "(['".implode("', '", $index->columns())."']);".PHP_EOL; + } else { + $index_definition .= "('{$index->columns()[0]}');".PHP_EOL; + } + $definition .= $index_definition; + } if ($model->usesTimestamps()) { $definition .= self::INDENT.'$table->'.$model->timestampsDataType().'();'.PHP_EOL; } @@ -292,7 +302,7 @@ protected function getTablePath($tableName, Carbon $timestamp, $overwrite = fals { $dir = 'database/migrations/'; $name = '_create_'.$tableName.'_table.php'; - + $file = $overwrite ? collect($this->files->files($dir))->first(function ($file) use ($tableName) { return str_contains($file, $tableName); }) : false; diff --git a/src/Lexers/ModelLexer.php b/src/Lexers/ModelLexer.php index c1302fcc..8701f734 100644 --- a/src/Lexers/ModelLexer.php +++ b/src/Lexers/ModelLexer.php @@ -4,6 +4,7 @@ use Blueprint\Contracts\Lexer; use Blueprint\Models\Column; +use Blueprint\Models\Index; use Blueprint\Models\Model; use Illuminate\Support\Str; @@ -166,6 +167,13 @@ private function buildModel(string $name, array $columns) unset($columns['relationships']); } + if (isset($columns['indexes'])) { + foreach ($columns['indexes'] as $index) { + $model->addIndex(new Index(key($index), array_map('trim', explode(',', current($index))))); + } + unset($columns['indexes']); + } + if (!isset($columns['id']) && $model->usesPrimaryKey()) { $column = $this->buildColumn('id', 'id'); $model->addColumn($column); diff --git a/src/Models/Index.php b/src/Models/Index.php new file mode 100644 index 00000000..07d6d0e5 --- /dev/null +++ b/src/Models/Index.php @@ -0,0 +1,25 @@ +type = $type; + $this->columns = $columns; + } + + public function type() + { + return $this->type; + } + + public function columns() + { + return $this->columns; + } +} diff --git a/src/Models/Model.php b/src/Models/Model.php index d40f3210..36936289 100644 --- a/src/Models/Model.php +++ b/src/Models/Model.php @@ -15,6 +15,7 @@ class Model private $columns = []; private $relationships = []; private $pivotTables = []; + private $indexes = []; /** * @param $name @@ -159,6 +160,16 @@ public function addPivotTable(string $reference) $this->pivotTables[] = $segments; } + public function indexes(): array + { + return $this->indexes; + } + + public function addIndex(Index $index) + { + $this->indexes[] = $index; + } + public function pivotTables(): array { return $this->pivotTables; diff --git a/tests/Feature/Generator/MigrationGeneratorTest.php b/tests/Feature/Generator/MigrationGeneratorTest.php index 7c9cf5f8..e367f973 100644 --- a/tests/Feature/Generator/MigrationGeneratorTest.php +++ b/tests/Feature/Generator/MigrationGeneratorTest.php @@ -70,7 +70,7 @@ public function output_writes_migration_for_model_tree($definition, $path, $migr $this->files->expects('put') ->with($timestamp_path, $this->fixture($migration)); - $tokens = $this->blueprint->parse($this->fixture($definition)); + $tokens = $this->blueprint->parse($this->fixture($definition), $definition !== 'drafts/indexes.yaml'); $tree = $this->blueprint->analyze($tokens); $this->assertEquals(['created' => [$timestamp_path]], $this->subject->output($tree)); @@ -103,7 +103,7 @@ public function output_updates_migration_for_model_tree($definition, $path, $mig $this->files->expects('put') ->with($yesterday_path, $this->fixture($migration)); - $tokens = $this->blueprint->parse($this->fixture($definition)); + $tokens = $this->blueprint->parse($this->fixture($definition), $definition !== 'drafts/indexes.yaml'); $tree = $this->blueprint->analyze($tokens); $this->assertEquals(['updated' => [$yesterday_path]], $this->subject->output($tree, true)); @@ -746,6 +746,7 @@ public function modelTreeDataProvider() ['drafts/soft-deletes.yaml', 'database/migrations/timestamp_create_comments_table.php', 'migrations/soft-deletes.php'], ['drafts/with-timezones.yaml', 'database/migrations/timestamp_create_comments_table.php', 'migrations/with-timezones.php'], ['drafts/relationships.yaml', 'database/migrations/timestamp_create_comments_table.php', 'migrations/relationships.php'], + ['drafts/indexes.yaml', 'database/migrations/timestamp_create_posts_table.php', 'migrations/indexes.php'], ['drafts/unconventional.yaml', 'database/migrations/timestamp_create_teams_table.php', 'migrations/unconventional.php'], ['drafts/optimize.yaml', 'database/migrations/timestamp_create_optimizes_table.php', 'migrations/optimize.php'], ['drafts/model-key-constraints.yaml', 'database/migrations/timestamp_create_orders_table.php', 'migrations/model-key-constraints.php'], diff --git a/tests/Unit/BuilderTest.php b/tests/Unit/BuilderTest.php index 2fbd0170..86cd280e 100644 --- a/tests/Unit/BuilderTest.php +++ b/tests/Unit/BuilderTest.php @@ -24,7 +24,7 @@ public function execute_builds_draft_content() $blueprint = \Mockery::mock(Blueprint::class); $blueprint->expects('parse') - ->with($draft) + ->with($draft, true) ->andReturn($tokens); $blueprint->expects('analyze') ->with($tokens + ['cache' => []]) @@ -72,7 +72,7 @@ public function execute_uses_cache_and_remembers_models() $blueprint = \Mockery::mock(Blueprint::class); $blueprint->expects('parse') - ->with($draft) + ->with($draft, true) ->andReturn($tokens); $blueprint->expects('parse') ->with('cached blueprint content') @@ -108,4 +108,56 @@ public function execute_uses_cache_and_remembers_models() $this->assertSame($generated, $actual); } + + /** + * @test + */ + public function execute_calls_builder_without_stripping_dashes_for_draft_file_with_indexes_defined() + { + $draft = 'models:'; + $draft .= PHP_EOL . ' Post:'; + $draft .= PHP_EOL . ' indexes:'; + $draft .= PHP_EOL . ' - index: author_id'; + $draft .= PHP_EOL . ' - index: author_id, published_at'; + + $tokens = [ + 'models' => [1, 2, 3] + ]; + $registry = new Tree(['registry']); + $only = []; + $skip = []; + $generated = ['created' => [1, 2], 'updated' => [3]]; + + $blueprint = \Mockery::mock(Blueprint::class); + $blueprint->expects('parse') + ->with($draft, false) + ->andReturn($tokens); + $blueprint->expects('analyze') + ->with($tokens + ['cache' => []]) + ->andReturn($registry); + $blueprint->expects('generate') + ->with($registry, $only, $skip, false) + ->andReturn($generated); + $blueprint->expects('dump') + ->with([ + 'created' => [1, 2], + 'updated' => [3], + 'models' => [1, 2, 3] + ]) + ->andReturn('cacheable blueprint content'); + + $file = \Mockery::mock(Filesystem::class); + $file->expects('get') + ->with('draft.yaml') + ->andReturn($draft); + $file->expects('exists') + ->with('.blueprint') + ->andReturnFalse(); + $file->expects('put') + ->with('.blueprint', 'cacheable blueprint content'); + + $actual = (new Builder)->execute($blueprint, $file, 'draft.yaml'); + + $this->assertSame($generated, $actual); + } } diff --git a/tests/fixtures/drafts/indexes.yaml b/tests/fixtures/drafts/indexes.yaml new file mode 100644 index 00000000..262514c9 --- /dev/null +++ b/tests/fixtures/drafts/indexes.yaml @@ -0,0 +1,16 @@ +models: + Post: + id: unsignedInteger + title: string + parent_post_id: id + author_id: id + published_at: timestamp nullable + word_count: integer unsigned + location: geometry + indexes: + - primary: id + - index: author_id + - index: author_id, published_at + - unique: title + - unique: title, parent_post_id + - spatialIndex: location diff --git a/tests/fixtures/migrations/indexes.php b/tests/fixtures/migrations/indexes.php new file mode 100644 index 00000000..ee6ca0b3 --- /dev/null +++ b/tests/fixtures/migrations/indexes.php @@ -0,0 +1,43 @@ +unsignedInteger('id'); + $table->string('title'); + $table->unsignedBigInteger('parent_post_id'); + $table->unsignedBigInteger('author_id'); + $table->timestamp('published_at')->nullable(); + $table->unsignedInteger('word_count'); + $table->geometry('location'); + $table->primary('id'); + $table->index('author_id'); + $table->index(['author_id', 'published_at']); + $table->unique('title'); + $table->unique(['title', 'parent_post_id']); + $table->spatialIndex('location'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('posts'); + } +}