From 1859f4124a63586b20fe86cf1e0c051f6357999a Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 6 Dec 2023 19:15:42 +0330 Subject: [PATCH 1/5] get foreign keys --- .../Query/Processors/MySqlProcessor.php | 23 ++++++++++ .../Query/Processors/PostgresProcessor.php | 37 +++++++++++++++ .../Database/Query/Processors/Processor.php | 11 +++++ .../Query/Processors/SQLiteProcessor.php | 23 ++++++++++ .../Query/Processors/SqlServerProcessor.php | 23 ++++++++++ src/Illuminate/Database/Schema/Builder.php | 15 +++++++ .../Database/Schema/Grammars/MySqlGrammar.php | 26 +++++++++++ .../Schema/Grammars/PostgresGrammar.php | 31 +++++++++++++ .../Schema/Grammars/SQLiteGrammar.php | 17 +++++++ .../Schema/Grammars/SqlServerGrammar.php | 29 ++++++++++++ .../Database/Schema/MySqlBuilder.php | 17 +++++++ .../Database/Schema/PostgresBuilder.php | 17 +++++++ src/Illuminate/Support/Facades/Schema.php | 1 + .../Database/SchemaBuilderTest.php | 45 +++++++++++++++++++ 14 files changed, 315 insertions(+) diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 7e6c66face3a..091fc80b52b0 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -63,4 +63,27 @@ public function processIndexes($results) ]; }, $results); } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + 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' => strtolower($result->on_update), + 'on_delete' => strtolower($result->on_delete), + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index c45ae4ae57ee..ddfbfe722da2 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -91,4 +91,41 @@ public function processIndexes($results) ]; }, $results); } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + 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/Illuminate/Database/Query/Processors/Processor.php b/src/Illuminate/Database/Query/Processors/Processor.php index 7abf156ad662..87a15c6d7a1d 100755 --- a/src/Illuminate/Database/Query/Processors/Processor.php +++ b/src/Illuminate/Database/Query/Processors/Processor.php @@ -99,6 +99,17 @@ public function processIndexes($results) return $results; } + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($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 6c6da5567dba..8f5fb98206e0 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -79,4 +79,27 @@ public function processIndexes($results) return $indexes; } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + return array_map(function ($result) { + $result = (object) $result; + + return [ + 'name' => null, + 'columns' => explode(',', $result->columns), + 'foreign_schema' => null, + 'foreign_table' => $result->foreign_table, + 'foreign_columns' => explode(',', $result->foreign_columns), + 'on_update' => strtolower($result->on_update), + 'on_delete' => strtolower($result->on_delete), + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php index 15fa4d740745..c089593ed86a 100755 --- a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -121,4 +121,27 @@ public function processIndexes($results) ]; }, $results); } + + /** + * Process the results of a foreign keys query. + * + * @param array $results + * @return array + */ + public function processForeignKeys($results) + { + 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' => strtolower(str_replace('_', ' ', $result->on_update)), + 'on_delete' => strtolower(str_replace('_', ' ', $result->on_delete)), + ]; + }, $results); + } } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 07698e7fc206..efcad17fced3 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -357,6 +357,21 @@ public function getIndexes($table) ); } + /** + * Get the foreign keys for a given table. + * + * @param string $table + * @return array + */ + public function getForeignKeys($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection($this->grammar->compileForeignKeys($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 43875591e07f..ad77b273cd15 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -185,6 +185,32 @@ public function compileIndexes($database, $table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $database + * @param string $table + * @return string + */ + public function compileForeignKeys($database, $table) + { + return sprintf( + 'select kc.constraint_name as `name`, ' + .'group_concat(kc.column_name order by kc.ordinal_position) as `columns `, ' + .'kc.referenced_table_schema as `foreign_schema`, ' + .'kc.referenced_table_name as `foreign_table`, ' + .'group_concat(kc.referenced_column_name order by kc.ordinal_position) as `foreign_columns`, ' + .'rc.update_rule as `on_update`, ' + .'rc.delete_rule as `on_delete` ' + .'from information_schema.key_column_usage kc join information_schema.referential_constraints rc ' + .'on kc.constraint_schema = rc.constraint_schema and kc.constraint_name = rc.constraint_name ' + .'where kc.table_schema = %s and kc.table_name = %s and kc.referenced_table_name is not null ' + .'group by kc.constraint_name, kc.referenced_table_schema, kc.referenced_table_name, rc.update_rule, rc.delete_rule', + $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 2995e339d0d5..6fbcfd981408 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -189,6 +189,37 @@ public function compileIndexes($schema, $table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $schema + * @param string $table + * @return string + */ + public function compileForeignKeys($schema, $table) + { + 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 confseq.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 lateral unnest(c.confkey) with ordinality as confseq(num, ord) on true ' + .'join pg_attribute fa on fa.attrelid = c.confrelid and fa.attnum = confseq.num ' + ."where c.contype = 'f' and tn.nspname = %s and tc.relname = %s " + .'group by c.conname, fn.nspname, fc.relname, c.confupdtype, c.confdeltype', + $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 a008fb73b0c6..e9c7d8c80490 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -146,6 +146,23 @@ public function compileIndexes($table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $table + * @return string + */ + public function compileForeignKeys($table) + { + return sprintf( + 'select group_concat("from") as columns, "table" as foreign_table, ' + .'group_concat("to") as foreign_columns, on_update, on_delete ' + .'from (select * from pragma_foreign_key_list(%s) order by id desc, seq) ' + .'group by id, "table", on_update, on_delete', + $this->wrap(str_replace('.', '__', $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 b4927d5f3432..c64e65cddd42 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -189,6 +189,35 @@ public function compileIndexes($table) ); } + /** + * Compile the query to determine the foreign keys. + * + * @param string $table + * @return string + */ + public function compileForeignKeys($table) + { + return sprintf( + 'select fk.name as name, ' + ."string_agg(lc.name, ',') within group (order by fkc.constraint_column_id) as columns, " + .'fs.name as foreign_schema, ft.name as foreign_table, ' + ."string_agg(fc.name, ',') within group (order by fkc.constraint_column_id) as foreign_columns, " + .'fk.update_referential_action_desc as on_update, ' + .'fk.delete_referential_action_desc as on_delete, ' + .'from sys.foreign_keys as fk ' + .'join sys.foreign_key_columns as fkc on fkc.constraint_object_id = fk.object_id ' + .'join sys.tables as lt on lt.object_id = fk.parent_object_id ' + .'join sys.schemas as ls on lt.schema_id = ls.schema_id ' + .'join sys.columns as lc on fkc.parent_object_id = lc.object_id and fkc.parent_column_id = lc.column_id ' + .'join sys.tables as ft on ft.object_id = fk.referenced_object_id ' + .'join sys.schemas as fs on ft.schema_id = fs.schema_id ' + .'join sys.columns as fc on fkc.referenced_object_id = fc.object_id and fkc.referenced_column_id = fc.column_id ' + .'where lt.name = %s and ls.name = SCHEMA_NAME() ' + .'group by fk.name, fs.name, ft.name, fk.update_referential_action_desc, fk.delete_referential_action_desc', + $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 e51305002480..0c537ba980cd 100755 --- a/src/Illuminate/Database/Schema/MySqlBuilder.php +++ b/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -120,6 +120,23 @@ public function getIndexes($table) ); } + /** + * Get the foreign keys for a given table. + * + * @param string $table + * @return array + */ + public function getForeignKeys($table) + { + $table = $this->connection->getTablePrefix().$table; + + return $this->connection->getPostProcessor()->processForeignKeys( + $this->connection->selectFromWriteConnection( + $this->grammar->compileForeignKeys($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 0efe5dc62161..4990cb445d98 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -220,6 +220,23 @@ public function getIndexes($table) ); } + /** + * Get the foreign keys for a given table. + * + * @param string $table + * @return array + */ + public function getForeignKeys($table) + { + [, $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 schemas for the connection. * diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 1bf87ba9a151..934d27e6f508 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -22,6 +22,7 @@ * @method static array getColumnListing(string $table) * @method static array getColumns(string $table) * @method static array getIndexes(string $table) + * @method static array getForeignKeys(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/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 2e79303b0574..ee1fc82ee4d5 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -261,4 +261,49 @@ public function testGetFullTextIndexes() $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', function (Blueprint $table) { + $table->id(); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->nullOnDelete(); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains( + fn ($foreign) => $foreign['columns'] === ['user_id'] + && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['id'] + && $foreign['on_update'] === 'cascade' && $foreign['on_delete'] === 'set null' + )); + } + + public function testGetCompoundForeignKeys() + { + Schema::create('parent', function (Blueprint $table) { + $table->id(); + $table->integer('a'); + $table->integer('b'); + }); + + Schema::create('child', function (Blueprint $table) { + $table->integer('c'); + $table->integer('d'); + + $table->foreign(['d', 'c'], 'test_fk')->references(['b', 'a'])->on('parent'); + }); + + $foreignKeys = Schema::getForeignKeys('child'); + + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains( + fn ($foreign) => $foreign['columns'] === ['d', 'c'] + && $foreign['foreign_table'] === 'parent' + && $foreign['foreign_columns'] === ['b', 'a'] + )); + } } From 82cd13453b90e76c672b9a1e8245236fcb7cf8c6 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 6 Dec 2023 19:53:27 +0330 Subject: [PATCH 2/5] fix tests --- tests/Integration/Database/SchemaBuilderTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index ee1fc82ee4d5..ea9a6d905031 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -269,7 +269,7 @@ public function testGetForeignKeys() }); Schema::create('posts', function (Blueprint $table) { - $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->nullOnDelete(); + $table->foreignId('user_id')->nullable()->constrained()->cascadeOnUpdate()->nullOnDelete(); }); $foreignKeys = Schema::getForeignKeys('posts'); @@ -288,6 +288,8 @@ public function testGetCompoundForeignKeys() $table->id(); $table->integer('a'); $table->integer('b'); + + $table->unique(['b', 'a']); }); Schema::create('child', function (Blueprint $table) { From 40257c7f40122ab2b8aadcf463d0215b3bfd5327 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 6 Dec 2023 19:59:02 +0330 Subject: [PATCH 3/5] fix tests --- src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php | 2 +- src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index ad77b273cd15..11ec0df9079b 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -196,7 +196,7 @@ public function compileForeignKeys($database, $table) { return sprintf( 'select kc.constraint_name as `name`, ' - .'group_concat(kc.column_name order by kc.ordinal_position) as `columns `, ' + .'group_concat(kc.column_name order by kc.ordinal_position) as `columns`, ' .'kc.referenced_table_schema as `foreign_schema`, ' .'kc.referenced_table_name as `foreign_table`, ' .'group_concat(kc.referenced_column_name order by kc.ordinal_position) as `foreign_columns`, ' diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index c64e65cddd42..4b9091a1d6ef 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -203,7 +203,7 @@ public function compileForeignKeys($table) .'fs.name as foreign_schema, ft.name as foreign_table, ' ."string_agg(fc.name, ',') within group (order by fkc.constraint_column_id) as foreign_columns, " .'fk.update_referential_action_desc as on_update, ' - .'fk.delete_referential_action_desc as on_delete, ' + .'fk.delete_referential_action_desc as on_delete ' .'from sys.foreign_keys as fk ' .'join sys.foreign_key_columns as fkc on fkc.constraint_object_id = fk.object_id ' .'join sys.tables as lt on lt.object_id = fk.parent_object_id ' From d65acd0edc3ba6c942cb3cea1e1e81462a9f7441 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 6 Dec 2023 22:18:08 +0330 Subject: [PATCH 4/5] fix tests --- src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 6fbcfd981408..75eb5c423cd0 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -213,7 +213,7 @@ public function compileForeignKeys($schema, $table) .'join pg_attribute la on la.attrelid = c.conrelid and la.attnum = conseq.num ' .'join lateral unnest(c.confkey) with ordinality as confseq(num, ord) on true ' .'join pg_attribute fa on fa.attrelid = c.confrelid and fa.attnum = confseq.num ' - ."where c.contype = 'f' and tn.nspname = %s and tc.relname = %s " + ."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) From 41e4d4a99db51a2953f3eb2cc97b5166b1c14769 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Thu, 7 Dec 2023 10:06:36 +0330 Subject: [PATCH 5/5] fix tests --- src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 75eb5c423cd0..235539ddec99 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -202,7 +202,7 @@ public function compileForeignKeys($schema, $table) '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 confseq.ord) as foreign_columns, " + ."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 ' @@ -211,8 +211,7 @@ public function compileForeignKeys($schema, $table) .'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 lateral unnest(c.confkey) with ordinality as confseq(num, ord) on true ' - .'join pg_attribute fa on fa.attrelid = c.confrelid and fa.attnum = confseq.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),