diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 9d43e5577943..c4a3d851447c 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -130,7 +130,7 @@ class BaseBuilder /** * QB data sets * - * @var array|list> + * @var array|list> */ protected $QBSet = []; @@ -1852,8 +1852,16 @@ public function setData($set, ?bool $escape = null, string $alias = '') $clean = []; - foreach ($row as $rowValue) { - $clean[] = $escape ? $this->db->escape($rowValue) : $rowValue; + foreach ($row as $key => $rowValue) { + [$keyName, $keyType] = $this->parseKey($key); + + $escapedValue = $escape ? $this->db->escape($rowValue) : $rowValue; + + if ($keyType !== null) { + $clean[] = new SqlValue($escapedValue, $keyType); + } else { + $clean[] = $escapedValue; + } } $row = $clean; @@ -1862,16 +1870,32 @@ public function setData($set, ?bool $escape = null, string $alias = '') } foreach ($keys as $k) { - $k = $this->db->protectIdentifiers($k, false); + [$keyName] = $this->parseKey($k); - if (! in_array($k, $this->QBKeys, true)) { - $this->QBKeys[] = $k; + $keyName = $this->db->protectIdentifiers($keyName, false); + + if (! in_array($keyName, $this->QBKeys, true)) { + $this->QBKeys[] = $keyName; } } return $this; } + /** + * Parses column name (with type) and returns name and type + * Key examples: + * - 'updated_at::TIMESTAMP' + */ + private function parseKey(string $key): array + { + $keyInfo = explode('::', $key, 2); + $keyName = $keyInfo[0]; + $keyType = $keyInfo[1] ?? null; + + return [$keyName, $keyType]; + } + /** * Compiles an upsert query and returns the sql * @@ -2560,9 +2584,9 @@ public function updateBatch($set = null, $constraints = null, int $batchSize = 1 * * @used-by batchExecute * - * @param string $table Protected table name - * @param list $keys QBKeys - * @param list> $values QBSet + * @param string $table Protected table name + * @param list $keys QBKeys + * @param list> $values QBSet */ protected function _updateBatch(string $table, array $keys, array $values): string { diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 81c45520d6f1..20fef129e852 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -14,6 +14,7 @@ use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\RawSql; +use CodeIgniter\Database\SqlValue; use InvalidArgumentException; /** @@ -317,9 +318,9 @@ public function join(string $table, $cond, string $type = '', ?bool $escape = nu * * @used-by batchExecute * - * @param string $table Protected table name - * @param list $keys QBKeys - * @param list> $values QBSet + * @param string $table Protected table name + * @param list $keys QBKeys + * @param list> $values QBSet */ protected function _updateBatch(string $table, array $keys, array $values): string { @@ -393,7 +394,13 @@ protected function _updateBatch(string $table, array $keys, array $values): stri " UNION ALL\n", array_map( static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static function ($key, $index) { + if ($index instanceof SqlValue) { + return $index->getValue() . '::' . $index->getType() . ' ' . $key; + } + + return $index . ' ' . $key; + }, $keys, $value )), diff --git a/system/Database/SqlValue.php b/system/Database/SqlValue.php new file mode 100644 index 000000000000..d95b3dd79d29 --- /dev/null +++ b/system/Database/SqlValue.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database; + +/** + * @interal + */ +class SqlValue +{ + /** + * @var string Escaped column value. + */ + private string $value; + + /** + * @var string|null Column type. + */ + private ?string $type; + + /** + * @param string $value Escaped column value. + * @param string|null $type Column type. + */ + public function __construct(string $value, ?string $type = null) + { + $this->value = $value; + $this->type = $type; + } + + public function getValue(): string + { + return $this->value; + } + + public function getType(): string + { + return $this->type; + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 1c6ae8a430b6..49a05630ea9d 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -115,24 +115,44 @@ public function testUpdateBatch(): void { $data = [ [ - 'name' => 'Derek Jones', - 'country' => 'Greece', + 'name' => 'Derek Jones', + 'country' => 'Greece', + 'updated_at' => '2023-12-02 18:47:52', ], [ - 'name' => 'Ahmadinejad', - 'country' => 'Greece', + 'name' => 'Ahmadinejad', + 'country' => 'Greece', + 'updated_at' => '2023-12-02 18:47:52', ], ]; + if ($this->db->DBDriver === 'Postgre') { + // PostgreSQL needs column type. + $data = [ + [ + 'name' => 'Derek Jones', + 'country' => 'Greece', + 'updated_at::TIMESTAMP' => '2023-12-02 18:47:52', + ], + [ + 'name' => 'Ahmadinejad', + 'country' => 'Greece', + 'updated_at::TIMESTAMP' => '2023-12-02 18:47:52', + ], + ]; + } + $this->db->table('user')->updateBatch($data, 'name'); $this->seeInDatabase('user', [ - 'name' => 'Derek Jones', - 'country' => 'Greece', + 'name' => 'Derek Jones', + 'country' => 'Greece', + 'updated_at' => '2023-12-02 18:47:52', ]); $this->seeInDatabase('user', [ - 'name' => 'Ahmadinejad', - 'country' => 'Greece', + 'name' => 'Ahmadinejad', + 'country' => 'Greece', + 'updated_at' => '2023-12-02 18:47:52', ]); }