use crate::{
db::{
QueryError,
executor::projection::eval_binary_expr,
query::{
builder::{
ValueProjectionExpr, scalar_projection::render_scalar_projection_expr_sql_label,
},
plan::expr::{BinaryOp, Expr, FieldId},
},
},
traits::{FieldValue, NumericValue},
value::Value,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NumericProjectionExpr {
field: String,
expr: Expr,
}
impl NumericProjectionExpr {
pub(in crate::db) fn add_value(
field: impl Into<String>,
literal: Value,
) -> Result<Self, QueryError> {
if !matches!(
literal,
Value::Int(_)
| Value::Int128(_)
| Value::IntBig(_)
| Value::Uint(_)
| Value::Uint128(_)
| Value::UintBig(_)
| Value::Decimal(_)
| Value::Float32(_)
| Value::Float64(_)
| Value::Duration(_)
| Value::Timestamp(_)
| Value::Date(_)
) {
return Err(QueryError::unsupported_query(format!(
"scalar numeric projection requires a numeric literal, found {literal:?}",
)));
}
let field = field.into();
Ok(Self {
expr: Expr::Binary {
op: BinaryOp::Add,
left: Box::new(Expr::Field(FieldId::new(field.clone()))),
right: Box::new(Expr::Literal(literal)),
},
field,
})
}
pub(in crate::db) fn add_numeric_literal(
field: impl Into<String>,
literal: impl FieldValue + NumericValue,
) -> Self {
let literal = literal.to_value();
Self::add_value(field, literal)
.expect("typed numeric projection helpers should always produce numeric literals")
}
#[must_use]
pub(in crate::db) const fn expr(&self) -> &Expr {
&self.expr
}
}
impl ValueProjectionExpr for NumericProjectionExpr {
fn field(&self) -> &str {
self.field.as_str()
}
fn sql_label(&self) -> String {
render_scalar_projection_expr_sql_label(&self.expr)
}
fn apply_value(&self, value: Value) -> Result<Value, QueryError> {
let Expr::Binary { op, right, .. } = &self.expr else {
return Err(QueryError::invariant(
"numeric projection helper must retain one binary expression",
));
};
let Expr::Literal(literal) = right.as_ref() else {
return Err(QueryError::invariant(
"numeric projection helper must retain one literal right operand",
));
};
eval_binary_expr(*op, &value, literal)
.map_err(|err| QueryError::unsupported_query(err.to_string()))
}
}
#[must_use]
pub fn add(
field: impl AsRef<str>,
literal: impl FieldValue + NumericValue,
) -> NumericProjectionExpr {
NumericProjectionExpr::add_numeric_literal(field.as_ref().to_string(), literal)
}