diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 27a72b58c3..8d0d569d5e 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -469,9 +469,8 @@ class Builder extends BaseBuilder return ($index->type === 'UNIQUE' || $index->type === 'PRIMARY') && $hasAllFields; }); - foreach (array_map(static fn ($index) => $index->fields, $allIndexes) as $index) { - $constraints[] = current($index); - // only one index can be used? + foreach ($allIndexes as $index) { + $constraints = $index->fields; break; } diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php index 6e62907ac1..3aa13edeb9 100644 --- a/system/Database/SQLite3/Builder.php +++ b/system/Database/SQLite3/Builder.php @@ -151,8 +151,8 @@ class Builder extends BaseBuilder return ($index->type === 'PRIMARY' || $index->type === 'UNIQUE') && $hasAllFields; }); - foreach (array_map(static fn ($index) => $index->fields, $allIndexes) as $index) { - $constraints[] = current($index); + foreach ($allIndexes as $index) { + $constraints = $index->fields; break; } diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 8a7cb5b7d1..6af55fa7c9 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -47,6 +47,15 @@ class Migration_Create_test_tables extends Migration 'value' => ['type' => 'VARCHAR', 'constraint' => 400, 'null' => true], ])->addKey('id', true)->createTable('misc', true); + // Team members Table (composite key) + $this->forge->addField([ + 'team_id' => ['type' => 'INTEGER', 'constraint' => 3], + 'person_id' => ['type' => 'INTEGER', 'constraint' => 3], + 'role' => ['type' => 'VARCHAR', 'constraint' => 40], + 'status' => ['type' => 'VARCHAR', 'constraint' => 40], + 'created_at' => ['type' => 'DATETIME', 'null' => true], + ])->addUniqueKey(['team_id', 'person_id'])->createTable('team_members', true); + // Database Type test table // missing types: // TINYINT,MEDIUMINT,BIT,YEAR,BINARY,VARBINARY,TINYTEXT,LONGTEXT, diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index 9ce0bce4f1..09dba65f15 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -107,6 +107,20 @@ class CITestSeeder extends Seeder 'value' => 'ടൈപ്പ്', ], ], + 'team_members' => [ + [ + 'team_id' => 1, + 'person_id' => 22, + 'role' => 'member', + 'status' => 'active', + ], + [ + 'team_id' => 1, + 'person_id' => 33, + 'role' => 'mentor', + 'status' => 'active', + ], + ], 'type_test' => [ [ 'type_varchar' => 'test', diff --git a/tests/system/Database/Live/MetadataTest.php b/tests/system/Database/Live/MetadataTest.php index 0488dd2f4a..643d0689fd 100644 --- a/tests/system/Database/Live/MetadataTest.php +++ b/tests/system/Database/Live/MetadataTest.php @@ -47,6 +47,7 @@ final class MetadataTest extends CIUnitTestCase $prefix . 'user', $prefix . 'job', $prefix . 'misc', + $prefix . 'team_members', $prefix . 'type_test', $prefix . 'empty', $prefix . 'secondary', diff --git a/tests/system/Database/Live/UpsertTest.php b/tests/system/Database/Live/UpsertTest.php index a702ff5e1b..000fa6fec7 100644 --- a/tests/system/Database/Live/UpsertTest.php +++ b/tests/system/Database/Live/UpsertTest.php @@ -448,7 +448,7 @@ final class UpsertTest extends CIUnitTestCase $this->assertSame('El Salvador', $data[4]->country); } - public function testUpsertWithMatchingDataOnUniqueIndexandPrimaryKey(): void + public function testUpsertWithMatchingDataOnUniqueIndexAndPrimaryKey(): void { $data = [ 'id' => 6, @@ -607,6 +607,36 @@ final class UpsertTest extends CIUnitTestCase } } + /** + * @see https://github.com/codeigniter4/CodeIgniter4/issues/9450 + */ + public function testUpsertBatchCompositeUniqueIndex(): void + { + $data = [ + [ + 'team_id' => 1, + 'person_id' => 22, + 'role' => 'leader', + 'status' => 'active', + ], + [ + 'team_id' => 1, + 'person_id' => 33, + 'role' => 'member', + 'status' => 'active', + ], + ]; + + // uses (team_id, person_id) - composite unique index + $this->db->table('team_members')->upsertBatch($data); + + $this->seeInDatabase('team_members', ['team_id' => 1, 'person_id' => 22, 'role' => 'leader']); + $this->dontSeeInDatabase('team_members', ['team_id' => 1, 'person_id' => 22, 'role' => 'member']); + + $this->seeInDatabase('team_members', ['team_id' => 1, 'person_id' => 33, 'role' => 'member']); + $this->dontSeeInDatabase('team_members', ['team_id' => 1, 'person_id' => 33, 'role' => 'mentor']); + } + public function testSetBatchOneRow(): void { $data = [ diff --git a/user_guide_src/source/changelogs/v4.6.1.rst b/user_guide_src/source/changelogs/v4.6.1.rst index 9a3ccfe764..9a5ea391d4 100644 --- a/user_guide_src/source/changelogs/v4.6.1.rst +++ b/user_guide_src/source/changelogs/v4.6.1.rst @@ -32,6 +32,7 @@ Bugs Fixed - **CURLRequest:** Fixed an issue where multiple header sections appeared in the CURL response body during multiple redirects from the target server. - **Cors:** Fixed a bug in the Cors filter that caused the appropriate headers to not be added when another filter returned a response object in the ``before`` filter. +- **Database:** Fixed a bug in ``Postgre`` and ``SQLite3`` handlers where composite unique keys were not fully taken into account for ``upsert`` type of queries. See the repo's `CHANGELOG.md `_ diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index 6e4b1359c8..31515768d5 100644 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -937,6 +937,10 @@ constraint by default. Here is an example using an array: .. literalinclude:: query_builder/112.php +.. note:: For databases other than MySQL, if a table has multiple keys (primary or unique), + the primary key will be prioritized by default when handling constraints. If you prefer + to use a different unique key instead of the primary key, use the ``onConstraint()`` method. + The first parameter is an associative array of values. Here is an example using an object: