Merge 'Fix cast' from Nikita Sivukhin

Fix codegen for `CAST` expression and also adjust implementation of
`CAST: Real -> Int` because `SQLite` use "closest integer between the
REAL value and zero" as a CAST result.

Closes #956
This commit is contained in:
Pekka Enberg
2025-02-10 10:53:56 +02:00
4 changed files with 31 additions and 4 deletions

View File

@@ -835,15 +835,14 @@ pub fn translate_expr(
}
ast::Expr::Cast { expr, type_name } => {
let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional?
let reg_expr = program.alloc_register();
let reg_expr = program.alloc_registers(2);
translate_expr(program, referenced_tables, expr, reg_expr, resolver)?;
let reg_type = program.alloc_register();
program.emit_insn(Insn::String8 {
// we make a comparison against uppercase static strs in the affinity() function,
// so we need to make sure we're comparing against the uppercase version,
// and it's better to do this once instead of every time we check affinity
value: type_name.name.to_uppercase(),
dest: reg_type,
dest: reg_expr + 1,
});
program.mark_last_insn_constant();
program.emit_insn(Insn::Function {

View File

@@ -3484,7 +3484,7 @@ fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue {
// then the result is the greatest possible signed integer and if the REAL is less than the least possible signed integer (-9223372036854775808)
// then the result is the least possible signed integer.
OwnedValue::Float(f) => {
let i = f.floor() as i128;
let i = f.trunc() as i128;
if i > i64::MAX as i128 {
OwnedValue::Integer(i64::MAX)
} else if i < i64::MIN as i128 {

View File

@@ -740,6 +740,21 @@ do_execsql_test cast-float-to-integer {
SELECT CAST(123.45 AS INTEGER);
} {123}
do_execsql_test cast-float-to-integer-rounding {
SELECT CAST(0.6 AS INTEGER);
SELECT CAST(1.0 AS INTEGER);
SELECT CAST(1.6 AS INTEGER);
SELECT CAST(-0.6 AS INTEGER);
SELECT CAST(-1.0 AS INTEGER);
SELECT CAST(-1.6 AS INTEGER);
} {0
1
1
0
-1
-1
}
do_execsql_test cast-large-float-to-integer {
SELECT CAST(9223372036854775808.0 AS INTEGER);
} {9223372036854775807}

View File

@@ -165,6 +165,7 @@ mod tests {
"SELECT like('a%', 'a') = 1",
"SELECT CASE ( NULL < NULL ) WHEN ( 0 ) THEN ( NULL ) ELSE ( 2.0 ) END;",
"SELECT (COALESCE(0, COALESCE(0, 0)));",
"SELECT CAST((1 > 0) AS INTEGER);",
] {
let limbo = limbo_exec_row(&limbo_conn, query);
let sqlite = sqlite_exec_row(&sqlite_conn, query);
@@ -238,6 +239,17 @@ mod tests {
.push_str(")")
.build();
let (cast_expr, cast_expr_builder) = g.create_handle();
cast_expr_builder
.concat(" ")
.push_str("CAST ( (")
.push(expr)
.push_str(") AS ")
// cast to INTEGER/REAL/TEXT types can be added when Limbo will use proper equality semantic between values (e.g. 1 = 1.0)
.push(g.create().choice().options_str(["NUMERIC"]).build())
.push_str(")")
.build();
let (case_expr, case_expr_builder) = g.create_handle();
case_expr_builder
.concat(" ")
@@ -309,6 +321,7 @@ mod tests {
expr_builder
.choice()
.option_w(cast_expr, 1.0)
.option_w(case_expr, 1.0)
.option_w(unary_infix_op, 2.0)
.option_w(bin_op, 3.0)