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