icydb_core/db/query/builder/
numeric_projection.rs1use crate::{
10 db::{
11 QueryError,
12 executor::projection::eval_value_projection_expr_with_value,
13 query::{
14 builder::{
15 ValueProjectionExpr, scalar_projection::render_scalar_projection_expr_sql_label,
16 },
17 plan::expr::{BinaryOp, Expr, FieldId, Function},
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 fn arithmetic_value(
43 field: impl Into<String>,
44 op: BinaryOp,
45 literal: Value,
46 ) -> Result<Self, QueryError> {
47 if !matches!(
48 literal,
49 Value::Int(_)
50 | Value::Int128(_)
51 | Value::IntBig(_)
52 | Value::Uint(_)
53 | Value::Uint128(_)
54 | Value::UintBig(_)
55 | Value::Decimal(_)
56 | Value::Float32(_)
57 | Value::Float64(_)
58 | Value::Duration(_)
59 | Value::Timestamp(_)
60 | Value::Date(_)
61 ) {
62 return Err(QueryError::unsupported_query(format!(
63 "scalar numeric projection requires a numeric literal, found {literal:?}",
64 )));
65 }
66
67 let field = field.into();
68
69 Ok(Self {
70 expr: Expr::Binary {
71 op,
72 left: Box::new(Expr::Field(FieldId::new(field.clone()))),
73 right: Box::new(Expr::Literal(literal)),
74 },
75 field,
76 })
77 }
78
79 fn arithmetic_numeric_literal(
82 field: impl Into<String>,
83 op: BinaryOp,
84 literal: impl FieldValue + NumericValue,
85 ) -> Self {
86 let literal = literal.to_value();
87
88 Self::arithmetic_value(field, op, literal)
89 .expect("typed numeric projection helpers should always produce numeric literals")
90 }
91
92 pub(in crate::db) fn add_value(
94 field: impl Into<String>,
95 literal: Value,
96 ) -> Result<Self, QueryError> {
97 Self::arithmetic_value(field, BinaryOp::Add, literal)
98 }
99
100 pub(in crate::db) fn sub_value(
102 field: impl Into<String>,
103 literal: Value,
104 ) -> Result<Self, QueryError> {
105 Self::arithmetic_value(field, BinaryOp::Sub, literal)
106 }
107
108 pub(in crate::db) fn mul_value(
110 field: impl Into<String>,
111 literal: Value,
112 ) -> Result<Self, QueryError> {
113 Self::arithmetic_value(field, BinaryOp::Mul, literal)
114 }
115
116 pub(in crate::db) fn div_value(
118 field: impl Into<String>,
119 literal: Value,
120 ) -> Result<Self, QueryError> {
121 Self::arithmetic_value(field, BinaryOp::Div, literal)
122 }
123
124 pub(in crate::db) fn add_numeric_literal(
127 field: impl Into<String>,
128 literal: impl FieldValue + NumericValue,
129 ) -> Self {
130 Self::arithmetic_numeric_literal(field, BinaryOp::Add, literal)
131 }
132
133 pub(in crate::db) fn sub_numeric_literal(
136 field: impl Into<String>,
137 literal: impl FieldValue + NumericValue,
138 ) -> Self {
139 Self::arithmetic_numeric_literal(field, BinaryOp::Sub, literal)
140 }
141
142 pub(in crate::db) fn mul_numeric_literal(
145 field: impl Into<String>,
146 literal: impl FieldValue + NumericValue,
147 ) -> Self {
148 Self::arithmetic_numeric_literal(field, BinaryOp::Mul, literal)
149 }
150
151 pub(in crate::db) fn div_numeric_literal(
154 field: impl Into<String>,
155 literal: impl FieldValue + NumericValue,
156 ) -> Self {
157 Self::arithmetic_numeric_literal(field, BinaryOp::Div, literal)
158 }
159
160 #[must_use]
162 pub(in crate::db) const fn expr(&self) -> &Expr {
163 &self.expr
164 }
165
166 pub(in crate::db) fn round_with_scale(
169 &self,
170 scale: u32,
171 ) -> Result<RoundProjectionExpr, QueryError> {
172 RoundProjectionExpr::new(
173 self.field.clone(),
174 self.expr.clone(),
175 Value::Uint(u64::from(scale)),
176 )
177 }
178}
179
180impl ValueProjectionExpr for NumericProjectionExpr {
181 fn field(&self) -> &str {
182 self.field.as_str()
183 }
184
185 fn sql_label(&self) -> String {
186 render_scalar_projection_expr_sql_label(&self.expr)
187 }
188
189 fn apply_value(&self, value: Value) -> Result<Value, QueryError> {
190 eval_value_projection_expr_with_value(&self.expr, self.field.as_str(), &value)
191 }
192}
193
194#[derive(Clone, Debug, Eq, PartialEq)]
204pub struct RoundProjectionExpr {
205 field: String,
206 expr: Expr,
207}
208
209impl RoundProjectionExpr {
210 pub(in crate::db) fn new(
213 field: impl Into<String>,
214 inner: Expr,
215 scale: Value,
216 ) -> Result<Self, QueryError> {
217 match scale {
218 Value::Int(value) if value < 0 => {
219 return Err(QueryError::unsupported_query(format!(
220 "ROUND(...) requires non-negative integer scale, found {value}",
221 )));
222 }
223 Value::Int(_) | Value::Uint(_) => {}
224 other => {
225 return Err(QueryError::unsupported_query(format!(
226 "ROUND(...) requires integer scale, found {other:?}",
227 )));
228 }
229 }
230
231 Ok(Self {
232 field: field.into(),
233 expr: Expr::FunctionCall {
234 function: Function::Round,
235 args: vec![inner, Expr::Literal(scale)],
236 },
237 })
238 }
239
240 pub(in crate::db) fn field(field: impl Into<String>, scale: u32) -> Result<Self, QueryError> {
242 let field = field.into();
243
244 Self::new(
245 field.clone(),
246 Expr::Field(FieldId::new(field)),
247 Value::Uint(u64::from(scale)),
248 )
249 }
250
251 #[must_use]
253 pub(in crate::db) const fn expr(&self) -> &Expr {
254 &self.expr
255 }
256}
257
258impl ValueProjectionExpr for RoundProjectionExpr {
259 fn field(&self) -> &str {
260 self.field.as_str()
261 }
262
263 fn sql_label(&self) -> String {
264 render_scalar_projection_expr_sql_label(&self.expr)
265 }
266
267 fn apply_value(&self, value: Value) -> Result<Value, QueryError> {
268 eval_value_projection_expr_with_value(&self.expr, self.field.as_str(), &value)
269 }
270}
271
272#[must_use]
274pub fn add(
275 field: impl AsRef<str>,
276 literal: impl FieldValue + NumericValue,
277) -> NumericProjectionExpr {
278 NumericProjectionExpr::add_numeric_literal(field.as_ref().to_string(), literal)
279}
280
281#[must_use]
283pub fn sub(
284 field: impl AsRef<str>,
285 literal: impl FieldValue + NumericValue,
286) -> NumericProjectionExpr {
287 NumericProjectionExpr::sub_numeric_literal(field.as_ref().to_string(), literal)
288}
289
290#[must_use]
292pub fn mul(
293 field: impl AsRef<str>,
294 literal: impl FieldValue + NumericValue,
295) -> NumericProjectionExpr {
296 NumericProjectionExpr::mul_numeric_literal(field.as_ref().to_string(), literal)
297}
298
299#[must_use]
301pub fn div(
302 field: impl AsRef<str>,
303 literal: impl FieldValue + NumericValue,
304) -> NumericProjectionExpr {
305 NumericProjectionExpr::div_numeric_literal(field.as_ref().to_string(), literal)
306}
307
308pub fn round(field: impl AsRef<str>, scale: u32) -> RoundProjectionExpr {
310 RoundProjectionExpr::field(field.as_ref().to_string(), scale)
311 .expect("ROUND(field, scale) helper should always produce a bounded projection")
312}
313
314#[must_use]
316pub fn round_expr(projection: &NumericProjectionExpr, scale: u32) -> RoundProjectionExpr {
317 projection
318 .round_with_scale(scale)
319 .expect("ROUND(expr, scale) helper should always produce a bounded projection")
320}