Skip to content

Commit 686e9ae

Browse files
authored
fix: upsert with composite unique index (#9454)
* fix: upsert with composite unique index * cs fix * update tests * docs: handling constraints for databases other than MySQL
1 parent 236dca3 commit 686e9ae

File tree

8 files changed

+64
-6
lines changed

8 files changed

+64
-6
lines changed

system/Database/Postgre/Builder.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,9 +469,8 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
469469
return ($index->type === 'UNIQUE' || $index->type === 'PRIMARY') && $hasAllFields;
470470
});
471471

472-
foreach (array_map(static fn ($index) => $index->fields, $allIndexes) as $index) {
473-
$constraints[] = current($index);
474-
// only one index can be used?
472+
foreach ($allIndexes as $index) {
473+
$constraints = $index->fields;
475474
break;
476475
}
477476

system/Database/SQLite3/Builder.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ protected function _upsertBatch(string $table, array $keys, array $values): stri
151151
return ($index->type === 'PRIMARY' || $index->type === 'UNIQUE') && $hasAllFields;
152152
});
153153

154-
foreach (array_map(static fn ($index) => $index->fields, $allIndexes) as $index) {
155-
$constraints[] = current($index);
154+
foreach ($allIndexes as $index) {
155+
$constraints = $index->fields;
156156
break;
157157
}
158158

tests/_support/Database/Migrations/20160428212500_Create_test_tables.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ public function up(): void
4747
'value' => ['type' => 'VARCHAR', 'constraint' => 400, 'null' => true],
4848
])->addKey('id', true)->createTable('misc', true);
4949

50+
// Team members Table (composite key)
51+
$this->forge->addField([
52+
'team_id' => ['type' => 'INTEGER', 'constraint' => 3],
53+
'person_id' => ['type' => 'INTEGER', 'constraint' => 3],
54+
'role' => ['type' => 'VARCHAR', 'constraint' => 40],
55+
'status' => ['type' => 'VARCHAR', 'constraint' => 40],
56+
'created_at' => ['type' => 'DATETIME', 'null' => true],
57+
])->addUniqueKey(['team_id', 'person_id'])->createTable('team_members', true);
58+
5059
// Database Type test table
5160
// missing types:
5261
// TINYINT,MEDIUMINT,BIT,YEAR,BINARY,VARBINARY,TINYTEXT,LONGTEXT,

tests/_support/Database/Seeds/CITestSeeder.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ public function run(): void
107107
'value' => 'ടൈപ്പ്',
108108
],
109109
],
110+
'team_members' => [
111+
[
112+
'team_id' => 1,
113+
'person_id' => 22,
114+
'role' => 'member',
115+
'status' => 'active',
116+
],
117+
[
118+
'team_id' => 1,
119+
'person_id' => 33,
120+
'role' => 'mentor',
121+
'status' => 'active',
122+
],
123+
],
110124
'type_test' => [
111125
[
112126
'type_varchar' => 'test',

tests/system/Database/Live/MetadataTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ protected function setUp(): void
4747
$prefix . 'user',
4848
$prefix . 'job',
4949
$prefix . 'misc',
50+
$prefix . 'team_members',
5051
$prefix . 'type_test',
5152
$prefix . 'empty',
5253
$prefix . 'secondary',

tests/system/Database/Live/UpsertTest.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ public function testUpsertBatchWithOnConflictAndUpdateFields(): void
448448
$this->assertSame('El Salvador', $data[4]->country);
449449
}
450450

451-
public function testUpsertWithMatchingDataOnUniqueIndexandPrimaryKey(): void
451+
public function testUpsertWithMatchingDataOnUniqueIndexAndPrimaryKey(): void
452452
{
453453
$data = [
454454
'id' => 6,
@@ -607,6 +607,36 @@ public function testUpsertBatchMultipleConstraints(): void
607607
}
608608
}
609609

610+
/**
611+
* @see https://github.com/codeigniter4/CodeIgniter4/issues/9450
612+
*/
613+
public function testUpsertBatchCompositeUniqueIndex(): void
614+
{
615+
$data = [
616+
[
617+
'team_id' => 1,
618+
'person_id' => 22,
619+
'role' => 'leader',
620+
'status' => 'active',
621+
],
622+
[
623+
'team_id' => 1,
624+
'person_id' => 33,
625+
'role' => 'member',
626+
'status' => 'active',
627+
],
628+
];
629+
630+
// uses (team_id, person_id) - composite unique index
631+
$this->db->table('team_members')->upsertBatch($data);
632+
633+
$this->seeInDatabase('team_members', ['team_id' => 1, 'person_id' => 22, 'role' => 'leader']);
634+
$this->dontSeeInDatabase('team_members', ['team_id' => 1, 'person_id' => 22, 'role' => 'member']);
635+
636+
$this->seeInDatabase('team_members', ['team_id' => 1, 'person_id' => 33, 'role' => 'member']);
637+
$this->dontSeeInDatabase('team_members', ['team_id' => 1, 'person_id' => 33, 'role' => 'mentor']);
638+
}
639+
610640
public function testSetBatchOneRow(): void
611641
{
612642
$data = [

user_guide_src/source/changelogs/v4.6.1.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Bugs Fixed
3232

3333
- **CURLRequest:** Fixed an issue where multiple header sections appeared in the CURL response body during multiple redirects from the target server.
3434
- **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.
35+
- **Database:** Fixed a bug in ``Postgre`` and ``SQLite3`` handlers where composite unique keys were not fully taken into account for ``upsert`` type of queries.
3536

3637
See the repo's
3738
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_

user_guide_src/source/database/query_builder.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,10 @@ constraint by default. Here is an example using an array:
937937

938938
.. literalinclude:: query_builder/112.php
939939

940+
.. note:: For databases other than MySQL, if a table has multiple keys (primary or unique),
941+
the primary key will be prioritized by default when handling constraints. If you prefer
942+
to use a different unique key instead of the primary key, use the ``onConstraint()`` method.
943+
940944
The first parameter is an associative array of values.
941945

942946
Here is an example using an object:

0 commit comments

Comments
 (0)