postrust_core/query/
builder.rs1use crate::error::Result;
4use crate::plan::{
5 CallPlan, CallParams, CoercibleFilter, CoercibleLogicTree, CoercibleOrderTerm,
6 CoercibleSelectField, MutatePlan, ReadPlan, ReadPlanTree,
7};
8use postrust_sql::{
9 escape_ident, from_qi, DeleteBuilder, InsertBuilder, OrderExpr, SelectBuilder,
10 SqlFragment, 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(
35 &postrust_sql::identifier::QualifiedIdentifier::new(&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 { negated, op, children } => {
105 let sep = match op {
106 crate::api_request::LogicOperator::And => " AND ",
107 crate::api_request::LogicOperator::Or => " OR ",
108 };
109
110 let child_frags: Result<Vec<_>> = children
111 .iter()
112 .map(|c| Self::build_logic_tree(c))
113 .collect();
114
115 let mut combined = SqlFragment::join(sep, child_frags?).parens();
116
117 if *negated {
118 let mut neg = SqlFragment::raw("NOT ");
119 neg.append(combined);
120 combined = neg;
121 }
122
123 Ok(combined)
124 }
125 CoercibleLogicTree::Stmt(filter) => Self::build_filter(filter),
126 CoercibleLogicTree::NullEmbed { negated, field_name } => {
127 let mut frag = SqlFragment::new();
128 frag.push(&escape_ident(field_name));
129 if *negated {
130 frag.push(" IS NOT NULL");
131 } else {
132 frag.push(" IS NULL");
133 }
134 Ok(frag)
135 }
136 }
137 }
138
139 fn build_filter(filter: &CoercibleFilter) -> Result<SqlFragment> {
141 let mut frag = SqlFragment::new();
142
143 frag.push(&escape_ident(&filter.field.name));
145
146 if filter.op_expr.negated {
148 frag.push(" NOT");
149 }
150
151 match &filter.op_expr.operation {
153 crate::api_request::Operation::Simple { op, value } => {
154 frag.push(" ");
155 frag.push(op.to_sql());
156 frag.push(" ");
157 frag.push_param(value.clone());
158 }
159 crate::api_request::Operation::Quant { op, quantifier, value } => {
160 frag.push(" ");
161 frag.push(op.to_sql());
162 frag.push(" ");
163 if let Some(q) = quantifier {
164 match q {
165 crate::api_request::OpQuantifier::Any => frag.push("ANY("),
166 crate::api_request::OpQuantifier::All => frag.push("ALL("),
167 };
168 frag.push_param(value.clone());
169 frag.push(")");
170 } else {
171 frag.push_param(value.clone());
172 }
173 }
174 crate::api_request::Operation::In(values) => {
175 frag.push(" IN (");
176 for (i, v) in values.iter().enumerate() {
177 if i > 0 {
178 frag.push(", ");
179 }
180 frag.push_param(v.clone());
181 }
182 frag.push(")");
183 }
184 crate::api_request::Operation::Is(is_val) => {
185 frag.push(" IS ");
186 frag.push(is_val.to_sql());
187 }
188 crate::api_request::Operation::IsDistinctFrom(value) => {
189 frag.push(" IS DISTINCT FROM ");
190 frag.push_param(value.clone());
191 }
192 crate::api_request::Operation::Fts { op, language, value } => {
193 frag.push(" @@ ");
194 frag.push(op.to_function());
195 frag.push("(");
196 if let Some(lang) = language {
197 frag.push_param(lang.clone());
198 frag.push(", ");
199 }
200 frag.push_param(value.clone());
201 frag.push(")");
202 }
203 }
204
205 Ok(frag)
206 }
207
208 fn build_order_term(term: &CoercibleOrderTerm) -> OrderExpr {
210 let mut order = OrderExpr::new(&term.field.name);
211
212 if let Some(dir) = &term.direction {
213 order = match dir {
214 crate::api_request::OrderDirection::Asc => order.asc(),
215 crate::api_request::OrderDirection::Desc => order.desc(),
216 };
217 }
218
219 if let Some(nulls) = &term.nulls {
220 order = match nulls {
221 crate::api_request::OrderNulls::First => order.nulls_first(),
222 crate::api_request::OrderNulls::Last => order.nulls_last(),
223 };
224 }
225
226 order
227 }
228
229 pub fn build_mutate(plan: &MutatePlan) -> Result<SqlFragment> {
231 match plan {
232 MutatePlan::Insert {
233 target,
234 columns,
235 body,
236 on_conflict,
237 returning,
238 ..
239 } => {
240 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
241 &target.schema,
242 &target.name,
243 );
244
245 let mut builder = InsertBuilder::new().into_table(&qi);
246
247 let col_names: Vec<String> = columns.iter().map(|c| c.name.clone()).collect();
249 builder = builder.columns(col_names);
250
251 if let Some(body_bytes) = body {
254 let body_str = String::from_utf8_lossy(body_bytes);
256 let mut frag = SqlFragment::new();
257 frag.push("SELECT * FROM json_populate_recordset(NULL::");
258 frag.push(&from_qi(&qi));
259 frag.push(", ");
260 frag.push_param(body_str.to_string());
261 frag.push("::json)");
262 return Ok(frag);
263 }
264
265 if let Some((resolution, conflict_cols)) = on_conflict {
267 match resolution {
268 crate::api_request::PreferResolution::IgnoreDuplicates => {
269 builder = builder.on_conflict_do_nothing();
270 }
271 crate::api_request::PreferResolution::MergeDuplicates => {
272 let set_cols: Vec<(String, SqlFragment)> = columns
273 .iter()
274 .map(|c| {
275 let mut frag = SqlFragment::new();
276 frag.push("EXCLUDED.");
277 frag.push(&escape_ident(&c.name));
278 (c.name.clone(), frag)
279 })
280 .collect();
281 builder = builder.on_conflict_do_update(conflict_cols.clone(), set_cols);
282 }
283 }
284 }
285
286 for col in returning {
288 builder = builder.returning(col);
289 }
290
291 Ok(builder.build())
292 }
293
294 MutatePlan::Update {
295 target,
296 columns,
297 body,
298 where_clauses,
299 returning,
300 ..
301 } => {
302 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
303 &target.schema,
304 &target.name,
305 );
306
307 let builder = UpdateBuilder::new().table(&qi);
308
309 if let Some(body_bytes) = body {
311 let body_str = String::from_utf8_lossy(body_bytes);
312 let mut frag = SqlFragment::new();
314 frag.push("UPDATE ");
315 frag.push(&from_qi(&qi));
316 frag.push(" SET ");
317
318 for (i, col) in columns.iter().enumerate() {
319 if i > 0 {
320 frag.push(", ");
321 }
322 frag.push(&escape_ident(&col.name));
323 frag.push(" = (");
324 frag.push_param(body_str.to_string());
325 frag.push("::json->>");
326 frag.push_param(col.name.clone());
327 frag.push(")::");
328 frag.push(&col.ir_type);
329 }
330
331 if !where_clauses.is_empty() {
333 frag.push(" WHERE ");
334 for (i, clause) in where_clauses.iter().enumerate() {
335 if i > 0 {
336 frag.push(" AND ");
337 }
338 frag.append(Self::build_logic_tree(clause)?);
339 }
340 }
341
342 if !returning.is_empty() {
344 frag.push(" RETURNING ");
345 for (i, col) in returning.iter().enumerate() {
346 if i > 0 {
347 frag.push(", ");
348 }
349 frag.push(&escape_ident(col));
350 }
351 }
352
353 return Ok(frag);
354 }
355
356 Ok(builder.build())
357 }
358
359 MutatePlan::Delete {
360 target,
361 where_clauses,
362 returning,
363 } => {
364 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
365 &target.schema,
366 &target.name,
367 );
368
369 let mut builder = DeleteBuilder::new().from_table(&qi);
370
371 for clause in where_clauses {
373 let expr = Self::build_logic_tree(clause)?;
374 builder = builder.where_raw(expr);
375 }
376
377 for col in returning {
379 builder = builder.returning(col);
380 }
381
382 Ok(builder.build())
383 }
384 }
385 }
386
387 pub fn build_call(plan: &CallPlan) -> Result<SqlFragment> {
389 let qi = postrust_sql::identifier::QualifiedIdentifier::new(
390 &plan.function.schema,
391 &plan.function.name,
392 );
393
394 let mut frag = SqlFragment::new();
395 frag.push("SELECT * FROM ");
396 frag.push(&from_qi(&qi));
397 frag.push("(");
398
399 match &plan.params {
400 CallParams::Named(params) => {
401 for (i, (name, value)) in params.iter().enumerate() {
402 if i > 0 {
403 frag.push(", ");
404 }
405 frag.push(&escape_ident(name));
406 frag.push(" => ");
407 frag.push_param(SqlParam::Text(value.clone()));
408 }
409 }
410 CallParams::Positional(values) => {
411 for (i, value) in values.iter().enumerate() {
412 if i > 0 {
413 frag.push(", ");
414 }
415 frag.push_param(SqlParam::Text(value.clone()));
416 }
417 }
418 CallParams::SingleObject(body) => {
419 let body_str = String::from_utf8_lossy(body);
420 frag.push_param(SqlParam::Text(body_str.to_string()));
421 }
422 CallParams::None => {}
423 }
424
425 frag.push(")");
426
427 Ok(frag)
428 }
429}