-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: [Postgre] QueryBuilder::updateBatch() does not work (No API change) #8439
fix: [Postgre] QueryBuilder::updateBatch() does not work (No API change) #8439
Conversation
|
SQLSRV
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed. 6d4ce25
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
' = ' . $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"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $table . '.' . $value . ' = ' . $alias . '.' . $that->cast($value, $that->getFieldType($table, $value)); | |
return $table . '.' . $value . ' = ' . $that->cast($alias . '.' . $value, $that->getFieldType($table, $value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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: