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 match expr {
39 Expr::Field(field) => field.as_str().to_string(),
40 Expr::Literal(value) => render_scalar_projection_literal(value),
41 Expr::FunctionCall { function, args } => {
42 let rendered_args = args
43 .iter()
44 .map(render_scalar_projection_expr_sql_label)
45 .collect::<Vec<_>>()
46 .join(", ");
47
48 format!("{}({rendered_args})", function.sql_label())
49 }
50 Expr::Binary { op, left, right } => {
51 let left = render_scalar_projection_expr_sql_label(left.as_ref());
52 let right = render_scalar_projection_expr_sql_label(right.as_ref());
53
54 format!("{left} {} {right}", binary_op_sql_label(*op))
55 }
56 Expr::Aggregate(aggregate) => {
57 let kind = aggregate.kind().sql_label();
58 let distinct = if aggregate.is_distinct() {
59 "DISTINCT "
60 } else {
61 ""
62 };
63
64 if let Some(field) = aggregate.target_field() {
65 return format!("{kind}({distinct}{field})");
66 }
67
68 format!("{kind}({distinct}*)")
69 }
70 #[cfg(test)]
71 Expr::Alias { expr, .. } => render_scalar_projection_expr_sql_label(expr.as_ref()),
72 #[cfg(test)]
73 Expr::Unary { .. } => "expr".to_string(),
74 }
75}
76
77const fn binary_op_sql_label(op: crate::db::query::plan::expr::BinaryOp) -> &'static str {
78 match op {
79 crate::db::query::plan::expr::BinaryOp::Add => "+",
80 crate::db::query::plan::expr::BinaryOp::Sub => "-",
81 crate::db::query::plan::expr::BinaryOp::Mul => "*",
82 crate::db::query::plan::expr::BinaryOp::Div => "/",
83 #[cfg(test)]
84 crate::db::query::plan::expr::BinaryOp::And => "AND",
85 #[cfg(test)]
86 crate::db::query::plan::expr::BinaryOp::Eq => "=",
87 }
88}
89
90fn render_scalar_projection_literal(value: &Value) -> String {
91 match value {
92 Value::Null => "NULL".to_string(),
93 Value::Text(text) => format!("'{}'", text.replace('\'', "''")),
94 Value::Int(value) => value.to_string(),
95 Value::Int128(value) => value.to_string(),
96 Value::IntBig(value) => value.to_string(),
97 Value::Uint(value) => value.to_string(),
98 Value::Uint128(value) => value.to_string(),
99 Value::UintBig(value) => value.to_string(),
100 Value::Decimal(value) => value.to_string(),
101 Value::Float32(value) => value.to_string(),
102 Value::Float64(value) => value.to_string(),
103 Value::Bool(value) => value.to_string().to_uppercase(),
104 other => format!("{other:?}"),
105 }
106}