Skip to content

Commit 1423fda

Browse files
committed
Refactor base builder (#6)
1 parent 44524ee commit 1423fda

File tree

20 files changed

+2441
-267
lines changed

20 files changed

+2441
-267
lines changed

system/Database/BaseBuilder.php

Lines changed: 500 additions & 151 deletions
Large diffs are not rendered by default.

system/Database/MySQLi/Builder.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace CodeIgniter\Database\MySQLi;
1313

1414
use CodeIgniter\Database\BaseBuilder;
15+
use CodeIgniter\Database\Exceptions\DatabaseException;
16+
use CodeIgniter\Database\RawSql;
1517

1618
/**
1719
* Builder for MySQLi
@@ -53,4 +55,80 @@ protected function _fromTables(): string
5355

5456
return implode(', ', $this->QBFrom);
5557
}
58+
59+
/**
60+
* Generates a platform-specific batch update string from the supplied data
61+
*/
62+
protected function _updateBatch(string $table, array $keys, array $values): string
63+
{
64+
$sql = $this->QBOptions['sql'] ?? '';
65+
66+
// if this is the first iteration of batch then we need to build skeleton sql
67+
if ($sql === '') {
68+
$constraints = $this->QBOptions['constraints'] ?? [];
69+
70+
if ($constraints === []) {
71+
if ($this->db->DBDebug) {
72+
throw new DatabaseException('You must specify a constraint to match on for batch updates.'); // @codeCoverageIgnore
73+
}
74+
75+
return ''; // @codeCoverageIgnore
76+
}
77+
78+
$updateFields = $this->QBOptions['updateFields'] ??
79+
$this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ??
80+
[];
81+
82+
$alias = $this->QBOptions['alias'] ?? '`_u`';
83+
84+
$sql = 'UPDATE ' . $this->compileIgnore('update') . $table . "\n";
85+
86+
$sql .= 'INNER JOIN (' . "\n%s";
87+
88+
$sql .= ') ' . $alias . "\n";
89+
90+
$sql .= 'ON ' . implode(
91+
' AND ',
92+
array_map(
93+
static fn ($key) => ($key instanceof RawSql ?
94+
$key :
95+
$table . '.' . $key . ' = ' . $alias . '.' . $key),
96+
$constraints
97+
)
98+
) . "\n";
99+
100+
$sql .= 'SET' . "\n";
101+
102+
$sql .= implode(
103+
",\n",
104+
array_map(
105+
static fn ($key, $value) => $table . '.' . $key . ($value instanceof RawSql ?
106+
' = ' . $value :
107+
' = ' . $alias . '.' . $value),
108+
array_keys($updateFields),
109+
$updateFields
110+
)
111+
);
112+
113+
$this->QBOptions['sql'] = $sql;
114+
}
115+
116+
if (isset($this->QBOptions['fromQuery'])) {
117+
$data = $this->QBOptions['fromQuery'];
118+
} else {
119+
$data = implode(
120+
" UNION ALL\n",
121+
array_map(
122+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
123+
static fn ($key, $index) => $index . ' ' . $key,
124+
$keys,
125+
$value
126+
)),
127+
$values
128+
)
129+
) . "\n";
130+
}
131+
132+
return sprintf($sql, $data);
133+
}
56134
}

system/Database/OCI8/Builder.php

Lines changed: 207 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use CodeIgniter\Database\BaseBuilder;
1515
use CodeIgniter\Database\Exceptions\DatabaseException;
16+
use CodeIgniter\Database\RawSql;
1617

1718
/**
1819
* Builder for OCI8
@@ -67,29 +68,141 @@ class Builder extends BaseBuilder
6768
*/
6869
protected function _insertBatch(string $table, array $keys, array $values): string
6970
{
70-
$insertKeys = implode(', ', $keys);
71-
$hasPrimaryKey = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true);
71+
$sql = $this->QBOptions['sql'] ?? '';
7272

