Merge 'Fix case and emit' from Nikita Sivukhin

(after fixing complicated b-tree bugs - I want to end my day with the
joy of fixing simple bugs)
This PR fix bug in emit (which was introduced after switch from
`HashMap` to `Vec`) and also align `CASE` codegen in case of `NULL`
result from `WHEN` clause with SQLite behaviour.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #953
This commit is contained in:
Pekka Enberg 2025-02-10 10:53:31 +02:00
commit 31886e8f4c
4 changed files with 71 additions and 11 deletions

View file

@ -792,7 +792,8 @@ pub fn translate_expr(
lhs: base_reg,
rhs: expr_reg,
target_pc: next_case_label,
flags: CmpInsFlags::default(),
// A NULL result is considered untrue when evaluating WHEN terms.
flags: CmpInsFlags::default().jump_if_null(),
}),
// CASE WHEN 0 THEN 0 ELSE 1 becomes ifnot 0 branch to next clause
None => program.emit_insn(Insn::IfNot {

View file

@ -110,10 +110,8 @@ impl ProgramBuilder {
pub fn emit_insn(&mut self, insn: Insn) {
if let Some(label) = self.next_insn_label {
self.label_to_resolved_offset.insert(
label.to_label_value() as usize,
Some(self.insns.len() as InsnReference),
);
self.label_to_resolved_offset[label.to_label_value() as usize] =
Some(self.insns.len() as InsnReference);
self.next_insn_label = None;
}
self.insns.push(insn);

View file

@ -108,6 +108,14 @@ do_execsql_test select_base_case_else {
select case 1 when 0 then 'zero' when 1 then 'one' else 'two' end;
} {one}
do_execsql_test select_base_case_null_result {
select case NULL when 0 then 'first' else 'second' end;
select case NULL when NULL then 'first' else 'second' end;
select case 0 when 0 then 'first' else 'second' end;
} {second
second
first}
do_execsql_test select_base_case_noelse_null {
select case 'null else' when 0 then 0 when 1 then 1 end;
} {}

View file

@ -163,6 +163,7 @@ mod tests {
"SELECT ((NULL) IS NOT TRUE <= ((NOT (FALSE))))",
"SELECT ifnull(0, NOT 0)",
"SELECT like('a%', 'a') = 1",
"SELECT CASE ( NULL < NULL ) WHEN ( 0 ) THEN ( NULL ) ELSE ( 2.0 ) END;",
] {
let limbo = limbo_exec_row(&limbo_conn, query);
let sqlite = sqlite_exec_row(&sqlite_conn, query);
@ -209,15 +210,66 @@ mod tests {
.push(expr)
.build();
let (like_pattern, like_pattern_builder) = g.create_handle();
like_pattern_builder
.choice()
.option_str("%")
.option_str("_")
.option_symbol(rand_str("", 1))
.repeat(1..10, "")
.build();
let (glob_pattern, glob_pattern_builder) = g.create_handle();
glob_pattern_builder
.choice()
.option_str("*")
.option_str("**")
.option_str("A")
.option_str("B")
.repeat(1..10, "")
.build();
let (case_expr, case_expr_builder) = g.create_handle();
case_expr_builder
.concat(" ")
.push_str("CASE (")
.push(expr)
.push_str(")")
.push(
g.create()
.concat(" ")
.push_str("WHEN (")
.push(expr)
.push_str(") THEN (")
.push(expr)
.push_str(")")
.repeat(1..5, " ")
.build(),
)
.push_str("ELSE (")
.push(expr)
.push_str(") END")
.build();
scalar_builder
.choice()
.option(
g.create()
.concat("")
.push_str("like('")
.push_symbol(rand_str("", 2))
.push(like_pattern)
.push_str("', '")
.push_symbol(rand_str("", 2))
.push(like_pattern)
.push_str("')")
.build(),
)
.option(
g.create()
.concat("")
.push_str("glob('")
.push(glob_pattern)
.push_str("', '")
.push(glob_pattern)
.push_str("')")
.build(),
)
@ -247,10 +299,11 @@ mod tests {
expr_builder
.choice()
.option_w(unary_infix_op, 1.0)
.option_w(bin_op, 1.0)
.option_w(paren, 1.0)
.option_w(scalar, 1.0)
.option_w(case_expr, 1.0)
.option_w(unary_infix_op, 2.0)
.option_w(bin_op, 3.0)
.option_w(paren, 2.0)
.option_w(scalar, 4.0)
// unfortunately, sqlite behaves weirdly when IS operator is used with TRUE/FALSE constants
// e.g. 8 IS TRUE == 1 (although 8 = TRUE == 0)
// so, we do not use TRUE/FALSE constants as they will produce diff with sqlite results