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 sql_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_sql_label(expr: &Expr) -> String {
38 render_scalar_projection_expr_sql_label_with_parent(expr, None, false)
39}
40
41fn render_scalar_projection_expr_sql_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_sql_label_with_parent(arg, None, false))
53 .collect::<Vec<_>>()
54 .join(", ");
55
56 format!("{}({rendered_args})", function.sql_label())
57 }
58 Expr::Case {
59 when_then_arms,
60 else_expr,
61 } => render_case_projection_expr_sql_label(when_then_arms, else_expr.as_ref()),
62 Expr::Binary { op, left, right } => {
63 let left = render_scalar_projection_expr_sql_label_with_parent(
64 left.as_ref(),
65 Some(*op),
66 false,
67 );
68 let right = render_scalar_projection_expr_sql_label_with_parent(
69 right.as_ref(),
70 Some(*op),
71 true,
72 );
73 let rendered = format!("{left} {} {right}", binary_op_sql_label(*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().sql_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_sql_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_sql_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_sql_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_sql_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_sql_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_sql_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_sql_label_with_parent(arm.result(), None, false).as_str(),
138 );
139 }
140
141 rendered.push_str(" ELSE ");
142 rendered.push_str(
143 render_scalar_projection_expr_sql_label_with_parent(else_expr, None, false).as_str(),
144 );
145 rendered.push_str(" END");
146
147 rendered
148}
149
150const fn binary_expr_requires_parentheses(
151 op: crate::db::query::plan::expr::BinaryOp,
152 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
153 is_right_child: bool,
154) -> bool {
155 let Some(parent_op) = parent_op else {
156 return false;
157 };
158 let precedence = binary_op_precedence(op);
159 let parent_precedence = binary_op_precedence(parent_op);
160
161 precedence < parent_precedence || (is_right_child && precedence == parent_precedence)
162}
163
164const fn binary_op_precedence(op: crate::db::query::plan::expr::BinaryOp) -> u8 {
165 match op {
166 crate::db::query::plan::expr::BinaryOp::Or => 0,
167 crate::db::query::plan::expr::BinaryOp::And => 1,
168 crate::db::query::plan::expr::BinaryOp::Eq
169 | crate::db::query::plan::expr::BinaryOp::Ne
170 | crate::db::query::plan::expr::BinaryOp::Lt
171 | crate::db::query::plan::expr::BinaryOp::Lte
172 | crate::db::query::plan::expr::BinaryOp::Gt
173 | crate::db::query::plan::expr::BinaryOp::Gte => 2,
174 crate::db::query::plan::expr::BinaryOp::Add
175 | crate::db::query::plan::expr::BinaryOp::Sub => 3,
176 crate::db::query::plan::expr::BinaryOp::Mul
177 | crate::db::query::plan::expr::BinaryOp::Div => 4,
178 }
179}
180
181const fn binary_op_sql_label(op: crate::db::query::plan::expr::BinaryOp) -> &'static str {
182 match op {
183 crate::db::query::plan::expr::BinaryOp::Or => "OR",
184 crate::db::query::plan::expr::BinaryOp::And => "AND",
185 crate::db::query::plan::expr::BinaryOp::Eq => "=",
186 crate::db::query::plan::expr::BinaryOp::Ne => "!=",
187 crate::db::query::plan::expr::BinaryOp::Lt => "<",
188 crate::db::query::plan::expr::BinaryOp::Lte => "<=",
189 crate::db::query::plan::expr::BinaryOp::Gt => ">",
190 crate::db::query::plan::expr::BinaryOp::Gte => ">=",
191 crate::db::query::plan::expr::BinaryOp::Add => "+",
192 crate::db::query::plan::expr::BinaryOp::Sub => "-",
193 crate::db::query::plan::expr::BinaryOp::Mul => "*",
194 crate::db::query::plan::expr::BinaryOp::Div => "/",
195 }
196}
197
198fn render_scalar_projection_literal(value: &Value) -> String {
199 match value {
200 Value::Null => "NULL".to_string(),
201 Value::Text(text) => format!("'{}'", text.replace('\'', "''")),
202 Value::Int(value) => value.to_string(),
203 Value::Int128(value) => value.to_string(),
204 Value::IntBig(value) => value.to_string(),
205 Value::Uint(value) => value.to_string(),
206 Value::Uint128(value) => value.to_string(),
207 Value::UintBig(value) => value.to_string(),
208 Value::Decimal(value) => value.to_string(),
209 Value::Float32(value) => value.to_string(),
210 Value::Float64(value) => value.to_string(),
211 Value::Bool(value) => value.to_string().to_uppercase(),
212 other => format!("{other:?}"),
213 }
214}