Some clean ups and correctly working on WHERE clauses

This commit is contained in:
Diego Reis
2025-10-07 02:34:33 -03:00
parent 79958f468d
commit da323fa0c4
6 changed files with 87 additions and 30 deletions

View File

@@ -165,12 +165,10 @@ fn expr_code_vector(program: &mut ProgramBuilder, expr: &ast::Expr) -> usize {
fn expr_can_be_null(expr: &ast::Expr) -> bool {
// todo: better handling columns. Check sqlite3ExprCanBeNull
match expr {
Expr::Literal(literal) => match literal {
ast::Literal::Numeric(_) => false,
ast::Literal::String(_) => false,
ast::Literal::Blob(_) => false,
_ => true,
},
Expr::Literal(literal) => !matches!(
literal,
ast::Literal::Numeric(_) | ast::Literal::String(_) | ast::Literal::Blob(_)
),
_ => true,
}
}
@@ -180,6 +178,30 @@ fn expr_can_be_null(expr: &ast::Expr) -> bool {
///
/// This is extracted from the original conditional implementation to be reusable.
/// The logic exactly matches the original conditional InList implementation.
///
/// An IN expression has one of the following formats:
/// ```sql
/// x IN (y1, y2,...,yN)
/// x IN (subquery) (Not yet implemented)
/// ```
/// The result of an IN operator is one of TRUE, FALSE, or NULL. A NULL result
/// means that it cannot be determined if the LHS is contained in the RHS due
/// to the presence of NULL values.
///
/// Currently, we do a simple full-scan, yet it's not ideal when there are many rows
/// on RHS. (Check sqlite's in-operator.md)
///
/// Algorithm:
/// 1. Set the null-flag to false
/// 2. For each row in the RHS:
/// - Compare LHS and RHS
/// - If LHS matches RHS, returns TRUE
/// - If the comparison results in NULL, set the null-flag to true
/// 3. If the null-flag is true, return NULL
/// 4. Return FALSE
///
/// A "NOT IN" operator is computed by first computing the equivalent IN
/// operator, then interchanging the TRUE and FALSE results.
// todo: Check right affinities
#[instrument(skip(program, referenced_tables, resolver), level = Level::DEBUG)]
fn translate_in_list(
@@ -237,21 +259,19 @@ fn translate_in_list(
});
}
// sqlite3VdbeChangeP5(v, zAff[0]);
} else if lhs_reg != rhs_reg {
program.emit_insn(Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
flags: CmpInsFlags::default(),
collation: program.curr_collation(),
});
} else {
if lhs_reg != rhs_reg {
program.emit_insn(Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
flags: CmpInsFlags::default(),
collation: program.curr_collation(),
});
} else {
program.emit_insn(Insn::IsNull {
reg: lhs_reg,
target_pc: condition_metadata.jump_target_when_false,
});
}
program.emit_insn(Insn::IsNull {
reg: lhs_reg,
target_pc: condition_metadata.jump_target_when_false,
});
}
}
@@ -400,15 +420,42 @@ pub fn translate_condition_expr(
translate_expr(program, Some(referenced_tables), expr, reg, resolver)?;
emit_cond_jump(program, condition_metadata, reg);
}
ast::Expr::InList { lhs, not, rhs } => {
let ConditionMetadata {
jump_if_condition_is_true,
jump_target_when_true,
jump_target_when_false,
jump_target_when_null,
} = condition_metadata;
// Adjust targets if `NOT IN`
let adjusted_metadata = if *not {
ConditionMetadata {
jump_if_condition_is_true,
jump_target_when_true,
jump_target_when_false: jump_target_when_true,
jump_target_when_null,
}
} else {
condition_metadata
};
translate_in_list(
program,
Some(referenced_tables),
lhs,
rhs,
condition_metadata,
adjusted_metadata,
resolver,
)?;
if *not {
program.emit_insn(Insn::Goto {
target_pc: jump_target_when_false,
});
program.resolve_label(adjusted_metadata.jump_target_when_false, program.offset());
}
}
ast::Expr::Like { not, .. } => {
let cur_reg = program.alloc_register();

View File

@@ -778,7 +778,6 @@ pub fn group_by_emit_row_phase<'a>(
if let Some(having) = &group_by.having {
for expr in having.iter() {
let if_true_target = program.allocate_label();
let if_null_target = program.allocate_label();
translate_condition_expr(
program,
&plan.table_references,
@@ -787,7 +786,8 @@ pub fn group_by_emit_row_phase<'a>(
jump_if_condition_is_true: false,
jump_target_when_false: labels.label_group_by_end_without_emitting_row,
jump_target_when_true: if_true_target,
jump_target_when_null: if_null_target,
// treat null result has false for now
jump_target_when_null: labels.label_group_by_end_without_emitting_row,
},
&t_ctx.resolver,
)?;

View File

@@ -237,7 +237,6 @@ pub fn translate_create_index(
let mut skip_row_label = None;
if let Some(where_clause) = where_clause {
let label = program.allocate_label();
let null_label = program.allocate_label();
translate_condition_expr(
&mut program,
&table_references,
@@ -246,7 +245,7 @@ pub fn translate_create_index(
jump_if_condition_is_true: false,
jump_target_when_false: label,
jump_target_when_true: BranchOffset::Placeholder,
jump_target_when_null: null_label,
jump_target_when_null: label,
},
resolver,
)?;

View File

@@ -384,12 +384,11 @@ pub fn init_loop(
.filter(|c| c.should_eval_before_loop(&[JoinOrderMember::default()]))
{
let jump_target = program.allocate_label();
let null_jump_target = program.allocate_label();
let meta = ConditionMetadata {
jump_if_condition_is_true: false,
jump_target_when_true: jump_target,
jump_target_when_false: t_ctx.label_main_loop_end.unwrap(),
jump_target_when_null: null_jump_target,
jump_target_when_null: t_ctx.label_main_loop_end.unwrap(),
};
translate_condition_expr(program, tables, &cond.expr, meta, &t_ctx.resolver)?;
program.preassign_label_to_next_insn(jump_target);
@@ -711,12 +710,11 @@ fn emit_conditions(
.filter(|cond| cond.should_eval_at_loop(join_index, join_order))
{
let jump_target_when_true = program.allocate_label();
let jump_target_when_null = program.allocate_label();
let condition_metadata = ConditionMetadata {
jump_if_condition_is_true: false,
jump_target_when_true,
jump_target_when_false: next,
jump_target_when_null,
jump_target_when_null: next,
};
translate_condition_expr(
program,

View File

@@ -1736,8 +1736,8 @@ impl<'a> Parser<'a> {
} else {
Box::new(Expr::InList {
lhs: result,
not,
rhs: exprs,
not,
})
}
}

View File

@@ -913,6 +913,19 @@ do_execsql_test_on_specific_db {:memory:} select-in-simple {
} {1
0}
do_execsql_test_on_specific_db {:memory:} select-in-with-nulls {
SELECT 4 IN (1, 4, null);
SELECT 4 NOT IN (1, 4, null);
} {1
0}
# All should be null
do_execsql_test_on_specific_db {:memory:} select-in-with-nulls-2 {
SELECT 1 IN (2, 3, null);
SELECT 1 NOT IN (2, 3, null);
SELECT null in (null);
} {\n\n}
do_execsql_test_on_specific_db {:memory:} select-in-complex {
CREATE TABLE test_table (id INTEGER, category TEXT, value INTEGER);
INSERT INTO test_table VALUES (1, 'A', 10), (2, 'B', 20), (3, 'A', 30), (4, 'C', 40);