icydb_core/db/query/builder/
scalar_projection.rs1#[cfg(test)]
10mod tests;
11
12use crate::{
13 db::{QueryError, query::plan::expr::Expr},
14 value::{
15 Value,
16 decimal::{ValueFormatWriter, write_signed_literal, write_unsigned_literal},
17 format::write_value_debug,
18 },
19};
20use std::fmt;
21
22pub(super) mod private {
23 pub trait Sealed {}
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct ScalarProjectionPlan {
37 expr: Expr,
38}
39
40impl ScalarProjectionPlan {
41 pub(in crate::db) const fn new(expr: Expr) -> Self {
42 Self { expr }
43 }
44}
45
46pub trait ValueProjectionExpr: private::Sealed {
56 fn field(&self) -> &str;
58
59 fn projection_plan(&self) -> ScalarProjectionPlan;
61
62 fn projection_label(&self) -> String;
64
65 fn apply_value(&self, value: Value) -> Result<Value, QueryError>;
67}
68
69#[must_use]
72pub(in crate::db) fn render_scalar_projection_expr_plan_label(expr: &Expr) -> String {
73 let mut rendered = String::new();
74 write_scalar_projection_expr_plan_label(expr, &mut rendered)
77 .expect("writing a planner label into String cannot fail");
78
79 rendered
80}
81
82pub(in crate::db) fn write_scalar_projection_expr_plan_label(
85 expr: &Expr,
86 output: &mut (impl ValueFormatWriter + ?Sized),
87) -> fmt::Result {
88 write_scalar_projection_expr_plan_label_with_parent(expr, None, false, output)
89}
90
91fn write_scalar_projection_expr_plan_label_with_parent(
92 expr: &Expr,
93 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
94 is_right_child: bool,
95 output: &mut (impl ValueFormatWriter + ?Sized),
96) -> fmt::Result {
97 match expr {
98 Expr::Field(field) => output.write_str(field.as_str()),
99 Expr::FieldPath(path) => {
100 output.write_str(path.root().as_str())?;
101 for segment in path.segments() {
102 output.write_char('.')?;
103 output.write_str(segment)?;
104 }
105 Ok(())
106 }
107 Expr::Literal(value) => write_scalar_projection_literal(value, output),
108 Expr::FunctionCall { function, args } => {
109 output.write_str(function.canonical_label())?;
110 output.write_char('(')?;
111 for (index, arg) in args.iter().enumerate() {
112 if index != 0 {
113 output.write_str(", ")?;
114 }
115 write_scalar_projection_expr_plan_label(arg, output)?;
116 }
117 output.write_char(')')
118 }
119 Expr::Case {
120 when_then_arms,
121 else_expr,
122 } => {
123 output.write_str("CASE")?;
124 for arm in when_then_arms {
125 output.write_str(" WHEN ")?;
126 write_scalar_projection_expr_plan_label(arm.condition(), output)?;
127 output.write_str(" THEN ")?;
128 write_scalar_projection_expr_plan_label(arm.result(), output)?;
129 }
130 output.write_str(" ELSE ")?;
131 write_scalar_projection_expr_plan_label(else_expr, output)?;
132 output.write_str(" END")
133 }
134 Expr::Binary { op, left, right } => {
135 let parenthesized = binary_expr_requires_parentheses(*op, parent_op, is_right_child);
136 if parenthesized {
137 output.write_char('(')?;
138 }
139 write_scalar_projection_expr_plan_label_with_parent(left, Some(*op), false, output)?;
140 write!(output, " {} ", binary_op_symbol(*op))?;
141 write_scalar_projection_expr_plan_label_with_parent(right, Some(*op), true, output)?;
142 if parenthesized {
143 output.write_char(')')?;
144 }
145 Ok(())
146 }
147 Expr::Aggregate(aggregate) => {
148 output.write_str(aggregate.kind().canonical_label())?;
152 output.write_char('(')?;
153 if aggregate.is_distinct() {
154 output.write_str("DISTINCT ")?;
155 }
156 if let Some(input_expr) = aggregate.input_expr() {
157 write_scalar_projection_expr_plan_label(input_expr, output)?;
158 } else {
159 output.write_char('*')?;
160 }
161 output.write_char(')')?;
162 if let Some(filter_expr) = aggregate.filter_expr() {
163 output.write_str(" FILTER (WHERE ")?;
164 write_scalar_projection_expr_plan_label(filter_expr, output)?;
165 output.write_char(')')?;
166 }
167 Ok(())
168 }
169 #[cfg(test)]
170 Expr::Alias { expr, .. } => write_scalar_projection_expr_plan_label_with_parent(
171 expr.as_ref(),
172 parent_op,
173 is_right_child,
174 output,
175 ),
176 Expr::Unary { op, expr } => {
177 match op {
178 crate::db::query::plan::expr::UnaryOp::Not => output.write_str("NOT ")?,
179 }
180 write_scalar_projection_expr_plan_label(expr, output)
181 }
182 }
183}
184
185const fn binary_expr_requires_parentheses(
186 op: crate::db::query::plan::expr::BinaryOp,
187 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
188 is_right_child: bool,
189) -> bool {
190 let Some(parent_op) = parent_op else {
191 return false;
192 };
193 let precedence = binary_op_precedence(op);
194 let parent_precedence = binary_op_precedence(parent_op);
195
196 precedence < parent_precedence || (is_right_child && precedence == parent_precedence)
197}
198
199const fn binary_op_precedence(op: crate::db::query::plan::expr::BinaryOp) -> u8 {
200 match op {
201 crate::db::query::plan::expr::BinaryOp::Or => 0,
202 crate::db::query::plan::expr::BinaryOp::And => 1,
203 crate::db::query::plan::expr::BinaryOp::Eq
204 | crate::db::query::plan::expr::BinaryOp::Ne
205 | crate::db::query::plan::expr::BinaryOp::Lt
206 | crate::db::query::plan::expr::BinaryOp::Lte
207 | crate::db::query::plan::expr::BinaryOp::Gt
208 | crate::db::query::plan::expr::BinaryOp::Gte => 2,
209 crate::db::query::plan::expr::BinaryOp::Add
210 | crate::db::query::plan::expr::BinaryOp::Sub => 3,
211 crate::db::query::plan::expr::BinaryOp::Mul
212 | crate::db::query::plan::expr::BinaryOp::Div => 4,
213 }
214}
215
216const fn binary_op_symbol(op: crate::db::query::plan::expr::BinaryOp) -> &'static str {
217 match op {
218 crate::db::query::plan::expr::BinaryOp::Or => "OR",
219 crate::db::query::plan::expr::BinaryOp::And => "AND",
220 crate::db::query::plan::expr::BinaryOp::Eq => "=",
221 crate::db::query::plan::expr::BinaryOp::Ne => "!=",
222 crate::db::query::plan::expr::BinaryOp::Lt => "<",
223 crate::db::query::plan::expr::BinaryOp::Lte => "<=",
224 crate::db::query::plan::expr::BinaryOp::Gt => ">",
225 crate::db::query::plan::expr::BinaryOp::Gte => ">=",
226 crate::db::query::plan::expr::BinaryOp::Add => "+",
227 crate::db::query::plan::expr::BinaryOp::Sub => "-",
228 crate::db::query::plan::expr::BinaryOp::Mul => "*",
229 crate::db::query::plan::expr::BinaryOp::Div => "/",
230 }
231}
232
233fn write_scalar_projection_literal(
234 value: &Value,
235 output: &mut (impl ValueFormatWriter + ?Sized),
236) -> fmt::Result {
237 match value {
238 Value::Null => output.write_str("NULL"),
239 Value::Text(text) => {
240 output.write_char('\'')?;
241 for (index, part) in text.split('\'').enumerate() {
242 if index != 0 {
243 output.write_str("''")?;
244 }
245 output.write_str(part)?;
246 }
247 output.write_char('\'')
248 }
249 Value::Int64(value) => write!(output, "{value}"),
250 Value::Int128(value) => write!(output, "{value}"),
251 Value::IntBig(value) => write_signed_literal(value, output),
252 Value::Nat64(value) => write!(output, "{value}"),
253 Value::Nat128(value) => write!(output, "{value}"),
254 Value::NatBig(value) => write_unsigned_literal(value, output),
255 Value::U256(value) => write!(output, "{value}"),
256 Value::Decimal(value) => write!(output, "{value}"),
257 Value::Float32(value) => write!(output, "{value}"),
258 Value::Float64(value) => write!(output, "{value}"),
259 Value::Bool(true) => output.write_str("TRUE"),
260 Value::Bool(false) => output.write_str("FALSE"),
261 other => write_value_debug(other, output),
262 }
263}