From 84f1a4a789359848f6b9900a3ae659555ea4a14a Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Mon, 6 Jan 2025 21:51:57 -0800 Subject: [PATCH 1/2] Refactor foreign key constraint introspection on SQLite --- src/Schema/SQLiteSchemaManager.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Schema/SQLiteSchemaManager.php b/src/Schema/SQLiteSchemaManager.php index 5fa8bec468..cc04bd1d07 100644 --- a/src/Schema/SQLiteSchemaManager.php +++ b/src/Schema/SQLiteSchemaManager.php @@ -322,20 +322,26 @@ protected function _getPortableTableForeignKeysList(array $tableForeignKeys): ar $list[$id]['local'][] = $value['from']; if ($value['to'] === null) { - // Inferring a shorthand form for the foreign key constraint, where the "to" field is empty. - // @see https://www.sqlite.org/foreignkeys.html#fk_indexes. - $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']); + continue; + } - if (! isset($foreignTableIndexes['primary'])) { - continue; - } + $list[$id]['foreign'][] = $value['to']; + } - $list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()]; + foreach ($list as $id => $value) { + if (count($value['foreign']) !== 0) { + continue; + } + // Inferring a shorthand form for the foreign key constraint, where the "to" field is empty. + // @see https://www.sqlite.org/foreignkeys.html#fk_indexes. + $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['foreignTable']); + + if (! isset($foreignTableIndexes['primary'])) { continue; } - $list[$id]['foreign'][] = $value['to']; + $list[$id]['foreign'] = $foreignTableIndexes['primary']->getColumns(); } return parent::_getPortableTableForeignKeysList($list); From f07e3696700e03803fb3f86e53a14f8afe8ab4ab Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Fri, 10 Jan 2025 16:25:10 -0800 Subject: [PATCH 2/2] Deprecate introspection of incomplete SQLite schema --- UPGRADE.md | 10 +++++ src/Schema/SQLiteSchemaManager.php | 8 ++++ .../Schema/SQLiteSchemaManagerTest.php | 38 +++++++++++++++---- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 96d2f8b8d3..9f270fe593 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,16 @@ awareness about deprecated code. # Upgrade to 4.3 +## Deprecated introspection of SQLite foreign key constraints with omitted referenced column names in an incomplete schema + +If the referenced column names are omitted in a foreign key constraint declaration, it implies that the constraint +references the primary key columns of the referenced table. If the referenced table is not present in the schema, the +constraint cannot be properly introspected, and the referenced column names are introspected as an empty list. +This behavior is deprecated. + +In order to mitigate this issue, either ensure that the referenced table is present in the schema when introspecting +foreign constraints, or provide the referenced column names explicitly in the constraint declaration. + ## Deprecated `UniqueConstraint` methods, property and behavior The following `UniqueConstraint` methods and property have been deprecated: diff --git a/src/Schema/SQLiteSchemaManager.php b/src/Schema/SQLiteSchemaManager.php index cc04bd1d07..2b402e3523 100644 --- a/src/Schema/SQLiteSchemaManager.php +++ b/src/Schema/SQLiteSchemaManager.php @@ -11,6 +11,7 @@ use Doctrine\DBAL\Types\StringType; use Doctrine\DBAL\Types\TextType; use Doctrine\DBAL\Types\Type; +use Doctrine\Deprecations\Deprecation; use function array_change_key_case; use function array_map; @@ -338,6 +339,13 @@ protected function _getPortableTableForeignKeysList(array $tableForeignKeys): ar $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['foreignTable']); if (! isset($foreignTableIndexes['primary'])) { + Deprecation::trigger( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/pull/6701', + 'Introspection of SQLite foreign key constraints with omitted referenced column names' + . ' in an incomplete schema is deprecated.', + ); + continue; } diff --git a/tests/Functional/Schema/SQLiteSchemaManagerTest.php b/tests/Functional/Schema/SQLiteSchemaManagerTest.php index d9d57f4a1f..bbfd7a8111 100644 --- a/tests/Functional/Schema/SQLiteSchemaManagerTest.php +++ b/tests/Functional/Schema/SQLiteSchemaManagerTest.php @@ -15,12 +15,15 @@ use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use function array_keys; use function array_shift; class SQLiteSchemaManagerTest extends SchemaManagerFunctionalTestCase { + use VerifyDeprecations; + protected function supportsPlatform(AbstractPlatform $platform): bool { return $platform instanceof SQLitePlatform; @@ -51,9 +54,7 @@ public function testListForeignKeysFromExistingDatabase(): void CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, page INTEGER CONSTRAINT FK_1 REFERENCES page (key) DEFERRABLE INITIALLY DEFERRED, - parent INTEGER REFERENCES user(id) ON DELETE CASCADE, - log INTEGER, - CONSTRAINT FK_3 FOREIGN KEY (log) REFERENCES log ON UPDATE SET NULL NOT DEFERRABLE + parent INTEGER REFERENCES user(id) ON DELETE CASCADE ) EOS); @@ -72,16 +73,37 @@ public function testListForeignKeysFromExistingDatabase(): void '', ['onUpdate' => 'NO ACTION', 'onDelete' => 'CASCADE', 'deferrable' => false, 'deferred' => false], ), + ]; + + $this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6701'); + + self::assertEquals($expected, $this->schemaManager->listTableForeignKeys('user')); + } + + public function testListForeignKeysWithImplicitColumnsFromIncompleteSchema(): void + { + $this->connection->executeStatement('DROP TABLE IF EXISTS t1'); + $this->connection->executeStatement(<<<'EOS' +CREATE TABLE t1 ( + id INTEGER, + t2_id INTEGER, + FOREIGN KEY (t2_id) REFERENCES t2 +) +EOS); + + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/6701'); + + $expected = [ new ForeignKeyConstraint( - ['log'], - 'log', + ['t2_id'], + 't2', [], - 'FK_3', - ['onUpdate' => 'SET NULL', 'onDelete' => 'NO ACTION', 'deferrable' => false, 'deferred' => false], + '', + ['onUpdate' => 'NO ACTION', 'onDelete' => 'NO ACTION', 'deferrable' => false, 'deferred' => false], ), ]; - self::assertEquals($expected, $this->schemaManager->listTableForeignKeys('user')); + self::assertEquals($expected, $this->schemaManager->listTableForeignKeys('t1')); } public function testColumnCollation(): void