icydb-core 0.94.3

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use crate::{
    db::{
        query::plan::expr::{BinaryOp, Expr, Function, UnaryOp},
        sql::lowering::SqlLoweringError,
    },
    value::Value,
};

// Validate one planner-owned boolean WHERE expression after shared SQL
// lowering. This owns clause admission only; it does not reshape semantics.
pub(super) fn validate_where_bool_expr(expr: &Expr) -> Result<(), SqlLoweringError> {
    match expr {
        // Keep bare field leaves admitted here so boolean-valued field
        // conditions can flow through CASE/NOT/AND/OR. Non-boolean fields still
        // fail closed later at normal schema validation once predicate
        // adaptation lowers them onto the canonical `field = TRUE/FALSE` seam.
        Expr::Field(_) => Ok(()),
        Expr::Literal(Value::Bool(_) | Value::Null) => Ok(()),
        Expr::Unary {
            op: UnaryOp::Not,
            expr,
        } => validate_where_bool_expr(expr.as_ref()),
        Expr::Binary {
            op: BinaryOp::And | BinaryOp::Or,
            left,
            right,
        } => {
            validate_where_bool_expr(left.as_ref())?;
            validate_where_bool_expr(right.as_ref())
        }
        Expr::Binary { op, left, right } => validate_where_bool_compare_expr(*op, left, right),
        Expr::FunctionCall { function, args } => validate_where_bool_function_call(*function, args),
        Expr::Case {
            when_then_arms,
            else_expr,
        } => {
            for arm in when_then_arms {
                validate_where_bool_expr(arm.condition())?;
                validate_where_bool_expr(arm.result())?;
            }

            validate_where_bool_expr(else_expr.as_ref())
        }
        #[cfg(test)]
        Expr::Alias { .. } => Err(SqlLoweringError::unsupported_where_expression()),
        Expr::Aggregate(_) | Expr::Literal(_) => {
            Err(SqlLoweringError::unsupported_where_expression())
        }
    }
}

// Validate one boolean comparison after shared SQL expression lowering so
// predicate compilation can stay total and structure-preserving.
fn validate_where_bool_compare_expr(
    op: BinaryOp,
    left: &Expr,
    right: &Expr,
) -> Result<(), SqlLoweringError> {
    match op {
        BinaryOp::Eq
        | BinaryOp::Ne
        | BinaryOp::Lt
        | BinaryOp::Lte
        | BinaryOp::Gt
        | BinaryOp::Gte
            if where_compare_operand_is_admitted(left)
                && where_compare_operand_is_admitted(right) =>
        {
            Ok(())
        }
        BinaryOp::Eq
        | BinaryOp::Ne
        | BinaryOp::Lt
        | BinaryOp::Lte
        | BinaryOp::Gt
        | BinaryOp::Gte
        | BinaryOp::Or
        | BinaryOp::And
        | BinaryOp::Add
        | BinaryOp::Sub
        | BinaryOp::Mul
        | BinaryOp::Div => Err(SqlLoweringError::unsupported_where_expression()),
    }
}

// Validate one boolean function-call shell in WHERE after SQL expression
// lowering so the compiler can assume a bounded function family.
fn validate_where_bool_function_call(
    function: Function,
    args: &[Expr],
) -> Result<(), SqlLoweringError> {
    match function {
        Function::IsNull | Function::IsNotNull => match args {
            [arg] if where_null_test_operand_is_admitted(arg) => Ok(()),
            _ => Err(SqlLoweringError::unsupported_where_expression()),
        },
        Function::StartsWith | Function::EndsWith | Function::Contains => match args {
            [left, Expr::Literal(Value::Text(_))] if where_text_target_is_admitted(left) => Ok(()),
            _ => Err(SqlLoweringError::unsupported_where_expression()),
        },
        _ => Err(SqlLoweringError::unsupported_where_expression()),
    }
}

// Keep WHERE compare admission bounded to the reduced shipped operand family.
fn where_compare_operand_is_admitted(expr: &Expr) -> bool {
    match expr {
        Expr::Field(_) | Expr::Literal(_) => true,
        Expr::FunctionCall {
            function: Function::Lower | Function::Upper,
            args,
        } => matches!(args.as_slice(), [Expr::Field(_)]),
        Expr::Aggregate(_)
        | Expr::Unary { .. }
        | Expr::Binary { .. }
        | Expr::Case { .. }
        | Expr::FunctionCall { .. } => false,
        #[cfg(test)]
        Expr::Alias { .. } => false,
    }
}

// Keep null-test admission aligned with the shipped field-or-literal surface.
const fn where_null_test_operand_is_admitted(expr: &Expr) -> bool {
    matches!(expr, Expr::Field(_) | Expr::Literal(_))
}

// Keep direct text-predicate admission aligned with field or casefold wrapper
// targets only.
fn where_text_target_is_admitted(expr: &Expr) -> bool {
    match expr {
        Expr::Field(_) => true,
        Expr::FunctionCall {
            function: Function::Lower | Function::Upper,
            args,
        } => matches!(args.as_slice(), [Expr::Field(_)]),
        Expr::Aggregate(_)
        | Expr::Literal(_)
        | Expr::Unary { .. }
        | Expr::Binary { .. }
        | Expr::Case { .. }
        | Expr::FunctionCall { .. } => false,
        #[cfg(test)]
        Expr::Alias { .. } => false,
    }
}