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