icydb-core 0.74.9

IcyDB — A type-safe, embedded ORM and schema system for the Internet Computer
Documentation
//! Module: executor::projection::eval
//! Responsibility: generic and grouped projection expression evaluation against runtime row contexts.
//! Does not own: scalar-compiled projection execution, expression type inference, or planner semantic validation policy.
//! Boundary: provides executor-side generic evaluation and shared projection error taxonomy.

mod operators;
mod scalar;

#[cfg(test)]
use crate::db::executor::projection::grouped::{
    GroupedRowView, compile_grouped_projection_expr, eval_grouped_projection_expr,
};
use crate::{
    db::query::plan::expr::Expr,
    error::InternalError,
    model::entity::{EntityModel, resolve_field_slot},
    value::Value,
};
#[cfg(any(test, feature = "sql"))]
use std::borrow::Cow;
use thiserror::Error as ThisError;

pub(in crate::db::executor) use operators::{eval_binary_expr, eval_unary_expr};
#[cfg(test)]
pub(in crate::db::executor) use scalar::eval_canonical_scalar_projection_expr;
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::executor) use scalar::eval_canonical_scalar_projection_expr_with_required_value_reader_cow;
#[cfg(test)]
pub(in crate::db::executor) use scalar::{ScalarProjectionEvalError, eval_scalar_projection_expr};
pub(in crate::db::executor) use scalar::{
    ScalarProjectionExpr, compile_scalar_projection_expr,
    eval_scalar_projection_expr_with_value_reader,
};

///
/// ProjectionEvalError
///
/// Pure expression-evaluation failures for scalar projection execution.
///

#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
pub(in crate::db::executor) enum ProjectionEvalError {
    #[error("projection expression references unknown field '{field}'")]
    UnknownField { field: String },

    #[error("projection expression could not read field '{field}' at index={index}")]
    MissingFieldValue { field: String, index: usize },

    #[error("projection expression cannot evaluate aggregate '{kind}' in scalar row context")]
    AggregateNotEvaluable { kind: String },

    #[error("projection unary operator '{op}' is incompatible with operand value {found:?}")]
    InvalidUnaryOperand { op: String, found: Box<Value> },

    #[error(
        "projection binary operator '{op}' is incompatible with operand values ({left:?}, {right:?})"
    )]
    InvalidBinaryOperands {
        op: String,
        left: Box<Value>,
        right: Box<Value>,
    },

    #[error(
        "grouped projection expression references unknown aggregate expression kind={kind} target_field={target_field:?} distinct={distinct}"
    )]
    UnknownGroupedAggregateExpression {
        kind: String,
        target_field: Option<String>,
        distinct: bool,
    },

    #[error(
        "grouped projection expression references aggregate output index={aggregate_index} but only {aggregate_count} outputs are available"
    )]
    MissingGroupedAggregateValue {
        aggregate_index: usize,
        aggregate_count: usize,
    },
}

impl ProjectionEvalError {
    /// Map one projection evaluation failure into the executor invalid-logical-plan boundary.
    pub(in crate::db::executor) fn into_invalid_logical_plan_internal_error(self) -> InternalError {
        InternalError::query_invalid_logical_plan(self.to_string())
    }

    /// Map one grouped projection evaluation failure into the grouped-output
    /// invalid-logical-plan boundary while preserving grouped context.
    pub(in crate::db::executor) fn into_grouped_projection_internal_error(self) -> InternalError {
        InternalError::query_invalid_logical_plan(format!(
            "grouped projection evaluation failed: {self}",
        ))
    }
}

/// Evaluate one projection expression through one runtime slot reader.
pub(in crate::db::executor) fn eval_expr_with_slot_reader(
    expr: &Expr,
    model: &EntityModel,
    read_slot: &mut dyn FnMut(usize) -> Option<Value>,
) -> Result<Value, ProjectionEvalError> {
    match expr {
        Expr::Field(field_id) => {
            let field_name = field_id.as_str();
            let Some(field_index) = resolve_field_slot(model, field_name) else {
                return Err(ProjectionEvalError::UnknownField {
                    field: field_name.to_string(),
                });
            };
            let Some(value) = read_slot(field_index) else {
                return Err(ProjectionEvalError::MissingFieldValue {
                    field: field_name.to_string(),
                    index: field_index,
                });
            };

            Ok(value)
        }
        Expr::Literal(value) => Ok(value.clone()),
        Expr::Unary { op, expr } => {
            let operand = eval_expr_with_slot_reader(expr.as_ref(), model, read_slot)?;
            operators::eval_unary_expr(*op, operand)
        }
        Expr::Binary { op, left, right } => {
            let left_value = eval_expr_with_slot_reader(left.as_ref(), model, read_slot)?;
            let right_value = eval_expr_with_slot_reader(right.as_ref(), model, read_slot)?;

            operators::eval_binary_expr(*op, left_value, right_value)
        }
        Expr::Aggregate(aggregate) => Err(ProjectionEvalError::AggregateNotEvaluable {
            kind: format!("{:?}", aggregate.kind()),
        }),
        Expr::Alias { expr, .. } => eval_expr_with_slot_reader(expr.as_ref(), model, read_slot),
    }
}

