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