From c3b7938266dfda50ac1ff3d0b1013e0f72305a01 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Sun, 9 Feb 2025 21:33:14 +0400 Subject: [PATCH 1/4] add case expr in fuzzer and adjust probabilities --- tests/integration/fuzz/mod.rs | 65 +++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index 8179dfde4..20f2fbc05 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -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) // unfortunatelly, 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 From 6d670164925dbaa73be2e73b10eb1644be6c9ff1 Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Sun, 9 Feb 2025 21:33:37 +0400 Subject: [PATCH 2/4] fix bug after switch from HashMap to Vec --- core/vdbe/builder.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index 67f0f2749..9e42b330f 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -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); From a61736bec18f815c797ea6dd88ffc6fddf4baaaa Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Sun, 9 Feb 2025 21:33:54 +0400 Subject: [PATCH 3/4] adjust evaluation of NULL results in WHEN clauses with SQLite spec --- core/translate/expr.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 36acfa935..ec895a4db 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -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 { From 0595e7308dcd4a94740d4fe4aaccba42c2202d4c Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Sun, 9 Feb 2025 21:53:50 +0400 Subject: [PATCH 4/4] add TCL "CASE ... WHEN" test for null evaluation result --- testing/select.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testing/select.test b/testing/select.test index 934c4e879..60f2e5bc2 100755 --- a/testing/select.test +++ b/testing/select.test @@ -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; } {}