diff --git a/system/Database/SQLite3/Table.php b/system/Database/SQLite3/Table.php index 1884d8c3ed44..3737822eaeeb 100644 --- a/system/Database/SQLite3/Table.php +++ b/system/Database/SQLite3/Table.php @@ -333,9 +333,16 @@ protected function createTable() } foreach ($this->foreignKeys as $foreignKey) { + $foreignTableName = $foreignKey->foreign_table_name; + $prefix = $this->db->DBPrefix; + + if ($prefix !== '' && str_starts_with($foreignTableName, $prefix)) { + $foreignTableName = substr($foreignTableName, strlen($prefix)); + } + $this->forge->addForeignKey( $foreignKey->column_name, - trim($foreignKey->foreign_table_name, $this->db->DBPrefix), + $foreignTableName, $foreignKey->foreign_column_name, ); } diff --git a/tests/system/Database/Live/SQLite3/AlterTableTest.php b/tests/system/Database/Live/SQLite3/AlterTableTest.php index a68d42ca1440..a7ba8ad13f03 100644 --- a/tests/system/Database/Live/SQLite3/AlterTableTest.php +++ b/tests/system/Database/Live/SQLite3/AlterTableTest.php @@ -271,6 +271,68 @@ public function testProcessCopiesOldData(): void $this->seeInDatabase('foo', ['email' => 'funkalicious@example.com']); } + public function testDropColumnKeepsForeignKeyTableNameWhenPrefixIsSet(): void + { + $config = [ + 'DBDriver' => 'SQLite3', + 'database' => ':memory:', + 'DBDebug' => true, + 'DBPrefix' => 'db_', + ]; + + $db = db_connect($config, false); + $this->assertInstanceOf(Connection::class, $db); + + $forge = Database::forge($db); + $this->assertInstanceOf(Forge::class, $forge); + + // The referenced table must begin with a character that also appears in + // the prefix, or the prefix stripping cannot damage its name. + $forge->addField([ + 'id' => [ + 'type' => 'integer', + 'constraint' => 11, + 'unsigned' => true, + 'auto_increment' => true, + ], + ]); + $forge->addPrimaryKey('id'); + $forge->createTable('bandit_fk'); + + $forge->addField([ + 'id' => [ + 'type' => 'integer', + 'constraint' => 11, + 'unsigned' => true, + 'auto_increment' => true, + ], + 'key_id' => [ + 'type' => 'integer', + 'constraint' => 11, + 'unsigned' => true, + ], + 'name' => [ + 'type' => 'varchar', + 'constraint' => 255, + 'null' => true, + ], + ]); + $forge->addPrimaryKey('id'); + $forge->addForeignKey('key_id', 'bandit_fk', 'id'); + $forge->createTable('bandit'); + + // Dropping a column rebuilds the table, recreating its foreign keys. + $this->assertTrue($forge->dropColumn('bandit', 'name')); + + $keys = array_values($db->getForeignKeyData('bandit')); + + $this->assertCount(1, $keys); + $this->assertSame($db->DBPrefix . 'bandit_fk', $keys[0]->foreign_table_name); + + $forge->dropTable('bandit', true); + $forge->dropTable('bandit_fk', true); + } + protected function createTable(string $tableName = 'foo'): void { // Create support table for foreign keys diff --git a/user_guide_src/source/changelogs/v4.7.5.rst b/user_guide_src/source/changelogs/v4.7.5.rst index 47d447dfbbc3..ea56c99af3cd 100644 --- a/user_guide_src/source/changelogs/v4.7.5.rst +++ b/user_guide_src/source/changelogs/v4.7.5.rst @@ -36,6 +36,7 @@ Bugs Fixed - **CLIRequest:** Fixed a bug where ``parseCommand()`` could throw a TypeError when ``argv`` is missing. - **Content Security Policy:** Fixed a bug where empty ``Content-Security-Policy``, ``Content-Security-Policy-Report-Only``, and ``Reporting-Endpoints`` response headers were generated when no corresponding values existed. +- **Database:** Fixed a bug where rebuilding a SQLite3 table (e.g., ``Forge::dropColumn()``, ``Forge::modifyColumn()``, ``Forge::dropForeignKey()`` and ``Forge::dropPrimaryKey()``) corrupted the table names referenced by its foreign keys when ``DBPrefix`` was set. - **Helpers:** Fixed a bug where ``get_dir_file_info()`` returned incomplete entries for subdirectories and missing files instead of omitting them. - **Honeypot:** Fixed a bug where bot detection returned an HTTP 500 response instead of 403 (Forbidden). - **Logger:** Fixed a bug where interpolating a log message with array or non-stringable context values could raise PHP warnings or errors.