1use crate::input::mutation::InputValue;
6use crate::resolver::query::TableFilter;
7use bytes::Bytes;
8use postrust_core::plan::{CoercibleField, CoercibleLogicTree, MutatePlan};
9use postrust_core::schema_cache::Table;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Default)]
15pub struct InsertArgs {
16 pub objects: Vec<HashMap<String, InputValue>>,
18 pub on_conflict: Option<OnConflictArgs>,
20 pub returning: Vec<String>,
22}
23
24impl InsertArgs {
25 pub fn new() -> Self {
27 Self::default()
28 }
29
30 pub fn with_object(mut self, object: HashMap<String, InputValue>) -> Self {
32 self.objects.push(object);
33 self
34 }
35
36 pub fn with_objects(mut self, objects: Vec<HashMap<String, InputValue>>) -> Self {
38 self.objects = objects;
39 self
40 }
41
42 pub fn with_on_conflict(mut self, on_conflict: OnConflictArgs) -> Self {
44 self.on_conflict = Some(on_conflict);
45 self
46 }
47
48 pub fn with_returning(mut self, returning: Vec<String>) -> Self {
50 self.returning = returning;
51 self
52 }
53
54 pub fn has_objects(&self) -> bool {
56 !self.objects.is_empty()
57 }
58
59 pub fn object_count(&self) -> usize {
61 self.objects.len()
62 }
63
64 pub fn to_json_bytes(&self) -> Option<Bytes> {
66 if self.objects.is_empty() {
67 return None;
68 }
69
70 let json_objects: Vec<serde_json::Value> = self
72 .objects
73 .iter()
74 .map(|obj| {
75 let map: serde_json::Map<String, serde_json::Value> = obj
76 .iter()
77 .map(|(k, v)| (k.clone(), input_value_to_json(v)))
78 .collect();
79 serde_json::Value::Object(map)
80 })
81 .collect();
82
83 let json = if json_objects.len() == 1 {
84 serde_json::to_vec(&json_objects[0]).ok()
85 } else {
86 serde_json::to_vec(&json_objects).ok()
87 };
88
89 json.map(Bytes::from)
90 }
91
92 pub fn column_names(&self) -> Vec<String> {
94 self.objects
95 .first()
96 .map(|obj| obj.keys().cloned().collect())
97 .unwrap_or_default()
98 }
99}
100
101#[derive(Debug, Clone, Default, Serialize, Deserialize)]
103pub struct OnConflictArgs {
104 pub constraint: Vec<String>,
106 pub update_columns: Vec<String>,
108 pub where_filter: Option<TableFilter>,
110}
111
112impl OnConflictArgs {
113 pub fn new(constraint: Vec<String>) -> Self {
115 Self {
116 constraint,
117 update_columns: vec![],
118 where_filter: None,
119 }
120 }
121
122 pub fn with_update_columns(mut self, columns: Vec<String>) -> Self {
124 self.update_columns = columns;
125 self
126 }
127
128 pub fn with_where(mut self, filter: TableFilter) -> Self {
130 self.where_filter = Some(filter);
131 self
132 }
133}
134
135#[derive(Debug, Clone, Default)]
137pub struct UpdateArgs {
138 pub filter: Option<TableFilter>,
140 pub set: HashMap<String, InputValue>,
142 pub returning: Vec<String>,
144}
145
146impl UpdateArgs {
147 pub fn new() -> Self {
149 Self::default()
150 }
151
152 pub fn with_filter(mut self, filter: TableFilter) -> Self {
154 self.filter = Some(filter);
155 self
156 }
157
158 pub fn with_set(mut self, set: HashMap<String, InputValue>) -> Self {
160 self.set = set;
161 self
162 }
163
164 pub fn with_returning(mut self, returning: Vec<String>) -> Self {
166 self.returning = returning;
167 self
168 }
169
170 pub fn has_filter(&self) -> bool {
172 self.filter.is_some()
173 }
174
175 pub fn has_set(&self) -> bool {
177 !self.set.is_empty()
178 }
179
180 pub fn to_json_bytes(&self) -> Option<Bytes> {
182 if self.set.is_empty() {
183 return None;
184 }
185
186 let map: serde_json::Map<String, serde_json::Value> = self
187 .set
188 .iter()
189 .map(|(k, v)| (k.clone(), input_value_to_json(v)))
190 .collect();
191
192 serde_json::to_vec(&serde_json::Value::Object(map))
193 .ok()
194 .map(Bytes::from)
195 }
196
197 pub fn column_names(&self) -> Vec<String> {
199 self.set.keys().cloned().collect()
200 }
201}
202
203#[derive(Debug, Clone, Default)]
205pub struct DeleteArgs {
206 pub filter: Option<TableFilter>,
208 pub returning: Vec<String>,
210}
211
212impl DeleteArgs {
213 pub fn new() -> Self {
215 Self::default()
216 }
217
218 pub fn with_filter(mut self, filter: TableFilter) -> Self {
220 self.filter = Some(filter);
221 self
222 }
223
224 pub fn with_returning(mut self, returning: Vec<String>) -> Self {
226 self.returning = returning;
227 self
228 }
229
230 pub fn has_filter(&self) -> bool {
232 self.filter.is_some()
233 }
234}
235
236fn input_value_to_json(value: &InputValue) -> serde_json::Value {
238 match value {
239 InputValue::Null => serde_json::Value::Null,
240 InputValue::Bool(b) => serde_json::Value::Bool(*b),
241 InputValue::Int(i) => serde_json::Value::Number((*i).into()),
242 InputValue::Float(f) => serde_json::Number::from_f64(*f)
243 .map(serde_json::Value::Number)
244 .unwrap_or(serde_json::Value::Null),
245 InputValue::String(s) => serde_json::Value::String(s.clone()),
246 InputValue::Object(obj) => {
247 let map: serde_json::Map<String, serde_json::Value> = obj
248 .iter()
249 .map(|(k, v)| (k.clone(), input_value_to_json(v)))
250 .collect();
251 serde_json::Value::Object(map)
252 }
253 InputValue::Array(arr) => {
254 serde_json::Value::Array(arr.iter().map(input_value_to_json).collect())
255 }
256 }
257}
258
259fn build_coercible_fields(columns: &[String], table: &Table) -> Vec<CoercibleField> {
261 columns
262 .iter()
263 .filter_map(|name| {
264 table
265 .columns
266 .get(name)
267 .map(|col| CoercibleField::simple(name, &col.data_type))
268 })
269 .collect()
270}
271
272fn build_where_clauses(filter: &Option<TableFilter>, table: &Table) -> Vec<CoercibleLogicTree> {
274 let Some(filter) = filter else {
275 return vec![];
276 };
277
278 let type_resolver = |name: &str| -> String {
279 table
280 .get_column(name)
281 .map(|c| c.data_type.clone())
282 .unwrap_or_else(|| "text".to_string())
283 };
284
285 filter
286 .to_logic_tree()
287 .map(|tree| vec![CoercibleLogicTree::from_logic_tree(&tree, type_resolver)])
288 .unwrap_or_default()
289}
290
291pub fn build_insert_plan(args: &InsertArgs, table: &Table) -> MutatePlan {
293 let columns = build_coercible_fields(&args.column_names(), table);
294 let body = args.to_json_bytes();
295 let returning = if args.returning.is_empty() {
296 table.pk_cols.clone()
297 } else {
298 args.returning.clone()
299 };
300
301 let on_conflict = args.on_conflict.as_ref().map(|oc| {
302 (
303 postrust_core::api_request::PreferResolution::MergeDuplicates,
304 oc.constraint.clone(),
305 )
306 });
307
308 MutatePlan::Insert {
309 target: table.qualified_identifier(),
310 columns,
311 body,
312 on_conflict,
313 where_clauses: vec![],
314 returning,
315 pk_cols: table.pk_cols.clone(),
316 apply_defaults: true,
317 reports_inserted: false,
318 }
319}
320
321pub fn build_update_plan(args: &UpdateArgs, table: &Table) -> MutatePlan {
323 let columns = build_coercible_fields(&args.column_names(), table);
324 let body = args.to_json_bytes();
325 let where_clauses = build_where_clauses(&args.filter, table);
326 let returning = if args.returning.is_empty() {
327 table.pk_cols.clone()
328 } else {
329 args.returning.clone()
330 };
331
332 MutatePlan::Update {
333 target: table.qualified_identifier(),
334 columns,
335 body,
336 where_clauses,
337 returning,
338 apply_defaults: false,
339 }
340}
341
342pub fn build_delete_plan(args: &DeleteArgs, table: &Table) -> MutatePlan {
344 let where_clauses = build_where_clauses(&args.filter, table);
345 let returning = if args.returning.is_empty() {
346 table.pk_cols.clone()
347 } else {
348 args.returning.clone()
349 };
350
351 MutatePlan::Delete {
352 target: table.qualified_identifier(),
353 where_clauses,
354 returning,
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::input::filter::IntFilterInput;
362 use crate::resolver::query::FieldFilter;
363 use indexmap::IndexMap;
364 use postrust_core::schema_cache::Column;
365 use pretty_assertions::assert_eq;
366
367 fn create_test_table() -> Table {
368 let mut columns = IndexMap::new();
369 columns.insert(
370 "id".into(),
371 Column {
372 name: "id".into(),
373 description: None,
374 nullable: false,
375 data_type: "integer".into(),
376 nominal_type: "int4".into(),
377 max_len: None,
378 default: Some("nextval('users_id_seq')".into()),
379 enum_values: vec![],
380 is_pk: true,
381 position: 1,
382 always_generated: false,
383 domain_type: None,
384 },
385 );
386 columns.insert(
387 "name".into(),
388 Column {
389 name: "name".into(),
390 description: None,
391 nullable: false,
392 data_type: "text".into(),
393 nominal_type: "text".into(),
394 max_len: None,
395 default: None,
396 enum_values: vec![],
397 is_pk: false,
398 position: 2,
399 always_generated: false,
400 domain_type: None,
401 },
402 );
403 columns.insert(
404 "email".into(),
405 Column {
406 name: "email".into(),
407 description: None,
408 nullable: true,
409 data_type: "text".into(),
410 nominal_type: "text".into(),
411 max_len: None,
412 default: None,
413 enum_values: vec![],
414 is_pk: false,
415 position: 3,
416 always_generated: false,
417 domain_type: None,
418 },
419 );
420
421 Table {
422 schema: "public".into(),
423 name: "users".into(),
424 description: None,
425 is_view: false,
426 insertable: true,
427 updatable: true,
428 deletable: true,
429 pk_cols: vec!["id".into()],
430 unique_constraints: Vec::new(),
431 columns,
432 computed_columns: Default::default(),
433 is_partitioned: false,
434 }
435 }
436
437 #[test]
442 fn test_insert_args_default() {
443 let args = InsertArgs::new();
444 assert!(!args.has_objects());
445 assert_eq!(args.object_count(), 0);
446 }
447
448 #[test]
449 fn test_insert_args_with_object() {
450 let mut object = HashMap::new();
451 object.insert("name".to_string(), InputValue::String("Alice".to_string()));
452 object.insert(
453 "email".to_string(),
454 InputValue::String("alice@example.com".to_string()),
455 );
456
457 let args = InsertArgs::new().with_object(object);
458 assert!(args.has_objects());
459 assert_eq!(args.object_count(), 1);
460 }
461
462 #[test]
463 fn test_insert_args_with_multiple_objects() {
464 let obj1: HashMap<String, InputValue> =
465 [("name".to_string(), InputValue::String("Alice".to_string()))]
466 .into_iter()
467 .collect();
468 let obj2: HashMap<String, InputValue> =
469 [("name".to_string(), InputValue::String("Bob".to_string()))]
470 .into_iter()
471 .collect();
472
473 let args = InsertArgs::new().with_objects(vec![obj1, obj2]);
474 assert_eq!(args.object_count(), 2);
475 }
476
477 #[test]
478 fn test_insert_args_column_names() {
479 let object: HashMap<String, InputValue> = [
480 ("name".to_string(), InputValue::String("Alice".to_string())),
481 (
482 "email".to_string(),
483 InputValue::String("alice@example.com".to_string()),
484 ),
485 ]
486 .into_iter()
487 .collect();
488
489 let args = InsertArgs::new().with_object(object);
490 let columns = args.column_names();
491 assert_eq!(columns.len(), 2);
492 assert!(columns.contains(&"name".to_string()));
493 assert!(columns.contains(&"email".to_string()));
494 }
495
496 #[test]
497 fn test_insert_args_to_json_bytes() {
498 let object: HashMap<String, InputValue> =
499 [("name".to_string(), InputValue::String("Alice".to_string()))]
500 .into_iter()
501 .collect();
502
503 let args = InsertArgs::new().with_object(object);
504 let bytes = args.to_json_bytes().unwrap();
505 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
506 assert_eq!(json["name"], "Alice");
507 }
508
509 #[test]
510 fn test_insert_args_with_returning() {
511 let args = InsertArgs::new().with_returning(vec!["id".to_string(), "name".to_string()]);
512 assert_eq!(args.returning.len(), 2);
513 }
514
515 #[test]
516 fn test_insert_args_with_on_conflict() {
517 let on_conflict = OnConflictArgs::new(vec!["email".to_string()])
518 .with_update_columns(vec!["name".to_string()]);
519
520 let args = InsertArgs::new().with_on_conflict(on_conflict);
521 assert!(args.on_conflict.is_some());
522 }
523
524 #[test]
529 fn test_on_conflict_args() {
530 let args = OnConflictArgs::new(vec!["id".to_string()])
531 .with_update_columns(vec!["name".to_string(), "email".to_string()]);
532
533 assert_eq!(args.constraint, vec!["id".to_string()]);
534 assert_eq!(args.update_columns.len(), 2);
535 }
536
537 #[test]
542 fn test_update_args_default() {
543 let args = UpdateArgs::new();
544 assert!(!args.has_filter());
545 assert!(!args.has_set());
546 }
547
548 #[test]
549 fn test_update_args_with_set() {
550 let set: HashMap<String, InputValue> = [(
551 "name".to_string(),
552 InputValue::String("Updated".to_string()),
553 )]
554 .into_iter()
555 .collect();
556
557 let args = UpdateArgs::new().with_set(set);
558 assert!(args.has_set());
559 assert_eq!(args.column_names().len(), 1);
560 }
561
562 #[test]
563 fn test_update_args_with_filter() {
564 let filter = TableFilter::new().with_field(
565 "id",
566 FieldFilter::int(IntFilterInput {
567 eq: Some(1),
568 ..Default::default()
569 }),
570 );
571
572 let args = UpdateArgs::new().with_filter(filter);
573 assert!(args.has_filter());
574 }
575
576 #[test]
577 fn test_update_args_to_json_bytes() {
578 let set: HashMap<String, InputValue> = [
579 (
580 "name".to_string(),
581 InputValue::String("Updated".to_string()),
582 ),
583 ("active".to_string(), InputValue::Bool(true)),
584 ]
585 .into_iter()
586 .collect();
587
588 let args = UpdateArgs::new().with_set(set);
589 let bytes = args.to_json_bytes().unwrap();
590 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
591 assert_eq!(json["name"], "Updated");
592 assert_eq!(json["active"], true);
593 }
594
595 #[test]
600 fn test_delete_args_default() {
601 let args = DeleteArgs::new();
602 assert!(!args.has_filter());
603 }
604
605 #[test]
606 fn test_delete_args_with_filter() {
607 let filter = TableFilter::new().with_field(
608 "id",
609 FieldFilter::int(IntFilterInput {
610 eq: Some(1),
611 ..Default::default()
612 }),
613 );
614
615 let args = DeleteArgs::new().with_filter(filter);
616 assert!(args.has_filter());
617 }
618
619 #[test]
620 fn test_delete_args_with_returning() {
621 let args = DeleteArgs::new().with_returning(vec!["id".to_string(), "name".to_string()]);
622 assert_eq!(args.returning.len(), 2);
623 }
624
625 #[test]
630 fn test_input_value_to_json_null() {
631 let json = input_value_to_json(&InputValue::Null);
632 assert!(json.is_null());
633 }
634
635 #[test]
636 fn test_input_value_to_json_bool() {
637 let json = input_value_to_json(&InputValue::Bool(true));
638 assert_eq!(json, serde_json::Value::Bool(true));
639 }
640
641 #[test]
642 fn test_input_value_to_json_int() {
643 let json = input_value_to_json(&InputValue::Int(42));
644 assert_eq!(json, serde_json::json!(42));
645 }
646
647 #[test]
648 fn test_input_value_to_json_float() {
649 let json = input_value_to_json(&InputValue::Float(1.5));
650 assert_eq!(json, serde_json::json!(1.5));
651 }
652
653 #[test]
654 fn test_input_value_to_json_string() {
655 let json = input_value_to_json(&InputValue::String("hello".to_string()));
656 assert_eq!(json, serde_json::json!("hello"));
657 }
658
659 #[test]
660 fn test_input_value_to_json_array() {
661 let arr = vec![InputValue::Int(1), InputValue::Int(2), InputValue::Int(3)];
662 let json = input_value_to_json(&InputValue::Array(arr));
663 assert_eq!(json, serde_json::json!([1, 2, 3]));
664 }
665
666 #[test]
667 fn test_input_value_to_json_object() {
668 let obj: HashMap<String, InputValue> = [
669 ("name".to_string(), InputValue::String("test".to_string())),
670 ("count".to_string(), InputValue::Int(5)),
671 ]
672 .into_iter()
673 .collect();
674 let json = input_value_to_json(&InputValue::Object(obj));
675 assert_eq!(json["name"], "test");
676 assert_eq!(json["count"], 5);
677 }
678
679 #[test]
684 fn test_build_insert_plan_basic() {
685 let table = create_test_table();
686 let object: HashMap<String, InputValue> =
687 [("name".to_string(), InputValue::String("Alice".to_string()))]
688 .into_iter()
689 .collect();
690
691 let args = InsertArgs::new().with_object(object);
692 let plan = build_insert_plan(&args, &table);
693
694 match plan {
695 MutatePlan::Insert {
696 target,
697 body,
698 returning,
699 ..
700 } => {
701 assert_eq!(target.name, "users");
702 assert!(body.is_some());
703 assert_eq!(returning, vec!["id".to_string()]);
704 }
705 _ => panic!("Expected Insert plan"),
706 }
707 }
708
709 #[test]
710 fn test_build_insert_plan_with_returning() {
711 let table = create_test_table();
712 let object: HashMap<String, InputValue> =
713 [("name".to_string(), InputValue::String("Alice".to_string()))]
714 .into_iter()
715 .collect();
716
717 let args = InsertArgs::new()
718 .with_object(object)
719 .with_returning(vec!["id".to_string(), "name".to_string()]);
720 let plan = build_insert_plan(&args, &table);
721
722 match plan {
723 MutatePlan::Insert { returning, .. } => {
724 assert_eq!(returning.len(), 2);
725 }
726 _ => panic!("Expected Insert plan"),
727 }
728 }
729
730 #[test]
731 fn test_build_insert_plan_with_on_conflict() {
732 let table = create_test_table();
733 let object: HashMap<String, InputValue> =
734 [("name".to_string(), InputValue::String("Alice".to_string()))]
735 .into_iter()
736 .collect();
737
738 let on_conflict = OnConflictArgs::new(vec!["id".to_string()]);
739 let args = InsertArgs::new()
740 .with_object(object)
741 .with_on_conflict(on_conflict);
742 let plan = build_insert_plan(&args, &table);
743
744 match plan {
745 MutatePlan::Insert { on_conflict, .. } => {
746 assert!(on_conflict.is_some());
747 let (_, cols) = on_conflict.unwrap();
748 assert_eq!(cols, vec!["id".to_string()]);
749 }
750 _ => panic!("Expected Insert plan"),
751 }
752 }
753
754 #[test]
755 fn test_build_update_plan_basic() {
756 let table = create_test_table();
757 let set: HashMap<String, InputValue> = [(
758 "name".to_string(),
759 InputValue::String("Updated".to_string()),
760 )]
761 .into_iter()
762 .collect();
763
764 let filter = TableFilter::new().with_field(
765 "id",
766 FieldFilter::int(IntFilterInput {
767 eq: Some(1),
768 ..Default::default()
769 }),
770 );
771
772 let args = UpdateArgs::new().with_set(set).with_filter(filter);
773 let plan = build_update_plan(&args, &table);
774
775 match plan {
776 MutatePlan::Update {
777 target,
778 body,
779 where_clauses,
780 ..
781 } => {
782 assert_eq!(target.name, "users");
783 assert!(body.is_some());
784 assert!(!where_clauses.is_empty());
785 }
786 _ => panic!("Expected Update plan"),
787 }
788 }
789
790 #[test]
791 fn test_build_update_plan_with_returning() {
792 let table = create_test_table();
793 let set: HashMap<String, InputValue> = [(
794 "name".to_string(),
795 InputValue::String("Updated".to_string()),
796 )]
797 .into_iter()
798 .collect();
799
800 let args = UpdateArgs::new()
801 .with_set(set)
802 .with_returning(vec!["id".to_string(), "name".to_string()]);
803 let plan = build_update_plan(&args, &table);
804
805 match plan {
806 MutatePlan::Update { returning, .. } => {
807 assert_eq!(returning.len(), 2);
808 }
809 _ => panic!("Expected Update plan"),
810 }
811 }
812
813 #[test]
814 fn test_build_delete_plan_basic() {
815 let table = create_test_table();
816 let filter = TableFilter::new().with_field(
817 "id",
818 FieldFilter::int(IntFilterInput {
819 eq: Some(1),
820 ..Default::default()
821 }),
822 );
823
824 let args = DeleteArgs::new().with_filter(filter);
825 let plan = build_delete_plan(&args, &table);
826
827 match plan {
828 MutatePlan::Delete {
829 target,
830 where_clauses,
831 returning,
832 } => {
833 assert_eq!(target.name, "users");
834 assert!(!where_clauses.is_empty());
835 assert_eq!(returning, vec!["id".to_string()]);
836 }
837 _ => panic!("Expected Delete plan"),
838 }
839 }
840
841 #[test]
842 fn test_build_delete_plan_with_returning() {
843 let table = create_test_table();
844 let args = DeleteArgs::new().with_returning(vec![
845 "id".to_string(),
846 "name".to_string(),
847 "email".to_string(),
848 ]);
849 let plan = build_delete_plan(&args, &table);
850
851 match plan {
852 MutatePlan::Delete { returning, .. } => {
853 assert_eq!(returning.len(), 3);
854 }
855 _ => panic!("Expected Delete plan"),
856 }
857 }
858
859 #[test]
860 fn test_build_delete_plan_no_filter() {
861 let table = create_test_table();
862 let args = DeleteArgs::new();
863 let plan = build_delete_plan(&args, &table);
864
865 match plan {
866 MutatePlan::Delete { where_clauses, .. } => {
867 assert!(where_clauses.is_empty());
868 }
869 _ => panic!("Expected Delete plan"),
870 }
871 }
872}