From 092bb666c51ad231d61e1412da5fc132dbe402a2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 19 Jan 2022 12:42:09 +0100 Subject: [PATCH] drivers: getForeignKeys() works with multi-column foreign keys --- src/Database/Drivers/MsSqlDriver.php | 9 +++++---- src/Database/Drivers/MySqlDriver.php | 9 +++++---- src/Database/Drivers/PgSqlDriver.php | 13 +++++++++++-- src/Database/Drivers/SqliteDriver.php | 7 +++++-- src/Database/Drivers/SqlsrvDriver.php | 6 +++++- src/Database/Structure.php | 12 +++--------- tests/Database/Reflection.postgre.phpt | 4 ++-- tests/Database/Structure.phpt | 8 ++++---- tests/Database/Structure.schemas.phpt | 4 ++-- 9 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/Database/Drivers/MsSqlDriver.php b/src/Database/Drivers/MsSqlDriver.php index 50b9b2610..538503237 100644 --- a/src/Database/Drivers/MsSqlDriver.php +++ b/src/Database/Drivers/MsSqlDriver.php @@ -181,11 +181,12 @@ public function getForeignKeys(string $table): array tab1.name = {$this->pdo->quote($table_name)} X; - foreach ($this->pdo->query($query) as $id => $row) { - $keys[$id]['name'] = $row['fk_name']; - $keys[$id]['local'] = $row['column']; + foreach ($this->pdo->query($query) as $row) { + $id = $row['fk_name']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['column']; $keys[$id]['table'] = $table_schema . '.' . $row['referenced_table']; - $keys[$id]['foreign'] = $row['referenced_column']; + $keys[$id]['foreign'][] = $row['referenced_column']; } return array_values($keys); diff --git a/src/Database/Drivers/MySqlDriver.php b/src/Database/Drivers/MySqlDriver.php index 659a3d543..6f676c8a8 100644 --- a/src/Database/Drivers/MySqlDriver.php +++ b/src/Database/Drivers/MySqlDriver.php @@ -176,11 +176,12 @@ public function getForeignKeys(string $table): array WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_NAME = {$this->pdo->quote($table)} - X) as $id => $row) { - $keys[$id]['name'] = $row['CONSTRAINT_NAME']; - $keys[$id]['local'] = $row['COLUMN_NAME']; + X) as $row) { + $id = $row['CONSTRAINT_NAME']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['COLUMN_NAME']; $keys[$id]['table'] = $row['REFERENCED_TABLE_NAME']; - $keys[$id]['foreign'] = $row['REFERENCED_COLUMN_NAME']; + $keys[$id]['foreign'][] = $row['REFERENCED_COLUMN_NAME']; } return array_values($keys); diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/PgSqlDriver.php index afd2e17c0..c4c66fd13 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/PgSqlDriver.php @@ -182,7 +182,8 @@ public function getIndexes(string $table): array public function getForeignKeys(string $table): array { /* Doesn't work with multi-column foreign keys */ - return $this->pdo->query(<<pdo->query(<<pdo->quote($this->delimiteFQN($table))}::regclass AND nf.nspname = ANY (pg_catalog.current_schemas(FALSE)) - X)->fetchAll(\PDO::FETCH_ASSOC); + X) as $row) { + $id = $row['name']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['local']; + $keys[$id]['table'] = $row['table']; + $keys[$id]['foreign'][] = $row['foreign']; + } + + return array_values($keys); } diff --git a/src/Database/Drivers/SqliteDriver.php b/src/Database/Drivers/SqliteDriver.php index cf097dcc7..1415ebafb 100644 --- a/src/Database/Drivers/SqliteDriver.php +++ b/src/Database/Drivers/SqliteDriver.php @@ -217,9 +217,12 @@ public function getForeignKeys(string $table): array foreach ($this->pdo->query("PRAGMA foreign_key_list({$this->delimite($table)})") as $row) { $id = $row['id']; $keys[$id]['name'] = $id; - $keys[$id]['local'] = $row['from']; + $keys[$id]['local'][] = $row['from']; $keys[$id]['table'] = $row['table']; - $keys[$id]['foreign'] = $row['to']; + $keys[$id]['foreign'][] = $row['to']; + if ($keys[$id]['foreign'][0] == null) { + $keys[$id]['foreign'] = []; + } } return array_values($keys); diff --git a/src/Database/Drivers/SqlsrvDriver.php b/src/Database/Drivers/SqlsrvDriver.php index 2477dd5ad..77fad9b13 100644 --- a/src/Database/Drivers/SqlsrvDriver.php +++ b/src/Database/Drivers/SqlsrvDriver.php @@ -207,7 +207,11 @@ public function getForeignKeys(string $table): array WHERE tl.name = {$this->pdo->quote($table)} X, \PDO::FETCH_ASSOC) as $row) { - $keys[$row['name']] = $row; + $id = $row['name']; + $keys[$id]['name'] = $id; + $keys[$id]['local'][] = $row['local']; + $keys[$id]['table'] = $row['table']; + $keys[$id]['foreign'][] = $row['column']; } return array_values($keys); diff --git a/src/Database/Structure.php b/src/Database/Structure.php index c62073755..33971ae4f 100644 --- a/src/Database/Structure.php +++ b/src/Database/Structure.php @@ -236,17 +236,11 @@ protected function analyzeForeignKeys(array &$structure, string $table): void $foreignKeys = $this->connection->getDriver()->getForeignKeys($table); - $fksColumnsCounts = []; - foreach ($foreignKeys as $foreignKey) { - $tmp = &$fksColumnsCounts[$foreignKey['name']]; - $tmp++; - } - - usort($foreignKeys, fn($a, $b): int => $fksColumnsCounts[$b['name']] <=> $fksColumnsCounts[$a['name']]); + usort($foreignKeys, fn($a, $b): int => count($b['local']) <=> count($a['local'])); foreach ($foreignKeys as $row) { - $structure['belongsTo'][$lowerTable][$row['local']] = $row['table']; - $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local']; + $structure['belongsTo'][$lowerTable][$row['local'][0]] = $row['table']; + $structure['hasMany'][strtolower($row['table'])][$table][] = $row['local'][0]; } if (isset($structure['belongsTo'][$lowerTable])) { diff --git a/tests/Database/Reflection.postgre.phpt b/tests/Database/Reflection.postgre.phpt index c5460661d..ab58272e5 100644 --- a/tests/Database/Reflection.postgre.phpt +++ b/tests/Database/Reflection.postgre.phpt @@ -63,9 +63,9 @@ test('Tables in schema', function () use ($connection) { $foreign = $driver->getForeignKeys('one.slave'); Assert::same([ 'name' => 'one_slave_fk', - 'local' => 'one_id', + 'local' => ['one_id'], 'table' => 'one.master', - 'foreign' => 'one_id', + 'foreign' => ['one_id'], ], (array) $foreign[0]); diff --git a/tests/Database/Structure.phpt b/tests/Database/Structure.phpt index 9c3d19d02..10a94c995 100644 --- a/tests/Database/Structure.phpt +++ b/tests/Database/Structure.phpt @@ -78,13 +78,13 @@ class StructureTestCase extends TestCase $this->connection->shouldReceive('getDriver')->times(4)->andReturn($this->driver); $this->driver->shouldReceive('getForeignKeys')->with('authors')->once()->andReturn([]); $this->driver->shouldReceive('getForeignKeys')->with('Books')->once()->andReturn([ - ['local' => 'author_id', 'table' => 'authors', 'foreign' => 'id', 'name' => 'authors_fk1'], - ['local' => 'translator_id', 'table' => 'authors', 'foreign' => 'id', 'name' => 'authors_fk2'], + ['local' => ['author_id'], 'table' => 'authors', 'foreign' => ['id'], 'name' => 'authors_fk1'], + ['local' => ['translator_id'], 'table' => 'authors', 'foreign' => ['id'], 'name' => 'authors_fk2'], ]); $this->driver->shouldReceive('getForeignKeys')->with('tags')->once()->andReturn([]); $this->driver->shouldReceive('getForeignKeys')->with('books_x_tags')->once()->andReturn([ - ['local' => 'book_id', 'table' => 'Books', 'foreign' => 'id', 'name' => 'books_x_tags_fk1'], - ['local' => 'tag_id', 'table' => 'tags', 'foreign' => 'id', 'name' => 'books_x_tags_fk2'], + ['local' => ['book_id'], 'table' => 'Books', 'foreign' => ['id'], 'name' => 'books_x_tags_fk1'], + ['local' => ['tag_id'], 'table' => 'tags', 'foreign' => ['id'], 'name' => 'books_x_tags_fk2'], ]); $this->structure = new StructureMock($this->connection, $this->storage); diff --git a/tests/Database/Structure.schemas.phpt b/tests/Database/Structure.schemas.phpt index 6152b2c95..5c9cabe46 100644 --- a/tests/Database/Structure.schemas.phpt +++ b/tests/Database/Structure.schemas.phpt @@ -64,8 +64,8 @@ class StructureSchemasTestCase extends TestCase $this->connection->shouldReceive('getDriver')->times(2)->andReturn($this->driver); $this->driver->shouldReceive('getForeignKeys')->with('authors.authors')->once()->andReturn([]); $this->driver->shouldReceive('getForeignKeys')->with('books.books')->once()->andReturn([ - ['local' => 'author_id', 'table' => 'authors.authors', 'foreign' => 'id', 'name' => 'authors_authors_fk1'], - ['local' => 'translator_id', 'table' => 'authors.authors', 'foreign' => 'id', 'name' => 'authors_authors_fk2'], + ['local' => ['author_id'], 'table' => 'authors.authors', 'foreign' => ['id'], 'name' => 'authors_authors_fk1'], + ['local' => ['translator_id'], 'table' => 'authors.authors', 'foreign' => ['id'], 'name' => 'authors_authors_fk2'], ]); $this->structure = new StructureMock($this->connection, $this->storage);