73-
// ORA-00001 measures
74-
if ($hasPrimaryKey) {
75-
$sql = 'INSERT INTO ' . $table . ' (' . $insertKeys . ") \n SELECT * FROM (\n";
76-
$selectQueryValues = [];
73+
// if this is the first iteration of batch then we need to build skeleton sql
74+
if ($sql === '') {
75+
$insertKeys = implode(', ', $keys);
76+
$hasPrimaryKey = in_array('PRIMARY', array_column($this->db->getIndexData($table), 'type'), true);
7777

78-
foreach ($values as $value) {
79-
$selectValues = implode(',', array_map(static fn ($value, $key) => $value . ' as ' . $key, explode(',', substr(substr($value, 1), 0, -1)), $keys));
80-
$selectQueryValues[] = 'SELECT ' . $selectValues . ' FROM DUAL';
81-
}
78+
// ORA-00001 measures
79+
$sql = 'INSERT' . ($hasPrimaryKey ? '' : ' ALL') . ' INTO ' . $table . ' (' . $insertKeys . ")\n%s";
80+
81+
$this->QBOptions['sql'] = $sql;
82+
}
8283

83-
return $sql . implode("\n UNION ALL \n", $selectQueryValues) . "\n)";
84+
if (isset($this->QBOptions['fromQuery'])) {
85+
$data = $this->QBOptions['fromQuery'];
86+
} else {
87+
$data = implode(
88+
" FROM DUAL UNION ALL\n",
89+
array_map(
90+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
91+
static fn ($key, $index) => $index . ' ' . $key,
92+
$keys,
93+
$value
94+
)),
95+
$values
96+
)
97+
) . " FROM DUAL\n";
8498
}
8599

86-
$sql = "INSERT ALL\n";
100+
return sprintf($sql, $data);
101+
}
102+
103+
/**
104+
* Generates a platform-specific upsertBatch string from the supplied data
105+
*
106+
* @throws DatabaseException
107+
*/
108+
protected function _upsertBatch(string $table, array $keys, array $values): string
109+
{
110+
$sql = $this->QBOptions['sql'] ?? '';
111+
112+
// if this is the first iteration of batch then we need to build skeleton sql
113+
if ($sql === '') {
114+
$constraints = $this->QBOptions['constraints'] ?? [];
115+
116+
if (empty($constraints)) {
117+
$fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys);
118+
119+
$uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames) {
120+
$hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields);
121+
122+
return ($index->type === 'PRIMARY' || $index->type === 'UNIQUE') && $hasAllFields;
123+
});
124+
125+
// only take first index
126+
foreach ($uniqueIndexes as $index) {
127+
$constraints = $index->fields;
128+
break;
129+
}
130+
131+
$constraints = $this->onConstraint($constraints)->QBOptions['constraints'] ?? [];
132+
}
133+
134+
if (empty($constraints)) {
135+
if ($this->db->DBDebug) {
136+
throw new DatabaseException('No constraint found for upsert.');
137+
}
138+
139+
return ''; // @codeCoverageIgnore
140+
}
141+
142+
$updateFields = $this->QBOptions['updateFields'] ?? $this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ?? [];
143+
144+
if (empty($updateFields)) {
145+
$updateFields = array_filter(
146+
$keys,
147+
static fn ($columnName) => ! (in_array($columnName, $constraints, true))
148+
);
149+
150+
$this->QBOptions['updateFields'] = $updateFields;
151+
}
152+
153+
$sql = 'MERGE INTO ' . $table . "\nUSING (\n%s";
154+
155+
$sql .= ') "_upsert"' . "\nON (";
156+
157+
$sql .= implode(
158+
' AND ',
159+
array_map(
160+
static fn ($key) => ($key instanceof RawSql ?
161+
$key :
162+
$table . '.' . $key . ' = ' . '"_upsert".' . $key),
163+
$constraints
164+
)
165+
) . ")\n";
166+
167+
$sql .= "WHEN MATCHED THEN UPDATE SET\n";
87168

