diff --git a/src/Query/Processors/PostgresProcessor.php b/src/Query/Processors/PostgresProcessor.php index 4311dc3..272317d 100644 --- a/src/Query/Processors/PostgresProcessor.php +++ b/src/Query/Processors/PostgresProcessor.php @@ -79,4 +79,38 @@ public function processIndexes(array $results): array ]; }, $results); } + + /** + * Process the results of a foreign keys query. + */ + public function processForeignKeys(array $results): array + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => $result->name, + 'columns' => explode(',', $result->columns), + 'foreign_schema' => $result->foreign_schema, + 'foreign_table' => $result->foreign_table, + 'foreign_columns' => explode(',', $result->foreign_columns), + 'on_update' => match (strtolower($result->on_update)) { + 'a' => 'no action', + 'r' => 'restrict', + 'c' => 'cascade', + 'n' => 'set null', + 'd' => 'set default', + default => null, + }, + 'on_delete' => match (strtolower($result->on_delete)) { + 'a' => 'no action', + 'r' => 'restrict', + 'c' => 'cascade', + 'n' => 'set null', + 'd' => 'set default', + default => null, + }, + ]; + }, $results); + } } diff --git a/src/Schema/Grammars/PostgresGrammar.php b/src/Schema/Grammars/PostgresGrammar.php index 5b1f199..d0de7a6 100644 --- a/src/Schema/Grammars/PostgresGrammar.php +++ b/src/Schema/Grammars/PostgresGrammar.php @@ -509,6 +509,32 @@ public function typeMultiLineString(Fluent $column) return $this->formatPostGisType('multilinestring', $column); } + /** + * Compile the query to determine the foreign keys. + */ + public function compileForeignKeys(string $schema, string $table): string + { + return sprintf( + 'select c.conname as name, ' + . "string_agg(la.attname, ',' order by conseq.ord) as columns, " + . 'fn.nspname as foreign_schema, fc.relname as foreign_table, ' + . "string_agg(fa.attname, ',' order by conseq.ord) as foreign_columns, " + . 'c.confupdtype as on_update, c.confdeltype as on_delete ' + . 'from pg_constraint c ' + . 'join pg_class tc on c.conrelid = tc.oid ' + . 'join pg_namespace tn on tn.oid = tc.relnamespace ' + . 'join pg_class fc on c.confrelid = fc.oid ' + . 'join pg_namespace fn on fn.oid = fc.relnamespace ' + . 'join lateral unnest(c.conkey) with ordinality as conseq(num, ord) on true ' + . 'join pg_attribute la on la.attrelid = c.conrelid and la.attnum = conseq.num ' + . 'join pg_attribute fa on fa.attrelid = c.confrelid and fa.attnum = c.confkey[conseq.ord] ' + . "where c.contype = 'f' and tc.relname = %s and tn.nspname = %s " + . 'group by c.conname, fn.nspname, fc.relname, c.confupdtype, c.confdeltype', + $this->quoteString($table), + $this->quoteString($schema) + ); + } + /** * Create the column definition for a char type. * diff --git a/src/Schema/PostgresBuilder.php b/src/Schema/PostgresBuilder.php index 276ae7d..ede2098 100644 --- a/src/Schema/PostgresBuilder.php +++ b/src/Schema/PostgresBuilder.php @@ -214,6 +214,20 @@ public function getColumnListing($table): array return $this->connection->getPostProcessor()->processColumnListing($results); } + /** + * Get the foreign keys for a given table. + */ + public function getForeignKeys(string $table): array + { + [$schema, $table] = $this->parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix() . $table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection($this->grammar->compileForeignKeys($schema, $table)) + ); + } + /** * Get the column type listing for a given table. */ diff --git a/tests/Cases/SchemaBuilderTest.php b/tests/Cases/SchemaBuilderTest.php index 64844c7..c7d69e5 100644 --- a/tests/Cases/SchemaBuilderTest.php +++ b/tests/Cases/SchemaBuilderTest.php @@ -217,4 +217,24 @@ public function index(): void $this->assertTrue(collect($indexes)->contains(fn ($index) => $index['columns'] === ['id'] && $index['primary'])); $this->assertTrue(collect($indexes)->contains('name', 'articles_body_title_fulltext')); } + + public function testGetForeignKeys() + { + Schema::create('users_copy', function (Blueprint $table) { + $table->id(); + }); + + Schema::create('posts_copy', function (Blueprint $table) { + $table->foreignId('user_id')->nullable()->constrained('users_copy')->cascadeOnUpdate()->nullOnDelete(); + }); + + $foreignKeys = Schema::getForeignKeys('posts_copy'); + + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains( + fn ($foreign) => $foreign['columns'] === ['user_id'] + && $foreign['foreign_table'] === 'users_copy' && $foreign['foreign_columns'] === ['id'] + && $foreign['on_update'] === 'cascade' && $foreign['on_delete'] === 'set null' + )); + } }