icydb_core/db/query/builder/
scalar_projection.rs1use crate::{
10 db::{
11 QueryError,
12 query::plan::expr::{Expr, FieldPath},
13 },
14 value::Value,
15};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct ScalarProjectionPlan {
28 expr: Expr,
29}
30
31impl ScalarProjectionPlan {
32 pub(in crate::db) const fn new(expr: Expr) -> Self {
33 Self { expr }
34 }
35
36 pub(in crate::db) fn into_expr(self) -> Expr {
37 self.expr
38 }
39}
40
41pub trait ValueProjectionExpr {
51 fn field(&self) -> &str;
53
54 fn projection_plan(&self) -> ScalarProjectionPlan;
56
57 fn projection_label(&self) -> String;
59
60 fn apply_value(&self, value: Value) -> Result<Value, QueryError>;
62}
63
64#[must_use]
67pub(in crate::db) fn render_scalar_projection_expr_plan_label(expr: &Expr) -> String {
68 render_scalar_projection_expr_plan_label_with_parent(expr, None, false)
69}
70
71fn render_scalar_projection_expr_plan_label_with_parent(
72 expr: &Expr,
73 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
74 is_right_child: bool,
75) -> String {
76 match expr {
77 Expr::Field(field) => field.as_str().to_string(),
78 Expr::FieldPath(path) => render_field_path_plan_label(path),
79 Expr::Literal(value) => render_scalar_projection_literal(value),
80 Expr::FunctionCall { function, args } => {
81 let rendered_args = args
82 .iter()
83 .map(|arg| render_scalar_projection_expr_plan_label_with_parent(arg, None, false))
84 .collect::<Vec<_>>()
85 .join(", ");
86
87 format!("{}({rendered_args})", function.canonical_label())
88 }
89 Expr::Case {
90 when_then_arms,
91 else_expr,
92 } => render_case_projection_expr_plan_label(when_then_arms, else_expr.as_ref()),
93 Expr::Binary { op, left, right } => {
94 let left = render_scalar_projection_expr_plan_label_with_parent(
95 left.as_ref(),
96 Some(*op),
97 false,
98 );
99 let right = render_scalar_projection_expr_plan_label_with_parent(
100 right.as_ref(),
101 Some(*op),
102 true,
103 );
104 let rendered = format!("{left} {} {right}", binary_op_symbol(*op));
105
106 if binary_expr_requires_parentheses(*op, parent_op, is_right_child) {
107 format!("({rendered})")
108 } else {
109 rendered
110 }
111 }
112 Expr::Aggregate(aggregate) => {
113 let kind = aggregate.kind().canonical_label();
117 let distinct = if aggregate.is_distinct() {
118 "DISTINCT "
119 } else {
120 ""
121 };
122 let filter = aggregate.filter_expr().map(|filter_expr| {
123 format!(
124 " FILTER (WHERE {})",
125 render_scalar_projection_expr_plan_label_with_parent(filter_expr, None, false,)
126 )
127 });
128
129 if let Some(input_expr) = aggregate.input_expr() {
130 let input =
131 render_scalar_projection_expr_plan_label_with_parent(input_expr, None, false);
132
133 return format!("{kind}({distinct}{input}){}", filter.unwrap_or_default());
134 }
135
136 format!("{kind}({distinct}*){}", filter.unwrap_or_default())
137 }
138 #[cfg(test)]
139 Expr::Alias { expr, .. } => render_scalar_projection_expr_plan_label_with_parent(
140 expr.as_ref(),
141 parent_op,
142 is_right_child,
143 ),
144 Expr::Unary { op, expr } => {
145 let rendered =
146 render_scalar_projection_expr_plan_label_with_parent(expr.as_ref(), None, false);
147 match op {
148 crate::db::query::plan::expr::UnaryOp::Not => format!("NOT {rendered}"),
149 }
150 }
151 }
152}
153
154fn render_field_path_plan_label(path: &FieldPath) -> String {
155 let mut label = path.root().as_str().to_string();
156 for segment in path.segments() {
157 label.push('.');
158 label.push_str(segment);
159 }
160
161 label
162}
163
164fn render_case_projection_expr_plan_label(
165 when_then_arms: &[crate::db::query::plan::expr::CaseWhenArm],
166 else_expr: &Expr,
167) -> String {
168 let mut rendered = String::from("CASE");
169
170 for arm in when_then_arms {
171 rendered.push_str(" WHEN ");
172 rendered.push_str(
173 render_scalar_projection_expr_plan_label_with_parent(arm.condition(), None, false)
174 .as_str(),
175 );
176 rendered.push_str(" THEN ");
177 rendered.push_str(
178 render_scalar_projection_expr_plan_label_with_parent(arm.result(), None, false)
179 .as_str(),
180 );
181 }
182
183 rendered.push_str(" ELSE ");
184 rendered.push_str(
185 render_scalar_projection_expr_plan_label_with_parent(else_expr, None, false).as_str(),
186 );
187 rendered.push_str(" END");
188
189 rendered
190}
191
192const fn binary_expr_requires_parentheses(
193 op: crate::db::query::plan::expr::BinaryOp,
194 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
195 is_right_child: bool,
196) -> bool {
197 let Some(parent_op) = parent_op else {
198 return false;
199 };
200 let precedence = binary_op_precedence(op);
201 let parent_precedence = binary_op_precedence(parent_op);
202
203 precedence < parent_precedence || (is_right_child && precedence == parent_precedence)
204}
205
206const fn binary_op_precedence(op: crate::db::query::plan::expr::BinaryOp) -> u8 {
207 match op {
208 crate::db::query::plan::expr::BinaryOp::Or => 0,
209 crate::db::query::plan::expr::BinaryOp::And => 1,
210 crate::db::query::plan::expr::BinaryOp::Eq
211 | crate::db::query::plan::expr::BinaryOp::Ne
212 | crate::db::query::plan::expr::BinaryOp::Lt
213 | crate::db::query::plan::expr::BinaryOp::Lte
214 | crate::db::query::plan::expr::BinaryOp::Gt
215 | crate::db::query::plan::expr::BinaryOp::Gte => 2,
216 crate::db::query::plan::expr::BinaryOp::Add
217 | crate::db::query::plan::expr::BinaryOp::Sub => 3,
218 crate::db::query::plan::expr::BinaryOp::Mul
219 | crate::db::query::plan::expr::BinaryOp::Div => 4,
220 }
221}
222
223const fn binary_op_symbol(op: crate::db::query::plan::expr::BinaryOp) -> &'static str {
224 match op {
225 crate::db::query::plan::expr::BinaryOp::Or => "OR",
226 crate::db::query::plan::expr::BinaryOp::And => "AND",
227 crate::db::query::plan::expr::BinaryOp::Eq => "=",
228 crate::db::query::plan::expr::BinaryOp::Ne => "!=",
229 crate::db::query::plan::expr::BinaryOp::Lt => "<",
230 crate::db::query::plan::expr::BinaryOp::Lte => "<=",
231 crate::db::query::plan::expr::BinaryOp::Gt => ">",
232 crate::db::query::plan::expr::BinaryOp::Gte => ">=",
233 crate::db::query::plan::expr::BinaryOp::Add => "+",
234 crate::db::query::plan::expr::BinaryOp::Sub => "-",
235 crate::db::query::plan::expr::BinaryOp::Mul => "*",
236 crate::db::query::plan::expr::BinaryOp::Div => "/",
237 }
238}
239
240fn render_scalar_projection_literal(value: &Value) -> String {
241 match value {
242 Value::Null => "NULL".to_string(),
243 Value::Text(text) => format!("'{}'", text.replace('\'', "''")),
244 Value::Int(value) => value.to_string(),
245 Value::Int128(value) => value.to_string(),
246 Value::IntBig(value) => value.to_string(),
247 Value::Uint(value) => value.to_string(),
248 Value::Uint128(value) => value.to_string(),
249 Value::UintBig(value) => value.to_string(),
250 Value::Decimal(value) => value.to_string(),
251 Value::Float32(value) => value.to_string(),
252 Value::Float64(value) => value.to_string(),
253 Value::Bool(value) => value.to_string().to_uppercase(),
254 other => format!("{other:?}"),
255 }
256}