1mod consts;
2
3use crate::consts::{
4 BASE, DATA_LABEL, JSONB_BUILD_ARRAY, JSONB_BUILD_OBJECT, JSON_AGG, JSON_BUILD_OBJECT, ON,
5 QUOTE_CHAR, ROOT_LABEL, TO_JSON, TO_JSONB,
6};
7use anyhow::anyhow;
8use async_graphql_parser::{
9 types::{
10 Directive, DocumentOperations, ExecutableDocument, Field, OperationType, Selection,
11 VariableDefinition,
12 },
13 Positioned,
14};
15use async_graphql_value::{Name, Value as GqlValue};
16use indexmap::IndexMap;
17use sqlparser::ast::{
18 Assignment, BinaryOperator, Cte, DataType, Expr, Function, FunctionArg, FunctionArgExpr, Ident,
19 Join, JoinConstraint, JoinOperator, ObjectName, Offset, OffsetRows, OrderByExpr, Query, Select,
20 SelectItem, SetExpr, Statement, TableAlias, TableFactor, TableWithJoins, Value, Values,
21 WildcardAdditionalOptions, With,
22};
23use std::{
24 collections::HashSet,
25 fmt::{Debug, Formatter},
26 iter::zip,
27};
28
29type JsonValue = serde_json::Value;
30type AnyResult<T> = anyhow::Result<T>;
31
32fn get_value<'a>(value: &'a GqlValue, sql_vars: &'a IndexMap<Name, JsonValue>) -> AnyResult<Expr> {
33 match value {
34 GqlValue::Variable(v) => {
35 let index = sql_vars
36 .get_index_of(v)
37 .map(|i| i + 1)
38 .ok_or(anyhow!("variable not found"))?;
39 Ok(Expr::Value(Value::Placeholder(format!("${index}"))))
40 }
41 GqlValue::Null => Ok(Expr::Value(Value::Null)),
42 GqlValue::String(s) => Ok(Expr::Value(Value::SingleQuotedString(s.clone()))),
43 GqlValue::Number(f) => Ok(Expr::Value(Value::Number(f.to_string(), false))),
44 GqlValue::Boolean(b) => Ok(Expr::Value(Value::Boolean(b.to_owned()))),
45 GqlValue::Enum(e) => Ok(Expr::Value(Value::SingleQuotedString(e.as_ref().into()))),
46 GqlValue::Binary(_b) => Err(anyhow!("binary not supported")),
47 GqlValue::List(l) => Ok(Expr::Function(Function {
48 name: ObjectName(vec![Ident::new(JSONB_BUILD_ARRAY)]),
49 args: l
50 .into_iter()
51 .map(|v| {
52 let value = get_value(v, sql_vars).unwrap();
53 FunctionArg::Unnamed(FunctionArgExpr::Expr(value))
54 })
55 .collect::<Vec<FunctionArg>>(),
56 over: None,
57 distinct: false,
58 special: false,
59 order_by: vec![],
60 })),
61 GqlValue::Object(o) => {
62 if o.contains_key("_parentRef") {
63 if let Some(GqlValue::String(s)) = o.get("_parentRef") {
64 return Ok(Expr::CompoundIdentifier(vec![
65 Ident::with_quote(QUOTE_CHAR, BASE.to_owned()),
66 Ident::with_quote(QUOTE_CHAR, s),
67 ]));
68 }
69 }
70 Ok(Expr::Function(Function {
71 name: ObjectName(vec![Ident::new(JSONB_BUILD_OBJECT)]),
72 args: o
73 .into_iter()
74 .flat_map(|(k, v)| {
75 let value = get_value(v, sql_vars).unwrap();
76 vec![
77 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
78 Value::SingleQuotedString(k.to_string()),
79 ))),
80 FunctionArg::Unnamed(FunctionArgExpr::Expr(value)),
81 ]
82 })
83 .collect::<Vec<FunctionArg>>(),
84 over: None,
85 distinct: false,
86 special: false,
87 order_by: vec![],
88 }))
89 }
90 }
91}
92
93fn get_logical_operator(op: &str) -> AnyResult<BinaryOperator> {
94 let value = match op {
95 "AND" => BinaryOperator::And,
96 "OR" => BinaryOperator::Or,
97 _ => {
98 return Err(anyhow!("logical operator not supported: {}", op));
99 }
100 };
101 Ok(value)
102}
103
104fn get_op(op: &str) -> AnyResult<BinaryOperator> {
105 let value = match op {
106 "eq" | "equals" => BinaryOperator::Eq,
107 "neq" | "not_equals" => BinaryOperator::NotEq,
108 "lt" | "less_than" => BinaryOperator::Lt,
109 "lte" | "less_than_or_equals" => BinaryOperator::LtEq,
110 "gt" | "greater_than" => BinaryOperator::Gt,
111 "gte" | "greater_than_or_equals" => BinaryOperator::GtEq,
112 _ => BinaryOperator::Custom(op.to_owned()),
113 };
114 Ok(value)
115}
116
117fn get_expr<'a>(
118 left: Expr,
119 operator: &'a str,
120 value: &'a GqlValue,
121 variables: &'a IndexMap<Name, JsonValue>,
122) -> AnyResult<Option<Expr>> {
123 let right_value = get_value(value, variables)?;
124 match operator {
125 "like" => Ok(Some(Expr::Like {
126 negated: false,
127 expr: Box::new(left),
128 pattern: Box::new(right_value),
129 escape_char: None,
130 })),
131 "ilike" => Ok(Some(Expr::ILike {
132 negated: false,
133 expr: Box::new(left),
134 pattern: Box::new(right_value),
135 escape_char: None,
136 })),
137 _ => {
138 let op = get_op(operator)?;
139 Ok(Some(Expr::BinaryOp {
140 left: Box::new(left),
141 op,
142 right: Box::new(right_value),
143 }))
144 }
145 }
146}
147
148fn get_string_or_variable(
149 value: &GqlValue,
150 variables: &IndexMap<Name, JsonValue>,
151) -> AnyResult<String> {
152 match value {
153 GqlValue::Variable(v) => {
154 if let Some(JsonValue::String(s)) = variables.get(v) {
155 Ok(s.clone())
156 } else {
157 Err(anyhow!("variable not found"))
158 }
159 }
160 GqlValue::String(s) => Ok(s.clone()),
161 _ => Err(anyhow!("value not supported")),
162 }
163}
164
165fn get_filter(
166 args: &IndexMap<Name, GqlValue>,
167 variables: &IndexMap<Name, JsonValue>,
168) -> AnyResult<Option<Expr>> {
169 let field = args
170 .get("field")
171 .map(|v| get_string_or_variable(v, variables))
172 .ok_or(anyhow!("field not found"))??;
173 let operator = args
174 .get("operator")
175 .map(|v| get_string_or_variable(v, variables))
176 .ok_or(anyhow!("operator not found"))??;
177 if let Some(value) = args.get("value") {
178 let left = Expr::Identifier(Ident {
179 value: field,
180 quote_style: Some(QUOTE_CHAR),
181 });
182 let primary = get_expr(left, operator.as_str(), value, variables)?;
183 if args.contains_key("children") {
184 if let Some(GqlValue::List(children)) = args.get("children") {
185 let op = if let Some(GqlValue::String(op)) = args.get("logicalOperator") {
186 get_logical_operator(op.as_str())?
187 } else {
188 BinaryOperator::And
189 };
190 if let Some(filters) = children
191 .iter()
192 .map(|v| match v {
193 GqlValue::Object(o) => get_filter(o, variables),
194 _ => Ok(None),
195 })
196 .fold(Ok(primary), |acc: AnyResult<Option<Expr>>, item| {
197 if let Ok(Some(acc)) = acc {
198 let item = item?;
199 let expr = Expr::BinaryOp {
200 left: Box::new(acc),
201 op: op.clone(),
202 right: Box::new(item.ok_or(anyhow!("invalid filter"))?),
203 };
204 Ok(Some(expr))
205 } else if let Ok(None) = acc {
206 Ok(None)
207 } else {
208 Err(anyhow!("invalid filter"))
209 }
210 })?
211 {
212 return Ok(Some(Expr::Nested(Box::new(filters))));
213 }
214 return Ok(None);
215 }
216 } else {
217 return Ok(primary);
218 }
219 }
220 Ok(None)
221}
222
223fn get_agg_query(
224 aggs: Vec<FunctionArg>,
225 from: Vec<TableWithJoins>,
226 selection: Option<Expr>,
227 alias: &str,
228) -> SetExpr {
229 SetExpr::Select(Box::new(Select {
230 distinct: None,
231 named_window: Vec::new(),
232 top: None,
233 into: None,
234 projection: vec![SelectItem::ExprWithAlias {
235 alias: Ident {
236 value: alias.to_string(),
237 quote_style: Some(QUOTE_CHAR),
238 },
239 expr: Expr::Function(Function {
240 order_by: vec![],
241 name: ObjectName(vec![Ident {
242 value: JSON_BUILD_OBJECT.to_string(),
243 quote_style: None,
244 }]),
245 args: aggs,
246 over: None,
247 distinct: false,
248 special: false,
249 }),
250 }],
251 from,
252 lateral_views: Vec::new(),
253 selection,
254 group_by: Vec::new(),
255 cluster_by: Vec::new(),
256 distribute_by: Vec::new(),
257 sort_by: Vec::new(),
258 having: None,
259 qualify: None,
260 }))
261}
262
263fn get_root_query(
264 projection: Vec<SelectItem>,
265 from: Vec<TableWithJoins>,
266 selection: Option<Expr>,
267 merges: &[Merge],
268 is_single: bool,
269 alias: &str,
270) -> SetExpr {
271 let mut base = Expr::Function(Function {
272 order_by: vec![],
273 name: ObjectName(vec![Ident {
274 value: TO_JSON.to_string(),
275 quote_style: None,
276 }]),
277 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Subquery(
278 Box::new(Query {
279 with: None,
280 body: Box::new(SetExpr::Select(Box::new(Select {
281 distinct: None,
282 named_window: Vec::new(),
283 top: None,
284 projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
285 value: ROOT_LABEL.to_string(),
286 quote_style: Some(QUOTE_CHAR),
287 }))],
288 into: None,
289 from: vec![TableWithJoins {
290 relation: TableFactor::Derived {
291 lateral: false,
292 subquery: Box::new(Query {
293 with: None,
294 body: Box::new(SetExpr::Select(Box::new(Select {
295 distinct: None,
296 named_window: vec![],
297 top: None,
298 projection,
299 into: None,
300 from: vec![],
301 lateral_views: vec![],
302 selection: None,
303 group_by: vec![],
304 cluster_by: vec![],
305 distribute_by: vec![],
306 sort_by: vec![],
307 having: None,
308 qualify: None,
309 }))),
310 order_by: vec![],
311 limit: None,
312 offset: None,
313 fetch: None,
314 locks: vec![],
315 }),
316 alias: Some(TableAlias {
317 name: Ident {
318 value: ROOT_LABEL.to_string(),
319 quote_style: Some(QUOTE_CHAR),
320 },
321 columns: vec![],
322 }),
323 },
324 joins: vec![],
325 }],
326 lateral_views: vec![],
327 selection: None,
328 group_by: vec![],
329 cluster_by: vec![],
330 distribute_by: vec![],
331 sort_by: vec![],
332 having: None,
333 qualify: None,
334 }))),
335 order_by: vec![],
336 limit: None,
337 offset: None,
338 fetch: None,
339 locks: vec![],
340 }),
341 )))],
342 over: None,
343 distinct: false,
344 special: false,
345 });
346 if !merges.is_empty() {
347 base = Expr::BinaryOp {
348 left: Box::new(Expr::Cast {
349 expr: Box::new(base),
350 data_type: DataType::Custom(
351 ObjectName(vec![Ident {
352 value: "jsonb".to_string(),
353 quote_style: None,
354 }]),
355 vec![],
356 ),
357 }),
358 op: BinaryOperator::StringConcat,
359 right: Box::new(Expr::Case {
360 operand: None,
361 conditions: merges.iter().map(|m| m.condition.clone()).collect(),
362 results: merges.iter().map(|m| m.expr.clone()).collect(),
363 else_result: Some(Box::new(Expr::Function(Function {
364 order_by: vec![],
365 name: ObjectName(vec![Ident {
366 value: "jsonb_build_object".to_string(),
367 quote_style: None,
368 }]),
369 args: vec![],
370 over: None,
371 distinct: false,
372 special: false,
373 }))),
374 }),
375 };
376 }
377 if !is_single {
378 base = Expr::Function(Function {
379 order_by: vec![],
380 over: None,
381 distinct: false,
382 special: false,
383 name: ObjectName(vec![Ident {
384 value: "coalesce".to_string(),
385 quote_style: None,
386 }]),
387 args: vec![
388 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(Function {
389 order_by: vec![],
390 distinct: false,
391 over: None,
392 special: false,
393 name: ObjectName(vec![Ident {
394 value: JSON_AGG.to_string(),
395 quote_style: None,
396 }]),
397 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(base))],
398 }))),
399 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
400 Value::SingleQuotedString("[]".to_string()),
401 ))),
402 ],
403 });
404 }
405 SetExpr::Select(Box::new(Select {
406 distinct: None,
407 named_window: Vec::new(),
408 top: None,
409 projection: vec![SelectItem::ExprWithAlias {
410 alias: Ident {
411 value: alias.to_string(),
412 quote_style: Some(QUOTE_CHAR),
413 },
414 expr: base,
415 }],
416 into: None,
417 from,
418 lateral_views: Vec::new(),
419 selection,
420 group_by: Vec::new(),
421 cluster_by: Vec::new(),
422 distribute_by: Vec::new(),
423 sort_by: Vec::new(),
424 having: None,
425 qualify: None,
426 }))
427}
428
429fn get_agg_agg_projection(field: &Field, table_name: String) -> Vec<FunctionArg> {
430 let name = field.name.node.as_ref();
431 match name {
432 "__typename" => {
433 vec![
434 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
435 Value::SingleQuotedString(field.name.node.to_string()),
436 ))),
437 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(Function {
438 order_by: vec![],
439 name: ObjectName(vec![Ident {
440 value: "MIN".to_string(),
441 quote_style: None,
442 }]),
443 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
444 Value::SingleQuotedString(table_name),
445 )))],
446 over: None,
447 distinct: false,
448 special: false,
449 }))),
450 ]
451 }
452 "count" => {
453 vec![
454 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
455 Value::SingleQuotedString(field.name.node.to_string()),
456 ))),
457 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(Function {
458 order_by: vec![],
459 name: ObjectName(vec![Ident {
460 value: name.to_uppercase(),
461 quote_style: None,
462 }]),
463 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)],
464 over: None,
465 distinct: false,
466 special: false,
467 }))),
468 ]
469 }
470 "min" | "max" | "avg" => {
471 let projection = field
472 .selection_set
473 .node
474 .items
475 .iter()
476 .flat_map(|arg| {
477 if let Selection::Field(field) = &arg.node {
478 let field = &field.node;
479 vec![
480 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
481 Value::SingleQuotedString(field.name.node.to_string()),
482 ))),
483 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(Function {
484 order_by: vec![],
485 name: ObjectName(vec![Ident {
486 value: name.to_uppercase(),
487 quote_style: None,
488 }]),
489 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
490 Expr::Identifier(Ident {
491 value: field.name.node.to_string(),
492 quote_style: Some(QUOTE_CHAR),
493 }),
494 ))],
495 over: None,
496 distinct: false,
497 special: false,
498 }))),
499 ]
500 } else {
501 vec![]
502 }
503 })
504 .collect();
505 vec![
506 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
507 Value::SingleQuotedString(field.name.node.to_string()),
508 ))),
509 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(Function {
510 order_by: vec![],
511 name: ObjectName(vec![Ident {
512 value: JSON_BUILD_OBJECT.to_string(),
513 quote_style: None,
514 }]),
515 args: projection,
516 over: None,
517 distinct: false,
518 special: false,
519 }))),
520 ]
521 }
522 _ => vec![],
523 }
524}
525
526fn get_aggregate_projection(
527 items: &Vec<Positioned<Selection>>,
528 table_name: String,
529) -> AnyResult<Vec<FunctionArg>> {
530 let mut aggs = Vec::new();
531 for selection in items {
532 match &selection.node {
533 Selection::Field(field) => {
534 aggs.extend(get_agg_agg_projection(&field.node, table_name.clone()));
535 }
536 Selection::FragmentSpread(_) => {
537 return Err(anyhow!(
538 "Fragment spread is not supported in aggregate query"
539 ));
540 }
541 Selection::InlineFragment(_) => {
542 return Err(anyhow!(
543 "Inline fragment is not supported in aggregate query"
544 ));
545 }
546 }
547 }
548 Ok(aggs)
549}
550
551fn get_join<'a>(
552 arguments: &'a Vec<(Positioned<Name>, Positioned<GqlValue>)>,
553 directives: &'a Vec<Positioned<Directive>>,
554 selection_items: &'a Vec<Positioned<Selection>>,
555 path: Option<&'a str>,
556 name: &'a str,
557 variables: &'a IndexMap<Name, GqlValue>,
558 sql_vars: &'a IndexMap<Name, JsonValue>,
559 parent: &'a str,
560 tags: &'a mut IndexMap<String, HashSet<Tag>>,
561) -> AnyResult<Join> {
562 let (selection, distinct, distinct_order, order_by, mut first, after, keys) =
563 parse_args(arguments, variables, sql_vars)?;
564 let (relation, fks, pks, is_single, is_aggregate, is_many, schema_name) =
565 get_relation(directives, sql_vars)?;
566 if is_single {
567 first = Some(Expr::Value(Value::Number("1".to_string(), false)));
568 }
569 if let Some(keys) = keys {
570 tags.insert(name.to_owned(), keys.into_iter().collect());
571 } else {
572 tags.insert(name.to_owned(), HashSet::new());
573 };
574
575 let table_name = schema_name.as_ref().map_or_else(
576 || {
577 ObjectName(vec![Ident {
578 value: relation.to_string(),
579 quote_style: Some(QUOTE_CHAR),
580 }])
581 },
582 |schema_name| {
583 ObjectName(vec![
584 Ident {
585 value: schema_name.clone(),
586 quote_style: Some(QUOTE_CHAR),
587 },
588 Ident {
589 value: relation.to_string(),
590 quote_style: Some(QUOTE_CHAR),
591 },
592 ])
593 },
594 );
595
596 let sub_path = path.map_or_else(|| relation.to_string(), |v| format!("{v}.{relation}"));
597 let mut additional_select_items = Vec::new();
598 let mut join_name = None;
599 if is_many {
600 let (a, b) = if relation.as_str() < parent {
601 (relation.as_str(), parent)
602 } else {
603 (parent, relation.as_str())
604 };
605 join_name = Some(format!("_{a}To{b}"));
606 }
607 let join_filter = join_name.as_ref().map_or_else(
608 || {
609 zip(pks, fks)
610 .map(|(pk, fk)| {
611 additional_select_items.push(SelectItem::UnnamedExpr(
612 Expr::CompoundIdentifier(vec![
613 Ident {
614 value: sub_path.to_string(),
615 quote_style: Some(QUOTE_CHAR),
616 },
617 Ident {
618 value: fk.clone(),
619 quote_style: Some(QUOTE_CHAR),
620 },
621 ]),
622 ));
623 let mut new_tags = HashSet::new();
624 if let Some(table_tags) = tags.get(parent) {
625 for tag in table_tags {
626 if tag.key == pk {
627 new_tags.insert(Tag {
628 key: fk.clone(),
629 value: tag.value.clone(),
630 });
631 } else if tag.key == fk {
632 new_tags.insert(Tag {
633 key: pk.clone(),
634 value: tag.value.clone(),
635 });
636 } else if is_single {
637 new_tags.insert(Tag {
638 key: fk.clone(),
639 value: format!("{{{{{fk}}}}}"),
640 });
641 } else {
642 new_tags.insert(Tag {
643 key: fk.clone(),
644 value: format!("{{{{{fk}}}}}"),
645 });
646 }
647 }
648 } else if is_single {
649 new_tags.insert(Tag {
650 key: fk.clone(),
651 value: format!("{{{{{fk}}}}}"),
652 });
653 } else {
654 new_tags.insert(Tag {
655 key: fk.clone(),
656 value: format!("{{{{{fk}}}}}"),
657 });
658 }
659 if let Some(v) = tags.get_mut(name) {
660 v.extend(new_tags);
661 } else {
662 tags.insert(name.to_string(), new_tags);
663 };
664 let mut identifier = vec![
665 Ident {
666 value: relation.to_string(),
667 quote_style: Some(QUOTE_CHAR),
668 },
669 Ident {
670 value: fk,
671 quote_style: Some(QUOTE_CHAR),
672 },
673 ];
674 if let Some(schema_name) = schema_name.as_ref() {
675 identifier.insert(
676 0,
677 Ident {
678 value: schema_name.clone(),
679 quote_style: Some(QUOTE_CHAR),
680 },
681 );
682 }
683 Expr::BinaryOp {
684 left: Box::new(Expr::CompoundIdentifier(identifier)),
685 op: BinaryOperator::Eq,
686 right: Box::new(Expr::CompoundIdentifier(vec![
687 Ident {
688 value: path
689 .map_or(BASE.to_string(), std::string::ToString::to_string),
690 quote_style: Some(QUOTE_CHAR),
691 },
692 Ident {
693 value: pk,
694 quote_style: Some(QUOTE_CHAR),
695 },
696 ])),
697 }
698 })
699 .reduce(|acc, expr| Expr::BinaryOp {
700 left: Box::new(acc),
701 op: BinaryOperator::And,
702 right: Box::new(expr),
703 })
704 },
705 |join_name| {
706 let (join_col, value_col) = if relation.as_str() < parent {
707 ("A", "B")
708 } else {
709 ("B", "A")
710 };
711 Some(Expr::BinaryOp {
712 left: Box::new(Expr::BinaryOp {
713 left: Box::new(Expr::CompoundIdentifier(vec![
714 Ident {
715 value: join_name.to_string(),
716 quote_style: Some(QUOTE_CHAR),
717 },
718 Ident {
719 value: join_col.to_string(),
720 quote_style: Some(QUOTE_CHAR),
721 },
722 ])),
723 op: BinaryOperator::Eq,
724 right: Box::new(Expr::CompoundIdentifier(vec![
725 Ident {
726 value: relation.clone(),
727 quote_style: Some(QUOTE_CHAR),
728 },
729 Ident {
730 value: "id".to_string(),
731 quote_style: Some(QUOTE_CHAR),
732 },
733 ])),
734 }),
735 op: BinaryOperator::And,
736 right: Box::new(Expr::BinaryOp {
737 left: Box::new(Expr::CompoundIdentifier(vec![
738 Ident {
739 value: join_name.to_string(),
740 quote_style: Some(QUOTE_CHAR),
741 },
742 Ident {
743 value: value_col.to_string(),
744 quote_style: Some(QUOTE_CHAR),
745 },
746 ])),
747 op: BinaryOperator::Eq,
748 right: Box::new(Expr::CompoundIdentifier(vec![
749 Ident {
750 value: path.map_or(BASE.to_string(), std::string::ToString::to_string),
751 quote_style: Some(QUOTE_CHAR),
752 },
753 Ident {
754 value: "id".to_string(),
755 quote_style: Some(QUOTE_CHAR),
756 },
757 ])),
758 }),
759 })
760 },
761 );
762
763 let sub_query = get_filter_query(
764 selection.map_or_else(
765 || join_filter.clone(),
766 |s| {
767 Some(join_filter.clone().map_or_else(
768 || s.clone(),
769 |jf| Expr::BinaryOp {
770 left: Box::new(jf),
771 op: BinaryOperator::And,
772 right: Box::new(s.clone()),
773 },
774 ))
775 },
776 ),
777 order_by,
778 first,
779 after,
780 join_name.map_or_else(
781 || vec![table_name.clone()],
782 |name| {
783 vec![
784 table_name.clone(),
785 ObjectName(vec![Ident {
786 value: name,
787 quote_style: Some(QUOTE_CHAR),
788 }]),
789 ]
790 },
791 ),
792 distinct,
793 distinct_order,
794 );
795 if is_aggregate {
796 let aggs = get_aggregate_projection(selection_items, format!("Agg_{name}"))?;
797 Ok(Join {
798 relation: TableFactor::Derived {
799 lateral: true,
800 subquery: Box::new(Query {
801 with: None,
802 body: Box::new(get_agg_query(
803 aggs,
804 vec![TableWithJoins {
805 relation: TableFactor::Derived {
806 lateral: false,
807 subquery: Box::new(sub_query),
808 alias: Some(TableAlias {
809 name: Ident {
810 value: sub_path,
811 quote_style: Some(QUOTE_CHAR),
812 },
813 columns: vec![],
814 }),
815 },
816 joins: vec![],
817 }],
818 None,
819 name,
820 )),
821 order_by: vec![],
822 limit: None,
823 offset: None,
824 fetch: None,
825 locks: vec![],
826 }),
827 alias: Some(TableAlias {
828 name: Ident {
829 value: format!("{name}.{relation}"),
830 quote_style: Some(QUOTE_CHAR),
831 },
832 columns: vec![],
833 }),
834 },
835 join_operator: JoinOperator::LeftOuter(JoinConstraint::On(Expr::Nested(Box::new(
836 Expr::Value(Value::SingleQuotedString("true".to_string())),
837 )))),
838 })
839 } else {
840 let (sub_projection, sub_joins, merges) = get_projection(
841 selection_items,
842 &relation,
843 Some(&sub_path),
844 variables,
845 sql_vars,
846 tags,
847 )?;
848 additional_select_items.extend(sub_projection);
849 Ok(Join {
850 relation: TableFactor::Derived {
851 lateral: true,
852 subquery: Box::new(Query {
853 with: None,
854 body: Box::new(get_root_query(
855 additional_select_items,
856 vec![TableWithJoins {
857 relation: TableFactor::Derived {
858 lateral: false,
859 subquery: Box::new(sub_query),
860 alias: Some(TableAlias {
861 name: Ident {
862 value: sub_path,
863 quote_style: Some(QUOTE_CHAR),
864 },
865 columns: vec![],
866 }),
867 },
868 joins: sub_joins,
869 }],
870 None,
871 &merges,
872 is_single,
873 name,
874 )),
875 order_by: vec![],
876 limit: None,
877 offset: None,
878 fetch: None,
879 locks: vec![],
880 }),
881 alias: Some(TableAlias {
882 name: Ident {
883 value: format!("{name}.{relation}"),
884 quote_style: Some(QUOTE_CHAR),
885 },
886 columns: vec![],
887 }),
888 },
889 join_operator: JoinOperator::LeftOuter(JoinConstraint::On(Expr::Nested(Box::new(
890 Expr::Value(Value::SingleQuotedString("true".to_string())),
891 )))),
892 })
893 }
894}
895
896struct Merge {
897 condition: Expr,
898 expr: Expr,
899}
900
901fn get_static<'a>(
902 name: &'a str,
903 directives: &Vec<Positioned<Directive>>,
904 sql_vars: &'a IndexMap<Name, JsonValue>,
905) -> AnyResult<Option<SelectItem>> {
906 for p_directive in directives {
907 let directive = &p_directive.node;
908 let directive_name: &str = directive.name.node.as_ref();
909 if directive_name == "static" {
910 let (_, value) = directive
911 .arguments
912 .iter()
913 .find(|(name, _)| name.node.as_ref() == "value")
914 .ok_or_else(|| anyhow!("static value not found"))?;
915 let value = match &value.node {
916 GqlValue::String(value) => value.to_string(),
917 GqlValue::Number(value) => value.as_i64().expect("value is not an int").to_string(),
918 GqlValue::Variable(name) => {
919 if let Some(value) = sql_vars.get(name) {
920 value.to_string()
921 } else {
922 return Err(anyhow!("variable not found: {}", name));
923 }
924 }
925 GqlValue::Boolean(value) => value.to_string(),
926 _ => {
927 return Err(anyhow!("static value is not a string"));
928 }
929 };
930 return Ok(Some(SelectItem::ExprWithAlias {
931 expr: Expr::Value(Value::SingleQuotedString(value)),
932 alias: Ident {
933 value: name.to_string(),
934 quote_style: Some(QUOTE_CHAR),
935 },
936 }));
937 }
938 }
939 Ok(None)
940}
941
942fn get_projection<'a>(
943 items: &'a Vec<Positioned<Selection>>,
944 relation: &'a str,
945 path: Option<&'a str>,
946 variables: &'a IndexMap<Name, GqlValue>,
947 sql_vars: &'a IndexMap<Name, JsonValue>,
948 tags: &mut IndexMap<String, HashSet<Tag>>,
949) -> AnyResult<(Vec<SelectItem>, Vec<Join>, Vec<Merge>)> {
950 let mut projection = Vec::new();
951 let mut joins = Vec::new();
952 let mut merges = Vec::new();
953 for selection in items {
954 let selection = &selection.node;
955 match selection {
956 Selection::Field(field) => {
957 let field = &field.node;
958 if !field.selection_set.node.items.is_empty() {
959 let name = format!("join.{}", field.name.node.as_ref());
960 let join = get_join(
961 &field.arguments,
962 &field.directives,
963 &field.selection_set.node.items,
964 path,
965 &name,
966 variables,
967 sql_vars,
968 relation,
969 tags,
970 )?;
971 joins.push(join);
972 match &field.alias {
973 Some(alias) => {
974 projection.push(SelectItem::ExprWithAlias {
975 expr: Expr::Identifier(Ident {
976 value: name,
977 quote_style: Some(QUOTE_CHAR),
978 }),
979 alias: Ident {
980 value: alias.node.to_string(),
981 quote_style: Some(QUOTE_CHAR),
982 },
983 });
984 }
985 None => {
986 projection.push(SelectItem::ExprWithAlias {
987 expr: Expr::Identifier(Ident {
988 value: name,
989 quote_style: Some(QUOTE_CHAR),
990 }),
991 alias: Ident {
992 value: field.name.node.to_string(),
993 quote_style: Some(QUOTE_CHAR),
994 },
995 });
996 }
997 }
998 } else {
999 if let Some(value) = get_static(&field.name.node, &field.directives, sql_vars)?
1000 {
1001 projection.push(value);
1002 continue;
1003 }
1004 match &field.alias {
1005 Some(alias) => {
1006 projection.push(SelectItem::ExprWithAlias {
1007 expr: path.map_or_else(
1008 || {
1009 Expr::Identifier(Ident {
1010 value: field.name.node.to_string(),
1011 quote_style: Some(QUOTE_CHAR),
1012 })
1013 },
1014 |path| {
1015 Expr::CompoundIdentifier(vec![
1016 Ident {
1017 value: path.to_string(),
1018 quote_style: Some(QUOTE_CHAR),
1019 },
1020 Ident {
1021 value: field.name.node.to_string(),
1022 quote_style: Some(QUOTE_CHAR),
1023 },
1024 ])
1025 },
1026 ),
1027 alias: Ident {
1028 value: alias.to_string(),
1029 quote_style: Some(QUOTE_CHAR),
1030 },
1031 });
1032 }
1033 None => {
1034 let name = field.name.node.to_string();
1035 if name == "__typename" {
1036 projection.push(SelectItem::ExprWithAlias {
1037 alias: Ident {
1038 value: name,
1039 quote_style: Some(QUOTE_CHAR),
1040 },
1041 expr: Expr::Value(Value::SingleQuotedString(
1042 relation.to_string(),
1043 )),
1044 });
1045 } else {
1046 projection.push(SelectItem::UnnamedExpr(path.map_or_else(
1047 || {
1048 Expr::Identifier(Ident {
1049 value: name.clone(),
1050 quote_style: Some(QUOTE_CHAR),
1051 })
1052 },
1053 |path| {
1054 Expr::CompoundIdentifier(vec![
1055 Ident {
1056 value: path.to_string(),
1057 quote_style: Some(QUOTE_CHAR),
1058 },
1059 Ident {
1060 value: name.clone(),
1061 quote_style: Some(QUOTE_CHAR),
1062 },
1063 ])
1064 },
1065 )));
1066 }
1067 }
1068 }
1069 }
1070 }
1071 Selection::InlineFragment(frag) => {
1072 let frag = &frag.node;
1073 if let Some(type_condition) = &frag.type_condition {
1074 let name = &type_condition.node.on.node;
1075 let args = frag
1076 .directives
1077 .iter()
1078 .find(|d| d.node.name.node.as_ref() == "args");
1079 let (relation, _fks, _pks, _is_single, _is_aggregate, _is_many, schema_name) =
1080 get_relation(&frag.directives, sql_vars)?;
1081 let join = get_join(
1082 args.map_or(&vec![], |dir| &dir.node.arguments),
1083 &frag.directives,
1084 &frag.selection_set.node.items,
1085 path,
1086 name,
1087 variables,
1088 sql_vars,
1089 &relation,
1090 tags,
1091 )?;
1092 joins.push(join);
1093 let table_name = schema_name.map_or_else(
1094 || relation.to_string(),
1095 |schema_name| schema_name + "." + &relation,
1096 );
1097 merges.push(Merge {
1098 expr: Expr::Function(Function {
1099 order_by: vec![],
1100 name: ObjectName(vec![Ident {
1101 value: TO_JSONB.to_string(),
1102 quote_style: None,
1103 }]),
1104 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
1105 Expr::Identifier(Ident {
1106 value: name.to_string(),
1107 quote_style: Some(QUOTE_CHAR),
1108 }),
1109 ))],
1110 over: None,
1111 distinct: false,
1112 special: false,
1113 }),
1114 condition: Expr::IsNotNull(Box::new(Expr::CompoundIdentifier(vec![
1115 Ident {
1116 value: format!("{name}.{relation}"),
1117 quote_style: Some(QUOTE_CHAR),
1118 },
1119 Ident {
1120 value: table_name,
1121 quote_style: Some(QUOTE_CHAR),
1122 },
1123 ]))),
1124 });
1125 }
1126 }
1127 Selection::FragmentSpread(_) => {
1128 return Err(anyhow!("Fragment spread is not supported"));
1129 }
1130 }
1131 }
1132 Ok((projection, joins, merges))
1133}
1134
1135fn value_to_string<'a>(
1136 value: &'a GqlValue,
1137 sql_vars: &'a IndexMap<Name, JsonValue>,
1138) -> AnyResult<String> {
1139 let output = match value {
1140 GqlValue::String(s) => s.clone(),
1141 GqlValue::Number(f) => f.to_string(),
1142 GqlValue::Boolean(b) => b.to_string(),
1143 GqlValue::Enum(e) => e.to_string(),
1144 GqlValue::List(l) => l
1145 .iter()
1146 .map(|l| value_to_string(l, sql_vars))
1147 .collect::<AnyResult<Vec<String>>>()?
1148 .join(","),
1149 GqlValue::Null => "null".to_owned(),
1150 GqlValue::Object(obj) => serde_json::to_string(obj).unwrap(),
1151 GqlValue::Variable(name) => {
1152 if let Some(value) = sql_vars.get(name) {
1153 value.to_string()
1154 } else {
1155 return Err(anyhow!("Variable {} is not defined", name));
1156 }
1157 }
1158 GqlValue::Binary(_) => {
1159 return Err(anyhow!("Binary value is not supported"));
1160 }
1161 };
1162 Ok(output)
1163}
1164
1165fn get_relation<'a>(
1166 directives: &'a [Positioned<Directive>],
1167 sql_vars: &'a IndexMap<Name, JsonValue>,
1168) -> AnyResult<(
1169 String,
1170 Vec<String>,
1171 Vec<String>,
1172 bool,
1173 bool,
1174 bool,
1175 Option<String>,
1176)> {
1177 let mut relation: String = String::new();
1178 let mut fk = vec![];
1179 let mut pk = vec![];
1180 let mut is_single = false;
1181 let mut is_aggregate = false;
1182 let mut is_many = false;
1183 let mut schema_name = None;
1184 if let Some(p_directive) = directives
1185 .iter()
1186 .find(|d| d.node.name.node.as_str() == "relation")
1187 {
1188 let directive = &p_directive.node;
1189 let name = directive.name.node.as_str();
1190 if name == "relation" {
1191 for (name, value) in &directive.arguments {
1192 let name = name.node.as_str();
1193 let value = &value.node;
1194 match name {
1195 "table" => relation = value_to_string(value, sql_vars)?,
1196 "schema" => schema_name = Some(value_to_string(value, sql_vars)?),
1197 "field" | "fields" => {
1198 fk = match &value {
1199 GqlValue::String(s) => vec![s.clone()],
1200 GqlValue::List(e) => e
1201 .iter()
1202 .map(|l| value_to_string(l, sql_vars))
1203 .collect::<AnyResult<Vec<String>>>()?,
1204 _ => {
1205 return Err(anyhow!("Invalid value for field in relation"));
1206 }
1207 }
1208 }
1209 "reference" | "references" => {
1210 pk = match value {
1211 GqlValue::String(s) => vec![s.clone()],
1212 GqlValue::List(e) => e
1213 .iter()
1214 .map(|l| value_to_string(l, sql_vars))
1215 .collect::<AnyResult<Vec<String>>>()?,
1216 _ => {
1217 return Err(anyhow!("Invalid value for reference in relation"));
1218 }
1219 }
1220 }
1221 "single" => {
1222 if let GqlValue::Boolean(b) = value {
1223 is_single = *b;
1224 }
1225 }
1226 "aggregate" => {
1227 if let GqlValue::Boolean(b) = value {
1228 is_aggregate = *b;
1229 }
1230 }
1231 "many" => {
1232 if let GqlValue::Boolean(b) = value {
1233 is_many = *b;
1234 }
1235 }
1236 _ => {}
1237 }
1238 }
1239 }
1240 }
1241 Ok((
1242 relation,
1243 fk,
1244 pk,
1245 is_single,
1246 is_aggregate,
1247 is_many,
1248 schema_name,
1249 ))
1250}
1251
1252fn get_filter_query(
1253 selection: Option<Expr>,
1254 order_by: Vec<OrderByExpr>,
1255 first: Option<Expr>,
1256 after: Option<Offset>,
1257 table_names: Vec<ObjectName>,
1258 distinct: Option<Vec<String>>,
1259 distinct_order: Option<Vec<OrderByExpr>>,
1260) -> Query {
1261 let mut projection = vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())];
1262 let is_distinct = distinct.is_some();
1263 let has_distinct_order = distinct_order.is_some();
1264 let mut distinct_order_by = distinct_order.unwrap_or_else(|| order_by.clone());
1265 if let Some(distinct) = distinct {
1266 let columns = distinct
1267 .into_iter()
1268 .map(|s| Value::DoubleQuotedString(s).to_string())
1269 .collect::<Vec<String>>();
1270 projection = vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident {
1271 value: ON.to_owned() + " (" + &columns.join(",") + ") *",
1272 quote_style: None,
1273 }))];
1274 columns.into_iter().rev().for_each(|c| {
1275 distinct_order_by.insert(
1276 0,
1277 OrderByExpr {
1278 expr: Expr::Identifier(Ident {
1279 value: c,
1280 quote_style: None,
1281 }),
1282 asc: Some(true),
1283 nulls_first: None,
1284 },
1285 );
1286 });
1287 }
1288 let q = Query {
1289 with: None,
1290 body: Box::new(SetExpr::Select(Box::new(Select {
1291 distinct: if is_distinct {
1292 Some(sqlparser::ast::Distinct::Distinct)
1293 } else {
1294 None
1295 },
1296 named_window: vec![],
1297 top: None,
1298 projection,
1299 into: None,
1300 from: table_names
1301 .into_iter()
1302 .map(|table_name| TableWithJoins {
1303 relation: TableFactor::Table {
1304 name: table_name,
1305 alias: None,
1306 args: None,
1307 with_hints: vec![],
1308 },
1309 joins: vec![],
1310 })
1311 .collect(),
1312 lateral_views: vec![],
1313 selection: selection.map(|s| {
1314 if let Expr::Nested(nested) = s {
1315 *nested
1316 } else {
1317 s
1318 }
1319 }),
1320 group_by: vec![],
1321 cluster_by: vec![],
1322 distribute_by: vec![],
1323 sort_by: vec![],
1324 having: None,
1325 qualify: None,
1326 }))),
1327 order_by: distinct_order_by,
1328 limit: first,
1329 offset: after,
1330 fetch: None,
1331 locks: vec![],
1332 };
1333 if has_distinct_order && !order_by.is_empty() {
1334 Query {
1335 with: None,
1336 body: Box::new(SetExpr::Select(Box::new(Select {
1337 distinct: None,
1338 named_window: vec![],
1339 top: None,
1340 projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())],
1341 into: None,
1342 from: vec![TableWithJoins {
1343 relation: TableFactor::Derived {
1344 lateral: false,
1345 subquery: Box::new(q),
1346 alias: Some(TableAlias {
1347 name: Ident {
1348 value: "sorter".to_string(),
1349 quote_style: None,
1350 },
1351 columns: vec![],
1352 }),
1353 },
1354 joins: vec![],
1355 }],
1356 lateral_views: vec![],
1357 selection: None,
1358 group_by: vec![],
1359 cluster_by: vec![],
1360 distribute_by: vec![],
1361 sort_by: vec![],
1362 having: None,
1363 qualify: None,
1364 }))),
1365 order_by,
1366 limit: None,
1367 offset: None,
1368 fetch: None,
1369 locks: vec![],
1370 }
1371 } else {
1372 q
1373 }
1374}
1375
1376fn get_order<'a>(
1377 order: &IndexMap<Name, GqlValue>,
1378 variables: &'a IndexMap<Name, GqlValue>,
1379 sql_vars: &'a IndexMap<Name, JsonValue>,
1380) -> AnyResult<Vec<OrderByExpr>> {
1381 if order.contains_key("expr") && order.contains_key("dir") {
1382 let mut asc = None;
1383 if let Some(dir) = order.get("dir") {
1384 match dir {
1385 GqlValue::String(s) => {
1386 asc = Some(s == "ASC");
1387 }
1388 GqlValue::Enum(e) => {
1389 let s: &str = e.as_ref();
1390 asc = Some(s == "ASC");
1391 }
1392 GqlValue::Variable(v) => {
1393 if let Some(JsonValue::String(s)) = sql_vars.get(v) {
1394 asc = Some(s == "ASC");
1395 }
1396 }
1397 _ => {
1398 return Err(anyhow!("Invalid value for order direction"));
1399 }
1400 }
1401 }
1402 if let Some(expr) = order.get("expr") {
1403 match expr {
1404 GqlValue::String(s) => {
1405 return Ok(vec![OrderByExpr {
1406 expr: Expr::Identifier(Ident {
1407 value: s.clone(),
1408 quote_style: Some(QUOTE_CHAR),
1409 }),
1410 asc,
1411 nulls_first: None,
1412 }]);
1413 }
1414 GqlValue::Object(args) => {
1415 if let Some(expression) = get_filter(args, sql_vars)? {
1416 return Ok(vec![OrderByExpr {
1417 expr: expression,
1418 asc,
1419 nulls_first: None,
1420 }]);
1421 }
1422 }
1423 GqlValue::Variable(v) => {
1424 if let Some(JsonValue::String(s)) = sql_vars.get(v) {
1425 return Ok(vec![OrderByExpr {
1426 expr: Expr::Identifier(Ident {
1427 value: s.clone(),
1428 quote_style: Some(QUOTE_CHAR),
1429 }),
1430 asc,
1431 nulls_first: None,
1432 }]);
1433 }
1434 }
1435 _ => {
1436 return Err(anyhow!("Invalid value for order expression"));
1437 }
1438 }
1439 }
1440 }
1441 let mut order_by = vec![];
1442 for (key, mut value) in order.iter() {
1443 if let GqlValue::Variable(name) = value {
1444 if let Some(new_value) = variables.get(name) {
1445 value = new_value
1446 }
1447 }
1448 match value {
1449 GqlValue::String(s) => {
1450 order_by.push(OrderByExpr {
1451 expr: Expr::Identifier(Ident {
1452 value: key.as_str().to_owned(),
1453 quote_style: Some(QUOTE_CHAR),
1454 }),
1455 asc: Some(s == "ASC"),
1456 nulls_first: None,
1457 });
1458 }
1459 GqlValue::Enum(e) => {
1460 let s: &str = e.as_ref();
1461 order_by.push(OrderByExpr {
1462 expr: Expr::Identifier(Ident {
1463 value: key.as_str().to_owned(),
1464 quote_style: Some(QUOTE_CHAR),
1465 }),
1466 asc: Some(s == "ASC"),
1467 nulls_first: None,
1468 });
1469 }
1470 GqlValue::Variable(name) => {
1471 if let JsonValue::String(value) = sql_vars
1472 .get(name)
1473 .ok_or(anyhow!("Variable {} not found in sql_vars", name.as_str()))?
1474 {
1475 order_by.push(OrderByExpr {
1476 expr: Expr::Identifier(Ident {
1477 value: key.as_str().to_owned(),
1478 quote_style: Some(QUOTE_CHAR),
1479 }),
1480 asc: Some(value == "ASC"),
1481 nulls_first: None,
1482 });
1483 }
1484 }
1485 _ => return Err(anyhow!("Invalid value for order expression")),
1486 }
1487 }
1488 Ok(order_by)
1489}
1490
1491fn get_distinct(distinct: Vec<GqlValue>) -> Option<Vec<String>> {
1492 let values: Vec<String> = distinct
1493 .iter()
1494 .filter_map(|v| match v {
1495 GqlValue::String(s) => Some(s.clone()),
1496 _ => None,
1497 })
1498 .collect();
1499
1500 if values.is_empty() {
1501 None
1502 } else {
1503 Some(values)
1504 }
1505}
1506
1507fn flatten(name: Name, value: &JsonValue, sql_vars: &mut IndexMap<Name, JsonValue>) -> GqlValue {
1508 match value {
1509 JsonValue::Null => GqlValue::Null,
1510 JsonValue::Bool(b) => {
1511 sql_vars.insert(name.clone(), JsonValue::Bool(*b));
1512 GqlValue::Variable(name)
1513 }
1514 JsonValue::String(s) => {
1515 if s == "ASC" || s == "DESC" {
1516 return GqlValue::Enum(Name::new(s.clone()));
1517 }
1518 sql_vars.insert(name.clone(), JsonValue::String(s.clone()));
1519 GqlValue::Variable(name)
1520 }
1521 JsonValue::Number(n) => {
1522 sql_vars.insert(name.clone(), JsonValue::Number(n.clone()));
1523 GqlValue::Variable(name)
1524 }
1525 JsonValue::Array(list) => {
1526 let new_list = list
1527 .iter()
1528 .enumerate()
1529 .map(|(i, v)| {
1530 let new_name = format!("{name}_{i}");
1531 flatten(Name::new(new_name), v, sql_vars)
1532 })
1533 .collect();
1534 GqlValue::List(new_list)
1535 }
1536 JsonValue::Object(o) => {
1537 let mut out = IndexMap::with_capacity(o.len());
1538 for (k, v) in o {
1539 let new_name = format!("{name}_{k}");
1540 let name = Name::new(new_name);
1541 let key = Name::new(k);
1542 let new_value = flatten(name, v, sql_vars);
1543 out.insert(key, new_value);
1544 }
1545 GqlValue::Object(out)
1546 }
1547 }
1548}
1549
1550fn get_filter_key<'a>(
1551 args: &'a IndexMap<Name, GqlValue>,
1552 variables: &'a IndexMap<Name, JsonValue>,
1553) -> AnyResult<Option<HashSet<Tag>>> {
1554 let mut tags = HashSet::new();
1555 let operator = args
1556 .get("operator")
1557 .map(|v| get_string_or_variable(v, variables))
1558 .ok_or(anyhow!("operator not found"))??;
1559 if operator == "eq" {
1560 let field = args
1561 .get("field")
1562 .map(|v| get_string_or_variable(v, variables))
1563 .ok_or(anyhow!("field not found"))??;
1564 if let Some(value) = args.get("value") {
1565 if let Ok(value) = get_string_or_variable(value, variables) {
1566 tags.insert(Tag { key: field, value });
1567 }
1568 }
1569 }
1570 if args.contains_key("children") {
1571 if let Some(GqlValue::List(children)) = args.get("children") {
1572 children
1573 .iter()
1574 .map(|v| match v {
1575 GqlValue::Object(o) => get_filter_key(o, variables),
1576 _ => Ok(None),
1577 })
1578 .filter_map(|v| v.ok().flatten())
1579 .for_each(|v| {
1580 tags.extend(v);
1581 });
1582 }
1583 }
1584 if !tags.is_empty() {
1585 Ok(Some(tags))
1586 } else {
1587 Ok(None)
1588 }
1589}
1590
1591fn flatten_variables(
1592 variables: &Option<JsonValue>,
1593 definitions: Vec<Positioned<VariableDefinition>>,
1594) -> (IndexMap<Name, GqlValue>, IndexMap<Name, JsonValue>) {
1595 let mut sql_vars = IndexMap::new();
1596 let mut parameters = IndexMap::with_capacity(definitions.len());
1597 if let Some(JsonValue::Object(map)) = variables {
1598 for def in definitions {
1599 let def = def.node;
1600 let name = def.name.node;
1601 if let Some(value) = map.get(name.as_str()) {
1602 let new_value = flatten(name.clone(), value, &mut sql_vars);
1603 parameters.insert(name, new_value);
1604 }
1605 }
1606 }
1607 (parameters, sql_vars)
1608}
1609
1610fn parse_args<'a>(
1611 arguments: &'a Vec<(Positioned<Name>, Positioned<GqlValue>)>,
1612 variables: &'a IndexMap<Name, GqlValue>,
1613 sql_vars: &'a IndexMap<Name, JsonValue>,
1614) -> AnyResult<(
1615 Option<Expr>,
1616 Option<Vec<String>>,
1617 Option<Vec<OrderByExpr>>,
1618 Vec<OrderByExpr>,
1619 Option<Expr>,
1620 Option<Offset>,
1621 Option<HashSet<Tag>>,
1622)> {
1623 let mut selection = None;
1624 let mut order_by = vec![];
1625 let mut distinct = None;
1626 let mut distinct_order = None;
1627 let mut first = None;
1628 let mut after = None;
1629 let mut keys = None;
1630 for argument in arguments {
1631 let (p_key, p_value) = argument;
1632 let key = p_key.node.as_str();
1633 let mut value = p_value.node.clone();
1634 if let GqlValue::Variable(ref name) = value {
1635 if let Some(new_value) = variables.get(name) {
1636 value = new_value.clone();
1637 if let GqlValue::Null = value {
1638 continue;
1639 }
1640 }
1641 }
1642 match (key, value) {
1643 ("id" | "email", value) => {
1644 selection = get_expr(
1645 Expr::Identifier(Ident {
1646 value: key.to_string(),
1647 quote_style: Some(QUOTE_CHAR),
1648 }),
1649 "eq",
1650 &value,
1651 sql_vars,
1652 )?;
1653 }
1654 ("filter" | "where", GqlValue::Object(filter)) => {
1655 keys = get_filter_key(&filter, sql_vars)?;
1656 selection = get_filter(&filter, sql_vars)?;
1657 }
1658 ("distinct", GqlValue::Object(d)) => {
1659 if let Some(GqlValue::List(list)) = d.get("on") {
1660 distinct = get_distinct(list.clone());
1661 }
1662 match d.get("order") {
1663 Some(GqlValue::Object(order)) => {
1664 distinct_order = Some(get_order(order, variables, sql_vars)?);
1665 }
1666 Some(GqlValue::List(list)) => {
1667 let order = list
1668 .iter()
1669 .filter_map(|v| match v {
1670 GqlValue::Object(o) => Some(o),
1671 _ => None,
1672 })
1673 .map(|o| get_order(o, variables, sql_vars))
1674 .collect::<AnyResult<Vec<Vec<OrderByExpr>>>>()?;
1675 distinct_order = Some(order.into_iter().flatten().collect());
1676 }
1677 _ => {
1678 return Err(anyhow!("Invalid value for distinct order"));
1679 }
1680 }
1681 }
1682 ("order", GqlValue::Object(order)) => {
1683 order_by = get_order(&order, variables, sql_vars)?;
1684 }
1685 ("order", GqlValue::List(list)) => {
1686 let items = list
1687 .iter()
1688 .filter_map(|v| match v {
1689 GqlValue::Object(o) => Some(o),
1690 _ => None,
1691 })
1692 .map(|o| get_order(o, variables, sql_vars))
1693 .collect::<AnyResult<Vec<Vec<OrderByExpr>>>>()?;
1694 order_by.append(
1695 items
1696 .into_iter()
1697 .flatten()
1698 .collect::<Vec<OrderByExpr>>()
1699 .as_mut(),
1700 );
1701 }
1702 ("first" | "limit", GqlValue::Variable(name)) => {
1703 first = Some(get_value(&GqlValue::Variable(name), sql_vars)?);
1704 }
1705 ("first" | "limit", GqlValue::Number(count)) => {
1706 first = Some(Expr::Value(Value::Number(
1707 count.as_i64().expect("int to be an i64").to_string(),
1708 false,
1709 )));
1710 }
1711 ("after" | "offset", GqlValue::Variable(name)) => {
1712 after = Some(Offset {
1713 value: get_value(&GqlValue::Variable(name), sql_vars)?,
1714 rows: OffsetRows::None,
1715 });
1716 }
1717 ("after" | "offset", GqlValue::Number(count)) => {
1718 after = Some(Offset {
1719 value: Expr::Value(Value::Number(
1720 count.as_i64().expect("int to be an i64").to_string(),
1721 false,
1722 )),
1723 rows: OffsetRows::None,
1724 });
1725 }
1726 _ => {
1727 return Err(anyhow!("Invalid argument for: {}", key));
1728 }
1729 }
1730 }
1731 Ok((
1732 selection,
1733 distinct,
1734 distinct_order,
1735 order_by,
1736 first,
1737 after,
1738 keys,
1739 ))
1740}
1741
1742fn get_mutation_columns<'a>(
1743 arguments: &'a Vec<(Positioned<Name>, Positioned<GqlValue>)>,
1744 variables: &'a IndexMap<Name, GqlValue>,
1745 sql_vars: &'a IndexMap<Name, JsonValue>,
1746) -> AnyResult<(Vec<Ident>, Vec<Vec<Expr>>)> {
1747 let mut columns = vec![];
1748 let mut rows = vec![];
1749 for argument in arguments {
1750 let (key, value) = argument;
1751 let (key, mut value) = (&key.node, &value.node);
1752 if let GqlValue::Variable(name) = value {
1753 if let Some(new_value) = variables.get(name) {
1754 value = new_value;
1755 if let GqlValue::Null = value {
1756 continue;
1757 }
1758 }
1759 }
1760 match (key.as_ref(), value) {
1761 ("data", GqlValue::Object(data)) => {
1762 let mut row = vec![];
1763 for (key, value) in data.iter() {
1764 columns.push(Ident {
1765 value: key.to_string(),
1766 quote_style: Some(QUOTE_CHAR),
1767 });
1768 row.push(get_value(value, sql_vars)?);
1769 }
1770 rows.push(row);
1771 }
1772 ("data", GqlValue::List(list)) => {
1773 if list.is_empty() {
1774 continue;
1775 }
1776 for (i, item) in list.iter().enumerate() {
1777 let mut row = vec![];
1778 if let GqlValue::Object(data) = item {
1779 for (key, value) in data.iter() {
1780 if i == 0 {
1781 columns.push(Ident {
1782 value: key.to_string(),
1783 quote_style: Some(QUOTE_CHAR),
1784 });
1785 }
1786 row.push(get_value(value, sql_vars)?);
1787 }
1788 }
1789 rows.push(row);
1790 }
1791 }
1792 _ => continue,
1793 }
1794 }
1795 Ok((columns, rows))
1796}
1797
1798fn get_mutation_assignments<'a>(
1799 arguments: &'a Vec<(Positioned<Name>, Positioned<GqlValue>)>,
1800 variables: &'a IndexMap<Name, GqlValue>,
1801 sql_vars: &'a IndexMap<Name, JsonValue>,
1802 has_updated_at_directive: bool,
1803) -> AnyResult<(Option<Expr>, Vec<Assignment>)> {
1804 let mut selection = None;
1805 let mut assignments = vec![];
1806 if has_updated_at_directive {
1807 assignments.push(Assignment {
1808 id: vec![Ident {
1809 value: "updated_at".to_string(),
1810 quote_style: Some(QUOTE_CHAR),
1811 }],
1812 value: Expr::Function(Function {
1813 order_by: vec![],
1814 name: ObjectName(vec![Ident {
1815 value: "now".to_string(),
1816 quote_style: None,
1817 }]),
1818 special: false,
1819 args: vec![],
1820 over: None,
1821 distinct: false,
1822 }),
1823 });
1824 }
1825 for argument in arguments {
1826 let (p_key, p_value) = argument;
1827 let (key, mut value) = (&p_key.node, &p_value.node);
1828 if let GqlValue::Variable(name) = value {
1829 if let Some(new_value) = variables.get(name) {
1830 value = new_value;
1831 if let GqlValue::Null = value {
1832 continue;
1833 }
1834 }
1835 }
1836 match (key.as_ref(), value) {
1837 ("id" | "email", value) => {
1838 selection = get_expr(
1839 Expr::Identifier(Ident {
1840 value: key.to_string(),
1841 quote_style: Some(QUOTE_CHAR),
1842 }),
1843 "eq",
1844 value,
1845 sql_vars,
1846 )?;
1847 }
1848 ("filter" | "where", GqlValue::Object(filter)) => {
1849 selection = get_filter(filter, sql_vars)?;
1850 }
1851 ("set", GqlValue::Object(data)) => {
1852 for (key, value) in data.iter() {
1853 assignments.push(Assignment {
1854 id: vec![Ident {
1855 value: key.to_string(),
1856 quote_style: Some(QUOTE_CHAR),
1857 }],
1858 value: get_value(value, sql_vars)?,
1859 });
1860 }
1861 }
1862 ("inc" | "increment", GqlValue::Object(data)) => {
1863 for (key, value) in data.iter() {
1864 let column_ident = Ident {
1865 value: key.to_string(),
1866 quote_style: Some(QUOTE_CHAR),
1867 };
1868 assignments.push(Assignment {
1869 id: vec![column_ident.clone()],
1870 value: Expr::BinaryOp {
1871 left: Box::new(Expr::Identifier(column_ident)),
1872 op: BinaryOperator::Plus,
1873 right: Box::new(get_value(value, sql_vars)?),
1874 },
1875 });
1876 }
1877 }
1878 _ => return Err(anyhow!("Invalid argument for update at: {}", key)),
1879 }
1880 }
1881 Ok((selection, assignments))
1882}
1883
1884pub fn parse_query_meta(field: &Field) -> AnyResult<(&str, &str, bool, bool, Option<&str>)> {
1885 let mut is_aggregate = false;
1886 let mut is_single = false;
1887 let mut name = field.name.node.as_str();
1888 let mut schema_name = None;
1889 let key = field
1890 .alias
1891 .as_ref()
1892 .map_or_else(|| field.name.node.as_str(), |alias| alias.node.as_str());
1893
1894 if name.ends_with("_aggregate") {
1895 name = &name[..name.len() - 10];
1896 is_aggregate = true;
1897 } else if name.ends_with("_one") {
1898 name = &name[..name.len() - 4];
1899 is_single = true;
1900 }
1901
1902 if let Some(p_directive) = field
1903 .directives
1904 .iter()
1905 .find(|directive| directive.node.name.node.as_str() == "meta")
1906 {
1907 let directive = &p_directive.node;
1908 directive.arguments.iter().for_each(|(arg_name, argument)| {
1909 let arg_name = arg_name.node.as_str();
1910 if arg_name == "table" {
1911 if let GqlValue::String(table) = &argument.node {
1912 name = table.as_ref();
1913 }
1914 } else if arg_name == "aggregate" {
1915 if let GqlValue::Boolean(aggregate) = &argument.node {
1916 is_aggregate = *aggregate;
1917 }
1918 } else if arg_name == "single" {
1919 if let GqlValue::Boolean(single) = &argument.node {
1920 is_single = *single;
1921 }
1922 } else if arg_name == "schema" {
1923 if let GqlValue::String(schema) = &argument.node {
1924 schema_name = Some(schema.as_ref());
1925 }
1926 }
1927 });
1928 }
1929
1930 if is_aggregate && is_single {
1931 return Err(anyhow!("Query cannot be both aggregate and single"));
1932 }
1933
1934 Ok((name, key, is_aggregate, is_single, schema_name))
1935}
1936
1937#[must_use]
1938pub fn parse_mutation_meta(
1939 field: &Field,
1940) -> AnyResult<(&str, &str, bool, bool, bool, bool, Option<&str>)> {
1941 let mut is_insert = false;
1942 let mut is_update = false;
1943 let mut is_delete = false;
1944 let mut is_single = false;
1945 let mut schema_name = None;
1946 let mut name = field.name.node.as_ref();
1947 let key = field
1948 .alias
1949 .as_ref()
1950 .map_or_else(|| field.name.node.as_str(), |alias| alias.node.as_str());
1951
1952 if name.starts_with("insert_") {
1953 name = &name[7..];
1954 is_insert = true;
1955 } else if name.starts_with("update_") {
1956 name = &name[7..];
1957 is_update = true;
1958 } else if name.starts_with("delete_") {
1959 name = &name[7..];
1960 is_delete = true;
1961 }
1962
1963 if let Some(p_directive) = field
1964 .directives
1965 .iter()
1966 .find(|directive| directive.node.name.node.as_str() == "meta")
1967 {
1968 let directive = &p_directive.node;
1969 directive.arguments.iter().for_each(|(arg_name, argument)| {
1970 let arg_name = arg_name.node.as_str();
1971 if arg_name == "table" {
1972 if let GqlValue::String(table) = &argument.node {
1973 name = table.as_ref();
1974 }
1975 } else if arg_name == "insert" {
1976 if let GqlValue::Boolean(insert) = &argument.node {
1977 is_insert = *insert;
1978 }
1979 } else if arg_name == "update" {
1980 if let GqlValue::Boolean(update) = &argument.node {
1981 is_update = *update;
1982 }
1983 } else if arg_name == "delete" {
1984 if let GqlValue::Boolean(delete) = &argument.node {
1985 is_delete = *delete;
1986 }
1987 } else if arg_name == "single" {
1988 if let GqlValue::Boolean(delete) = &argument.node {
1989 is_single = *delete;
1990 }
1991 } else if arg_name == "schema" {
1992 if let GqlValue::String(schema) = &argument.node {
1993 schema_name = Some(schema.as_ref());
1994 }
1995 }
1996 });
1997 }
1998
1999 if is_insert && is_update {
2000 return Err(anyhow!("Mutation cannot be both insert and update"));
2001 } else if is_insert && is_delete {
2002 return Err(anyhow!("Mutation cannot be both insert and delete"));
2003 } else if is_update && is_delete {
2004 return Err(anyhow!("Mutation cannot be both update and delete"));
2005 }
2006
2007 Ok((
2008 name,
2009 key,
2010 is_insert,
2011 is_update,
2012 is_delete,
2013 is_single,
2014 schema_name,
2015 ))
2016}
2017
2018#[must_use]
2019pub fn wrap_mutation(key: &str, value: Statement, is_single: bool) -> Statement {
2020 let mut base = Expr::Function(Function {
2021 order_by: vec![],
2022 over: None,
2023 distinct: false,
2024 special: false,
2025 name: ObjectName(vec![Ident {
2026 value: "coalesce".to_string(),
2027 quote_style: None,
2028 }]),
2029 args: vec![
2030 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Function(Function {
2031 order_by: vec![],
2032 name: ObjectName(vec![Ident {
2033 value: JSON_AGG.to_string(),
2034 quote_style: None,
2035 }]),
2036 args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
2037 Expr::Identifier(Ident {
2038 value: "result".to_string(),
2039 quote_style: Some(QUOTE_CHAR),
2040 }),
2041 ))],
2042 over: None,
2043 distinct: false,
2044 special: false,
2045 }))),
2046 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
2047 Value::SingleQuotedString("[]".to_string()),
2048 ))),
2049 ],
2050 });
2051 if is_single {
2052 base = Expr::BinaryOp {
2053 left: Box::new(base),
2054 op: BinaryOperator::Custom("->".to_string()),
2055 right: Box::new(Expr::Value(Value::Number("0".to_string(), false))),
2056 }
2057 }
2058 Statement::Query(Box::new(Query {
2059 with: Some(With {
2060 cte_tables: vec![Cte {
2061 alias: TableAlias {
2062 name: Ident {
2063 value: "result".to_string(),
2064 quote_style: Some(QUOTE_CHAR),
2065 },
2066 columns: vec![],
2067 },
2068 query: Box::new(Query {
2069 with: None,
2070 body: Box::new(SetExpr::Insert(value)),
2071 order_by: vec![],
2072 limit: None,
2073 offset: None,
2074 fetch: None,
2075 locks: vec![],
2076 }),
2077 from: None,
2078 }],
2079 recursive: false,
2080 }),
2081 body: Box::new(SetExpr::Select(Box::new(Select {
2082 distinct: None,
2083 named_window: vec![],
2084 top: None,
2085 into: None,
2086 projection: vec![SelectItem::ExprWithAlias {
2087 expr: Expr::Function(Function {
2088 order_by: vec![],
2089 name: ObjectName(vec![Ident {
2090 value: JSON_BUILD_OBJECT.to_string(),
2091 quote_style: None,
2092 }]),
2093 args: vec![
2094 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
2095 Value::SingleQuotedString(key.to_string()),
2096 ))),
2097 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Subquery(Box::new(
2098 Query {
2099 with: None,
2100 body: Box::new(SetExpr::Select(Box::new(Select {
2101 distinct: None,
2102 named_window: vec![],
2103 top: None,
2104 projection: vec![SelectItem::UnnamedExpr(base)],
2105 into: None,
2106 from: vec![TableWithJoins {
2107 relation: TableFactor::Table {
2108 name: ObjectName(vec![Ident {
2109 value: "result".to_string(),
2110 quote_style: Some(QUOTE_CHAR),
2111 }]),
2112 alias: None,
2113 args: None,
2114 with_hints: vec![],
2115 },
2116 joins: vec![],
2117 }],
2118 lateral_views: Vec::new(),
2119 selection: None,
2120 group_by: Vec::new(),
2121 cluster_by: Vec::new(),
2122 distribute_by: Vec::new(),
2123 sort_by: Vec::new(),
2124 having: None,
2125 qualify: None,
2126 }))),
2127 order_by: vec![],
2128 limit: None,
2129 offset: None,
2130 fetch: None,
2131 locks: vec![],
2132 },
2133 )))),
2134 ],
2135 over: None,
2136 distinct: false,
2137 special: false,
2138 }),
2139 alias: Ident {
2140 value: DATA_LABEL.to_string(),
2141 quote_style: Some(QUOTE_CHAR),
2142 },
2143 }],
2144 from: vec![],
2145 lateral_views: Vec::new(),
2146 selection: None,
2147 group_by: Vec::new(),
2148 cluster_by: Vec::new(),
2149 distribute_by: Vec::new(),
2150 sort_by: Vec::new(),
2151 having: None,
2152 qualify: None,
2153 }))),
2154 order_by: vec![],
2155 limit: None,
2156 offset: None,
2157 fetch: None,
2158 locks: vec![],
2159 }))
2160}
2161
2162#[derive(PartialEq, Eq, Hash)]
2163struct Tag {
2164 key: String,
2165 value: String,
2166}
2167
2168impl Debug for Tag {
2169 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2170 write!(f, "{}:{}", self.key, self.value)
2171 }
2172}
2173
2174impl ToString for Tag {
2175 fn to_string(&self) -> String {
2176 format!("{}:{}", self.key, self.value)
2177 }
2178}
2179
2180pub fn gql2sql<'a>(
2181 ast: ExecutableDocument,
2182 variables: &Option<JsonValue>,
2183 operation_name: Option<String>,
2184) -> AnyResult<(Statement, Option<Vec<JsonValue>>, Option<Vec<String>>)> {
2185 let mut statements = Vec::new();
2186 let operation = match ast.operations {
2187 DocumentOperations::Single(operation) => operation.node,
2188 DocumentOperations::Multiple(map) => {
2189 if let Some(name) = operation_name {
2190 map.get(name.as_str())
2191 .ok_or_else(|| anyhow::anyhow!("Operation {} not found in the document", name))?
2192 .node
2193 .clone()
2194 } else {
2195 map.values()
2196 .next()
2197 .ok_or_else(|| {
2198 anyhow::anyhow!("No operation found in the document, please specify one")
2199 })?
2200 .node
2201 .clone()
2202 }
2203 }
2204 };
2205
2206 let (variables, sql_vars) = flatten_variables(variables, operation.variable_definitions);
2207 let mut tags: IndexMap<String, HashSet<Tag>> = IndexMap::new();
2208
2209 match operation.ty {
2210 OperationType::Query => {
2211 for selection in &operation.selection_set.node.items {
2212 match &selection.node {
2213 Selection::Field(p_field) => {
2214 let field = &p_field.node;
2215 let (name, key, is_aggregate, is_single, schema_name) =
2216 parse_query_meta(field)?;
2217 let (selection, distinct, distinct_order, order_by, mut first, after, keys) =
2218 parse_args(&field.arguments, &variables, &sql_vars)?;
2219 if is_single {
2220 first = Some(Expr::Value(Value::Number("1".to_string(), false)));
2221 }
2222 if let Some(keys) = keys {
2223 tags.insert(name.to_string(), keys.into_iter().collect());
2224 } else {
2225 tags.insert(name.to_string(), HashSet::new());
2226 };
2227 let table_name = schema_name.map_or_else(
2228 || {
2229 ObjectName(vec![Ident {
2230 value: name.to_string(),
2231 quote_style: Some(QUOTE_CHAR),
2232 }])
2233 },
2234 |schema_name| {
2235 ObjectName(vec![
2236 Ident {
2237 value: schema_name.to_string(),
2238 quote_style: Some(QUOTE_CHAR),
2239 },
2240 Ident {
2241 value: name.to_string(),
2242 quote_style: Some(QUOTE_CHAR),
2243 },
2244 ])
2245 },
2246 );
2247 let base_query = get_filter_query(
2248 selection,
2249 order_by,
2250 first,
2251 after,
2252 vec![table_name],
2253 distinct,
2254 distinct_order,
2255 );
2256 if is_aggregate {
2257 let aggs = get_aggregate_projection(
2258 &field.selection_set.node.items,
2259 format!("Agg_{name}"),
2260 )?;
2261 statements.push((
2262 key,
2263 Query {
2264 with: None,
2265 body: Box::new(get_agg_query(
2266 aggs,
2267 vec![TableWithJoins {
2268 relation: TableFactor::Derived {
2269 lateral: false,
2270 subquery: Box::new(base_query),
2271 alias: Some(TableAlias {
2272 name: Ident {
2273 value: BASE.to_string(),
2274 quote_style: Some(QUOTE_CHAR),
2275 },
2276 columns: vec![],
2277 }),
2278 },
2279 joins: vec![],
2280 }],
2281 None,
2282 ROOT_LABEL,
2283 )),
2284 order_by: vec![],
2285 limit: None,
2286 offset: None,
2287 fetch: None,
2288 locks: vec![],
2289 },
2290 ));
2291 } else {
2292 let (projection, joins, merges) = get_projection(
2293 &field.selection_set.node.items,
2294 name,
2295 Some(BASE),
2296 &variables,
2297 &sql_vars,
2298 &mut tags,
2299 )?;
2300 let root_query = get_root_query(
2301 projection,
2302 vec![TableWithJoins {
2303 relation: TableFactor::Derived {
2304 lateral: false,
2305 subquery: Box::new(base_query),
2306 alias: Some(TableAlias {
2307 name: Ident {
2308 value: BASE.to_string(),
2309 quote_style: Some(QUOTE_CHAR),
2310 },
2311 columns: vec![],
2312 }),
2313 },
2314 joins,
2315 }],
2316 None,
2317 &merges,
2318 is_single,
2319 ROOT_LABEL,
2320 );
2321 statements.push((
2322 key,
2323 Query {
2324 with: None,
2325 body: Box::new(root_query),
2326 order_by: vec![],
2327 limit: None,
2328 offset: None,
2329 fetch: None,
2330 locks: vec![],
2331 },
2332 ));
2333 };
2334 }
2335 Selection::FragmentSpread(_) => {
2336 return Err(anyhow::anyhow!("Fragment not supported"))
2337 }
2338 Selection::InlineFragment(_) => {
2339 return Err(anyhow::anyhow!("Fragment not supported"))
2340 }
2341 }
2342 }
2343 let statement = Statement::Query(Box::new(Query {
2344 with: None,
2345 body: Box::new(SetExpr::Select(Box::new(Select {
2346 distinct: None,
2347 named_window: vec![],
2348 top: None,
2349 into: None,
2350 projection: vec![SelectItem::ExprWithAlias {
2351 alias: Ident {
2352 value: DATA_LABEL.into(),
2353 quote_style: Some(QUOTE_CHAR),
2354 },
2355 expr: Expr::Function(Function {
2356 order_by: vec![],
2357 name: ObjectName(vec![Ident {
2358 value: JSON_BUILD_OBJECT.to_string(),
2359 quote_style: None,
2360 }]),
2361 args: statements
2362 .into_iter()
2363 .flat_map(|(key, query)| {
2364 vec![
2365 FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
2366 Value::SingleQuotedString(key.to_string()),
2367 ))),
2368 FunctionArg::Unnamed(FunctionArgExpr::Expr(
2369 Expr::Subquery(Box::new(query)),
2370 )),
2371 ]
2372 })
2373 .collect(),
2374 over: None,
2375 distinct: false,
2376 special: false,
2377 }),
2378 }],
2379 from: vec![],
2380 lateral_views: Vec::new(),
2381 selection: None,
2382 group_by: Vec::new(),
2383 cluster_by: Vec::new(),
2384 distribute_by: Vec::new(),
2385 sort_by: Vec::new(),
2386 having: None,
2387 qualify: None,
2388 }))),
2389 order_by: vec![],
2390 limit: None,
2391 offset: None,
2392 fetch: None,
2393 locks: vec![],
2394 }));
2395 let params = if sql_vars.is_empty() {
2396 None
2397 } else {
2398 Some(sql_vars.into_values().collect())
2399 };
2400 if tags.is_empty() {
2401 return Ok((statement, params, None));
2402 }
2403 let mut sub_tags = tags
2404 .into_iter()
2405 .flat_map(|(key, values)| {
2406 if values.is_empty() {
2407 return vec![format!("type:{key}")];
2408 }
2409 values
2410 .into_iter()
2411 .map(|v| format!("type:{key}:{}", v.to_string()))
2412 .collect::<Vec<_>>()
2413 })
2414 .collect::<Vec<String>>();
2415 sub_tags.sort_unstable();
2416 return Ok((statement, params, Some(sub_tags)));
2417 }
2418 OperationType::Mutation => {
2419 for selection in operation.selection_set.node.items {
2420 match &selection.node {
2421 Selection::Field(p_field) => {
2422 let field = &p_field.node;
2423 let (name, key, is_insert, is_update, is_delete, is_single, schema_name) =
2424 parse_mutation_meta(field)?;
2425
2426 let table_name = schema_name.map_or_else(
2427 || {
2428 ObjectName(vec![Ident {
2429 value: name.to_string(),
2430 quote_style: Some(QUOTE_CHAR),
2431 }])
2432 },
2433 |schema_name| {
2434 ObjectName(vec![
2435 Ident {
2436 value: schema_name.to_string(),
2437 quote_style: Some(QUOTE_CHAR),
2438 },
2439 Ident {
2440 value: name.to_string(),
2441 quote_style: Some(QUOTE_CHAR),
2442 },
2443 ])
2444 },
2445 );
2446 if is_insert {
2447 let (columns, rows) =
2448 get_mutation_columns(&field.arguments, &variables, &sql_vars)?;
2449 let (projection, _, _) = get_projection(
2450 &field.selection_set.node.items,
2451 name,
2452 None,
2453 &variables,
2454 &sql_vars,
2455 &mut tags,
2456 )?;
2457 let params = if sql_vars.is_empty() {
2458 None
2459 } else {
2460 Some(sql_vars.into_values().collect())
2461 };
2462 return Ok((
2463 wrap_mutation(
2464 key,
2465 Statement::Insert {
2466 or: None,
2467 into: true,
2468 table_name,
2469 columns,
2470 overwrite: false,
2471 source: Box::new(Query {
2472 with: None,
2473 body: Box::new(SetExpr::Values(Values {
2474 explicit_row: false,
2475 rows,
2476 })),
2477 order_by: vec![],
2478 limit: None,
2479 offset: None,
2480 fetch: None,
2481 locks: vec![],
2482 }),
2483 partitioned: None,
2484 after_columns: vec![],
2485 table: false,
2486 on: None,
2487 returning: Some(projection),
2488 },
2489 is_single,
2490 ),
2491 params,
2492 None,
2493 ));
2494 } else if is_update {
2495 let has_updated_at_directive = field
2496 .directives
2497 .iter()
2498 .any(|d| d.node.name.node == "updatedAt");
2499 let (projection, _, _) = get_projection(
2500 &field.selection_set.node.items,
2501 name,
2502 None,
2503 &variables,
2504 &sql_vars,
2505 &mut tags,
2506 )?;
2507 let (selection, assignments) = get_mutation_assignments(
2508 &field.arguments,
2509 &variables,
2510 &sql_vars,
2511 has_updated_at_directive,
2512 )?;
2513 let params = if sql_vars.is_empty() {
2514 None
2515 } else {
2516 Some(sql_vars.into_values().collect())
2517 };
2518 return Ok((
2519 wrap_mutation(
2520 key,
2521 Statement::Update {
2522 table: TableWithJoins {
2523 relation: TableFactor::Table {
2524 name: table_name,
2525 alias: None,
2526 args: None,
2527 with_hints: vec![],
2528 },
2529 joins: vec![],
2530 },
2531 assignments,
2532 from: None,
2533 selection,
2534 returning: Some(projection),
2535 },
2536 is_single,
2537 ),
2538 params,
2539 None,
2540 ));
2541 } else if is_delete {
2542 let (projection, _, _) = get_projection(
2543 &field.selection_set.node.items,
2544 name,
2545 None,
2546 &variables,
2547 &sql_vars,
2548 &mut tags,
2549 )?;
2550 let (selection, _) = get_mutation_assignments(
2551 &field.arguments,
2552 &variables,
2553 &sql_vars,
2554 false,
2555 )?;
2556 let params = if sql_vars.is_empty() {
2557 None
2558 } else {
2559 Some(sql_vars.into_values().collect())
2560 };
2561 return Ok((
2562 wrap_mutation(
2563 key,
2564 Statement::Delete {
2565 tables: vec![],
2566 from: vec![TableWithJoins {
2567 relation: TableFactor::Table {
2568 name: table_name,
2569 alias: None,
2570 args: None,
2571 with_hints: vec![],
2572 },
2573 joins: vec![],
2574 }],
2575 using: None,
2576 selection,
2577 returning: Some(projection),
2578 },
2579 is_single,
2580 ),
2581 params,
2582 None,
2583 ));
2584 }
2585 }
2586 Selection::FragmentSpread(_) => {
2587 return Err(anyhow::anyhow!("Fragment not supported"))
2588 }
2589 Selection::InlineFragment(_) => {
2590 return Err(anyhow::anyhow!("Fragment not supported"))
2591 }
2592 }
2593 }
2594 }
2595 OperationType::Subscription => return Err(anyhow::anyhow!("Subscription not supported")),
2596 }
2597 Err(anyhow!("No operation found"))
2598}
2599
2600#[cfg(test)]
2601mod tests {
2602 use super::*;
2603 use async_graphql_parser::parse_query;
2604
2605 use insta::assert_snapshot;
2606 use serde_json::json;
2607 use sqlparser::dialect::PostgreSqlDialect;
2608 use sqlparser::parser::Parser;
2609
2610 #[test]
2611 fn simple() -> Result<(), anyhow::Error> {
2612 let gqlast = parse_query(
2613 r#"query App {
2614 app(filter: { field: "id", operator: "eq", value: "345810043118026832" }, order: { name: ASC }) @meta(table: "App") {
2615 id
2616 components @relation(table: "Component", field: ["appId"], references: ["id"]) {
2617 id
2618 pageMeta @relation(table: "PageMeta", field: ["componentId"], references: ["id"], single: true) {
2619 id
2620 path
2621 }
2622 elements(order: { order: ASC }) @relation(table: "Element", field: ["componentParentId"], references: ["id"]) {
2623 id
2624 name
2625 }
2626 }
2627 }
2628 Component_aggregate(filter: { field: "appId", operator: "eq", value: "345810043118026832" }) {
2629 count
2630 min {
2631 createdAt
2632 }
2633 }
2634 }
2635 query Another {
2636 Component_aggregate(filter: { field: "appId", operator: "eq", value: "345810043118026832" }) {
2637 count
2638 min {
2639 createdAt
2640 }
2641 }
2642 }
2643 "#,
2644 )?;
2645 let (statement, _params, _tags) = gql2sql(gqlast, &None, Some("App".to_owned()))?;
2646 assert_snapshot!(statement.to_string());
2647 Ok(())
2648 }
2649
2650 #[test]
2651 fn mutation_insert() -> Result<(), anyhow::Error> {
2652 let gqlast = parse_query(
2653 r#"mutation insertVillains($data: [Villain_insert_input!]!) {
2654 insert(data: $data) @meta(table: "Villain", insert: true, schema: "auth") { id name }
2655 }"#,
2656 )?;
2657 let (statement, _params, _tags) = gql2sql(
2658 gqlast,
2659 &Some(json!({
2660 "data": [
2661 { "name": "Ronan the Accuser" },
2662 { "name": "Red Skull" },
2663 { "name": "The Vulture" }
2664 ]
2665 })),
2666 None,
2667 )?;
2668 assert_snapshot!(statement.to_string());
2669 Ok(())
2670 }
2671
2672 #[test]
2673 fn mutation_update() -> Result<(), anyhow::Error> {
2674 let gqlast = parse_query(
2675 r#"mutation updateHero {
2676 update(
2677 filter: { field: "secret_identity", operator: "eq", value: "Sam Wilson" },
2678 set: {
2679 name: "Captain America",
2680 }
2681 increment: {
2682 number_of_movies: 1
2683 }
2684 ) @meta(table: "Hero", update: true, schema: "auth") @updatedAt {
2685 id
2686 name
2687 secret_identity
2688 number_of_movies
2689 }
2690 }"#,
2691 )?;
2692 let (statement, _params, _tags) = gql2sql(gqlast, &None, None)?;
2693 assert_snapshot!(statement.to_string());
2694 Ok(())
2695 }
2696
2697 #[test]
2698 fn query_mega() -> Result<(), anyhow::Error> {
2699 let gqlast = parse_query(
2700 r#"query GetApp($orgId: String!, $appId: String!, $branch: String!) {
2701 app: App_one(
2702 filter: {
2703 field: "orgId",
2704 operator: "eq",
2705 value: $orgId,
2706 logicalOperator: "AND",
2707 children: [
2708 { field: "id", operator: "eq", value: $appId },
2709 { field: "branch", operator: "eq", value: $branch }
2710 ]
2711 }
2712 ) {
2713 orgId
2714 id
2715 branch
2716 name
2717 description
2718 theme
2719 favicon
2720 customCSS
2721 analytics
2722 customDomain
2723 components
2724 @relation(
2725 table: "Component"
2726 field: ["appId", "branch"]
2727 references: ["id", "branch"]
2728 ) {
2729 id
2730 branch
2731 ... on PageMeta
2732 @relation(
2733 table: "PageMeta"
2734 field: ["componentId", "branch"]
2735 references: ["id", "branch"]
2736 single: true
2737 ) {
2738 title
2739 description
2740 path
2741 socialImage
2742 urlParams
2743 loader
2744 protection
2745 maxAge
2746 sMaxAge
2747 staleWhileRevalidate
2748 }
2749 ... on ComponentMeta
2750 @relation(
2751 table: "ComponentMeta"
2752 field: ["componentId", "branch"]
2753 references: ["id", "branch"]
2754 single: true
2755 ) {
2756 title
2757 sources
2758 @relation(
2759 table: "Source"
2760 field: ["componentId", "branch"]
2761 references: ["id", "branch"]
2762 ) {
2763 id
2764 branch
2765 name
2766 provider
2767 description
2768 template
2769 instanceTemplate
2770 outputType
2771 source
2772 sourceProp
2773 componentId
2774 utilityId
2775 component(order: { order: ASC })
2776 @relation(
2777 table: "Element"
2778 field: ["id", "branch"]
2779 references: ["componentId", "branch"]
2780 single: true
2781 ) {
2782 id
2783 branch
2784 name
2785 kind
2786 source
2787 styles
2788 props
2789 order
2790 conditions
2791 }
2792 utility
2793 @relation(
2794 table: "Utility"
2795 field: ["id", "branch"]
2796 references: ["componentId", "branch"]
2797 single: true
2798 ) {
2799 id
2800 branch
2801 name
2802 kind
2803 kindId
2804 data
2805 }
2806 }
2807 events @relation(table: "Event", field: ["componentMetaId", "branch"], references: ["id", "branch"]) {
2808 id
2809 branch
2810 name
2811 label
2812 help
2813 type
2814 }
2815 }
2816 }
2817 connections @relation(table: "Connection", field: ["appId", "branch"], references: ["id", "branch"]) {
2818 id
2819 branch
2820 name
2821 kind
2822 prodUrl
2823 mutationSchema @relation(table: "Schema", field: ["mutationConnectionId", "branch"], references: ["id", "branch"], single: true) {
2824 id
2825 branch
2826 schema
2827 }
2828 endpoints @relation(table: "Endpoint", field: ["connectionId", "branch"], references: ["id", "branch"]) {
2829 id
2830 branch
2831 name
2832 method
2833 path
2834 responseSchemaId
2835 headers @relation(table: "Header", field: ["parentEndpointId", "branch"], references: ["id", "branch"]) {
2836 id
2837 branch
2838 key
2839 value
2840 dynamic
2841 }
2842 search @relation(table: "Search", field: ["endpointId", "branch"], references: ["id", "branch"]) {
2843 id
2844 branch
2845 key
2846 value
2847 dynamic
2848 }
2849 }
2850 headers @relation(table: "Header", field: ["parentConnectionId", "branch"], references: ["id", "branch"]) {
2851 id
2852 branch
2853 key
2854 value
2855 dynamic
2856 }
2857 }
2858 layouts @relation(table: "Layout", field: ["appId", "branch"], references: ["id", "branch"]) {
2859 id
2860 branch
2861 name
2862 source
2863 kind
2864 styles
2865 props
2866 }
2867 plugins @relation(table: "Plugin", field: ["appId", "branch"], references: ["id", "branch"]) {
2868 instanceId
2869 kind
2870 }
2871 schemas @relation(table: "Schema", field: ["appId", "branch"], references: ["id", "branch"]) {
2872 id
2873 branch
2874 schema
2875 }
2876 styles @relation(table: "Style", field: ["appId", "branch"], references: ["id", "branch"]) {
2877 id
2878 branch
2879 name
2880 kind
2881 styles
2882 isDefault
2883 }
2884 workflows @relation(table: "Workflow", field: ["appId", "branch"], references: ["id", "branch"]) {
2885 id
2886 branch
2887 name
2888 args
2889 steps(order: { order: ASC }) @relation(table: "Step", field: ["workflowId", "branch"], references: ["id", "branch"]) {
2890 id
2891 branch
2892 parentId
2893 kind
2894 kindId
2895 data
2896 order
2897 }
2898 }
2899 }
2900 }
2901"#,
2902 )?;
2903 let (statement, _params, _tags) = gql2sql(
2904 gqlast,
2905 &Some(json!({
2906 "orgId": "org",
2907 "appId": "app",
2908 "branch": "branch"
2909 })),
2910 None,
2911 )?;
2912 assert_snapshot!(statement.to_string());
2913 Ok(())
2914 }
2915
2916 #[test]
2917 fn query_frag() -> Result<(), anyhow::Error> {
2918 let gqlast = parse_query(
2919 r#"query GetApp($componentId: String!, $branch: String!) {
2920 component: Component_one(filter: { field: "id", operator: "eq", value: $componentId }) {
2921 id
2922 branch
2923 ... on ComponentMeta @relation(
2924 table: "ComponentMeta"
2925 field: ["componentId"]
2926 references: ["id"]
2927 single: true
2928 ) @args(
2929 filter: {
2930 field: "branch"
2931 operator: "eq",
2932 value: $branch,
2933 logicalOperator: "OR",
2934 children: [
2935 { field: "branch", operator: "eq", value: "main" }
2936 ]
2937 }
2938 ) {
2939 title
2940 }
2941 }
2942 }"#,
2943 )?;
2944 let (statement, _params, _tags) = gql2sql(
2945 gqlast,
2946 &Some(json!({
2947 "componentId": "comp",
2948 "branch": "branch"
2949 })),
2950 None,
2951 )?;
2952 assert_snapshot!(statement.to_string());
2953 Ok(())
2954 }
2955
2956 #[test]
2957 fn query_static() -> Result<(), anyhow::Error> {
2958 let gqlast = parse_query(
2959 r#"query GetApp($componentId: String!) {
2960 component: Component_one(filter: { field: "id", operator: "eq", value: $componentId }) {
2961 id
2962 branch
2963 kind @static(value: "page")
2964 }
2965 }"#,
2966 )?;
2967 let (statement, _params, _tags) = gql2sql(
2968 gqlast,
2969 &Some(json!({
2970 "componentId": "fake"
2971 })),
2972 None,
2973 )?;
2974 assert_snapshot!(statement.to_string());
2975 Ok(())
2976 }
2977
2978 #[test]
2979 fn query_distinct() -> Result<(), anyhow::Error> {
2980 let gqlast = parse_query(
2981 r#"query GetApp($componentId: String!, $branch: String!) {
2982 component: Component_one(
2983 filter: {
2984 field: "id",
2985 operator: "eq",
2986 value: $componentId
2987 logicalOperator: "AND",
2988 children: [
2989 { field: "branch", operator: "eq", value: $branch, logicalOperator: "OR", children: [
2990 { field: "branch", operator: "eq", value: "main" }
2991 ]}
2992 ]
2993 },
2994 order: [
2995 { orderKey: ASC }
2996 ],
2997 distinct: { on: ["id"], order: [{ expr: { field: "branch", operator: "eq", value: $branch }, dir: DESC }] }
2998 ) {
2999 id
3000 branch
3001 kind @static(value: "page")
3002 stuff(filter: { field: "componentId", operator: "eq", value: { _parentRef: "id" } }) @relation(table: "Stuff") {
3003 id
3004 }
3005 }
3006 }"#,
3007 )?;
3008 let (statement, _params, _tags) = gql2sql(
3009 gqlast,
3010 &Some(json!({
3011 "componentId": "fake",
3012 "branch": "branch",
3013 })),
3014 None,
3015 )?;
3016 assert_snapshot!(statement.to_string());
3017 Ok(())
3018 }
3019
3020 #[test]
3021 fn query_ast() -> Result<(), anyhow::Error> {
3022 let sql = r#"
3023 SELECT
3024 DISTINCT ON (column1)
3025 column2
3026 FROM
3027 table_name
3028 WHERE
3029 column1 = 'value'
3030 AND (column2 = 'value' OR column3 = 'value')
3031 ORDER BY
3032 column1,
3033 column2;
3034 "#;
3035 let dialect = PostgreSqlDialect {};
3036 let _sqlast = Parser::parse_sql(&dialect, sql)?;
3037 Ok(())
3038 }
3039
3040 #[test]
3041 fn query_sub_agg() -> Result<(), anyhow::Error> {
3042 let gqlast = parse_query(
3043 r#"query GetData {
3044 testing @meta(table: "UcwtYEtmmpXagcpcRiYKC") {
3045 id
3046 created_at
3047 updated_at
3048 anothers @relation(table: "N8Ag4Vgad4rYwcRmMJhGR", fields: ["id"], reference:["xb8nemrkchVQgxkXkCPhE"], aggregate: true) {
3049 count
3050 avg {
3051 value
3052 }
3053 }
3054 stuff @relation(table: "iYrk3kyTqaDQrLgjDaE9n", fields: ["eT86hgrpFB49r7N6AXz63"], references: ["id"], single: true) {
3055 id
3056 }
3057 }
3058 }"#,
3059 )?;
3060 let (statement, _params, _tags) = gql2sql(gqlast, &None, None)?;
3061 assert_snapshot!(statement.to_string());
3062 Ok(())
3063 }
3064
3065 #[test]
3066 fn query_schema_arg() -> Result<(), anyhow::Error> {
3067 let gqlast = parse_query(
3068 r#"
3069 query GetSession($sessionToken: String!) {
3070 session(
3071 filter: {
3072 field: "sessionToken"
3073 operator: "eq"
3074 value: $sessionToken
3075 }
3076 ) @meta(table: "sessions", single: true, schema: "auth") {
3077 sessionToken
3078 userId
3079 expires
3080 user2: user
3081 @relation(
3082 table: "users"
3083 field: ["id"]
3084 references: ["userId"]
3085 single: true
3086 schema: "auth"
3087 ) {
3088 id
3089 name
3090 email
3091 emailVerified
3092 image
3093 }
3094 }
3095}
3096 "#,
3097 )?;
3098 let (statement, _params, _tags) = gql2sql(
3099 gqlast,
3100 &Some(json!({
3101 "sessionToken": "fake"
3102 })),
3103 None,
3104 )?;
3105 assert_snapshot!(statement.to_string());
3106 Ok(())
3107 }
3108
3109 #[test]
3110 fn query_wrap_arg() -> Result<(), anyhow::Error> {
3111 let gqlast = parse_query(
3112 r#"
3113 mutation CreateVerificationToken($data: [VerificationToken!]!) {
3114 insert(data: $data)
3115 @meta(table: "verification_tokens", insert: true, schema: "auth", single: true) {
3116 identifier
3117 token
3118 expires
3119 }
3120 }
3121 "#,
3122 )?;
3123 let (statement, _params, _tags) = gql2sql(
3124 gqlast,
3125 &Some(json!({
3126 "data": [{
3127 "identifier": "nick@brevity.io",
3128 "token": "da978cc2c1e0e7b61e1be31b2e3979af576e494d68bd6f5dc156084d9924ee12",
3129 "expires": "2023-04-26T21:38:26"
3130 }]
3131 })),
3132 None,
3133 )?;
3134 assert_snapshot!(statement.to_string());
3135 Ok(())
3136 }
3137
3138 #[test]
3139 fn query_json_arg() -> Result<(), anyhow::Error> {
3140 let gqlast = parse_query(
3141 r#"
3142 query BrevityQuery($order_getTodoList: tXY7bJTNXP7RAhLFGybN4d_Order, $filter: tXY7bJTNXP7RAhLFGybN4d_Filter) {
3143 getTodoList(order: $order_getTodoList, filter: $filter) @meta(table: "tXY7bJTNXP7RAhLFGybN4d") {
3144 id
3145 cJ9jmpnjfYhRbCQBpWAzB8
3146 cPQdcYiWcPWWVeKVniUMjy
3147 }
3148 }
3149 "#,
3150 )?;
3151 let (_statement, _params, _tags) = gql2sql(
3153 gqlast,
3154 &Some(json!({
3155 "order_getTodoList": {
3156 "cPQdcYiWcPWWVeKVniUMjy": "ASC"
3157 },
3158 "filter": null
3159 })),
3160 None,
3161 )?;
3162 Ok(())
3164 }
3165
3166 #[test]
3167 fn query_simple_filter() -> Result<(), anyhow::Error> {
3168 let gqlast = parse_query(
3169 r#"
3170 query Test($id: String!) {
3171 record(id: $id) @meta(table: "Record") {
3172 id
3173 name
3174 age
3175 }
3176 }
3177 "#,
3178 )?;
3179 let (statement, _params, _tags) = gql2sql(
3180 gqlast,
3181 &Some(json!({
3182 "id": "fake"
3183 })),
3184 None,
3185 )?;
3186 assert_snapshot!(statement.to_string());
3187 Ok(())
3188 }
3189
3190 #[test]
3191 fn query_many_to_many() -> Result<(), anyhow::Error> {
3192 let gqlast = parse_query(
3193 r#"
3194 query ManyToMany($id: String!) {
3195 currentUser(id: $id) @meta(table: "User") {
3196 id
3197 lists @relation(table: "wrHJEgwMUmdJ3eWtPLPk8", many: true) {
3198 id
3199 }
3200 }
3201 }
3202 "#,
3203 )?;
3204 let (statement, _params, _tags) = gql2sql(
3205 gqlast,
3206 &Some(json!({
3207 "id": "fake"
3208 })),
3209 None,
3210 )?;
3211 assert_snapshot!(statement.to_string());
3212 Ok(())
3213 }
3214
3215 #[test]
3216 fn query_andre() -> Result<(), anyhow::Error> {
3217 let gqlast = parse_query(
3218 r#"
3219 query BrevityQuery($id_getH33iDwNVqqMxAnVEgPaThById: ID) {
3220 getH33iDwNVqqMxAnVEgPaThById(id: $id_getH33iDwNVqqMxAnVEgPaThById)
3221 @meta(table: "H33iDwNVqqMxAnVEgPaTh", single: true) {
3222 d8GJJg9DjNehPAeJcpTjM
3223 Fjjm3XAhyDmbhzymrrkRT_Aggregate
3224 @relation(
3225 table: "Fjjm3XAhyDmbhzymrrkRT"
3226 fields: ["id"]
3227 aggregate: true
3228 references: ["TbFeY8XVMaYnkQjDPWMkb_id"]
3229 ) {
3230 avg {
3231 XF4f6Qrhk86AX6dFWjYDt
3232 }
3233 }
3234 q6pJYTjmbprTNRdqG9Jrw
3235 egeyQ33H3z4EqzcRVFchV
3236 HYWfawTyxPNUf9a4DAH79
3237 H33iDwNVqqMxAnVEgPaTh_by_MdYg7jdht8ByhnKdfXBAb
3238 @relation(
3239 table: "MdYg7jdht8ByhnKdfXBAb"
3240 fields: ["id"]
3241 single: true
3242 references: ["MiyNcUJzKGJgQ9BERD8fr_id"]
3243 ) {
3244 H6hp6JGhzgPTYmLYwLk8P
3245 id
3246 }
3247 zFjEBPkLYmEAxLHrt3N4B
3248 LJDX6neXAYeXt9aVWxTRk
3249 FwpKpCegQH4EkzbjbNqVn
3250 ayipLT8iKHNTdhmiVqmxq
3251 Mr3R877DKbWTNWRzmEjxE_Aggregate
3252 @relation(many: true, table: "Mr3R877DKbWTNWRzmEjxE", aggregate: true) {
3253 count
3254 }
3255 r7xwAFrckDaVLwPzUAADB
3256 H33iDwNVqqMxAnVEgPaTh_by_User
3257 @relation(
3258 table: "User"
3259 fields: ["id"]
3260 single: true
3261 references: ["Gb8jAGqGDbYqfeqDDxKUF_id"]
3262 ) {
3263 gnHezR9MdBFH9kCthN3aB
3264 created_at
3265 id
3266 }
3267 id
3268 }
3269 }
3270 "#,
3271 )?;
3272 let (statement, params, _tags) = gql2sql(
3273 gqlast,
3274 &Some(json!({
3275 "id_getH33iDwNVqqMxAnVEgPaThById": "HAzqFfhQGbaB6WKBr6LA7"
3276 })),
3277 None,
3278 )?;
3279 assert_snapshot!(statement.to_string());
3280 assert_snapshot!(serde_json::to_string_pretty(¶ms)?);
3281 Ok(())
3282 }
3283
3284 #[test]
3285 fn mutation_delete() -> Result<(), anyhow::Error> {
3286 let gqlast = parse_query(
3287 r#"
3288 mutation DeleteVerificationToken(
3289 $identifier: String!
3290 $token: String!
3291 ) {
3292 delete(
3293 filter: {
3294 field: "identifier"
3295 operator: "eq"
3296 value: $identifier
3297 logicalOperator: "AND"
3298 children: [{ field: "token", operator: "eq", value: $token }]
3299 }
3300 ) @meta(table: "verification_tokens", delete: true, schema: "auth") {
3301 identifier
3302 token
3303 expires
3304 }
3305 }
3306 "#,
3307 )?;
3308 let (statement, _params, _tags) = gql2sql(
3309 gqlast,
3310 &Some(json!({ "token": "12345", "identifier": "fake@email.com" })),
3311 None,
3312 )?;
3313 assert_snapshot!(statement.to_string());
3314 Ok(())
3315 }
3316
3317 #[test]
3318 fn mutation_image() -> Result<(), anyhow::Error> {
3319 let gqlast = parse_query(
3320 r#"
3321 mutation Update($id: String!, $set: dogUpdateInput!) {
3322 update(
3323 filter: {
3324 field: "id"
3325 operator: "eq"
3326 value: $id
3327 }
3328 set: $set
3329 ) @meta(table: "WFqGH6dk8MpxfpHXh7awi", update: true) {
3330 id
3331 }
3332 }
3333 "#,
3334 )?;
3335 let (statement, params, _tags) = gql2sql(
3336 gqlast,
3337 &Some(
3338 json!({"id":"ffj9ACLQqpzjyh8yNFeQ6","set":{"updated_at":"2023-06-06T19:41:47+00:00","ynWfqMzGjjVQYzbKx4rMX":"DOGGY","QYtpTcmJCe6zfCHWwpNjR":"MYDOG","a8heQgUMyFync44JACwKA":{"src":"https://assets.brevity.io/uploads/jwy1g8rs7bxr9ptkaf6sy/lp_image-1685987665741.png","width":588,"height":1280}}}),
3339 ),
3340 None,
3341 )?;
3342 assert_snapshot!(statement.to_string());
3343 assert_snapshot!(serde_json::to_string_pretty(¶ms)?);
3344 Ok(())
3345 }
3346 #[test]
3347 fn nested_query() -> Result<(), anyhow::Error> {
3348 let gqlast = parse_query(
3349 r#"
3350 query BrevityQuery($id_getU7BBKiUwTgwiWMcgUYA4CById: ID) {
3351 getU7BBKiUwTgwiWMcgUYA4CById(id: $id_getU7BBKiUwTgwiWMcgUYA4CById) @meta(table: "U7BBKiUwTgwiWMcgUYA4C", single: true) {
3352 BtaHL8fRtKFw8gDJULFYp
3353 WFqGH6dk8MpxfpHXh7awi_by_U7BBKiUwTgwiWMcgUYA4C @relation(table: "WFqGH6dk8MpxfpHXh7awi", fields: ["MHPB9NP84gr3eXBmBfbxh_id"], references: ["id"]) {
3354 ynWfqMzGjjVQYzbKx4rMX
3355 QYtpTcmJCe6zfCHWwpNjR
3356 MHPB9NP84gr3eXBmBfbxh_id @relation(table: "U7BBKiUwTgwiWMcgUYA4C", fields: ["id"], single: true, references: ["MHPB9NP84gr3eXBmBfbxh_id"]) {
3357 id
3358 __typename
3359 }
3360 id
3361 }
3362 id
3363 }
3364 }
3365 "#,
3366 )?;
3367 let (statement, params, _tags) = gql2sql(
3368 gqlast,
3369 &Some(json!({ "id_getU7BBKiUwTgwiWMcgUYA4CById": "piWkMrFFXgdQBBkzf84MD" })),
3370 None,
3371 )?;
3372 assert_snapshot!(statement.to_string());
3373 assert_snapshot!(serde_json::to_string_pretty(¶ms)?);
3374 Ok(())
3375 }
3376}