refactor: use walk_expr() in resolving vtab constraints

This commit is contained in:
Jussi Saurio
2025-05-23 16:20:13 +03:00
parent fbfd2b2c38
commit c18c6a00fa
3 changed files with 38 additions and 42 deletions

View File

@@ -365,6 +365,7 @@ pub fn open_loop(
.filter_map(|(i, p)| {
// Build ConstraintInfo from the predicates
convert_where_to_vtab_constraint(p, table_index, i)
.unwrap_or(None)
})
.collect::<Vec<_>>();
// TODO: get proper order_by information to pass to the vtab.

View File

@@ -161,14 +161,14 @@ pub fn convert_where_to_vtab_constraint(
term: &WhereTerm,
table_index: usize,
pred_idx: usize,
) -> Option<ConstraintInfo> {
) -> Result<Option<ConstraintInfo>> {
if term.from_outer_join.is_some() {
return None;
return Ok(None);
}
let Expr::Binary(lhs, op, rhs) = &term.expr else {
return None;
return Ok(None);
};
let expr_is_ready = |e: &Expr| -> bool { can_pushdown_predicate(e, table_index) };
let expr_is_ready = |e: &Expr| -> Result<bool> { can_pushdown_predicate(e, table_index) };
let (vcol_idx, op_for_vtab, usable, is_rhs) = match (&**lhs, &**rhs) {
(
Expr::Column {
@@ -186,7 +186,7 @@ pub fn convert_where_to_vtab_constraint(
let vtab_on_l = *tbl_l == table_index;
let vtab_on_r = *tbl_r == table_index;
if vtab_on_l == vtab_on_r {
return None; // either both or none -> not convertible
return Ok(None); // either both or none -> not convertible
}
if vtab_on_l {
@@ -203,26 +203,30 @@ pub fn convert_where_to_vtab_constraint(
(
column,
op,
expr_is_ready(other), // literal / earliertable / deterministic func ?
expr_is_ready(other)?, // literal / earliertable / deterministic func ?
false,
)
}
(other, Expr::Column { table, column, .. }) if *table == table_index => (
column,
&reverse_operator(op).unwrap_or(*op),
expr_is_ready(other),
expr_is_ready(other)?,
true,
),
_ => return None, // does not involve the virtual table at all
_ => return Ok(None), // does not involve the virtual table at all
};
Some(ConstraintInfo {
let Some(op) = to_ext_constraint_op(op_for_vtab) else {
return Ok(None);
};
Ok(Some(ConstraintInfo {
column_index: *vcol_idx as u32,
op: to_ext_constraint_op(op_for_vtab)?,
op,
usable,
plan_info: ConstraintInfo::pack_plan_info(pred_idx as u32, is_rhs),
})
}))
}
/// The loop index where to evaluate the condition.
/// For example, in `SELECT * FROM u JOIN p WHERE u.id = 5`, the condition can already be evaluated at the first loop (idx 0),

View File

@@ -1,7 +1,6 @@
use crate::{
function::Func,
schema::{self, Column, Schema, Type},
translate::collate::CollationSeq,
translate::{collate::CollationSeq, expr::walk_expr},
types::{Value, ValueType},
LimboError, OpenFlags, Result, Statement, StepResult, SymbolTable, IO,
};
@@ -584,35 +583,27 @@ pub fn columns_from_create_table_body(body: &ast::CreateTableBody) -> crate::Res
/// This function checks if a given expression is a constant value that can be pushed down to the database engine.
/// It is expected to be called with the other half of a binary expression with an Expr::Column
pub fn can_pushdown_predicate(expr: &Expr, table_idx: usize) -> bool {
match expr {
Expr::Literal(_) => true,
Expr::Column { table, .. } => *table <= table_idx,
Expr::Binary(lhs, _, rhs) => {
can_pushdown_predicate(lhs, table_idx) && can_pushdown_predicate(rhs, table_idx)
}
Expr::Parenthesized(exprs) => can_pushdown_predicate(exprs.first().unwrap(), table_idx),
Expr::Unary(_, expr) => can_pushdown_predicate(expr, table_idx),
Expr::FunctionCall { args, name, .. } => {
let function = crate::function::Func::resolve_function(
&name.0,
args.as_ref().map_or(0, |a| a.len()),
);
// is deterministic
matches!(function, Ok(Func::Scalar(_)))
}
Expr::Like { lhs, rhs, .. } => {
can_pushdown_predicate(lhs, table_idx) && can_pushdown_predicate(rhs, table_idx)
}
Expr::Between {
lhs, start, end, ..
} => {
can_pushdown_predicate(lhs, table_idx)
&& can_pushdown_predicate(start, table_idx)
&& can_pushdown_predicate(end, table_idx)
}
_ => false,
}
pub fn can_pushdown_predicate(top_level_expr: &Expr, table_idx: usize) -> Result<bool> {
let mut can_pushdown = true;
walk_expr(top_level_expr, &mut |expr: &Expr| -> Result<()> {
match expr {
Expr::Column { table, .. } | Expr::RowId { table, .. } => {
can_pushdown &= *table <= table_idx;
}
Expr::FunctionCall { args, name, .. } => {
let function = crate::function::Func::resolve_function(
&name.0,
args.as_ref().map_or(0, |a| a.len()),
)?;
// is deterministic
can_pushdown &= function.is_deterministic();
}
_ => {}
};
Ok(())
})?;
Ok(can_pushdown)
}
#[derive(Debug, Default, PartialEq)]