icydb_core/db/query/builder/
numeric_projection.rs1use crate::{
10 db::{
11 QueryError,
12 query::{
13 builder::{
14 ValueProjectionExpr, scalar_projection::render_scalar_projection_expr_plan_label,
15 },
16 plan::expr::{BinaryOp, Expr, FieldId, Function, eval_builder_expr_for_value_preview},
17 },
18 },
19 traits::NumericValue,
20 value::{InputValue, Value},
21};
22
23#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct NumericProjectionExpr {
34 field: String,
35 expr: Expr,
36}
37
38impl NumericProjectionExpr {
39 fn arithmetic_value(
42 field: impl Into<String>,
43 op: BinaryOp,
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,
71 left: Box::new(Expr::Field(FieldId::new(field.clone()))),
72 right: Box::new(Expr::Literal(literal)),
73 },
74 field,
75 })
76 }
77
78 fn arithmetic_numeric_literal(
81 field: impl Into<String>,
82 op: BinaryOp,
83 literal: impl Into<InputValue> + NumericValue,
84 ) -> Self {
85 let literal = Value::from(literal.into());
86
87 Self::arithmetic_value(field, op, literal)
88 .expect("typed numeric projection helpers should always produce numeric literals")
89 }
90
91 pub(in crate::db) fn add_value(
93 field: impl Into<String>,
94 literal: Value,
95 ) -> Result<Self, QueryError> {
96 Self::arithmetic_value(field, BinaryOp::Add, literal)
97 }
98
99 pub(in crate::db) fn sub_value(
101 field: impl Into<String>,
102 literal: Value,
103 ) -> Result<Self, QueryError> {
104 Self::arithmetic_value(field, BinaryOp::Sub, literal)
105 }
106
107 pub(in crate::db) fn mul_value(
109 field: impl Into<String>,
110 literal: Value,
111 ) -> Result<Self, QueryError> {
112 Self::arithmetic_value(field, BinaryOp::Mul, literal)
113 }
114
115 pub(in crate::db) fn div_value(
117 field: impl Into<String>,
118 literal: Value,
119 ) -> Result<Self, QueryError> {
120 Self::arithmetic_value(field, BinaryOp::Div, literal)
121 }
122
123 pub(in crate::db) fn add_numeric_literal(
126 field: impl Into<String>,
127 literal: impl Into<InputValue> + NumericValue,
128 ) -> Self {
129 Self::arithmetic_numeric_literal(field, BinaryOp::Add, literal)
130 }
131
132 pub(in crate::db) fn sub_numeric_literal(
135 field: impl Into<String>,
136 literal: impl Into<InputValue> + NumericValue,
137 ) -> Self {
138 Self::arithmetic_numeric_literal(field, BinaryOp::Sub, literal)
139 }
140
141 pub(in crate::db) fn mul_numeric_literal(
144 field: impl Into<String>,
145 literal: impl Into<InputValue> + NumericValue,
146 ) -> Self {
147 Self::arithmetic_numeric_literal(field, BinaryOp::Mul, literal)
148 }
149
150 pub(in crate::db) fn div_numeric_literal(
153 field: impl Into<String>,
154 literal: impl Into<InputValue> + NumericValue,
155 ) -> Self {
156 Self::arithmetic_numeric_literal(field, BinaryOp::Div, literal)
157 }
158
159 #[must_use]
161 pub(in crate::db) const fn expr(&self) -> &Expr {
162 &self.expr
163 }
164
165 pub(in crate::db) fn round_with_scale(
168 &self,
169 scale: u32,
170 ) -> Result<RoundProjectionExpr, QueryError> {
171 RoundProjectionExpr::new(
172 self.field.clone(),
173 self.expr.clone(),
174 Value::Uint(u64::from(scale)),
175 )
176 }
177}
178
179impl ValueProjectionExpr for NumericProjectionExpr {
180 fn field(&self) -> &str {
181 self.field.as_str()
182 }
183
184 fn projection_label(&self) -> String {
185 render_scalar_projection_expr_plan_label(&self.expr)
186 }
187
188 fn apply_value(&self, value: Value) -> Result<Value, QueryError> {
189 eval_builder_expr_for_value_preview(&self.expr, self.field.as_str(), &value)
190 }
191}
192
193#[derive(Clone, Debug, Eq, PartialEq)]
203pub struct RoundProjectionExpr {
204 field: String,
205 expr: Expr,
206}
207
208impl RoundProjectionExpr {
209 pub(in crate::db) fn new(
212 field: impl Into<String>,
213 inner: Expr,
214 scale: Value,
215 ) -> Result<Self, QueryError> {
216 match scale {
217 Value::Int(value) if value < 0 => {
218 return Err(QueryError::unsupported_query(format!(
219 "ROUND(...) requires non-negative integer scale, found {value}",
220 )));
221 }
222 Value::Int(_) | Value::Uint(_) => {}
223 other => {
224 return Err(QueryError::unsupported_query(format!(
225 "ROUND(...) requires integer scale, found {other:?}",
226 )));
227 }
228 }
229
230 Ok(Self {
231 field: field.into(),
232 expr: Expr::FunctionCall {
233 function: Function::Round,
234 args: vec![inner, Expr::Literal(scale)],
235 },
236 })
237 }
238
239 pub(in crate::db) fn field(field: impl Into<String>, scale: u32) -> Result<Self, QueryError> {
241 let field = field.into();
242
243 Self::new(
244 field.clone(),
245 Expr::Field(FieldId::new(field)),
246 Value::Uint(u64::from(scale)),
247 )
248 }
249
250 #[must_use]
252 pub(in crate::db) const fn expr(&self) -> &Expr {
253 &self.expr
254 }
255}
256
257impl ValueProjectionExpr for RoundProjectionExpr {
258 fn field(&self) -> &str {
259 self.field.as_str()
260 }
261
262 fn projection_label(&self) -> String {
263 render_scalar_projection_expr_plan_label(&self.expr)
264 }
265
266 fn apply_value(&self, value: Value) -> Result<Value, QueryError> {
267 eval_builder_expr_for_value_preview(&self.expr, self.field.as_str(), &value)
268 }
269}
270
271#[must_use]
273pub fn add(
274 field: impl AsRef<str>,
275 literal: impl Into<InputValue> + NumericValue,
276) -> NumericProjectionExpr {
277 NumericProjectionExpr::add_numeric_literal(field.as_ref().to_string(), literal)
278}
279
280#[must_use]
282pub fn sub(
283 field: impl AsRef<str>,
284 literal: impl Into<InputValue> + NumericValue,
285) -> NumericProjectionExpr {
286 NumericProjectionExpr::sub_numeric_literal(field.as_ref().to_string(), literal)
287}
288
289#[must_use]
291pub fn mul(
292 field: impl AsRef<str>,
293 literal: impl Into<InputValue> + NumericValue,
294) -> NumericProjectionExpr {
295 NumericProjectionExpr::mul_numeric_literal(field.as_ref().to_string(), literal)
296}
297
298#[must_use]
300pub fn div(
301 field: impl AsRef<str>,
302 literal: impl Into<InputValue> + NumericValue,
303) -> NumericProjectionExpr {
304 NumericProjectionExpr::div_numeric_literal(field.as_ref().to_string(), literal)
305}
306
307pub fn round(field: impl AsRef<str>, scale: u32) -> RoundProjectionExpr {
309 RoundProjectionExpr::field(field.as_ref().to_string(), scale)
310 .expect("ROUND(field, scale) helper should always produce a bounded projection")
311}
312
313#[must_use]
315pub fn round_expr(projection: &NumericProjectionExpr, scale: u32) -> RoundProjectionExpr {
316 projection
317 .round_with_scale(scale)
318 .expect("ROUND(expr, scale) helper should always produce a bounded projection")
319}