1use crate::ast::{
14 BinOp, CompareOp, Expr, FieldRef, Filter, InsertQuery, Projection, QueryExpr, QueueCommand,
15 TableQuery, UpdateQuery, UpdateTarget,
16};
17use reddb_types::types::Value;
18
19pub fn render(expr: &QueryExpr) -> String {
23 match expr {
24 QueryExpr::Table(tq) => render_table(tq),
25 QueryExpr::Insert(iq) => render_insert(iq),
26 QueryExpr::Update(uq) => render_update(uq),
27 QueryExpr::QueueCommand(qc) => render_queue_command(qc),
28 _ => String::new(),
29 }
30}
31
32fn render_table(tq: &TableQuery) -> String {
33 let cols = if tq.columns.is_empty() {
34 "*".to_string()
35 } else {
36 tq.columns
37 .iter()
38 .map(render_projection)
39 .collect::<Vec<_>>()
40 .join(", ")
41 };
42 let mut sql = format!("SELECT {} FROM {}", cols, tq.table);
43 if let Some(filter) = &tq.filter {
44 sql.push_str(" WHERE ");
45 sql.push_str(&render_filter(filter));
46 }
47 sql
48}
49
50fn render_insert(iq: &InsertQuery) -> String {
51 let cols = iq.columns.join(", ");
52 let rows: Vec<String> = iq
53 .values
54 .iter()
55 .map(|row| {
56 let vals = row
57 .iter()
58 .map(render_value_sql)
59 .collect::<Vec<_>>()
60 .join(", ");
61 format!("({})", vals)
62 })
63 .collect();
64 format!(
65 "INSERT INTO {} ({}) VALUES {}",
66 iq.table,
67 cols,
68 rows.join(", ")
69 )
70}
71
72fn render_update(uq: &UpdateQuery) -> String {
73 let mut sql = format!("UPDATE {}", uq.table);
74 if let Some(target) = render_update_target(uq.target) {
75 sql.push(' ');
76 sql.push_str(target);
77 }
78 sql.push_str(" SET ");
79 let assignments = uq
80 .assignment_exprs
81 .iter()
82 .enumerate()
83 .map(|(idx, (column, expr))| {
84 let op = uq
85 .compound_assignment_ops
86 .get(idx)
87 .copied()
88 .flatten()
89 .map(render_compound_assignment_op)
90 .unwrap_or("=");
91 format!("{} {} {}", column, op, render_expr_sql(expr))
92 })
93 .collect::<Vec<_>>()
94 .join(", ");
95 sql.push_str(&assignments);
96 if let Some(filter) = &uq.filter {
97 sql.push_str(" WHERE ");
98 sql.push_str(&render_filter(filter));
99 }
100 if let Some(claim_limit) = uq.claim_limit {
101 if uq.claim_exact {
102 sql.push_str(" CLAIM EXACT ");
103 } else {
104 sql.push_str(" CLAIM LIMIT ");
105 }
106 sql.push_str(&claim_limit.to_string());
107 }
108 sql
109}
110
111fn render_update_target(target: UpdateTarget) -> Option<&'static str> {
112 match target {
113 UpdateTarget::Rows | UpdateTarget::Documents | UpdateTarget::Kv => None,
118 UpdateTarget::Nodes => Some("NODES"),
119 UpdateTarget::Edges => Some("EDGES"),
120 }
121}
122
123fn render_compound_assignment_op(op: BinOp) -> &'static str {
124 match op {
125 BinOp::Add => "+=",
126 BinOp::Sub => "-=",
127 BinOp::Mul => "*=",
128 BinOp::Div => "/=",
129 BinOp::Mod => "%=",
130 _ => "=",
131 }
132}
133
134fn render_queue_command(qc: &QueueCommand) -> String {
135 match qc {
136 QueueCommand::Push { queue, value, .. } => {
137 format!("QUEUE PUSH {} {}", queue, render_value_sql(value))
138 }
139 _ => String::new(),
140 }
141}
142
143fn render_expr_sql(expr: &Expr) -> String {
144 match expr {
145 Expr::Literal { value, .. } => render_value_sql(value),
146 Expr::Column { field, .. } => render_field_ref(field),
147 Expr::BinaryOp { op, lhs, rhs, .. } => format!(
148 "{} {} {}",
149 render_expr_sql(lhs),
150 render_bin_op(*op),
151 render_expr_sql(rhs)
152 ),
153 _ => "NULL".to_string(),
154 }
155}
156
157fn render_bin_op(op: BinOp) -> &'static str {
158 match op {
159 BinOp::Add => "+",
160 BinOp::Sub => "-",
161 BinOp::Mul => "*",
162 BinOp::Div => "/",
163 BinOp::Mod => "%",
164 BinOp::Eq => "=",
165 BinOp::Ne => "!=",
166 BinOp::Lt => "<",
167 BinOp::Le => "<=",
168 BinOp::Gt => ">",
169 BinOp::Ge => ">=",
170 BinOp::And => "AND",
171 BinOp::Or => "OR",
172 _ => "",
173 }
174}
175
176fn render_projection(p: &Projection) -> String {
177 match p {
178 Projection::All => "*".to_string(),
179 Projection::Column(col) => col.clone(),
180 Projection::Alias(col, alias) => format!("{} AS {}", col, alias),
181 Projection::Field(field, alias) => {
182 let col = render_field_ref(field);
183 match alias {
184 Some(a) => format!("{} AS {}", col, a),
185 None => col,
186 }
187 }
188 _ => "*".to_string(),
189 }
190}
191
192pub(crate) fn render_field_ref(f: &FieldRef) -> String {
193 match f {
194 FieldRef::TableColumn { table, column } if table.is_empty() => column.clone(),
195 FieldRef::TableColumn { table, column } => format!("{}.{}", table, column),
196 _ => "field".to_string(),
197 }
198}
199
200fn render_filter(filter: &Filter) -> String {
201 match filter {
202 Filter::Compare { field, op, value } => {
203 format!(
204 "{} {} {}",
205 render_field_ref(field),
206 op,
207 render_value_sql(value)
208 )
209 }
210 Filter::And(a, b) => format!("({}) AND ({})", render_filter(a), render_filter(b)),
211 Filter::Or(a, b) => format!("({}) OR ({})", render_filter(a), render_filter(b)),
212 _ => "1=1".to_string(),
213 }
214}
215
216pub fn render_value_sql(v: &Value) -> String {
222 match v {
223 Value::Null => "NULL".to_string(),
224 Value::Integer(i) => i.to_string(),
225 Value::UnsignedInteger(u) => u.to_string(),
226 Value::Float(f) => {
227 if f.fract() == 0.0 {
229 format!("{:.1}", f)
230 } else {
231 format!("{}", f)
232 }
233 }
234 Value::Boolean(b) => {
235 if *b {
236 "true".to_string()
237 } else {
238 "false".to_string()
239 }
240 }
241 Value::Text(s) => format!("'{}'", s.replace('\'', "''")),
242 Value::Json(bytes) => String::from_utf8_lossy(bytes).to_string(),
245 Value::Array(items) => {
249 let rendered: Vec<String> = items.iter().map(render_value_sql).collect();
250 format!("[{}]", rendered.join(", "))
251 }
252 _ => "NULL".to_string(),
253 }
254}