/// Evaluate one projection expression through one required-value reader on the
/// canonical structural row path.
#[cfg(test)]
pub(in crate::db::executor) fn eval_expr_with_required_value_reader(
    expr: &Expr,
    model: &EntityModel,
    read_slot: &mut dyn FnMut(usize) -> Result<Value, InternalError>,
) -> Result<Value, InternalError> {
    match expr {
        Expr::Field(field_id) => {
            let field_name = field_id.as_str();
            let Some(field_index) = resolve_field_slot(model, field_name) else {
                return Err(ProjectionEvalError::UnknownField {
                    field: field_name.to_string(),
                }
                .into_invalid_logical_plan_internal_error());
            };

            read_slot(field_index)
        }
        Expr::Literal(value) => Ok(value.clone()),
        Expr::Unary { op, expr } => {
            let operand = eval_expr_with_required_value_reader(expr.as_ref(), model, read_slot)?;
            operators::eval_unary_expr(*op, operand)
                .map_err(ProjectionEvalError::into_invalid_logical_plan_internal_error)
        }
        Expr::Binary { op, left, right } => {
            let left_value = eval_expr_with_required_value_reader(left.as_ref(), model, read_slot)?;
            let right_value =
                eval_expr_with_required_value_reader(right.as_ref(), model, read_slot)?;

            operators::eval_binary_expr(*op, left_value, right_value)
                .map_err(ProjectionEvalError::into_invalid_logical_plan_internal_error)
        }
        Expr::Aggregate(aggregate) => Err(ProjectionEvalError::AggregateNotEvaluable {
            kind: format!("{:?}", aggregate.kind()),
        }
        .into_invalid_logical_plan_internal_error()),
        Expr::Alias { expr, .. } => {
            eval_expr_with_required_value_reader(expr.as_ref(), model, read_slot)
        }
    }
}

/// Evaluate one projection expression through one canonical reader that can
/// borrow decoded values from the structural row cache.
/// Field-only projections can stay borrowed until the SQL projection layer
/// needs owned output, while computed expressions still materialize exactly
/// once at the operator boundary.
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::executor) fn eval_expr_with_required_value_reader_cow<'a>(
    expr: &Expr,
    model: &EntityModel,
    read_slot: &mut dyn FnMut(usize) -> Result<Cow<'a, Value>, InternalError>,
) -> Result<Cow<'a, Value>, InternalError> {
    match expr {
        Expr::Field(field_id) => {
            let field_name = field_id.as_str();
            let Some(field_index) = resolve_field_slot(model, field_name) else {
                return Err(ProjectionEvalError::UnknownField {
                    field: field_name.to_string(),
                }
                .into_invalid_logical_plan_internal_error());
            };

            read_slot(field_index)
        }
        Expr::Literal(value) => Ok(Cow::Owned(value.clone())),
        Expr::Unary { op, expr } => {
            let operand =
                eval_expr_with_required_value_reader_cow(expr.as_ref(), model, read_slot)?
                    .into_owned();

            operators::eval_unary_expr(*op, operand)
                .map(Cow::Owned)
                .map_err(ProjectionEvalError::into_invalid_logical_plan_internal_error)
        }
        Expr::Binary { op, left, right } => {
            let left_value =
                eval_expr_with_required_value_reader_cow(left.as_ref(), model, read_slot)?
                    .into_owned();
            let right_value =
                eval_expr_with_required_value_reader_cow(right.as_ref(), model, read_slot)?
                    .into_owned();

            operators::eval_binary_expr(*op, left_value, right_value)
                .map(Cow::Owned)
                .map_err(ProjectionEvalError::into_invalid_logical_plan_internal_error)
        }
        Expr::Aggregate(aggregate) => Err(ProjectionEvalError::AggregateNotEvaluable {
            kind: format!("{:?}", aggregate.kind()),
        }
        .into_invalid_logical_plan_internal_error()),
        Expr::Alias { expr, .. } => {
            eval_expr_with_required_value_reader_cow(expr.as_ref(), model, read_slot)
        }
    }
}

/// Evaluate one projection expression against one grouped output row view.
#[cfg(test)]
pub(in crate::db::executor) fn eval_expr_grouped(
    expr: &Expr,
    grouped_row: &GroupedRowView<'_>,
) -> Result<Value, ProjectionEvalError> {
    let compiled = compile_grouped_projection_expr(
        expr,
        grouped_row.group_fields,
        grouped_row.aggregate_execution_specs,
    )?;

    eval_grouped_projection_expr(&compiled, grouped_row)
}