fix: [Postgre] QueryBuilder::updateBatch() does not work (No API change)#8439
fix: [Postgre] QueryBuilder::updateBatch() does not work (No API change)#8439kenjis merged 23 commits intocodeigniter4:developfrom
Conversation
https://github.com/codeigniter4/CodeIgniter4/actions/runs/7598402465/job/20694371974?pr=8439 |
|
SQLSRV https://github.com/codeigniter4/CodeIgniter4/actions/runs/7598402465/job/20695798884?pr=8439 It seems we cannot compare $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',
]);SELECT COUNT(*) AS "numrows"
FROM "test"."dbo"."db_type_test"
WHERE "type_varchar" = 'test1'
AND "type_text" = 'updated'
AND "type_bigint" = 9999999
AND "type_date" = '2024-01-01'
AND "type_datetime" = '2024-01-01 09:00:00'Data type |
|
OCI8 https://github.com/codeigniter4/CodeIgniter4/actions/runs/7598402465/job/20695798698 It seems OCI8 Forge maps |
2b4ddc7 to
ef13c8e
Compare
|
Might run into problems in the where part as well WHERE "db_type_test"."type_varchar" = _u."type_varchar" It probably works here because its text to varchar. We could cast everything in the select part but it adds a lot of redundancy. Maybe try the query where the constraint is timestamp and see what it does. |
|
Looks like we should take a look at sqsrv->forge->_attributeType(array &$attributes) Maybe text should be mapped to nvarchar(max) and enum as well. This could be problematic changing these though I suppose. |
Yes, fixed in c4ecd69 |
|
Maybe uppercase ::text to ::TEXT. This is part of SQL and might be easier to read. Could do this in getFieldTypes() |
I'm not sure what types are safe to remove. If we remove UPDATE "db_type_test"
SET
"type_bigint" = _u."type_bigint",
"type_integer" = _u."type_integer"
FROM (
SELECT '2448114396435166946' "type_bigint", '9999999' "type_integer", 'test1' "type_varchar" UNION ALL
SELECT '2448114396435166946' "type_bigint", '9999999' "type_integer", 'test2' "type_varchar"
) _u
WHERE "db_type_test"."type_varchar" = _u."type_varchar"In this case, the value |
5c01940 to
b008b0d
Compare
| // 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 |
There was a problem hiding this comment.
Float is an approximation of a number. The following would work:
SELECT * FROM t WHERE f > 99.89 AND f < 99.91;There was a problem hiding this comment.
Yes, and we cannot use seeInDatabase().
system/Database/Postgre/Builder.php
Outdated
| private function castValue(string $fieldName, $value): string | ||
| { | ||
| if (! isset($this->QBOptions['fieldTypes'])) { | ||
| throw new LogicException( |
There was a problem hiding this comment.
I'm not sure I like throwing an exception here. I think rather than an exception if fieldTypes aren't defined then getFieldTypes() should be called. This would require the additional parameter $table though. The way it is the method is dependent on another method being called to get some data. If you don't want to add $table as another parameter then you could add $fieldTypes.
You also would not need to pass $value to the method as you could simply append ::TEXT or empty string.
$value . $that->getFieldType($fieldName, $fieldTypes);
$value . $that->getFieldType($fieldName, $table); There was a problem hiding this comment.
On second thought might should include value because with other DBMS as well as Postgre the format is:
CAST ('100' AS INTEGER)
So:
$that->castValue($table, $fieldName, $value);There was a problem hiding this comment.
private function castValue(string $table, string $fieldName, $value): string
{
if (! isset($this->QBOptions['fieldTypes'])) {
$this->QBOptions['fieldTypes'] = $this->getFieldTypes($table);
}
$type = $this->QBOptions['fieldTypes'][$fieldName] ?? null;
return ($type === null) ? $value : $value . '::' . strtoupper($type);
// return ($type === null) ? $value : 'CAST(' . $value . ' AS ' . strtoupper($type) . ')';
}There was a problem hiding this comment.
$that->castValue($table, $fieldName, "'2024-01-01'"); // CAST('2024-01-01' AS DATE)
$that->castValue($table, $fieldName, $alias . $fieldName); // CAST(_u."created_date" AS DATE)There was a problem hiding this comment.
You could still use the ::DATE format but in case this was ever implemented with other DBMS then it would work with the CAST(expression AS type) format.
If you were using it in the SELECT part then you would be using values but in the SET part you are using field names.
There was a problem hiding this comment.
call it expression:
$that->castExpression($table, $fieldName, $expression);
$that->cast($table, $fieldName, $expression);
$that->castExpression($table, $fieldName, '1!=2'); // CAST(1!=2 AS BOOLEAN)There was a problem hiding this comment.
$this->cast($expression, $type); // CAST(expression AS type)
$this->cast($expression, $this->getFieldType($table, $fieldName));
private function getFieldType(string $table, string $fieldName): string
{
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;
}
}
return $this->QBOptions['fieldTypes'][$table][$fieldName] ?? null;
}
// BaseBuilder
protected function cast($expression, $type): string // possible future public method
{
return $this->_cast($expression, $type);
}
protected function _cast($expression, $type): string
{
//return ($type === null) ? $expression : $expression . '::' . strtoupper($type);
return ($type === null) ? $expression : 'CAST(' . $expression . ' AS ' . strtoupper($type) . ')'; // covers most all dbms
}There was a problem hiding this comment.
I made some updates to last post.. see what ya think
I don't like castValue() because it might not always be a value but rather an expression. Most DBMS document as CAST(expression AS type)
There was a problem hiding this comment.
cast($expression, $type) makes sense. But we can't add API in a patch version.
Changed. 92cc84e
e18caaf to
6d4ce25
Compare
e5cfe9e to
92cc84e
Compare
|
@codeigniter4/database-team Review, please. |
michalsn
left a comment
There was a problem hiding this comment.
Looks great - thank you @kenjis and @sclubricants!
system/Database/Postgre/Builder.php
Outdated
| array_map( | ||
| static fn ($key, $value) => $key . ($value instanceof RawSql ? | ||
| ' = ' . $value : | ||
| ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $key))), |
There was a problem hiding this comment.
| ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $key))), | |
| ' = ' . $that->cast($alias . '.' . $value, $that->getFieldType($table, $key))), |
$alias . '.' . $value is the full part of the expression. I would like to see us use the more universal format of CAST(expression AS type). If we use this then you definitely have to include the alias as part of expression. The expression here is "table"."column"
system/Database/Postgre/Builder.php
Outdated
| return $value; | ||
| } | ||
|
|
||
| return $table . '.' . $value . ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $value)); |
There was a problem hiding this comment.
| return $table . '.' . $value . ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $value)); | |
| return $table . '.' . $value . ' = ' . $that->cast($alias . '.' . $value, $that->getFieldType($table, $value)); |
system/Database/Postgre/Builder.php
Outdated
| */ | ||
| private function cast($expression, ?string $type): string | ||
| { | ||
| return ($type === null) ? $expression : $expression . '::' . strtoupper($type); |
There was a problem hiding this comment.
| return ($type === null) ? $expression : $expression . '::' . strtoupper($type); | |
| return ($type === null) ? $expression : 'CAST(' . $expression . ' AS ' . strtoupper($type) . ')'; |
The expression::TYPE format is a shorthand format postgre supports. However Postgre and all other DBMS support the format CAST(expression AS type). We should go ahead and use this format so that if this method is moved to BaseBuilder it will generate code compatible with all DBMS.
https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-cast/
https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj33562.html
https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast
https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16
https://www.sqlite.org/lang_expr.html#castexpr
|
This looks good to me. We should probably check upsertBatch() and deleteBatch() as well. I think insertBatch() should be ok. |
|
Thank you! @sclubricants @michalsn |
See |
Description
Supersedes #8426
Fixes #7387
See https://forum.codeigniter.com/showthread.php?tid=88871
References:
Checklist: