diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 3aa8c9cb9852..7e6c66face3a 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -42,4 +42,25 @@ public function processColumns($results) ]; }, $results); } + + /** + * Process the results of an indexes query. + * + * @param array $results + * @return array + */ + public function processIndexes($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $name = strtolower($result->name), + 'columns' => explode(',', $result->columns), + 'type' => strtolower($result->type), + 'unique' => (bool) $result->unique, + 'primary' => $name === 'primary', + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index efa6cf027fb6..c45ae4ae57ee 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -70,4 +70,25 @@ public function processColumns($results) ]; }, $results); } + + /** + * Process the results of an indexes query. + * + * @param array $results + * @return array + */ + public function processIndexes($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => strtolower($result->name), + 'columns' => explode(',', $result->columns), + 'type' => strtolower($result->type), + 'unique' => (bool) $result->unique, + 'primary' => (bool) $result->primary, + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 03f8ea982763..7abf156ad662 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -88,6 +88,17 @@ public function processColumns($results) return $results; } + /** + * Process the results of an indexes query. + * + * @param array $results + * @return array + */ + public function processIndexes($results) + { + return $results; + } + /** * Process the results of a column listing query. * diff --git a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php index cb851efb4cc6..6c6da5567dba 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -46,4 +46,37 @@ public function processColumns($results) ]; }, $results); } + + /** + * Process the results of an indexes query. + * + * @param array $results + * @return array + */ + public function processIndexes($results) + { + $primaryCount = 0; + + $indexes = array_map(function ($result) use (&$primaryCount) { + $result = (object) $result; + + if ($isPrimary = (bool) $result->primary) { + $primaryCount += 1; + } + + return [ + 'name' => strtolower($result->name), + 'columns' => explode(',', $result->columns), + 'type' => null, + 'unique' => (bool) $result->unique, + 'primary' => $isPrimary, + ]; + }, $results); + + if ($primaryCount > 1) { + $indexes = array_filter($indexes, fn ($index) => $index['name'] !== 'primary'); + } + + return $indexes; + } } diff --git a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php index 0384335ac6db..15fa4d740745 100755 --- a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -100,4 +100,25 @@ public function processColumns($results) ]; }, $results); } + + /** + * Process the results of an indexes query. + * + * @param array $results + * @return array + */ + public function processIndexes($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => strtolower($result->name), + 'columns' => explode(',', $result->columns), + 'type' => strtolower($result->type), + 'unique' => (bool) $result->unique, + 'primary' => (bool) $result->primary, + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 0807bdd76c17..07698e7fc206 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -342,6 +342,21 @@ public function getColumns($table) ); } + /** + * Get the indexes for a given table. + * + * @param string $table + * @return array + */ + public function getIndexes($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processIndexes( + $this->connection->selectFromWriteConnection($this->grammar->compileIndexes($table)) + ); + } + /** * Modify a table on the schema. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 8ec5fb4e3040..43875591e07f 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -166,6 +166,25 @@ public function compileColumns($database, $table) ); } + /** + * Compile the query to determine the indexes. + * + * @param string $database + * @param string $table + * @return string + */ + public function compileIndexes($database, $table) + { + return sprintf( + 'select index_name as `name`, group_concat(column_name order by seq_in_index) as `columns`, ' + .'index_type as `type`, not non_unique as `unique` ' + .'from information_schema.statistics where table_schema = %s and table_name = %s ' + .'group by index_name, index_type, non_unique', + $this->quoteString($database), + $this->quoteString($table) + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 061984bdbaed..2995e339d0d5 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -163,6 +163,32 @@ public function compileColumns($database, $schema, $table) ); } + /** + * Compile the query to determine the indexes. + * + * @param string $schema + * @param string $table + * @return string + */ + public function compileIndexes($schema, $table) + { + return sprintf( + "select ic.relname as name, string_agg(a.attname, ',' order by indseq.ord) as columns, " + .'am.amname as "type", i.indisunique as "unique", i.indisprimary as "primary" ' + .'from pg_index i ' + .'join pg_class tc on tc.oid = i.indrelid ' + .'join pg_namespace tn on tn.oid = tc.relnamespace ' + .'join pg_class ic on ic.oid = i.indexrelid ' + .'join pg_am am on am.oid = ic.relam ' + .'join lateral unnest(i.indkey) with ordinality as indseq(num, ord) on true ' + .'left join pg_attribute a on a.attrelid = i.indrelid and a.attnum = indseq.num ' + .'where tc.relname = %s and tn.nspname = %s ' + .'group by ic.relname, am.amname, i.indisunique, i.indisprimary', + $this->quoteString($table), + $this->quoteString($schema) + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 02a75eeaf58c..a008fb73b0c6 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -127,6 +127,25 @@ public function compileColumns($table) ); } + /** + * Compile the query to determine the indexes. + * + * @param string $table + * @return string + */ + public function compileIndexes($table) + { + return sprintf( + 'select "primary" as name, group_concat(col) as columns, 1 as "unique", 1 as "primary" ' + .'from (select name as col from pragma_table_info(%s) where pk > 0 order by pk, cid) group by name ' + .'union select name, group_concat(col) as columns, "unique", origin = "pk" as "primary" ' + .'from (select il.*, ii.name as col from pragma_index_list(%s) il, pragma_index_info(il.name) ii order by il.seq, ii.seqno) ' + .'group by name, "unique", "primary"', + $table = $this->wrap(str_replace('.', '__', $table)), + $table + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 084021aa05c6..b4927d5f3432 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -167,6 +167,28 @@ public function compileColumns($table) ); } + /** + * Compile the query to determine the indexes. + * + * @param string $table + * @return string + */ + public function compileIndexes($table) + { + return sprintf( + "select idx.name as name, string_agg(col.name, ',') within group (order by idxcol.key_ordinal) as columns, " + .'idx.type_desc as [type], idx.is_unique as [unique], idx.is_primary_key as [primary] ' + .'from sys.indexes as idx ' + .'join sys.tables as tbl on idx.object_id = tbl.object_id ' + .'join sys.schemas as scm on tbl.schema_id = scm.schema_id ' + .'join sys.index_columns as idxcol on idx.object_id = idxcol.object_id and idx.index_id = idxcol.index_id ' + .'join sys.columns as col on idxcol.object_id = col.object_id and idxcol.column_id = col.column_id ' + .'where tbl.name = %s and scm.name = SCHEMA_NAME() ' + .'group by idx.name, idx.type_desc, idx.is_unique, idx.is_primary_key', + $this->quoteString($table), + ); + } + /** * Compile a create table command. * diff --git a/src/Illuminate/Database/Schema/MySqlBuilder.php b/src/Illuminate/Database/Schema/MySqlBuilder.php index 1c8c767bd997..e51305002480 100755 --- a/src/Illuminate/Database/Schema/MySqlBuilder.php +++ b/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -103,6 +103,23 @@ public function getColumns($table) return $this->connection->getPostProcessor()->processColumns($results); } + /** + * Get the indexes for a given table. + * + * @param string $table + * @return array + */ + public function getIndexes($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processIndexes( + $this->connection->selectFromWriteConnection( + $this->grammar->compileIndexes($this->connection->getDatabaseName(), $table) + ) + ); + } + /** * Drop all tables from the database. * diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 2074e3b17cbb..0efe5dc62161 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -203,6 +203,23 @@ public function getColumns($table) return $this->connection->getPostProcessor()->processColumns($results); } + /** + * Get the indexes for a given table. + * + * @param string $table + * @return array + */ + public function getIndexes($table) + { + [, $schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processIndexes( + $this->connection->selectFromWriteConnection($this->grammar->compileIndexes($schema, $table)) + ); + } + /** * Get the schemas for the connection. * diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 7a29296130eb..1bf87ba9a151 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -21,6 +21,7 @@ * @method static string getColumnType(string $table, string $column, bool $fullDefinition = false) * @method static array getColumnListing(string $table) * @method static array getColumns(string $table) + * @method static array getIndexes(string $table) * @method static void table(string $table, \Closure $callback) * @method static void create(string $table, \Closure $callback) * @method static void drop(string $table) diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 9e48556d9f12..60339647c0f9 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -164,20 +164,21 @@ public function testRenameIndex() $table->index(['name', 'email'], 'index1'); }); - $manager = $db->getConnection()->getDoctrineSchemaManager(); - $details = $manager->listTableDetails('prefix_users'); - $this->assertTrue($details->hasIndex('index1')); - $this->assertFalse($details->hasIndex('index2')); + $indexes = array_column($schema->getIndexes('users'), 'name'); + + $this->assertContains('index1', $indexes); + $this->assertNotContains('index2', $indexes); $schema->table('users', function (Blueprint $table) { $table->renameIndex('index1', 'index2'); }); - $details = $manager->listTableDetails('prefix_users'); - $this->assertFalse($details->hasIndex('index1')); - $this->assertTrue($details->hasIndex('index2')); + $indexes = $schema->getIndexes('users'); - $this->assertEquals(['name', 'email'], $details->getIndex('index2')->getUnquotedColumns()); + $this->assertNotContains('index1', array_column($indexes, 'name')); + $this->assertTrue(collect($indexes)->contains( + fn ($index) => $index['name'] === 'index2' && $index['columns'] === ['name', 'email'] + )); } public function testAddingPrimaryKey() diff --git a/tests/Database/DatabaseSchemaBuilderIntegrationTest.php b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php index d469645fc8a1..ce261ab4e94d 100644 --- a/tests/Database/DatabaseSchemaBuilderIntegrationTest.php +++ b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php @@ -87,7 +87,10 @@ public function testHasColumnAndIndexWithPrefixIndexDisabled() $table->string('name')->index(); }); - $this->assertArrayHasKey('table1_name_index', $this->db->connection()->getDoctrineSchemaManager()->listTableIndexes('example_table1')); + $this->assertContains( + 'table1_name_index', + array_column($this->db->connection()->getSchemaBuilder()->getIndexes('table1'), 'name') + ); } public function testHasColumnAndIndexWithPrefixIndexEnabled() @@ -104,7 +107,10 @@ public function testHasColumnAndIndexWithPrefixIndexEnabled() $table->string('name')->index(); }); - $this->assertArrayHasKey('example_table1_name_index', $this->db->connection()->getDoctrineSchemaManager()->listTableIndexes('example_table1')); + $this->assertContains( + 'example_table1_name_index', + array_column($this->db->connection()->getSchemaBuilder()->getIndexes('table1'), 'name') + ); } public function testDropColumnWithTablePrefix() diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index a8ea8fbaba63..2e79303b0574 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -181,4 +181,84 @@ public function testGetViews() $this->assertEmpty(array_diff(['foo', 'bar', 'baz'], array_column($views, 'name'))); } + + public function testGetIndexes() + { + Schema::create('foo', function (Blueprint $table) { + $table->string('bar')->index('my_index'); + }); + + $indexes = Schema::getIndexes('foo'); + + $this->assertCount(1, $indexes); + $this->assertTrue( + $indexes[0]['name'] === 'my_index' + && $indexes[0]['columns'] === ['bar'] + && ! $indexes[0]['unique'] + && ! $indexes[0]['primary'] + ); + } + + public function testGetUniqueIndexes() + { + Schema::create('foo', function (Blueprint $table) { + $table->id(); + $table->string('bar'); + $table->integer('baz'); + + $table->unique(['baz', 'bar']); + }); + + $indexes = Schema::getIndexes('foo'); + + $this->assertCount(2, $indexes); + $this->assertTrue(collect($indexes)->contains( + fn ($index) => $index['columns'] === ['id'] && $index['primary'] + )); + $this->assertTrue(collect($indexes)->contains( + fn ($index) => $index['name'] === 'foo_baz_bar_unique' && $index['columns'] === ['baz', 'bar'] && $index['unique'] + )); + } + + public function testGetIndexesWithCompositeKeys() + { + Schema::create('foo', function (Blueprint $table) { + $table->unsignedBigInteger('key'); + $table->string('bar')->unique(); + $table->integer('baz'); + + $table->primary(['baz', 'key']); + }); + + $indexes = Schema::getIndexes('foo'); + + $this->assertCount(2, $indexes); + $this->assertTrue(collect($indexes)->contains( + fn ($index) => $index['columns'] === ['baz', 'key'] && $index['primary'] + )); + $this->assertTrue(collect($indexes)->contains( + fn ($index) => $index['name'] === 'foo_bar_unique' && $index['columns'] === ['bar'] && $index['unique'] + )); + } + + public function testGetFullTextIndexes() + { + if (! in_array($this->driver, ['pgsql', 'mysql'])) { + $this->markTestSkipped('Test requires a MySQL or a PostgreSQL connection.'); + } + + Schema::create('articles', function (Blueprint $table) { + $table->id(); + $table->string('title', 200); + $table->text('body'); + + $table->fulltext(['body', 'title']); + }); + + $indexes = Schema::getIndexes('articles'); + + $this->assertCount(2, $indexes); + $this->assertTrue(collect($indexes)->contains(fn ($index) => $index['columns'] === ['id'] && $index['primary'])); + $this->assertTrue(collect($indexes)->contains('name', 'articles_body_title_fulltext')); + } }