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