#[cfg(test)]
use crate::db::{data::SlotReader, scalar_expr::eval_scalar_value_program};
use crate::{
db::{
data::CanonicalSlotReader,
executor::projection::eval::{ProjectionEvalError, operators},
query::plan::expr::{BinaryOp, Expr, UnaryOp},
scalar_expr::{
ScalarExprValue, ScalarValueProgram, compile_scalar_field_program,
compile_scalar_literal_expr_value, eval_canonical_scalar_value_program,
scalar_expr_value_into_value,
},
},
error::InternalError,
model::entity::{EntityModel, resolve_field_slot},
value::Value,
};
#[derive(Debug)]
#[cfg(test)]
#[allow(dead_code)]
pub(in crate::db::executor) enum ScalarProjectionEvalError {
Eval(ProjectionEvalError),
Internal(InternalError),
}
#[cfg(test)]
#[allow(dead_code)]
impl ScalarProjectionEvalError {
pub(in crate::db::executor) fn into_internal_error(self) -> InternalError {
match self {
Self::Eval(err) => err.into_invalid_logical_plan_internal_error(),
Self::Internal(err) => err,
}
}
}
#[derive(Clone, Debug)]
pub(in crate::db::executor) enum ScalarProjectionExpr {
Field(ScalarProjectionField),
Literal(ScalarExprValue<'static>),
Unary {
op: UnaryOp,
expr: Box<Self>,
},
Binary {
op: BinaryOp,
left: Box<Self>,
right: Box<Self>,
},
}
#[derive(Clone, Debug)]
pub(in crate::db::executor) struct ScalarProjectionField {
field: String,
slot: usize,
#[cfg(any(test, feature = "sql"))]
program: ScalarValueProgram,
}
#[must_use]
pub(in crate::db::executor) fn compile_scalar_projection_expr(
model: &'static EntityModel,
expr: &Expr,
) -> Option<ScalarProjectionExpr> {
match expr {
Expr::Field(field_id) => {
let slot = resolve_field_slot(model, field_id.as_str())?;
#[cfg(any(test, feature = "sql"))]
let program = compile_scalar_field_program(model, field_id.as_str())?;
Some(ScalarProjectionExpr::Field(ScalarProjectionField {
field: field_id.as_str().to_string(),
slot,
#[cfg(any(test, feature = "sql"))]
program,
}))
}
Expr::Literal(value) => {
compile_scalar_literal_expr_value(value).map(ScalarProjectionExpr::Literal)
}
Expr::Unary { op, expr } => {
compile_scalar_projection_expr(model, expr.as_ref()).map(|expr| {
ScalarProjectionExpr::Unary {
op: *op,
expr: Box::new(expr),
}
})
}
Expr::Binary { op, left, right } => {
let left = compile_scalar_projection_expr(model, left.as_ref())?;
let right = compile_scalar_projection_expr(model, right.as_ref())?;
Some(ScalarProjectionExpr::Binary {
op: *op,
left: Box::new(left),
right: Box::new(right),
})
}
Expr::Aggregate(_) => None,
Expr::Alias { expr, .. } => compile_scalar_projection_expr(model, expr.as_ref()),
}
}
#[cfg(test)]
pub(in crate::db::executor) fn eval_scalar_projection_expr(
expr: &ScalarProjectionExpr,
slots: &dyn SlotReader,
) -> Result<Value, ScalarProjectionEvalError> {
eval_scalar_projection_expr_core(
expr,
&mut |field| eval_scalar_projection_field(field, slots),
&mut ScalarProjectionEvalError::Eval,
)
}
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::executor) fn eval_canonical_scalar_projection_expr(
expr: &ScalarProjectionExpr,
slots: &dyn CanonicalSlotReader,
) -> Result<Value, InternalError> {
eval_scalar_projection_expr_core(
expr,
&mut |field| eval_canonical_scalar_projection_field(field, slots),
&mut ProjectionEvalError::into_invalid_logical_plan_internal_error,
)
}
pub(in crate::db::executor) fn eval_scalar_projection_expr_with_value_reader(
expr: &ScalarProjectionExpr,
read_slot: &mut dyn FnMut(usize) -> Option<Value>,
) -> Result<Value, ProjectionEvalError> {
eval_scalar_projection_expr_core(
expr,
&mut |field| {
let Some(value) = read_slot(field.slot) else {
return Err(ProjectionEvalError::MissingFieldValue {
field: field.field.clone(),
index: field.slot,
});
};
Ok(value)
},
&mut |err| err,
)
}
fn eval_scalar_projection_expr_core<E>(
expr: &ScalarProjectionExpr,
eval_field: &mut dyn FnMut(&ScalarProjectionField) -> Result<Value, E>,
map_projection_error: &mut dyn FnMut(ProjectionEvalError) -> E,
) -> Result<Value, E> {
match expr {
ScalarProjectionExpr::Field(field) => eval_field(field),
ScalarProjectionExpr::Literal(value) => Ok(scalar_expr_value_into_value(value.clone())),
ScalarProjectionExpr::Unary { op, expr } => {
let operand = eval_scalar_projection_expr_core(expr, eval_field, map_projection_error)?;
operators::eval_unary_expr(*op, operand).map_err(map_projection_error)
}
ScalarProjectionExpr::Binary { op, left, right } => {
let left = eval_scalar_projection_expr_core(left, eval_field, map_projection_error)?;
let right = eval_scalar_projection_expr_core(right, eval_field, map_projection_error)?;
operators::eval_binary_expr(*op, left, right).map_err(map_projection_error)
}
}
}
#[cfg(test)]
fn eval_scalar_projection_field(
field: &ScalarProjectionField,
slots: &dyn SlotReader,
) -> Result<Value, ScalarProjectionEvalError> {
let Some(value) = eval_scalar_value_program(&field.program, slots)
.map_err(ScalarProjectionEvalError::Internal)?
else {
return Err(ScalarProjectionEvalError::Eval(
ProjectionEvalError::MissingFieldValue {
field: field.field.clone(),
index: field.slot,
},
));
};
Ok(scalar_expr_value_into_value(value))
}
#[cfg(any(test, feature = "sql"))]
fn eval_canonical_scalar_projection_field(
field: &ScalarProjectionField,
slots: &dyn CanonicalSlotReader,
) -> Result<Value, InternalError> {
let value = eval_canonical_scalar_value_program(&field.program, slots)?;
Ok(scalar_expr_value_into_value(value))
}