mod compile;
mod normalize;
mod validate;
use crate::db::{
predicate::{CoercionId, ComparePredicate, Predicate},
sql::{
lowering::{
SqlLoweringError,
expr::{SqlExprPhase, lower_sql_expr},
},
parser::{SqlExpr, SqlTextFunction, SqlTextFunctionCall},
},
};
use crate::value::Value;
pub(in crate::db) fn lower_sql_where_expr(expr: &SqlExpr) -> Result<Predicate, SqlLoweringError> {
if let Some(predicate) = lower_sql_membership_where_expr(expr) {
return Ok(predicate);
}
let expr = lower_sql_expr(expr, SqlExprPhase::PreAggregate)?;
validate::validate_where_bool_expr(&expr)?;
let expr = normalize::normalize_where_bool_expr(expr);
debug_assert!(normalize::is_normalized_where_bool_expr(&expr));
Ok(compile::compile_where_bool_expr_to_predicate(&expr))
}
fn lower_sql_membership_where_expr(expr: &SqlExpr) -> Option<Predicate> {
let SqlExpr::Membership {
expr,
values,
negated,
} = expr
else {
return None;
};
compile_sql_membership_where_expr(expr.as_ref(), values.as_slice(), *negated)
}
fn compile_sql_membership_where_expr(
expr: &SqlExpr,
values: &[Value],
negated: bool,
) -> Option<Predicate> {
let (field, coercion) = match expr {
SqlExpr::Field(field) => (field.clone(), CoercionId::Strict),
SqlExpr::TextFunction(SqlTextFunctionCall {
function: SqlTextFunction::Lower | SqlTextFunction::Upper,
field,
literal: None,
literal2: None,
literal3: None,
}) => (field.clone(), CoercionId::TextCasefold),
_ => return None,
};
if values
.iter()
.any(|value| matches!(value, Value::List(_) | Value::Map(_)))
{
return None;
}
if coercion == CoercionId::TextCasefold
&& !values.iter().all(|value| matches!(value, Value::Text(_)))
{
return None;
}
Some(Predicate::Compare(ComparePredicate::with_coercion(
field,
if negated {
crate::db::predicate::CompareOp::NotIn
} else {
crate::db::predicate::CompareOp::In
},
Value::List(values.to_vec()),
coercion,
)))
}