use crate::{
db::{
QueryError,
executor::projection::eval_value_projection_expr_with_value,
query::{
builder::{
ValueProjectionExpr, scalar_projection::render_scalar_projection_expr_sql_label,
},
plan::expr::{BinaryOp, Expr, FieldId, Function},
},
},
traits::{FieldValue, NumericValue},
value::Value,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NumericProjectionExpr {
field: String,
expr: Expr,
}
impl NumericProjectionExpr {
fn arithmetic_value(
field: impl Into<String>,
op: BinaryOp,
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,
left: Box::new(Expr::Field(FieldId::new(field.clone()))),
right: Box::new(Expr::Literal(literal)),
},
field,
})
}
fn arithmetic_numeric_literal(
field: impl Into<String>,
op: BinaryOp,
literal: impl FieldValue + NumericValue,
) -> Self {
let literal = literal.to_value();
Self::arithmetic_value(field, op, literal)
.expect("typed numeric projection helpers should always produce numeric literals")
}
pub(in crate::db) fn add_value(
field: impl Into<String>,
literal: Value,
) -> Result<Self, QueryError> {
Self::arithmetic_value(field, BinaryOp::Add, literal)
}
pub(in crate::db) fn sub_value(
field: impl Into<String>,
literal: Value,
) -> Result<Self, QueryError> {
Self::arithmetic_value(field, BinaryOp::Sub, literal)
}
pub(in crate::db) fn mul_value(
field: impl Into<String>,
literal: Value,
) -> Result<Self, QueryError> {
Self::arithmetic_value(field, BinaryOp::Mul, literal)
}
pub(in crate::db) fn div_value(
field: impl Into<String>,
literal: Value,
) -> Result<Self, QueryError> {
Self::arithmetic_value(field, BinaryOp::Div, literal)
}
pub(in crate::db) fn add_numeric_literal(
field: impl Into<String>,
literal: impl FieldValue + NumericValue,
) -> Self {
Self::arithmetic_numeric_literal(field, BinaryOp::Add, literal)
}
pub(in crate::db) fn sub_numeric_literal(
field: impl Into<String>,
literal: impl FieldValue + NumericValue,
) -> Self {
Self::arithmetic_numeric_literal(field, BinaryOp::Sub, literal)
}
pub(in crate::db) fn mul_numeric_literal(
field: impl Into<String>,
literal: impl FieldValue + NumericValue,
) -> Self {
Self::arithmetic_numeric_literal(field, BinaryOp::Mul, literal)
}
pub(in crate::db) fn div_numeric_literal(
field: impl Into<String>,
literal: impl FieldValue + NumericValue,
) -> Self {
Self::arithmetic_numeric_literal(field, BinaryOp::Div, literal)
}
#[must_use]
pub(in crate::db) const fn expr(&self) -> &Expr {
&self.expr
}
pub(in crate::db) fn round_with_scale(
&self,
scale: u32,
) -> Result<RoundProjectionExpr, QueryError> {
RoundProjectionExpr::new(
self.field.clone(),
self.expr.clone(),
Value::Uint(u64::from(scale)),
)
}
}
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> {
eval_value_projection_expr_with_value(&self.expr, self.field.as_str(), &value)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RoundProjectionExpr {
field: String,
expr: Expr,
}
impl RoundProjectionExpr {
pub(in crate::db) fn new(
field: impl Into<String>,
inner: Expr,
scale: Value,
) -> Result<Self, QueryError> {
match scale {
Value::Int(value) if value < 0 => {
return Err(QueryError::unsupported_query(format!(
"ROUND(...) requires non-negative integer scale, found {value}",
)));
}
Value::Int(_) | Value::Uint(_) => {}
other => {
return Err(QueryError::unsupported_query(format!(
"ROUND(...) requires integer scale, found {other:?}",
)));
}
}
Ok(Self {
field: field.into(),
expr: Expr::FunctionCall {
function: Function::Round,
args: vec![inner, Expr::Literal(scale)],
},
})
}
pub(in crate::db) fn field(field: impl Into<String>, scale: u32) -> Result<Self, QueryError> {
let field = field.into();
Self::new(
field.clone(),
Expr::Field(FieldId::new(field)),
Value::Uint(u64::from(scale)),
)
}
#[must_use]
pub(in crate::db) const fn expr(&self) -> &Expr {
&self.expr
}
}
impl ValueProjectionExpr for RoundProjectionExpr {
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> {
eval_value_projection_expr_with_value(&self.expr, self.field.as_str(), &value)
}
}
#[must_use]
pub fn add(
field: impl AsRef<str>,
literal: impl FieldValue + NumericValue,
) -> NumericProjectionExpr {
NumericProjectionExpr::add_numeric_literal(field.as_ref().to_string(), literal)
}
#[must_use]
pub fn sub(
field: impl AsRef<str>,
literal: impl FieldValue + NumericValue,
) -> NumericProjectionExpr {
NumericProjectionExpr::sub_numeric_literal(field.as_ref().to_string(), literal)
}
#[must_use]
pub fn mul(
field: impl AsRef<str>,
literal: impl FieldValue + NumericValue,
) -> NumericProjectionExpr {
NumericProjectionExpr::mul_numeric_literal(field.as_ref().to_string(), literal)
}
#[must_use]
pub fn div(
field: impl AsRef<str>,
literal: impl FieldValue + NumericValue,
) -> NumericProjectionExpr {
NumericProjectionExpr::div_numeric_literal(field.as_ref().to_string(), literal)
}
pub fn round(field: impl AsRef<str>, scale: u32) -> RoundProjectionExpr {
RoundProjectionExpr::field(field.as_ref().to_string(), scale)
.expect("ROUND(field, scale) helper should always produce a bounded projection")
}
#[must_use]
pub fn round_expr(projection: &NumericProjectionExpr, scale: u32) -> RoundProjectionExpr {
projection
.round_with_scale(scale)
.expect("ROUND(expr, scale) helper should always produce a bounded projection")
}