Skip to content

Commit dd42a1a

Browse files
committed
Redesign BatchUpdate() SQL
1 parent c82cbd6 commit dd42a1a

File tree

7 files changed

+181
-106
lines changed

7 files changed

+181
-106
lines changed

system/Database/BaseBuilder.php

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,30 +2202,44 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc
22022202
*/
22032203
protected function _updateBatch(string $table, array $values, string $index): string
22042204
{
2205-
$ids = [];
2206-
$final = [];
2205+
$keys = array_keys(current($values));
22072206

2208-
foreach ($values as $val) {
2209-
$ids[] = $val[$index];
2207+
// make array for future use with composite keys - `field`
2208+
// future: $this->QBOptions['constraints']
2209+
$constraints = [$index];
22102210

2211-
foreach (array_keys($val) as $field) {
2212-
if ($field !== $index) {
2213-
$final[$field][] = 'WHEN ' . $index . ' = ' . $val[$index] . ' THEN ' . $val[$field];
2214-
}
2215-
}
2216-
}
2211+
// future: $this->QBOptions['updateFields']
2212+
$updateFields = array_filter($keys, static fn ($index) => ! in_array($index, $constraints, true));
22172213

2218-
$cases = '';
2214+
$sql = 'UPDATE ' . $this->compileIgnore('update') . $table . "\n";
22192215

2220-
foreach ($final as $k => $v) {
2221-
$cases .= $k . " = CASE \n"
2222-
. implode("\n", $v) . "\n"
2223-
. 'ELSE ' . $k . ' END, ';
2224-
}
2216+
$sql .= 'SET' . "\n";
22252217

2226-
$this->where($index . ' IN(' . implode(',', $ids) . ')', null, false);
2218+
$sql .= implode(
2219+
",\n",
2220+
array_map(static fn ($key) => $key . ' = u.' . $key, $updateFields)
2221+
) . "\n";
22272222

2228-
return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
2223+
$sql .= 'FROM (' . "\n";
2224+
2225+
$sql .= implode(
2226+
" UNION ALL\n",
2227+
array_map(
2228+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
2229+
static fn ($key, $index) => $index . ' ' . $key,
2230+
$keys,
2231+
$value
2232+
)),
2233+
$values
2234+
)
2235+
) . "\n";
2236+
2237+
$sql .= ') u' . "\n";
2238+
2239+
return $sql .= 'WHERE ' . implode(
2240+
' AND ',
2241+
array_map(static fn ($key) => $table . '.' . $key . ' = u.' . $key, $constraints)
2242+
);
22292243
}
22302244

22312245
/**

system/Database/MySQLi/Builder.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,49 @@ protected function _fromTables(): string
5353

5454
return implode(', ', $this->QBFrom);
5555
}
56+
57+
/**
58+
* Generates a platform-specific batch update string from the supplied data
59+
*/
60+
protected function _updateBatch(string $table, array $values, string $index): string
61+
{
62+
$keys = array_keys(current($values));
63+
64+
// make array for future use with composite keys - `field`
65+
// future: $this->QBOptions['constraints']
66+
$constraints = [$index];
67+
68+
// future: $this->QBOptions['updateFields']
69+
$updateFields = array_filter($keys, static fn ($index) => ! in_array($index, $constraints, true));
70+
71+
$sql = 'UPDATE ' . $this->compileIgnore('update') . $table . " AS t\n";
72+
73+
$sql .= 'INNER JOIN (' . "\n";
74+
75+
$sql .= implode(
76+
" UNION ALL\n",
77+
array_map(
78+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
79+
static fn ($key, $index) => $index . ' ' . $key,
80+
$keys,
81+
$value
82+
)),
83+
$values
84+
)
85+
) . "\n";
86+
87+
$sql .= ') u' . "\n";
88+
89+
$sql .= 'ON ' . implode(
90+
' AND ',
91+
array_map(static fn ($key) => 't.' . $key . ' = u.' . $key, $constraints)
92+
) . "\n";
93+
94+
$sql .= 'SET' . "\n";
95+
96+
return $sql .= implode(
97+
",\n",
98+
array_map(static fn ($key) => 't.' . $key . ' = u.' . $key, $updateFields)
99+
);
100+
}
56101
}