88-
foreach ($values as $value) {
89-
$sql .= ' INTO ' . $table . ' (' . $insertKeys . ') VALUES ' . $value . "\n";
169+
$sql .= implode(
170+
",\n",
171+
array_map(
172+
static fn ($key, $value) => $key . ($value instanceof RawSql ?
173+
' = ' . $value :
174+
' = ' . '"_upsert".' . $value),
175+
array_keys($updateFields),
176+
$updateFields
177+
)
178+
);
179+
180+
$sql .= "\nWHEN NOT MATCHED THEN INSERT (" . implode(', ', $keys) . ")\nVALUES ";
181+
182+
$sql .= (' ('
183+
. implode(', ', array_map(static fn ($columnName) => '"_upsert".' . $columnName, $keys))
184+
. ')');
185+
186+
$this->QBOptions['sql'] = $sql;
187+
}
188+
189+
if (isset($this->QBOptions['fromQuery'])) {
190+
$data = $this->QBOptions['fromQuery'];
191+
} else {
192+
$data = implode(
193+
" FROM DUAL UNION ALL\n",
194+
array_map(
195+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
196+
static fn ($key, $index) => $index . ' ' . $key,
197+
$keys,
198+
$value
199+
)),
200+
$values
201+
)
202+
) . " FROM DUAL\n";
90203
}
91204

92-
return $sql . 'SELECT * FROM DUAL';
205+
return sprintf($sql, $data);
93206
}
94207

95208
/**
@@ -227,4 +340,83 @@ protected function resetSelect()
227340
$this->limitUsed = false;
228341
parent::resetSelect();
229342
}
343+
344+
/**
345+
* Generates a platform-specific batch update string from the supplied data
346+
*/
347+
protected function _updateBatch(string $table, array $keys, array $values): string
348+
{
349+
$sql = $this->QBOptions['sql'] ?? '';
350+
351+
// if this is the first iteration of batch then we need to build skeleton sql
352+
if ($sql === '') {
353+
$constraints = $this->QBOptions['constraints'] ?? [];
354+
355+
if ($constraints === []) {
356+
if ($this->db->DBDebug) {
357+
throw new DatabaseException('You must specify a constraint to match on for batch updates.');
358+
}
359+
360+
return ''; // @codeCoverageIgnore
361+
}
362+
363+
$updateFields = $this->QBOptions['updateFields'] ??
364+
$this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ??
365+
[];
366+
367+
$alias = $this->QBOptions['alias'] ?? '"_u"';
368+
369+
// Oracle doesn't support ignore on updates so we will use MERGE
370+
$sql = 'MERGE INTO ' . $table . "\n";
371+
372+
$sql .= 'USING (' . "\n%s";
373+
374+
$sql .= ') ' . $alias . "\n";
375+
376+
$sql .= 'ON (' . implode(
377+
' AND ',
378+
array_map(
379+
static fn ($key) => ($key instanceof RawSql ?
380+
$key :
381+
$table . '.' . $key . ' = ' . $alias . '.' . $key),
382+
$constraints
383+
)
384+
) . ")\n";
385+
386+
$sql .= "WHEN MATCHED THEN UPDATE\n";
387+
388+
$sql .= 'SET' . "\n";
389+
390+
$sql .= implode(
391+
",\n",
392+
array_map(
393+
static fn ($key, $value) => $table . '.' . $key . ($value instanceof RawSql ?
394+
' = ' . $value :
395+
' = ' . $alias . '.' . $value),
396+
array_keys($updateFields),
397+
$updateFields
398+
)
399+
);
400+
401+
$this->QBOptions['sql'] = $sql;
402+
}
403+
404+
if (isset($this->QBOptions['fromQuery'])) {
405+
$data = $this->QBOptions['fromQuery'];
406+
} else {
407+
$data = implode(
408+
" UNION ALL\n",
409+
array_map(
410+
static fn ($value) => 'SELECT ' . implode(', ', array_map(
411+
static fn ($key, $index) => $index . ' ' . $key,
412+
$keys,
413+
$value
414+
)) . ' FROM DUAL',
415+
$values
416+
)
417+
) . "\n";
418+
}
419+
420+
return sprintf($sql, $data);
421+
}
230422
}

system/Database/OCI8/Forge.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ protected function _attributeAutoIncrement(array &$attributes, array &$field)
163163
&& stripos($field['type'], 'NUMBER') !== false
164164
&& version_compare($this->db->getVersion(), '12.1', '>=')
165165
) {
166-
$field['auto_increment'] = ' GENERATED BY DEFAULT AS IDENTITY';
166+
$field['auto_increment'] = ' GENERATED BY DEFAULT ON NULL AS IDENTITY';
167167
}
168168
}
169169

0 commit comments

Comments
 (0)