Skip to content

Commit 9a2e8a6

Browse files
committed
Merge 'Add some missing supported features in WHERE clause' from Jussi Saurio
This PR is sourced from the fuzzing exploration PR in #1021 **Adds missing support:** Support all the same literals in WHERE clause position as in SELECT position Support CAST in WHERE clause position Support FunctionCall in WHERE clause position Support Column in WHERE clause position Support Rowid in WHERE clause position Support CASE in WHERE clause position Support LIKE in SELECT position Support Unary expressions in WHERE clause position Support rest of the Binary expressions in WHERE clause position Support TEXT in remainder operations **Fix:** Remove incorrect constant folding optimization for NULL **Testing utils:** Enhance sqlite fuzzer to mostly be able to work with the same set of possible expressions in both SELECT and WHERE clause position Closes #1024
2 parents e3cfba8 + 55ff1d2 commit 9a2e8a6

File tree

5 files changed

+441
-180
lines changed

5 files changed

+441
-180
lines changed

core/translate/expr.rs

+110-72
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,19 @@ pub fn translate_condition_expr(
237237
resolver,
238238
)?;
239239
}
240-
ast::Expr::Binary(lhs, op, rhs) => {
240+
ast::Expr::Binary(lhs, op, rhs)
241+
if matches!(
242+
op,
243+
ast::Operator::Greater
244+
| ast::Operator::GreaterEquals
245+
| ast::Operator::Less
246+
| ast::Operator::LessEquals
247+
| ast::Operator::Equals
248+
| ast::Operator::NotEquals
249+
| ast::Operator::Is
250+
| ast::Operator::IsNot
251+
) =>
252+
{
241253
let lhs_reg = program.alloc_register();
242254
let rhs_reg = program.alloc_register();
243255
translate_and_mark(program, Some(referenced_tables), lhs, lhs_reg, resolver)?;
@@ -267,35 +279,24 @@ pub fn translate_condition_expr(
267279
ast::Operator::IsNot => {
268280
emit_cmp_null_insn!(program, condition_metadata, Ne, Eq, lhs_reg, rhs_reg)
269281
}
270-
_ => {
271-
todo!("op {:?} not implemented", op);
272-
}
282+
_ => unreachable!(),
273283
}
274284
}
275-
ast::Expr::Literal(lit) => match lit {
276-
ast::Literal::Numeric(val) => {
277-
let maybe_int = val.parse::<i64>();
278-
if let Ok(int_value) = maybe_int {
279-
let reg = program.alloc_register();
280-
program.emit_insn(Insn::Integer {
281-
value: int_value,
282-
dest: reg,
283-
});
284-
emit_cond_jump(program, condition_metadata, reg);
285-
} else {
286-
crate::bail_parse_error!("unsupported literal type in condition");
287-
}
288-
}
289-
ast::Literal::String(string) => {
290-
let reg = program.alloc_register();
291-
program.emit_insn(Insn::String8 {
292-
value: string.clone(),
293-
dest: reg,
294-
});
295-
emit_cond_jump(program, condition_metadata, reg);
296-
}
297-
unimpl => todo!("literal {:?} not implemented", unimpl),
298-
},
285+
ast::Expr::Binary(_, _, _) => {
286+
let result_reg = program.alloc_register();
287+
translate_expr(program, Some(referenced_tables), expr, result_reg, resolver)?;
288+
emit_cond_jump(program, condition_metadata, result_reg);
289+
}
290+
ast::Expr::Literal(_)
291+
| ast::Expr::Cast { .. }
292+
| ast::Expr::FunctionCall { .. }
293+
| ast::Expr::Column { .. }
294+
| ast::Expr::RowId { .. }
295+
| ast::Expr::Case { .. } => {
296+
let reg = program.alloc_register();
297+
translate_expr(program, Some(referenced_tables), expr, reg, resolver)?;
298+
emit_cond_jump(program, condition_metadata, reg);
299+
}
299300
ast::Expr::InList { lhs, not, rhs } => {
300301
// lhs is e.g. a column reference
301302
// rhs is an Option<Vec<Expr>>
@@ -410,49 +411,9 @@ pub fn translate_condition_expr(
410411
program.resolve_label(jump_target_when_true, program.offset());
411412
}
412413
}
413-
ast::Expr::Like {
414-
lhs,
415-
not,
416-
op,
417-
rhs,
418-
escape: _,
419-
} => {
414+
ast::Expr::Like { not, .. } => {
420415
let cur_reg = program.alloc_register();
421-
match op {
422-
ast::LikeOperator::Like | ast::LikeOperator::Glob => {
423-
let start_reg = program.alloc_registers(2);
424-
let mut constant_mask = 0;
425-
translate_and_mark(
426-
program,
427-
Some(referenced_tables),
428-
lhs,
429-
start_reg + 1,
430-
resolver,
431-
)?;
432-
let _ =
433-
translate_expr(program, Some(referenced_tables), rhs, start_reg, resolver)?;
434-
if matches!(rhs.as_ref(), ast::Expr::Literal(_)) {
435-
program.mark_last_insn_constant();
436-
constant_mask = 1;
437-
}
438-
let func = match op {
439-
ast::LikeOperator::Like => ScalarFunc::Like,
440-
ast::LikeOperator::Glob => ScalarFunc::Glob,
441-
_ => unreachable!(),
442-
};
443-
program.emit_insn(Insn::Function {
444-
constant_mask,
445-
start_reg,
446-
dest: cur_reg,
447-
func: FuncCtx {
448-
func: Func::Scalar(func),
449-
arg_count: 2,
450-
},
451-
});
452-
}
453-
ast::LikeOperator::Match => todo!(),
454-
ast::LikeOperator::Regexp => todo!(),
455-
}
416+
translate_like_base(program, Some(referenced_tables), expr, cur_reg, resolver)?;
456417
if !*not {
457418
emit_cond_jump(program, condition_metadata, cur_reg);
458419
} else if condition_metadata.jump_if_condition_is_true {
@@ -500,7 +461,17 @@ pub fn translate_condition_expr(
500461
target_pc: condition_metadata.jump_target_when_false,
501462
});
502463
}
503-
_ => todo!("op {:?} not implemented", expr),
464+
ast::Expr::Unary(_, _) => {
465+
// This is an inefficient implementation for op::NOT, because translate_expr() will emit an Insn::Not,
466+
// and then we immediately emit an Insn::If/Insn::IfNot for the conditional jump. In reality we would not
467+
// like to emit the negation instruction Insn::Not at all, since we could just emit the "opposite" jump instruction
468+
// directly. However, using translate_expr() directly simplifies our conditional jump code for unary expressions,
469+
// and we'd rather be correct than maximally efficient, for now.
470+
let expr_reg = program.alloc_register();
471+
translate_expr(program, Some(referenced_tables), expr, expr_reg, resolver)?;
472+
emit_cond_jump(program, condition_metadata, expr_reg);
473+
}
474+
other => todo!("expression {:?} not implemented", other),
504475
}
505476
Ok(())
506477
}
@@ -1969,7 +1940,21 @@ pub fn translate_expr(
19691940
ast::Expr::InSelect { .. } => todo!(),
19701941
ast::Expr::InTable { .. } => todo!(),
19711942
ast::Expr::IsNull(_) => todo!(),
1972-
ast::Expr::Like { .. } => todo!(),
1943+
ast::Expr::Like { not, .. } => {
1944+
let like_reg = if *not {
1945+
program.alloc_register()
1946+
} else {
1947+
target_register
1948+
};
1949+
translate_like_base(program, referenced_tables, expr, like_reg, resolver)?;
1950+
if *not {
1951+
program.emit_insn(Insn::Not {
1952+
reg: like_reg,
1953+
dest: target_register,
1954+
});
1955+
}
1956+
Ok(target_register)
1957+
}
19731958
ast::Expr::Literal(lit) => match lit {
19741959
ast::Literal::Numeric(val) => {
19751960
let maybe_int = val.parse::<i64>();
@@ -2159,6 +2144,59 @@ pub fn translate_expr(
21592144
}
21602145
}
21612146

2147+
/// The base logic for translating LIKE and GLOB expressions.
2148+
/// The logic for handling "NOT LIKE" is different depending on whether the expression
2149+
/// is a conditional jump or not. This is why the caller handles the "NOT LIKE" behavior;
2150+
/// see [translate_condition_expr] and [translate_expr] for implementations.
2151+
fn translate_like_base(
2152+
program: &mut ProgramBuilder,
2153+
referenced_tables: Option<&[TableReference]>,
2154+
expr: &ast::Expr,
2155+
target_register: usize,
2156+
resolver: &Resolver,
2157+
) -> Result<usize> {
2158+
let ast::Expr::Like {
2159+
lhs,
2160+
op,
2161+
rhs,
2162+
escape: _,
2163+
..
2164+
} = expr
2165+
else {
2166+
crate::bail_parse_error!("expected Like expression");
2167+
};
2168+
match op {
2169+
ast::LikeOperator::Like | ast::LikeOperator::Glob => {
2170+
let start_reg = program.alloc_registers(2);
2171+
let mut constant_mask = 0;
2172+
translate_and_mark(program, referenced_tables, lhs, start_reg + 1, resolver)?;
2173+
let _ = translate_expr(program, referenced_tables, rhs, start_reg, resolver)?;
2174+
if matches!(rhs.as_ref(), ast::Expr::Literal(_)) {
2175+
program.mark_last_insn_constant();
2176+
constant_mask = 1;
2177+
}
2178+
let func = match op {
2179+
ast::LikeOperator::Like => ScalarFunc::Like,
2180+
ast::LikeOperator::Glob => ScalarFunc::Glob,
2181+
_ => unreachable!(),
2182+
};
2183+
program.emit_insn(Insn::Function {
2184+
constant_mask,
2185+
start_reg,
2186+
dest: target_register,
2187+
func: FuncCtx {
2188+
func: Func::Scalar(func),
2189+
arg_count: 2,
2190+
},
2191+
});
2192+
}
2193+
ast::LikeOperator::Match => todo!(),
2194+
ast::LikeOperator::Regexp => todo!(),
2195+
}
2196+
2197+
Ok(target_register)
2198+
}
2199+
21622200
/// Emits a whole insn for a function call.
21632201
/// Assumes the number of parameters is valid for the given function.
21642202
/// Returns the target register for the function.

core/translate/optimizer.rs

-1
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ impl Optimizable for ast::Expr {
367367
fn check_constant(&self) -> Result<Option<ConstantPredicate>> {
368368
match self {
369369
Self::Literal(lit) => match lit {
370-
ast::Literal::Null => Ok(Some(ConstantPredicate::AlwaysFalse)),
371370
ast::Literal::Numeric(b) => {
372371
if let Ok(int_value) = b.parse::<i64>() {
373372
return Ok(Some(if int_value == 0 {

testing/select.test

+9
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,12 @@ do_execsql_test select_bin_shl {
151151
-997623670|-1995247340|-1021566638080|-1071190259091374080
152152
997623670|1995247340|1021566638080|1071190259091374080
153153
-997623670|-1995247340|-1021566638080|-1071190259091374080}
154+
155+
# Test LIKE in SELECT position
156+
do_execsql_test select-like-expression {
157+
select 'bar' like 'bar%'
158+
} {1}
159+
160+
do_execsql_test select-not-like-expression {
161+
select 'bar' not like 'bar%'
162+
} {0}

testing/where.test

+92
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,95 @@ foreach {operator} {
472472
# NULL comparison AND id=1
473473
do_execsql_test where-binary-one-operand-null-and-$operator "select first_name from users where first_name $operator NULL AND id = 1" {}
474474
}
475+
476+
# Test literals in WHERE clause
477+
do_execsql_test where-literal-string {
478+
select count(*) from users where 'yes';
479+
} {0}
480+
481+
# FIXME: should return 0
482+
#do_execsql_test where-literal-number {
483+
# select count(*) from users where x'DEADBEEF';
484+
#} {0}
485+
486+
# Test CAST in WHERE clause
487+
do_execsql_test where-cast-string-to-int {
488+
select count(*) from users where cast('1' as integer);
489+
} {10000}
490+
491+
do_execsql_test where-cast-float-to-int {
492+
select count(*) from users where cast('0' as integer);
493+
} {0}
494+
495+
# Test FunctionCall in WHERE clause
496+
do_execsql_test where-function-length {
497+
select count(*) from users where length(first_name);
498+
} {10000}
499+
500+
# Test CASE in WHERE clause
501+
do_execsql_test where-case-simple {
502+
select count(*) from users where
503+
case when age > 0 then 1 else 0 end;
504+
} {10000}
505+
506+
do_execsql_test where-case-searched {
507+
select count(*) from users where
508+
case age
509+
when 0 then 0
510+
else 1
511+
end;
512+
} {10000}
513+
514+
# Test unary operators in WHERE clause
515+
do_execsql_test where-unary-not {
516+
select count(*) from users where not (id = 1);
517+
} {9999}
518+
519+
do_execsql_test where-unary-plus {
520+
select count(*) from users where +1;
521+
} {10000}
522+
523+
do_execsql_test where-unary-minus {
524+
select count(*) from users where -1;
525+
} {10000}
526+
527+
do_execsql_test where-unary-bitnot {
528+
select count(*) from users where ~1;
529+
} {10000}
530+
531+
# Test binary math operators in WHERE clause
532+
do_execsql_test where-binary-add {
533+
select count(*) from users where 1 + 1;
534+
} {10000}
535+
536+
do_execsql_test where-binary-subtract {
537+
select count(*) from users where 2 - 1;
538+
} {10000}
539+
540+
do_execsql_test where-binary-multiply {
541+
select count(*) from users where 2 * 1;
542+
} {10000}
543+
544+
do_execsql_test where-binary-divide {
545+
select count(*) from users where 2 / 2;
546+
} {10000}
547+
548+
do_execsql_test where-binary-modulo {
549+
select count(*) from users where 3 % 2;
550+
} {10000}
551+
552+
do_execsql_test where-binary-shift-left {
553+
select count(*) from users where 1 << 1;
554+
} {10000}
555+
556+
do_execsql_test where-binary-shift-right {
557+
select count(*) from users where 2 >> 1;
558+
} {10000}
559+
560+
do_execsql_test where-binary-bitwise-and {
561+
select count(*) from users where 3 & 1;
562+
} {10000}
563+
564+
do_execsql_test where-binary-bitwise-or {
565+
select count(*) from users where 2 | 1;
566+
} {10000}

0 commit comments

Comments
 (0)