system/Database/OCI8/Builder.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,53 @@ protected function resetSelect()
227227
$this->limitUsed = false;
228228
parent::resetSelect();
229229
}
230+
231+
/**
232+
* Generates a platform-specific batch update string from the supplied data
233+
*/
234+
protected function _updateBatch(string $table, array $values, string $index): string
235+
{
236+
$keys = array_keys(current($values));
237+
238+
// make array for future use with composite keys - `field`
239+
// future: $this->QBOptions['constraints']
240+
$constraints = [$index];
241+
242+
// future: $this->QBOptions['updateFields']
243+
$updateFields = array_filter($keys, static fn ($index) => ! in_array($index, $constraints, true));
244+
245+
// Oracle doesn't support ignore on updates so we will use MERGE
246+
$sql = 'MERGE INTO ' . $table . " \"t\"\n";
247+
248+
$sql .= 'USING (' . "\n";
249+
250+
$sql .= implode(
251+
" UNION ALL\n",
252+
array_map(
253+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
254+
static fn ($key, $index) => $index . ' ' . $key,
255+
$keys,
256+
$value
257+
)) . ' FROM DUAL',
258+
},
259+
$values
260+
)
261+
) . "\n";
262+
263+
$sql .= ') "u"' . "\n";
264+
265+
$sql .= 'ON (' . implode(
266+
' AND ',
267+
array_map(static fn ($key) => '"t".' . $key . ' = "u".' . $key, $constraints)
268+
) . ")\n";
269+
270+
$sql .= "WHEN MATCHED THEN UPDATE\n";
271+
272+
$sql .= 'SET' . "\n";
273+
274+
return $sql .= implode(
275+
",\n",
276+
array_map(static fn ($key) => '"t".' . $key . ' = "u".' . $key, $updateFields)
277+
);
278+
}
230279
}

system/Database/Postgre/Builder.php

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -241,39 +241,6 @@ protected function _update(string $table, array $values): string
241241
return parent::_update($table, $values);
242242
}
243243

244-
/**
245-
* Generates a platform-specific batch update string from the supplied data
246-
*/
247-
protected function _updateBatch(string $table, array $values, string $index): string
248-
{
249-
$ids = [];
250-
$final = [];
251-
252-
foreach ($values as $val) {
253-
$ids[] = $val[$index];
254-
255-
foreach (array_keys($val) as $field) {
256-
if ($field !== $index) {
257-
$final[$field] ??= [];
258-
259-
$final[$field][] = "WHEN {$val[$index]} THEN {$val[$field]}";
260-
}
261-
}
262-
}
263-
264-
$cases = '';
265-
266-
foreach ($final as $k => $v) {
267-
$cases .= "{$k} = (CASE {$index}\n"
268-
. implode("\n", $v)
269-
. "\nELSE {$k} END), ";
270-
}
271-
272-
$this->where("{$index} IN(" . implode(',', $ids) . ')', null, false);
273-
274-
return "UPDATE {$table} SET " . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
275-
}
276-
277244
/**
278245
* Generates a platform-specific delete string from the supplied data
279246
*/

system/Database/SQLSRV/Builder.php

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -205,39 +205,6 @@ protected function _update(string $table, array $values): string
205205
return $this->keyPermission ? $this->addIdentity($fullTableName, $statement) : $statement;
206206
}
207207

