From 02de3560dfc9acc817f08ffad31da9a67a138b02 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 21 Jan 2024 10:14:20 +0900 Subject: [PATCH 01/23] test: update comment --- .../Migrations/20160428212500_Create_test_tables.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 6e3d9be574fa..80969a3f23ec 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -46,9 +46,10 @@ public function up(): void ])->addKey('id', true)->createTable('misc', true); // Database Type test table - // missing types : - // TINYINT,MEDIUMINT,BIT,YEAR,BINARY , VARBINARY, TINYTEXT,LONGTEXT,YEAR,JSON,Spatial data types - // id must be interger else SQLite3 error on not null for autoinc field + // missing types: + // TINYINT,MEDIUMINT,BIT,YEAR,BINARY,VARBINARY,TINYTEXT,LONGTEXT, + // JSON,Spatial data types + // `id` must be INTEGER else SQLite3 error on not null for autoincrement field. $data_type_fields = [ 'id' => ['type' => 'INTEGER', 'constraint' => 20, 'auto_increment' => true], 'type_varchar' => ['type' => 'VARCHAR', 'constraint' => 40, 'null' => true], From b796f0b05154872a7b52f5ce292ba2e524f1c282 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 21 Jan 2024 10:16:15 +0900 Subject: [PATCH 02/23] test: add test for type date and datetime --- tests/system/Database/Live/UpdateTest.php | 58 ++++++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 1c6ae8a430b6..efa0c7b51102 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -113,26 +113,58 @@ public function testUpdateWithWhereAndLimit(): void public function testUpdateBatch(): void { + $table = 'type_test'; + + // Prepares test data. + $builder = $this->db->table($table); + $builder->truncate(); + + for ($i = 0; $i < 3; $i++) { + $builder->insert([ + 'type_varchar' => 'test' . $i, + 'type_char' => 'char', + 'type_text' => 'text', + 'type_smallint' => 32767, + 'type_integer' => 2147483647, + 'type_bigint' => 9223372036854775807, + 'type_float' => 10.1, + 'type_numeric' => 123.23, + 'type_date' => '2023-12-21', + 'type_datetime' => '2023-12-21 12:00:00', + ]); + } + $data = [ [ - 'name' => 'Derek Jones', - 'country' => 'Greece', + 'type_varchar' => 'test1', + 'type_text' => 'updated', + 'type_bigint' => 9999999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', ], [ - 'name' => 'Ahmadinejad', - 'country' => 'Greece', + 'type_varchar' => 'test2', + 'type_text' => 'updated', + 'type_bigint' => 9999999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', ], ]; - - $this->db->table('user')->updateBatch($data, 'name'); - - $this->seeInDatabase('user', [ - 'name' => 'Derek Jones', - 'country' => 'Greece', + $this->db->table($table)->updateBatch($data, 'type_varchar'); + + $this->seeInDatabase($table, [ + 'type_varchar' => 'test1', + 'type_text' => 'updated', + 'type_bigint' => 9999999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', ]); - $this->seeInDatabase('user', [ - 'name' => 'Ahmadinejad', - 'country' => 'Greece', + $this->seeInDatabase($table, [ + 'type_varchar' => 'test2', + 'type_text' => 'updated', + 'type_bigint' => 9999999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', ]); } From a71aa3c6bf7d6dd836891419b5a325c2073f4efb Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 17 Jan 2024 12:11:56 +0900 Subject: [PATCH 03/23] refactor: copy _updateBatch() to override --- system/Database/Postgre/Builder.php | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 3e3ed68a9356..e31568d0672a 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -312,6 +312,99 @@ public function join(string $table, $cond, string $type = '', ?bool $escape = nu return parent::join($table, $cond, $type, $escape); } + /** + * Generates a platform-specific batch update string from the supplied data + * + * @used-by batchExecute + * + * @param string $table Protected table name + * @param list $keys QBKeys + * @param list> $values QBSet + */ + protected function _updateBatch(string $table, array $keys, array $values): string + { + $sql = $this->QBOptions['sql'] ?? ''; + + // if this is the first iteration of batch then we need to build skeleton sql + if ($sql === '') { + $constraints = $this->QBOptions['constraints'] ?? []; + + if ($constraints === []) { + if ($this->db->DBDebug) { + throw new DatabaseException('You must specify a constraint to match on for batch updates.'); // @codeCoverageIgnore + } + + return ''; // @codeCoverageIgnore + } + + $updateFields = $this->QBOptions['updateFields'] ?? + $this->updateFields($keys, false, $constraints)->QBOptions['updateFields'] ?? + []; + + $alias = $this->QBOptions['alias'] ?? '_u'; + + $sql = 'UPDATE ' . $this->compileIgnore('update') . $table . "\n"; + + $sql .= "SET\n"; + + $sql .= implode( + ",\n", + array_map( + static fn ($key, $value) => $key . ($value instanceof RawSql ? + ' = ' . $value : + ' = ' . $alias . '.' . $value), + array_keys($updateFields), + $updateFields + ) + ) . "\n"; + + $sql .= "FROM (\n{:_table_:}"; + + $sql .= ') ' . $alias . "\n"; + + $sql .= 'WHERE ' . implode( + ' AND ', + array_map( + static fn ($key, $value) => ( + ($value instanceof RawSql && is_string($key)) + ? + $table . '.' . $key . ' = ' . $value + : + ( + $value instanceof RawSql + ? + $value + : + $table . '.' . $value . ' = ' . $alias . '.' . $value + ) + ), + array_keys($constraints), + $constraints + ) + ); + + $this->QBOptions['sql'] = $sql; + } + + if (isset($this->QBOptions['setQueryAsData'])) { + $data = $this->QBOptions['setQueryAsData']; + } else { + $data = implode( + " UNION ALL\n", + array_map( + static fn ($value) => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index) => $index . ' ' . $key, + $keys, + $value + )), + $values + ) + ) . "\n"; + } + + return str_replace('{:_table_:}', $data, $sql); + } + /** * Generates a platform-specific upsertBatch string from the supplied data * From f1b4a6c8872018255a7a5ceb153a166d8d239be3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 17 Jan 2024 13:14:09 +0900 Subject: [PATCH 04/23] refactor: make if condition more strict --- phpstan-baseline.php | 5 ----- system/Database/Postgre/Builder.php | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 48585d6fd969..22aae16b046e 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1341,11 +1341,6 @@ 'count' => 7, 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Only booleans are allowed in a negated boolean, array\\\\|string\\> given\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', -]; $ignoreErrors[] = [ 'message' => '#^Return type \\(CodeIgniter\\\\Database\\\\BaseBuilder\\) of method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:join\\(\\) should be covariant with return type \\(\\$this\\(CodeIgniter\\\\Database\\\\BaseBuilder\\)\\) of method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:join\\(\\)$#', 'count' => 1, diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index e31568d0672a..81c45520d6f1 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -146,7 +146,7 @@ public function replace(?array $set = null) $this->set($set); } - if (! $this->QBSet) { + if ($this->QBSet === []) { if ($this->db->DBDebug) { throw new DatabaseException('You must use the "set" method to update an entry.'); } From 88326013afe0f6915b60e7835dc98133246c6f2a Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 21 Jan 2024 11:09:22 +0900 Subject: [PATCH 05/23] fix: updateBatch() Postgre type error --- system/Database/Postgre/Builder.php | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 81c45520d6f1..4abc78ffc6bb 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -347,12 +347,19 @@ protected function _updateBatch(string $table, array $keys, array $values): stri $sql .= "SET\n"; + $fieldTypes = $this->getFieldTypes($table); + $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? - ' = ' . $value : - ' = ' . $alias . '.' . $value), + static function ($key, $value) use ($alias, $fieldTypes) { + $type = $fieldTypes[trim($key, '"')] ?? null; + $cast = ($type === null) ? '' : '::' . $type; + + return $key . ($value instanceof RawSql ? + ' = ' . $value : + ' = ' . $alias . '.' . $value . $cast); + }, array_keys($updateFields), $updateFields ) @@ -405,6 +412,20 @@ protected function _updateBatch(string $table, array $keys, array $values): stri return str_replace('{:_table_:}', $data, $sql); } + /** + * @return array [name => type] + */ + private function getFieldTypes(string $table): array + { + $types = []; + + foreach ($this->db->getFieldData($table) as $field) { + $types[$field->name] = $field->type; + } + + return $types; + } + /** * Generates a platform-specific upsertBatch string from the supplied data * From 1d967536d5ab061f08da6f91f3cf9084412f9e32 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 18 Jan 2024 10:49:18 +0900 Subject: [PATCH 06/23] docs: add sub section titles and tweaks --- .../source/database/query_builder.rst | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst index 0be07d71340b..5e6d54985a6c 100755 --- a/user_guide_src/source/database/query_builder.rst +++ b/user_guide_src/source/database/query_builder.rst @@ -1107,18 +1107,20 @@ $builder->updateBatch() .. note:: Since v4.3.0, the second parameter ``$index`` of ``updateBatch()`` has changed to ``$constraints``. It now accepts types array, string, or ``RawSql``. +Update from Data +^^^^^^^^^^^^^^^^ + Generates an update string based on the data you supply, and runs the query. You can either pass an **array** or an **object** to the method. Here is an example using an array: .. literalinclude:: query_builder/092.php -.. note:: Since v4.3.0, the generated SQL structure has been Improved. +The first parameter is an associative array of values, the second parameter is the where keys. -The first parameter is an associative array of values, the second parameter is the where key. +.. note:: Since v4.3.0, the generated SQL structure has been Improved. -Since v4.3.0, you can also use the ``setQueryAsData()``, ``onConstraint()``, and -``updateFields()`` methods: +Since v4.3.0, you can also use the ``onConstraint()`` and ``updateFields()`` methods: .. literalinclude:: query_builder/120.php @@ -1130,12 +1132,12 @@ Since v4.3.0, you can also use the ``setQueryAsData()``, ``onConstraint()``, and due to the very nature of how it works. Instead, ``updateBatch()`` returns the number of rows affected. -You can also update from a query: +Update from a Query +^^^^^^^^^^^^^^^^^^^ -.. literalinclude:: query_builder/116.php +Since v4.3.0, you can also update from a query with the ``setQueryAsData()`` method: -.. note:: The ``setQueryAsData()``, ``onConstraint()``, and ``updateFields()`` - methods can be used since v4.3.0. +.. literalinclude:: query_builder/116.php .. note:: It is required to alias the columns of the select query to match those of the target table. From 9ae66a26d80cb7e5bc1dd8048a129f3a9cfad701 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 21 Jan 2024 11:11:25 +0900 Subject: [PATCH 07/23] docs: add changelog --- user_guide_src/source/changelogs/v4.4.5.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/user_guide_src/source/changelogs/v4.4.5.rst b/user_guide_src/source/changelogs/v4.4.5.rst index e029a1d9f3a5..cc395dc9100f 100644 --- a/user_guide_src/source/changelogs/v4.4.5.rst +++ b/user_guide_src/source/changelogs/v4.4.5.rst @@ -30,6 +30,9 @@ Deprecations Bugs Fixed ********** +- **QueryBuilder:** Fixed a bug that the ``updateBatch()`` method does not work + due to type errors on PostgreSQL. + See the repo's `CHANGELOG.md `_ for a complete list of bugs fixed. From 560e7454fb6911b091acfabccaf5062e0cb5ea18 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 21 Jan 2024 11:21:54 +0900 Subject: [PATCH 08/23] refactory: by rector --- tests/system/Database/Live/UpdateTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index efa0c7b51102..7f035ef45ad5 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -125,8 +125,8 @@ public function testUpdateBatch(): void 'type_char' => 'char', 'type_text' => 'text', 'type_smallint' => 32767, - 'type_integer' => 2147483647, - 'type_bigint' => 9223372036854775807, + 'type_integer' => 2_147_483_647, + 'type_bigint' => 9_223_372_036_854_775_807, 'type_float' => 10.1, 'type_numeric' => 123.23, 'type_date' => '2023-12-21', @@ -138,14 +138,14 @@ public function testUpdateBatch(): void [ 'type_varchar' => 'test1', 'type_text' => 'updated', - 'type_bigint' => 9999999, + 'type_bigint' => 9_999_999, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ], [ 'type_varchar' => 'test2', 'type_text' => 'updated', - 'type_bigint' => 9999999, + 'type_bigint' => 9_999_999, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ], @@ -155,14 +155,14 @@ public function testUpdateBatch(): void $this->seeInDatabase($table, [ 'type_varchar' => 'test1', 'type_text' => 'updated', - 'type_bigint' => 9999999, + 'type_bigint' => 9_999_999, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ]); $this->seeInDatabase($table, [ 'type_varchar' => 'test2', 'type_text' => 'updated', - 'type_bigint' => 9999999, + 'type_bigint' => 9_999_999, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ]); From 1bffa76915416e19f9acbf220442d5371b7360f1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 21 Jan 2024 16:57:54 +0900 Subject: [PATCH 09/23] test: fix assertion for SQLSRV --- tests/system/Database/Live/UpdateTest.php | 49 ++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 7f035ef45ad5..240bdde49455 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -152,20 +152,41 @@ public function testUpdateBatch(): void ]; $this->db->table($table)->updateBatch($data, 'type_varchar'); - $this->seeInDatabase($table, [ - 'type_varchar' => 'test1', - 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - $this->seeInDatabase($table, [ - 'type_varchar' => 'test2', - 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); + if ($this->db->DBDriver === 'SQLSRV') { + // We cannot compare `text` and `varchar` with `=`. It causes the error: + // [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]The data types text and varchar are incompatible in the equal to operator. + // And data type `text`, `ntext`, `image` are deprecated in SQL Server 2016 + // See https://github.com/codeigniter4/CodeIgniter4/pull/8439#issuecomment-1902535909 + $this->seeInDatabase($table, [ + 'type_varchar' => 'test1', + // 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + $this->seeInDatabase($table, [ + 'type_varchar' => 'test2', + // 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + } else { + $this->seeInDatabase($table, [ + 'type_varchar' => 'test1', + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + $this->seeInDatabase($table, [ + 'type_varchar' => 'test2', + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + } } public function testUpdateWithWhereSameColumn(): void From 38fb5b266f248e449c4602befe974a5441b55fac Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 09:06:57 +0900 Subject: [PATCH 10/23] test: add test for updateBatch() with constrants DATE --- tests/system/Database/Live/UpdateTest.php | 78 ++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 240bdde49455..d9c4a43ea452 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -111,7 +111,7 @@ public function testUpdateWithWhereAndLimit(): void } } - public function testUpdateBatch(): void + public function testUpdateBatchConstraintsVarchar(): void { $table = 'type_test'; @@ -189,6 +189,82 @@ public function testUpdateBatch(): void } } + public function testUpdateBatchConstraintsDate(): void + { + $table = 'type_test'; + + // Prepares test data. + $builder = $this->db->table($table); + $builder->truncate(); + + for ($i = 1; $i < 4; $i++) { + $builder->insert([ + 'type_varchar' => 'test' . $i, + 'type_char' => 'char', + 'type_text' => 'text', + 'type_smallint' => 32767, + 'type_integer' => 2_147_483_647, + 'type_bigint' => 9_223_372_036_854_775_807, + 'type_float' => 10.1, + 'type_numeric' => 123.23, + 'type_date' => '2023-12-0' . $i, + 'type_datetime' => '2023-12-21 12:00:00', + ]); + } + + $data = [ + [ + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-01', // Key + 'type_datetime' => '2024-01-01 09:00:00', + ], + [ + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-02', // Key + 'type_datetime' => '2024-01-01 09:00:00', + ], + ]; + $this->db->table($table)->updateBatch($data, 'type_date'); + + if ($this->db->DBDriver === 'SQLSRV') { + // We cannot compare `text` and `varchar` with `=`. It causes the error: + // [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]The data types text and varchar are incompatible in the equal to operator. + // And data type `text`, `ntext`, `image` are deprecated in SQL Server 2016 + // See https://github.com/codeigniter4/CodeIgniter4/pull/8439#issuecomment-1902535909 + $this->seeInDatabase($table, [ + 'type_varchar' => 'test1', + // 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-01', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + $this->seeInDatabase($table, [ + 'type_varchar' => 'test2', + // 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-02', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + } else { + $this->seeInDatabase($table, [ + 'type_varchar' => 'test1', + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-01', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + $this->seeInDatabase($table, [ + 'type_varchar' => 'test2', + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-02', + 'type_datetime' => '2024-01-01 09:00:00', + ]); + } + } + public function testUpdateWithWhereSameColumn(): void { $this->db->table('user')->update(['country' => 'CA'], ['country' => 'US']); From 3336010b75d3e16a292f6c88968696cfc8e59f1c Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 09:07:48 +0900 Subject: [PATCH 11/23] fix: Postgre updateBatch() SQL type error in WHERE part --- system/Database/Postgre/Builder.php | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 4abc78ffc6bb..d3877c301db9 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -372,19 +372,20 @@ static function ($key, $value) use ($alias, $fieldTypes) { $sql .= 'WHERE ' . implode( ' AND ', array_map( - static fn ($key, $value) => ( - ($value instanceof RawSql && is_string($key)) - ? - $table . '.' . $key . ' = ' . $value - : - ( - $value instanceof RawSql - ? - $value - : - $table . '.' . $value . ' = ' . $alias . '.' . $value - ) - ), + static function ($key, $value) use ($table, $alias, $fieldTypes) { + if ($value instanceof RawSql && is_string($key)) { + return $table . '.' . $key . ' = ' . $value; + } + + if ($value instanceof RawSql) { + return $value; + } + + $type = $fieldTypes[trim($value, '"')] ?? null; + $cast = ($type === null) ? '' : '::' . $type; + + return $table . '.' . $value . ' = ' . $alias . '.' . $value . $cast; + }, array_keys($constraints), $constraints ) From f86c6d266fe2f438f2f0cfdb0954d8e4c6bfe9eb Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 09:41:27 +0900 Subject: [PATCH 12/23] test: add test for more types --- tests/system/Database/Live/UpdateTest.php | 28 +++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index d9c4a43ea452..70aa1de3bd75 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -136,16 +136,24 @@ public function testUpdateBatchConstraintsVarchar(): void $data = [ [ - 'type_varchar' => 'test1', + 'type_varchar' => 'test1', // Key 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ], [ - 'type_varchar' => 'test2', + 'type_varchar' => 'test2', // Key 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ], @@ -160,14 +168,22 @@ public function testUpdateBatchConstraintsVarchar(): void $this->seeInDatabase($table, [ 'type_varchar' => 'test1', // 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ]); $this->seeInDatabase($table, [ 'type_varchar' => 'test2', // 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ]); @@ -175,14 +191,22 @@ public function testUpdateBatchConstraintsVarchar(): void $this->seeInDatabase($table, [ 'type_varchar' => 'test1', 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ]); $this->seeInDatabase($table, [ 'type_varchar' => 'test2', 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', ]); From fc0a858799851a07c04d407f827c3b711266ca1f Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 10:24:00 +0900 Subject: [PATCH 13/23] refactor: add castValue() and use it --- system/Database/BaseBuilder.php | 3 ++- system/Database/Postgre/Builder.php | 42 ++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 9d43e5577943..509d5c748ff0 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -169,7 +169,8 @@ class BaseBuilder * constraints?: array, * setQueryAsData?: string, * sql?: string, - * alias?: string + * alias?: string, + * fieldTypes?: array * } */ protected $QBOptions; diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index d3877c301db9..8606760ee7dd 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -15,6 +15,7 @@ use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\RawSql; use InvalidArgumentException; +use LogicException; /** * Builder for Postgre @@ -347,18 +348,18 @@ protected function _updateBatch(string $table, array $keys, array $values): stri $sql .= "SET\n"; - $fieldTypes = $this->getFieldTypes($table); + $this->getFieldTypes($table); + $that = $this; $sql .= implode( ",\n", array_map( - static function ($key, $value) use ($alias, $fieldTypes) { - $type = $fieldTypes[trim($key, '"')] ?? null; - $cast = ($type === null) ? '' : '::' . $type; + static function ($key, $value) use ($alias, $that) { + $fieldName = trim($key, '"'); return $key . ($value instanceof RawSql ? ' = ' . $value : - ' = ' . $alias . '.' . $value . $cast); + ' = ' . $alias . '.' . $that->castValue($fieldName, $value)); }, array_keys($updateFields), $updateFields @@ -372,7 +373,7 @@ static function ($key, $value) use ($alias, $fieldTypes) { $sql .= 'WHERE ' . implode( ' AND ', array_map( - static function ($key, $value) use ($table, $alias, $fieldTypes) { + static function ($key, $value) use ($table, $alias, $that) { if ($value instanceof RawSql && is_string($key)) { return $table . '.' . $key . ' = ' . $value; } @@ -381,10 +382,9 @@ static function ($key, $value) use ($table, $alias, $fieldTypes) { return $value; } - $type = $fieldTypes[trim($value, '"')] ?? null; - $cast = ($type === null) ? '' : '::' . $type; + $fieldName = trim($value, '"'); - return $table . '.' . $value . ' = ' . $alias . '.' . $value . $cast; + return $table . '.' . $value . ' = ' . $alias . '.' . $that->castValue($fieldName, $value); }, array_keys($constraints), $constraints @@ -414,9 +414,27 @@ static function ($key, $value) use ($table, $alias, $fieldTypes) { } /** - * @return array [name => type] + * Returns cast value. + * + * @param float|int|string $value Escaped value + */ + private function castValue(string $fieldName, $value): string + { + if (! isset($this->QBOptions['fieldTypes'])) { + throw new LogicException( + 'You must call getFieldTypes() before calling castValue().' + ); + } + + $type = $this->QBOptions['fieldTypes'][$fieldName] ?? null; + + return ($type === null) ? $value : $value . '::' . $type; + } + + /** + * Gets filed types from database meta data. */ - private function getFieldTypes(string $table): array + private function getFieldTypes(string $table): void { $types = []; @@ -424,7 +442,7 @@ private function getFieldTypes(string $table): array $types[$field->name] = $field->type; } - return $types; + $this->QBOptions['fieldTypes'] = $types; } /** From ec1128489c67a73416a55c8cfed52cff5cb8f4c3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 11:09:25 +0900 Subject: [PATCH 14/23] test: refactor with dataProvider --- tests/system/Database/Live/UpdateTest.php | 238 +++++++++------------- 1 file changed, 95 insertions(+), 143 deletions(-) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index 70aa1de3bd75..d55328f493c2 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -111,7 +111,10 @@ public function testUpdateWithWhereAndLimit(): void } } - public function testUpdateBatchConstraintsVarchar(): void + /** + * @dataProvider provideUpdateBatch + */ + public function testUpdateBatch(string $constraints, array $data, array $expected): void { $table = 'type_test'; @@ -119,7 +122,7 @@ public function testUpdateBatchConstraintsVarchar(): void $builder = $this->db->table($table); $builder->truncate(); - for ($i = 0; $i < 3; $i++) { + for ($i = 1; $i < 4; $i++) { $builder->insert([ 'type_varchar' => 'test' . $i, 'type_char' => 'char', @@ -129,164 +132,113 @@ public function testUpdateBatchConstraintsVarchar(): void 'type_bigint' => 9_223_372_036_854_775_807, 'type_float' => 10.1, 'type_numeric' => 123.23, - 'type_date' => '2023-12-21', + 'type_date' => '2023-12-0' . $i, 'type_datetime' => '2023-12-21 12:00:00', ]); } - $data = [ - [ - 'type_varchar' => 'test1', // Key - 'type_text' => 'updated', - 'type_smallint' => 9999, - 'type_integer' => 9_999_999, - 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, - 'type_numeric' => 999999.99, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ], - [ - 'type_varchar' => 'test2', // Key - 'type_text' => 'updated', - 'type_smallint' => 9999, - 'type_integer' => 9_999_999, - 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, - 'type_numeric' => 999999.99, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ], - ]; - $this->db->table($table)->updateBatch($data, 'type_varchar'); + $this->db->table($table)->updateBatch($data, $constraints); if ($this->db->DBDriver === 'SQLSRV') { // We cannot compare `text` and `varchar` with `=`. It causes the error: // [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]The data types text and varchar are incompatible in the equal to operator. // And data type `text`, `ntext`, `image` are deprecated in SQL Server 2016 // See https://github.com/codeigniter4/CodeIgniter4/pull/8439#issuecomment-1902535909 - $this->seeInDatabase($table, [ - 'type_varchar' => 'test1', - // 'type_text' => 'updated', - 'type_smallint' => 9999, - 'type_integer' => 9_999_999, - 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, - 'type_numeric' => 999999.99, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - $this->seeInDatabase($table, [ - 'type_varchar' => 'test2', - // 'type_text' => 'updated', - 'type_smallint' => 9999, - 'type_integer' => 9_999_999, - 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, - 'type_numeric' => 999999.99, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - } else { - $this->seeInDatabase($table, [ - 'type_varchar' => 'test1', - 'type_text' => 'updated', - 'type_smallint' => 9999, - 'type_integer' => 9_999_999, - 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, - 'type_numeric' => 999999.99, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - $this->seeInDatabase($table, [ - 'type_varchar' => 'test2', - 'type_text' => 'updated', - 'type_smallint' => 9999, - 'type_integer' => 9_999_999, - 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, - 'type_numeric' => 999999.99, - 'type_date' => '2024-01-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); + unset($expected[0]['type_text'], $expected[1]['type_text']); } + + $this->seeInDatabase($table, $expected[0]); + $this->seeInDatabase($table, $expected[1]); } - public function testUpdateBatchConstraintsDate(): void + public static function provideUpdateBatch(): iterable { - $table = 'type_test'; - - // Prepares test data. - $builder = $this->db->table($table); - $builder->truncate(); - - for ($i = 1; $i < 4; $i++) { - $builder->insert([ - 'type_varchar' => 'test' . $i, - 'type_char' => 'char', - 'type_text' => 'text', - 'type_smallint' => 32767, - 'type_integer' => 2_147_483_647, - 'type_bigint' => 9_223_372_036_854_775_807, - 'type_float' => 10.1, - 'type_numeric' => 123.23, - 'type_date' => '2023-12-0' . $i, - 'type_datetime' => '2023-12-21 12:00:00', - ]); - } - - $data = [ - [ - 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2023-12-01', // Key - 'type_datetime' => '2024-01-01 09:00:00', + yield from [ + 'constraints varchar' => [ + 'type_varchar', + [ + [ + 'type_varchar' => 'test1', // Key + 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, + 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ], + [ + 'type_varchar' => 'test2', // Key + 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, + 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ], + ], + [ + [ + 'type_varchar' => 'test1', + 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, + 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ], + [ + 'type_varchar' => 'test2', + 'type_text' => 'updated', + 'type_smallint' => 9999, + 'type_integer' => 9_999_999, + 'type_bigint' => 9_999_999, + 'type_float' => 999999.9, + 'type_numeric' => 999999.99, + 'type_date' => '2024-01-01', + 'type_datetime' => '2024-01-01 09:00:00', + ], + ], ], - [ - 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2023-12-02', // Key - 'type_datetime' => '2024-01-01 09:00:00', + 'constraints date' => [ + 'type_date', + [ + [ + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-01', // Key + 'type_datetime' => '2024-01-01 09:00:00', + ], + [ + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-02', // Key + 'type_datetime' => '2024-01-01 09:00:00', + ], + ], + [ + [ + 'type_varchar' => 'test1', + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-01', + 'type_datetime' => '2024-01-01 09:00:00', + ], + [ + 'type_varchar' => 'test2', + 'type_text' => 'updated', + 'type_bigint' => 9_999_999, + 'type_date' => '2023-12-02', + 'type_datetime' => '2024-01-01 09:00:00', + ], + ], ], ]; - $this->db->table($table)->updateBatch($data, 'type_date'); - - if ($this->db->DBDriver === 'SQLSRV') { - // We cannot compare `text` and `varchar` with `=`. It causes the error: - // [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]The data types text and varchar are incompatible in the equal to operator. - // And data type `text`, `ntext`, `image` are deprecated in SQL Server 2016 - // See https://github.com/codeigniter4/CodeIgniter4/pull/8439#issuecomment-1902535909 - $this->seeInDatabase($table, [ - 'type_varchar' => 'test1', - // 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2023-12-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - $this->seeInDatabase($table, [ - 'type_varchar' => 'test2', - // 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2023-12-02', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - } else { - $this->seeInDatabase($table, [ - 'type_varchar' => 'test1', - 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2023-12-01', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - $this->seeInDatabase($table, [ - 'type_varchar' => 'test2', - 'type_text' => 'updated', - 'type_bigint' => 9_999_999, - 'type_date' => '2023-12-02', - 'type_datetime' => '2024-01-01 09:00:00', - ]); - } } public function testUpdateWithWhereSameColumn(): void From d4f3e928138fce897ec23b6b5eb3245fec9301f7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 11:23:29 +0900 Subject: [PATCH 15/23] refactor: make type uppercase in SQL string for readablitiy --- system/Database/Postgre/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 8606760ee7dd..09d77d9fa91b 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -428,7 +428,7 @@ private function castValue(string $fieldName, $value): string $type = $this->QBOptions['fieldTypes'][$fieldName] ?? null; - return ($type === null) ? $value : $value . '::' . $type; + return ($type === null) ? $value : $value . '::' . strtoupper($type); } /** From 5ca6dfa9414c46366c96d974f218d67f5c23fe23 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 11:16:22 +0900 Subject: [PATCH 16/23] test: add test for int as string --- tests/system/Database/Live/UpdateTest.php | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index d55328f493c2..c6348aba25d2 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -238,6 +238,33 @@ public static function provideUpdateBatch(): iterable ], ], ], + 'int as string' => [ + 'type_varchar', + [ + [ + 'type_varchar' => 'test1', // Key + 'type_integer' => '9999999', // PHP string + 'type_bigint' => '2448114396435166946', // PHP string + ], + [ + 'type_varchar' => 'test2', // Key + 'type_integer' => '9999999', // PHP string + 'type_bigint' => '2448114396435166946', // PHP string + ], + ], + [ + [ + 'type_varchar' => 'test1', + 'type_integer' => 9_999_999, + 'type_bigint' => 2_448_114_396_435_166_946, + ], + [ + 'type_varchar' => 'test2', + 'type_integer' => 9_999_999, + 'type_bigint' => 2_448_114_396_435_166_946, + ], + ], + ], ]; } From 64cd79119fc9546381d0f0ba20251fa982136bac Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 22 Jan 2024 13:42:41 +0900 Subject: [PATCH 17/23] test: FLOAT on MySQL cannot be used as WHERE = key See https://dev.mysql.com/doc/refman/8.0/en/floating-point-types.html --- .../20160428212500_Create_test_tables.php | 22 ++++++++++++------- tests/system/Database/Live/UpdateTest.php | 6 ++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php index 80969a3f23ec..e646e371fb7c 100644 --- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php +++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php @@ -47,16 +47,22 @@ public function up(): void // Database Type test table // missing types: - // TINYINT,MEDIUMINT,BIT,YEAR,BINARY,VARBINARY,TINYTEXT,LONGTEXT, - // JSON,Spatial data types + // TINYINT,MEDIUMINT,BIT,YEAR,BINARY,VARBINARY,TINYTEXT,LONGTEXT, + // JSON,Spatial data types // `id` must be INTEGER else SQLite3 error on not null for autoincrement field. $data_type_fields = [ - 'id' => ['type' => 'INTEGER', 'constraint' => 20, 'auto_increment' => true], - 'type_varchar' => ['type' => 'VARCHAR', 'constraint' => 40, 'null' => true], - 'type_char' => ['type' => 'CHAR', 'constraint' => 10, 'null' => true], - 'type_text' => ['type' => 'TEXT', 'null' => true], - 'type_smallint' => ['type' => 'SMALLINT', 'null' => true], - 'type_integer' => ['type' => 'INTEGER', 'null' => true], + 'id' => ['type' => 'INTEGER', 'constraint' => 20, 'auto_increment' => true], + 'type_varchar' => ['type' => 'VARCHAR', 'constraint' => 40, 'null' => true], + 'type_char' => ['type' => 'CHAR', 'constraint' => 10, 'null' => true], + // TEXT should not be used on SQLSRV. It is deprecated. + 'type_text' => ['type' => 'TEXT', 'null' => true], + 'type_smallint' => ['type' => 'SMALLINT', 'null' => true], + 'type_integer' => ['type' => 'INTEGER', 'null' => true], + // FLOAT should not be used on MySQL. + // CREATE TABLE t (f FLOAT, d DOUBLE); + // INSERT INTO t VALUES(99.9, 99.9); + // SELECT * FROM t WHERE f=99.9; // Empty set + // SELECT * FROM t WHERE d=99.9; // 1 row 'type_float' => ['type' => 'FLOAT', 'null' => true], 'type_numeric' => ['type' => 'NUMERIC', 'constraint' => '18,2', 'null' => true], 'type_date' => ['type' => 'DATE', 'null' => true], diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php index c6348aba25d2..f8b0ced088db 100644 --- a/tests/system/Database/Live/UpdateTest.php +++ b/tests/system/Database/Live/UpdateTest.php @@ -163,7 +163,7 @@ public static function provideUpdateBatch(): iterable 'type_smallint' => 9999, 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, + 'type_float' => 99.9, 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', @@ -174,7 +174,7 @@ public static function provideUpdateBatch(): iterable 'type_smallint' => 9999, 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, + 'type_float' => 99.9, 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', @@ -187,7 +187,6 @@ public static function provideUpdateBatch(): iterable 'type_smallint' => 9999, 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', @@ -198,7 +197,6 @@ public static function provideUpdateBatch(): iterable 'type_smallint' => 9999, 'type_integer' => 9_999_999, 'type_bigint' => 9_999_999, - 'type_float' => 999999.9, 'type_numeric' => 999999.99, 'type_date' => '2024-01-01', 'type_datetime' => '2024-01-01 09:00:00', From 6d4ce25435ef1aae1c3c9bfeaf23ec5399bc77c0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 23 Jan 2024 09:57:21 +0900 Subject: [PATCH 18/23] refactor: change castValue() API --- system/Database/Postgre/Builder.php | 31 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 09d77d9fa91b..7e0125c606ac 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -15,7 +15,6 @@ use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\RawSql; use InvalidArgumentException; -use LogicException; /** * Builder for Postgre @@ -348,18 +347,16 @@ protected function _updateBatch(string $table, array $keys, array $values): stri $sql .= "SET\n"; - $this->getFieldTypes($table); - $that = $this; $sql .= implode( ",\n", array_map( - static function ($key, $value) use ($alias, $that) { + static function ($key, $value) use ($table, $alias, $that) { $fieldName = trim($key, '"'); return $key . ($value instanceof RawSql ? ' = ' . $value : - ' = ' . $alias . '.' . $that->castValue($fieldName, $value)); + ' = ' . $alias . '.' . $that->castValue($table, $key, $value)); }, array_keys($updateFields), $updateFields @@ -382,9 +379,7 @@ static function ($key, $value) use ($table, $alias, $that) { return $value; } - $fieldName = trim($value, '"'); - - return $table . '.' . $value . ' = ' . $alias . '.' . $that->castValue($fieldName, $value); + return $table . '.' . $value . ' = ' . $alias . '.' . $that->castValue($table, $value, $value); }, array_keys($constraints), $constraints @@ -416,23 +411,27 @@ static function ($key, $value) use ($table, $alias, $that) { /** * Returns cast value. * - * @param float|int|string $value Escaped value + * @param string $table Protected Table name. + * @param string $fieldName Field name. May be protected. + * @param float|int|string $value Escaped value */ - private function castValue(string $fieldName, $value): string + private function castValue(string $table, string $fieldName, $value): string { - if (! isset($this->QBOptions['fieldTypes'])) { - throw new LogicException( - 'You must call getFieldTypes() before calling castValue().' - ); + $fieldName = trim($fieldName, $this->db->escapeChar); + + if (! isset($this->QBOptions['fieldTypes'][$table])) { + $this->getFieldTypes($table); } - $type = $this->QBOptions['fieldTypes'][$fieldName] ?? null; + $type = $this->QBOptions['fieldTypes'][$table][$fieldName] ?? null; return ($type === null) ? $value : $value . '::' . strtoupper($type); } /** * Gets filed types from database meta data. + * + * @param string $table Protected Table name. */ private function getFieldTypes(string $table): void { @@ -442,7 +441,7 @@ private function getFieldTypes(string $table): void $types[$field->name] = $field->type; } - $this->QBOptions['fieldTypes'] = $types; + $this->QBOptions['fieldTypes'][$table] = $types; } /** From cd644df4de7bfa0b9380d443ecb0804e6dca9358 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 23 Jan 2024 11:24:38 +0900 Subject: [PATCH 19/23] docs: add @param --- system/Database/BaseBuilder.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 509d5c748ff0..936b4c3be778 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1759,6 +1759,8 @@ public function getWhere($where = null, ?int $limit = null, ?int $offset = 0, bo /** * Compiles batch insert/update/upsert strings and runs the queries * + * @param '_deleteBatch'|'_insertBatch'|'_updateBatch'|'_upsertBatch' $renderMethod + * * @return false|int|string[] Number of rows inserted or FALSE on failure, SQL array when testMode * * @throws DatabaseException From 92cc84e1ff1ac91c596bed6d511633cc70c5457b Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 23 Jan 2024 11:25:12 +0900 Subject: [PATCH 20/23] refactor: change castValue(),getFieldTypes() to cast(),getFieldType() --- system/Database/Postgre/Builder.php | 45 +++++++++++++---------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 7e0125c606ac..b597e2df517c 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -352,11 +352,9 @@ protected function _updateBatch(string $table, array $keys, array $values): stri ",\n", array_map( static function ($key, $value) use ($table, $alias, $that) { - $fieldName = trim($key, '"'); - return $key . ($value instanceof RawSql ? ' = ' . $value : - ' = ' . $alias . '.' . $that->castValue($table, $key, $value)); + ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $key))); }, array_keys($updateFields), $updateFields @@ -379,7 +377,7 @@ static function ($key, $value) use ($table, $alias, $that) { return $value; } - return $table . '.' . $value . ' = ' . $alias . '.' . $that->castValue($table, $value, $value); + return $table . '.' . $value . ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $value)); }, array_keys($constraints), $constraints @@ -409,39 +407,36 @@ static function ($key, $value) use ($table, $alias, $that) { } /** - * Returns cast value. + * Returns cast expression. + * + * @TODO move this to BaseBuilder in 4.5.0 * - * @param string $table Protected Table name. - * @param string $fieldName Field name. May be protected. - * @param float|int|string $value Escaped value + * @param float|int|string $expression */ - private function castValue(string $table, string $fieldName, $value): string + private function cast($expression, ?string $type): string { - $fieldName = trim($fieldName, $this->db->escapeChar); - - if (! isset($this->QBOptions['fieldTypes'][$table])) { - $this->getFieldTypes($table); - } - - $type = $this->QBOptions['fieldTypes'][$table][$fieldName] ?? null; - - return ($type === null) ? $value : $value . '::' . strtoupper($type); + return ($type === null) ? $expression : $expression . '::' . strtoupper($type); } /** - * Gets filed types from database meta data. + * Returns the filed type from database meta data. * - * @param string $table Protected Table name. + * @param string $table Protected table name. + * @param string $fieldName Field name. May be protected. */ - private function getFieldTypes(string $table): void + private function getFieldType(string $table, string $fieldName): ?string { - $types = []; + $fieldName = trim($fieldName, $this->db->escapeChar); - foreach ($this->db->getFieldData($table) as $field) { - $types[$field->name] = $field->type; + if (! isset($this->QBOptions['fieldTypes'][$table])) { + $this->QBOptions['fieldTypes'][$table] = []; + + foreach ($this->db->getFieldData($table) as $field) { + $this->QBOptions['fieldTypes'][$table][$field->name] = $field->type; + } } - $this->QBOptions['fieldTypes'][$table] = $types; + return $this->QBOptions['fieldTypes'][$table][$fieldName] ?? null; } /** From fa2e07378dfe2f3bb6f70b5324601141ae5038c0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 23 Jan 2024 11:44:24 +0900 Subject: [PATCH 21/23] refactor: by rector --- system/Database/Postgre/Builder.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index b597e2df517c..496e9bd86bd6 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -351,11 +351,9 @@ protected function _updateBatch(string $table, array $keys, array $values): stri $sql .= implode( ",\n", array_map( - static function ($key, $value) use ($table, $alias, $that) { - return $key . ($value instanceof RawSql ? - ' = ' . $value : - ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $key))); - }, + static fn ($key, $value) => $key . ($value instanceof RawSql ? + ' = ' . $value : + ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $key))), array_keys($updateFields), $updateFields ) From fbacb3071b53b316de17064710f53d22b67e07b7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 24 Jan 2024 05:44:05 +0900 Subject: [PATCH 22/23] refactor: use `CAST(expression AS type)` instead of `::type` --- system/Database/Postgre/Builder.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index 496e9bd86bd6..3d566f6051a0 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -353,7 +353,7 @@ protected function _updateBatch(string $table, array $keys, array $values): stri array_map( static fn ($key, $value) => $key . ($value instanceof RawSql ? ' = ' . $value : - ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $key))), + ' = ' . $that->cast($alias . '.' . $value, $that->getFieldType($table, $key))), array_keys($updateFields), $updateFields ) @@ -375,7 +375,8 @@ static function ($key, $value) use ($table, $alias, $that) { return $value; } - return $table . '.' . $value . ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $value)); + return $table . '.' . $value . ' = ' + . $that->cast($alias . '.' . $value, $that->getFieldType($table, $value)); }, array_keys($constraints), $constraints @@ -413,7 +414,7 @@ static function ($key, $value) use ($table, $alias, $that) { */ private function cast($expression, ?string $type): string { - return ($type === null) ? $expression : $expression . '::' . strtoupper($type); + return ($type === null) ? $expression : 'CAST(' . $expression . ' AS ' . strtoupper($type) . ')'; } /** From baca36e4ee9119dd0976a383ca69e33f60af4762 Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 25 Jan 2024 07:07:03 +0900 Subject: [PATCH 23/23] docs: update PHPDoc --- system/Database/BaseBuilder.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 936b4c3be778..48302ab26fc7 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -170,8 +170,10 @@ class BaseBuilder * setQueryAsData?: string, * sql?: string, * alias?: string, - * fieldTypes?: array + * fieldTypes?: array> * } + * + * fieldTypes: [ProtectedTableName => [FieldName => Type]] */ protected $QBOptions;