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();
83 let distinct = if aggregate.is_distinct() {
84 "DISTINCT "
85 } else {
86 ""
87 };
88
89 if let Some(input_expr) = aggregate.input_expr() {
90 let input =
91 render_scalar_projection_expr_sql_label_with_parent(input_expr, None, false);
92
93 return format!("{kind}({distinct}{input})");
94 }
95
96 format!("{kind}({distinct}*)")
97 }
98 #[cfg(test)]
99 Expr::Alias { expr, .. } => render_scalar_projection_expr_sql_label_with_parent(
100 expr.as_ref(),
101 parent_op,
102 is_right_child,
103 ),
104 Expr::Unary { op, expr } => {
105 let rendered =
106 render_scalar_projection_expr_sql_label_with_parent(expr.as_ref(), None, false);
107 match op {
108 crate::db::query::plan::expr::UnaryOp::Not => format!("NOT {rendered}"),
109 }
110 }
111 }
112}
113
114fn render_case_projection_expr_sql_label(
115 when_then_arms: &[crate::db::query::plan::expr::CaseWhenArm],
116 else_expr: &Expr,
117) -> String {
118 let mut rendered = String::from("CASE");
119
120 for arm in when_then_arms {
121 rendered.push_str(" WHEN ");
122 rendered.push_str(
123 render_scalar_projection_expr_sql_label_with_parent(arm.condition(), None, false)
124 .as_str(),
125 );
126 rendered.push_str(" THEN ");
127 rendered.push_str(
128 render_scalar_projection_expr_sql_label_with_parent(arm.result(), None, false).as_str(),
129 );
130 }
131
132 rendered.push_str(" ELSE ");
133 rendered.push_str(
134 render_scalar_projection_expr_sql_label_with_parent(else_expr, None, false).as_str(),
135 );
136 rendered.push_str(" END");
137
138 rendered
139}
140
141const fn binary_expr_requires_parentheses(
142 op: crate::db::query::plan::expr::BinaryOp,
143 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
144 is_right_child: bool,
145) -> bool {
146 let Some(parent_op) = parent_op else {
147 return false;
148 };
149 let precedence = binary_op_precedence(op);
150 let parent_precedence = binary_op_precedence(parent_op);
151
152 precedence < parent_precedence || (is_right_child && precedence == parent_precedence)
153}
154
155const fn binary_op_precedence(op: crate::db::query::plan::expr::BinaryOp) -> u8 {
156 match op {
157 crate::db::query::plan::expr::BinaryOp::Or => 0,
158 crate::db::query::plan::expr::BinaryOp::And => 1,
159 crate::db::query::plan::expr::BinaryOp::Eq
160 | crate::db::query::plan::expr::BinaryOp::Ne
161 | crate::db::query::plan::expr::BinaryOp::Lt
162 | crate::db::query::plan::expr::BinaryOp::Lte
163 | crate::db::query::plan::expr::BinaryOp::Gt
164 | crate::db::query::plan::expr::BinaryOp::Gte => 2,
165 crate::db::query::plan::expr::BinaryOp::Add
166 | crate::db::query::plan::expr::BinaryOp::Sub => 3,
167 crate::db::query::plan::expr::BinaryOp::Mul
168 | crate::db::query::plan::expr::BinaryOp::Div => 4,
169 }
170}
171
172const fn binary_op_sql_label(op: crate::db::query::plan::expr::BinaryOp) -> &'static str {
173 match op {
174 crate::db::query::plan::expr::BinaryOp::Or => "OR",
175 crate::db::query::plan::expr::BinaryOp::And => "AND",
176 crate::db::query::plan::expr::BinaryOp::Eq => "=",
177 crate::db::query::plan::expr::BinaryOp::Ne => "!=",
178 crate::db::query::plan::expr::BinaryOp::Lt => "<",
179 crate::db::query::plan::expr::BinaryOp::Lte => "<=",
180 crate::db::query::plan::expr::BinaryOp::Gt => ">",
181 crate::db::query::plan::expr::BinaryOp::Gte => ">=",
182 crate::db::query::plan::expr::BinaryOp::Add => "+",
183 crate::db::query::plan::expr::BinaryOp::Sub => "-",
184 crate::db::query::plan::expr::BinaryOp::Mul => "*",
185 crate::db::query::plan::expr::BinaryOp::Div => "/",
186 }
187}
188
189fn render_scalar_projection_literal(value: &Value) -> String {
190 match value {
191 Value::Null => "NULL".to_string(),
192 Value::Text(text) => format!("'{}'", text.replace('\'', "''")),
193 Value::Int(value) => value.to_string(),
194 Value::Int128(value) => value.to_string(),
195 Value::IntBig(value) => value.to_string(),
196 Value::Uint(value) => value.to_string(),
197 Value::Uint128(value) => value.to_string(),
198 Value::UintBig(value) => value.to_string(),
199 Value::Decimal(value) => value.to_string(),
200 Value::Float32(value) => value.to_string(),
201 Value::Float64(value) => value.to_string(),
202 Value::Bool(value) => value.to_string().to_uppercase(),
203 other => format!("{other:?}"),
204 }
205}