208-
/**
209-
* Update_Batch statement
210-
*
211-
* Generates a platform-specific batch update string from the supplied data
212-
*/
213-
protected function _updateBatch(string $table, array $values, string $index): string
214-
{
215-
$ids = [];
216-
$final = [];
217-
218-
foreach ($values as $val) {
219-
$ids[] = $val[$index];
220-
221-
foreach (array_keys($val) as $field) {
222-
if ($field !== $index) {
223-
$final[$field][] = 'WHEN ' . $index . ' = ' . $val[$index] . ' THEN ' . $val[$field];
224-
}
225-
}
226-
}
227-
228-
$cases = '';
229-
230-
foreach ($final as $k => $v) {
231-
$cases .= $k . " = CASE \n"
232-
. implode("\n", $v) . "\n"
233-
. 'ELSE ' . $k . ' END, ';
234-
}
235-
236-
$this->where($index . ' IN(' . implode(',', $ids) . ')', null, false);
237-
238-
return 'UPDATE ' . $this->compileIgnore('update') . ' ' . $this->getFullName($table) . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
239-
}
240-
241208
/**
242209
* Increments a numeric column by the specified value.
243210
*

system/Database/SQLite3/Builder.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,39 @@ protected function _truncate(string $table): string
7070
{
7171
return 'DELETE FROM ' . $table;
7272
}
73+
74+
/**
75+
* Generates a platform-specific batch update string from the supplied data
76+
*/
77+
protected function _updateBatch(string $table, array $values, string $index): string
78+
{
79+
if ((float) $this->db->getVersion() >= 3.33) {
80+
return parent::_updateBatch($table, $values, $index);
81+
}
82+
83+
$ids = [];
84+
$final = [];
85+
86+
foreach ($values as $val) {
87+
$ids[] = $val[$index];
88+
89+
foreach (array_keys($val) as $field) {
90+
if ($field !== $index) {
91+
$final[$field][] = 'WHEN ' . $index . ' = ' . $val[$index] . ' THEN ' . $val[$field];
92+
}
93+
}
94+
}
95+
96+
$cases = '';
97+
98+
foreach ($final as $k => $v) {
99+
$cases .= $k . " = CASE \n"
100+
. implode("\n", $v) . "\n"
101+
. 'ELSE ' . $k . ' END, ';
102+
}
103+
104+
$this->where($index . ' IN(' . implode(',', $ids) . ')', null, false);
105+
106+
return 'UPDATE ' . $this->compileIgnore('update') . $table . ' SET ' . substr($cases, 0, -2) . $this->compileWhereHaving('QBWhere');
107+
}
73108
}

tests/system/Database/Builder/UpdateTest.php

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,16 @@ public function testUpdateBatch()
231231
$query = $this->db->getLastQuery();
232232
$this->assertInstanceOf(MockQuery::class, $query);
233233

234-
$space = ' ';
235-
236-
$expected = <<<EOF
237-
UPDATE "jobs" SET "name" = CASE{$space}
238-
WHEN "id" = 2 THEN 'Comedian'
239-
WHEN "id" = 3 THEN 'Cab Driver'
240-
ELSE "name" END, "description" = CASE{$space}
241-
WHEN "id" = 2 THEN 'There''s something in your teeth'
242-
WHEN "id" = 3 THEN 'I am yellow'
243-
ELSE "description" END
244-
WHERE "id" IN(2,3)
234+
$expected = <<<'EOF'
235+
UPDATE "jobs"
236+
SET
237+
"name" = u."name",
238+
"description" = u."description"
239+
FROM (
240+
SELECT 2 "id", 'Comedian' "name", 'There''s something in your teeth' "description" UNION ALL
241+
SELECT 3 "id", 'Cab Driver' "name", 'I am yellow' "description"
242+
) u
243+
WHERE "jobs"."id" = u."id"
245244
EOF;
246245

247246
$this->assertSame($expected, $query->getQuery());
@@ -271,17 +270,16 @@ public function testSetUpdateBatchWithoutEscape()
271270
$query = $this->db->getLastQuery();
272271
$this->assertInstanceOf(MockQuery::class, $query);
273272

274-
$space = ' ';
275-
276-
$expected = <<<EOF
277-
UPDATE "jobs" SET "name" = CASE{$space}
278-
WHEN "id" = 2 THEN SUBSTRING(name, 1)
279-
WHEN "id" = 3 THEN SUBSTRING(name, 2)
280-
ELSE "name" END, "description" = CASE{$space}
281-
WHEN "id" = 2 THEN SUBSTRING(description, 3)
282-
WHEN "id" = 3 THEN SUBSTRING(description, 4)
283-
ELSE "description" END
284-
WHERE "id" IN(2,3)
273+
$expected = <<<'EOF'
274+
UPDATE "jobs"
275+
SET
276+
"name" = u."name",
277+
"description" = u."description"
278+
FROM (
279+
SELECT 2 "id", SUBSTRING(name, 1) "name", SUBSTRING(description, 3) "description" UNION ALL
280+
SELECT 3 "id", SUBSTRING(name, 2) "name", SUBSTRING(description, 4) "description"
281+
) u
282+
WHERE "jobs"."id" = u."id"
285283
EOF;
286284

287285
$this->assertSame($expected, $query->getQuery());

0 commit comments

Comments
 (0)