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::Binary { op, left, right } => {
59 let left = render_scalar_projection_expr_sql_label_with_parent(
60 left.as_ref(),
61 Some(*op),
62 false,
63 );
64 let right = render_scalar_projection_expr_sql_label_with_parent(
65 right.as_ref(),
66 Some(*op),
67 true,
68 );
69 let rendered = format!("{left} {} {right}", binary_op_sql_label(*op));
70
71 if binary_expr_requires_parentheses(*op, parent_op, is_right_child) {
72 format!("({rendered})")
73 } else {
74 rendered
75 }
76 }
77 Expr::Aggregate(aggregate) => {
78 let kind = aggregate.kind().sql_label();
79 let distinct = if aggregate.is_distinct() {
80 "DISTINCT "
81 } else {
82 ""
83 };
84
85 if let Some(input_expr) = aggregate.input_expr() {
86 let input =
87 render_scalar_projection_expr_sql_label_with_parent(input_expr, None, false);
88
89 return format!("{kind}({distinct}{input})");
90 }
91
92 format!("{kind}({distinct}*)")
93 }
94 #[cfg(test)]
95 Expr::Alias { expr, .. } => render_scalar_projection_expr_sql_label_with_parent(
96 expr.as_ref(),
97 parent_op,
98 is_right_child,
99 ),
100 #[cfg(test)]
101 Expr::Unary { .. } => "expr".to_string(),
102 }
103}
104
105const fn binary_expr_requires_parentheses(
106 op: crate::db::query::plan::expr::BinaryOp,
107 parent_op: Option<crate::db::query::plan::expr::BinaryOp>,
108 is_right_child: bool,
109) -> bool {
110 let Some(parent_op) = parent_op else {
111 return false;
112 };
113 let precedence = binary_op_precedence(op);
114 let parent_precedence = binary_op_precedence(parent_op);
115
116 precedence < parent_precedence || (is_right_child && precedence == parent_precedence)
117}
118
119const fn binary_op_precedence(op: crate::db::query::plan::expr::BinaryOp) -> u8 {
120 match op {
121 crate::db::query::plan::expr::BinaryOp::Add
122 | crate::db::query::plan::expr::BinaryOp::Sub => 1,
123 crate::db::query::plan::expr::BinaryOp::Mul
124 | crate::db::query::plan::expr::BinaryOp::Div => 2,
125 #[cfg(test)]
126 crate::db::query::plan::expr::BinaryOp::And
127 | crate::db::query::plan::expr::BinaryOp::Eq => 0,
128 }
129}
130
131const fn binary_op_sql_label(op: crate::db::query::plan::expr::BinaryOp) -> &'static str {
132 match op {
133 crate::db::query::plan::expr::BinaryOp::Add => "+",
134 crate::db::query::plan::expr::BinaryOp::Sub => "-",
135 crate::db::query::plan::expr::BinaryOp::Mul => "*",
136 crate::db::query::plan::expr::BinaryOp::Div => "/",
137 #[cfg(test)]
138 crate::db::query::plan::expr::BinaryOp::And => "AND",
139 #[cfg(test)]
140 crate::db::query::plan::expr::BinaryOp::Eq => "=",
141 }
142}
143
144fn render_scalar_projection_literal(value: &Value) -> String {
145 match value {
146 Value::Null => "NULL".to_string(),
147 Value::Text(text) => format!("'{}'", text.replace('\'', "''")),
148 Value::Int(value) => value.to_string(),
149 Value::Int128(value) => value.to_string(),
150 Value::IntBig(value) => value.to_string(),
151 Value::Uint(value) => value.to_string(),
152 Value::Uint128(value) => value.to_string(),
153 Value::UintBig(value) => value.to_string(),
154 Value::Decimal(value) => value.to_string(),
155 Value::Float32(value) => value.to_string(),
156 Value::Float64(value) => value.to_string(),
157 Value::Bool(value) => value.to_string().to_uppercase(),
158 other => format!("{other:?}"),
159 }
160}