diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 771978cba..acf218f1d 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1146,9 +1146,10 @@ pub fn translate_expr( temp_reg, resolver, )?; + let before_copy_label = program.allocate_label(); program.emit_insn(Insn::NotNull { reg: temp_reg, - target_pc: program.offset().add(2u32), + target_pc: before_copy_label, }); translate_expr( @@ -1158,6 +1159,7 @@ pub fn translate_expr( temp_reg, resolver, )?; + program.resolve_label(before_copy_label, program.offset()); program.emit_insn(Insn::Copy { src_reg: temp_reg, dst_reg: target_register, @@ -1225,15 +1227,21 @@ pub fn translate_expr( srf.to_string() ); }; - for arg in args { - let _ = - translate_and_mark(program, referenced_tables, arg, resolver); + let func_registers = program.alloc_registers(args.len()); + for (i, arg) in args.iter().enumerate() { + let _ = translate_expr( + program, + referenced_tables, + arg, + func_registers + i, + resolver, + )?; } program.emit_insn(Insn::Function { // Only constant patterns for LIKE are supported currently, so this // is always 1 constant_mask: 1, - start_reg: target_register + 1, + start_reg: func_registers, dest: target_register, func: func_ctx, }); diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index c1119c9ba..be93248a5 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -686,6 +686,10 @@ fn rewrite_expr(expr: &mut ast::Expr) -> Result<()> { } Ok(()) } + ast::Expr::Unary(_, arg) => { + rewrite_expr(arg)?; + Ok(()) + } _ => Ok(()), } } diff --git a/tests/integration/fuzz/mod.rs b/tests/integration/fuzz/mod.rs index e7baf481b..925bfe771 100644 --- a/tests/integration/fuzz/mod.rs +++ b/tests/integration/fuzz/mod.rs @@ -10,7 +10,7 @@ mod tests { use crate::{ common::TempDatabase, - fuzz::grammar_generator::{rand_int, GrammarGenerator}, + fuzz::grammar_generator::{rand_int, rand_str, GrammarGenerator}, }; fn rng_from_time() -> (ChaCha8Rng, u64) { @@ -146,4 +146,132 @@ mod tests { ); } } + + #[test] + pub fn logical_expression_fuzz_ex1() { + let _ = env_logger::try_init(); + let db = TempDatabase::new_empty(); + let limbo_conn = db.connect_limbo(); + let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); + + for query in [ + "SELECT FALSE", + "SELECT NOT FALSE", + "SELECT ((NULL) IS NOT TRUE <= ((NOT (FALSE))))", + "SELECT ifnull(0, NOT 0)", + "SELECT like('a%', 'a') = 1", + ] { + let limbo = limbo_exec_row(&limbo_conn, query); + let sqlite = sqlite_exec_row(&sqlite_conn, query); + assert_eq!( + limbo, sqlite, + "query: {}, limbo: {:?}, sqlite: {:?}", + query, limbo, sqlite + ); + } + } + + #[test] + pub fn logical_expression_fuzz_run() { + let _ = env_logger::try_init(); + let g = GrammarGenerator::new(); + let (expr, expr_builder) = g.create_handle(); + let (bin_op, bin_op_builder) = g.create_handle(); + let (unary_infix_op, unary_infix_op_builder) = g.create_handle(); + let (scalar, scalar_builder) = g.create_handle(); + let (paren, paren_builder) = g.create_handle(); + + paren_builder + .concat("") + .push_str("(") + .push(expr) + .push_str(")") + .build(); + + unary_infix_op_builder + .concat(" ") + .push(g.create().choice().options_str(["NOT"]).build()) + .push(expr) + .build(); + + bin_op_builder + .concat(" ") + .push(expr) + .push( + g.create() + .choice() + .options_str(["AND", "OR", "IS", "IS NOT", "=", "<>", ">", "<", ">=", "<="]) + .build(), + ) + .push(expr) + .build(); + + scalar_builder + .choice() + .option( + g.create() + .concat("") + .push_str("like('") + .push_symbol(rand_str("", 2)) + .push_str("', '") + .push_symbol(rand_str("", 2)) + .push_str("')") + .build(), + ) + .option( + g.create() + .concat("") + .push_str("ifnull(") + .push(expr) + .push_str(",") + .push(expr) + .push_str(")") + .build(), + ) + .option( + g.create() + .concat("") + .push_str("iif(") + .push(expr) + .push_str(",") + .push(expr) + .push_str(",") + .push(expr) + .push_str(")") + .build(), + ) + .build(); + + 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) + // 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 + .options_str(["1", "0", "NULL", "2.0", "1.5", "-0.5", "-2.0", "(1 / 0)"]) + .build(); + + let sql = g.create().concat(" ").push_str("SELECT").push(expr).build(); + + let db = TempDatabase::new_empty(); + let limbo_conn = db.connect_limbo(); + let sqlite_conn = rusqlite::Connection::open_in_memory().unwrap(); + + let (mut rng, seed) = rng_from_time(); + log::info!("seed: {}", seed); + for _ in 0..1024 { + let query = g.generate(&mut rng, sql, 50); + log::info!("query: {}", query); + let limbo = limbo_exec_row(&limbo_conn, &query); + let sqlite = sqlite_exec_row(&sqlite_conn, &query); + assert_eq!( + limbo, sqlite, + "query: {}, limbo: {:?}, sqlite: {:?}", + query, limbo, sqlite + ); + } + } }