icydb_core/db/query/builder/
numeric_projection.rs1use crate::{
10 db::{
11 QueryError,
12 executor::projection::eval_binary_expr,
13 query::{
14 builder::{
15 ValueProjectionExpr, scalar_projection::render_scalar_projection_expr_sql_label,
16 },
17 plan::expr::{BinaryOp, Expr, FieldId},
18 },
19 },
20 traits::{FieldValue, NumericValue},
21 value::Value,
22};
23
24#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct NumericProjectionExpr {
35 field: String,
36 expr: Expr,
37}
38
39impl NumericProjectionExpr {
40 pub(in crate::db) fn add_value(
43 field: impl Into<String>,
44 literal: Value,
45 ) -> Result<Self, QueryError> {
46 if !matches!(
47 literal,
48 Value::Int(_)
49 | Value::Int128(_)
50 | Value::IntBig(_)
51 | Value::Uint(_)
52 | Value::Uint128(_)
53 | Value::UintBig(_)
54 | Value::Decimal(_)
55 | Value::Float32(_)
56 | Value::Float64(_)
57 | Value::Duration(_)
58 | Value::Timestamp(_)
59 | Value::Date(_)
60 ) {
61 return Err(QueryError::unsupported_query(format!(
62 "scalar numeric projection requires a numeric literal, found {literal:?}",
63 )));
64 }
65
66 let field = field.into();
67
68 Ok(Self {
69 expr: Expr::Binary {
70 op: BinaryOp::Add,
71 left: Box::new(Expr::Field(FieldId::new(field.clone()))),
72 right: Box::new(Expr::Literal(literal)),
73 },
74 field,
75 })
76 }
77
78 pub(in crate::db) fn add_numeric_literal(
81 field: impl Into<String>,
82 literal: impl FieldValue + NumericValue,
83 ) -> Self {
84 let literal = literal.to_value();
85
86 Self::add_value(field, literal)
87 .expect("typed numeric projection helpers should always produce numeric literals")
88 }
89
90 #[must_use]
92 pub(in crate::db) const fn expr(&self) -> &Expr {
93 &self.expr
94 }
95}
96
97impl ValueProjectionExpr for NumericProjectionExpr {
98 fn field(&self) -> &str {
99 self.field.as_str()
100 }
101
102 fn sql_label(&self) -> String {
103 render_scalar_projection_expr_sql_label(&self.expr)
104 }
105
106 fn apply_value(&self, value: Value) -> Result<Value, QueryError> {
107 let Expr::Binary { op, right, .. } = &self.expr else {
108 return Err(QueryError::invariant(
109 "numeric projection helper must retain one binary expression",
110 ));
111 };
112 let Expr::Literal(literal) = right.as_ref() else {
113 return Err(QueryError::invariant(
114 "numeric projection helper must retain one literal right operand",
115 ));
116 };
117
118 eval_binary_expr(*op, &value, literal)
119 .map_err(|err| QueryError::unsupported_query(err.to_string()))
120 }
121}
122
123#[must_use]
125pub fn add(
126 field: impl AsRef<str>,
127 literal: impl FieldValue + NumericValue,
128) -> NumericProjectionExpr {
129 NumericProjectionExpr::add_numeric_literal(field.as_ref().to_string(), literal)
130}