postrust_core/query/
builder.rs1use crate::error::Result;
4use crate::plan::{
5 CallParams, CallPlan, CoercibleFilter, CoercibleLogicTree, CoercibleOrderTerm,
6 CoercibleSelectField, MutatePlan, ReadPlan, ReadPlanTree,
7};
8use postrust_sql::{
9 escape_ident, from_qi, DeleteBuilder, InsertBuilder, OrderExpr, SelectBuilder, SqlFragment,
10 SqlParam, UpdateBuilder,
11};
12
13pub struct QueryBuilder;
15
16impl QueryBuilder {
17 pub fn build_read(tree: &ReadPlanTree) -> Result<SqlFragment> {
19 Self::build_read_plan(&tree.root)
20 }
21
22 fn build_read_plan(plan: &ReadPlan) -> Result<SqlFragment> {
24 let mut builder = SelectBuilder::new();
25
26 let qi = &plan.from;
28 if let Some(alias) = &plan.from_alias {
29 builder = builder.from_table_as(
30 &postrust_sql::identifier::QualifiedIdentifier::new(&qi.schema, &qi.name),
31 alias,
32 );
33 } else {
34 builder = builder.from_table(&postrust_sql::identifier::QualifiedIdentifier::new(
35 &qi.schema, &qi.name,
36 ));
37 }
38
39 for field in &plan.select {
41 let col_frag = Self::build_select_field(field)?;
42 builder = builder.column_raw(col_frag);
43 }
44
45 for clause in &plan.where_clauses {
47 let expr = Self::build_logic_tree(clause)?;
48 builder = builder.where_raw(expr);
49 }
50
51 for term in &plan.order {
53 let order = Self::build_order_term(term);
54 builder = builder.order_by(order);
55 }
56
57 if let Some(limit) = plan.range.limit {
59 builder = builder.limit(limit);
60 }
61 if plan.range.offset > 0 {
62 builder = builder.offset(plan.range.offset);
63 }
64
65 Ok(builder.build())
66 }
67
68 fn build_select_field(field: &CoercibleSelectField) -> Result<SqlFragment> {
70 let mut frag = SqlFragment::new();
71
72 if let Some(agg) = &field.aggregate {
74 frag.push(agg.to_sql());
75 frag.push("(");
76 }
77
78 frag.push(&escape_ident(&field.field.name));
80
81 if field.aggregate.is_some() {
83 frag.push(")");
84 }
85
86 if let Some(cast) = &field.cast {
88 frag.push("::");
89 frag.push(cast);
90 }
91
92 if let Some(alias) = &field.alias {
94 frag.push(" AS ");
95 frag.push(&escape_ident(alias));
96 }
97
98 Ok(frag)
99 }
100
101 fn build_logic_tree(tree: &CoercibleLogicTree) -> Result<SqlFragment> {
103 match tree {
104 CoercibleLogicTree::Expr {
105 negated,
106 op,
107 children,
108 } => {
109 let sep = match op {
110 crate::api_request::LogicOperator::And => " AND ",
111 crate::api_request::LogicOperator::Or => " OR ",
112 };
113
114 let child_frags: Result<Vec<_>> =
115 children.iter().map(Self::build_logic_tree).collect();
116
117 let mut combined = SqlFragment::join(sep, child_frags?).parens();
118
119 if *negated {
120 let mut neg = SqlFragment::raw("NOT ");
121 neg.append(combined);
122 combined = neg;
123 }
124
125 Ok(combined)
126 }
127 CoercibleLogicTree::Stmt(filter) => Self::build_filter(filter),
128 CoercibleLogicTree::NullEmbed {
129 negated,
130 field_name,
131 } => {
132 let mut frag = SqlFragment::new();
133 frag.push(&escape_ident(field_name));
134 if *negated {
135 frag.push(" IS NOT NULL");
136 } else {
137 frag.push(" IS NULL");
138 }
139 Ok(frag)
140 }
141 }
142 }
143
144 fn build_filter(filter: &CoercibleFilter) -> Result<SqlFragment> {
146 let mut frag = SqlFragment::new();
147
148 frag.push(&escape_ident(&filter.field.name));
150
151 if filter.op_expr.negated {
153 frag.push(" NOT");
154 }
155
156 match &filter.op_expr.operation {
158 crate::api_request::Operation::Simple { op, value } => {
159 frag.push(" ");
160 frag.push(op.to_sql());
161 frag.push(" ");
162 frag.push_param(value.clone());
163 }
164 crate::api_request::Operation::Quant {
165 op,
166 quantifier,
167 value,
168 } => {
169 frag.push(" ");
170 frag.push(op.to_sql());
171 frag.push(" ");
172 if let Some(q) = quantifier {
173 match q {
174 crate::api_request::OpQuantifier::Any => frag.push("ANY("),
175 crate::api_request::OpQuantifier::All => frag.push("ALL("),
176 };
177 frag.push_param(value.clone());
178 frag.push(")");
179 } else {
180 frag.push_param(value.clone());
181 }
182 }
183 crate::api_request::Operation::In(values) => {
184 frag.push(" IN (");
185 for (i, v) in values.iter().enumerate() {
186 if i > 0 {
187 frag.push(", ");
188 }
189 frag.push_param(v.clone());
190 }
191 frag.push(")");
192 }
193 crate::api_request::Operation::Is(is_val) => {
194 frag.push(" IS ");
195 frag.push(is_val.to_sql());
196 }
197 crate::api_request::Operation::IsDistinctFrom(value) => {
198 frag.push(" IS DISTINCT FROM ");
199 frag.push_param(value.clone());
200 }
201 crate::api_request::Operation::Fts {
202 op,
203 language,
204 value,
205 } => {
206 frag.push(" @@ ");
207 frag.push(op.to_function());
208 frag.push("(");
209 if let Some(lang) = language {
210 frag.push_param(lang.clone());
211 frag.push(", ");
212 }
213 frag.push_param(value.clone());
214 frag.push(")");
215 }
216 }
217
218 Ok(frag)
219 }
220
221 fn build_order_term(term: &CoercibleOrderTerm) -> OrderExpr {
223 let mut order = OrderExpr::new(&term.field.name);
224
225 if let Some(dir) = &term.direction {
226 order = match dir {
227 crate::api_request::OrderDirection::Asc => order.asc(),
228 crate::api_request::OrderDirection::Desc => order.desc(),
229 };
230 }
231
232 if let Some(nulls) = &term.nulls {
233 order = match nulls {
234 crate::api_request::OrderNulls::First => order.nulls_first(),
235 crate::api_request::OrderNulls::Last => order.nulls_last(),
236 };
237 }
238
239 order
240 }
241
242 pub fn build_mutate(plan: &MutatePlan) -> Result<SqlFragment> {
244 match plan {
245 MutatePlan::Insert {
246 target,
247 columns,
248 body,
249 on_conflict,
250 returning,
251 ..
252 } => {
253 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
254 &target.schema,
255 &target.name,
256 );
257
258 let mut builder = InsertBuilder::new().into_table(&qi);
259
260 let col_names: Vec<String> = columns.iter().map(|c| c.name.clone()).collect();
262 builder = builder.columns(col_names);
263
264 if let Some(body_bytes) = body {
267 let body_str = String::from_utf8_lossy(body_bytes);
269 let mut frag = SqlFragment::new();
270 frag.push("SELECT * FROM json_populate_recordset(NULL::");
271 frag.push(&from_qi(&qi));
272 frag.push(", ");
273 frag.push_param(body_str.to_string());
274 frag.push("::json)");
275 return Ok(frag);
276 }
277
278 if let Some((resolution, conflict_cols)) = on_conflict {
280 match resolution {
281 crate::api_request::PreferResolution::IgnoreDuplicates => {
282 builder = builder.on_conflict_do_nothing();
283 }
284 crate::api_request::PreferResolution::MergeDuplicates => {
285 let set_cols: Vec<(String, SqlFragment)> = columns
286 .iter()
287 .map(|c| {
288 let mut frag = SqlFragment::new();
289 frag.push("EXCLUDED.");
290 frag.push(&escape_ident(&c.name));
291 (c.name.clone(), frag)
292 })
293 .collect();
294 builder =
295 builder.on_conflict_do_update(conflict_cols.clone(), set_cols);
296 }
297 }
298 }
299
300 for col in returning {
302 builder = builder.returning(col);
303 }
304
305 Ok(builder.build())
306 }
307
308 MutatePlan::Update {
309 target,
310 columns,
311 body,
312 where_clauses,
313 returning,
314 ..
315 } => {
316 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
317 &target.schema,
318 &target.name,
319 );
320
321 let builder = UpdateBuilder::new().table(&qi);
322
323 if let Some(body_bytes) = body {
325 let body_str = String::from_utf8_lossy(body_bytes);
326 let mut frag = SqlFragment::new();
328 frag.push("UPDATE ");
329 frag.push(&from_qi(&qi));
330 frag.push(" SET ");
331
332 for (i, col) in columns.iter().enumerate() {
333 if i > 0 {
334 frag.push(", ");
335 }
336 frag.push(&escape_ident(&col.name));
337 frag.push(" = (");
338 frag.push_param(body_str.to_string());
339 frag.push("::json->>");
340 frag.push_param(col.name.clone());
341 frag.push(")::");
342 frag.push(&col.ir_type);
343 }
344
345 if !where_clauses.is_empty() {
347 frag.push(" WHERE ");
348 for (i, clause) in where_clauses.iter().enumerate() {
349 if i > 0 {
350 frag.push(" AND ");
351 }
352 frag.append(Self::build_logic_tree(clause)?);
353 }
354 }
355
356 if !returning.is_empty() {
358 frag.push(" RETURNING ");
359 for (i, col) in returning.iter().enumerate() {
360 if i > 0 {
361 frag.push(", ");
362 }
363 frag.push(&escape_ident(col));
364 }
365 }
366
367 return Ok(frag);
368 }
369
370 Ok(builder.build())
371 }
372
373 MutatePlan::Delete {
374 target,
375 where_clauses,
376 returning,
377 } => {
378 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
379 &target.schema,
380 &target.name,
381 );
382
383 let mut builder = DeleteBuilder::new().from_table(&qi);
384
385 for clause in where_clauses {
387 let expr = Self::build_logic_tree(clause)?;
388 builder = builder.where_raw(expr);
389 }
390
391 for col in returning {
393 builder = builder.returning(col);
394 }
395
396 Ok(builder.build())
397 }
398 }
399 }
400
401 pub fn build_call(plan: &CallPlan) -> Result<SqlFragment> {
403 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
404 &plan.function.schema,
405 &plan.function.name,
406 );
407
408 let mut frag = SqlFragment::new();
409 frag.push("SELECT * FROM ");
410 frag.push(&from_qi(&qi));
411 frag.push("(");
412
413 match &plan.params {
414 CallParams::Named(params) => {
415 for (i, (name, value)) in params.iter().enumerate() {
416 if i > 0 {
417 frag.push(", ");
418 }
419 frag.push(&escape_ident(name));
420 frag.push(" => ");
421 frag.push_param(SqlParam::Text(value.clone()));
422 }
423 }
424 CallParams::Positional(values) => {
425 for (i, value) in values.iter().enumerate() {
426 if i > 0 {
427 frag.push(", ");
428 }
429 frag.push_param(SqlParam::Text(value.clone()));
430 }
431 }
432 CallParams::SingleObject(body) => {
433 let body_str = String::from_utf8_lossy(body);
434 frag.push_param(SqlParam::Text(body_str.to_string()));
435 }
436 CallParams::None => {}
437 }
438
439 frag.push(")");
440
441 Ok(frag)
442 }
443}