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 }
318}
319
320pub fn build_update_plan(args: &UpdateArgs, table: &Table) -> MutatePlan {
322 let columns = build_coercible_fields(&args.column_names(), table);
323 let body = args.to_json_bytes();
324 let where_clauses = build_where_clauses(&args.filter, table);
325 let returning = if args.returning.is_empty() {
326 table.pk_cols.clone()
327 } else {
328 args.returning.clone()
329 };
330
331 MutatePlan::Update {
332 target: table.qualified_identifier(),
333 columns,
334 body,
335 where_clauses,
336 returning,
337 apply_defaults: false,
338 }
339}
340
341pub fn build_delete_plan(args: &DeleteArgs, table: &Table) -> MutatePlan {
343 let where_clauses = build_where_clauses(&args.filter, table);
344 let returning = if args.returning.is_empty() {
345 table.pk_cols.clone()
346 } else {
347 args.returning.clone()
348 };
349
350 MutatePlan::Delete {
351 target: table.qualified_identifier(),
352 where_clauses,
353 returning,
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use crate::input::filter::IntFilterInput;
361 use crate::resolver::query::FieldFilter;
362 use indexmap::IndexMap;
363 use postrust_core::schema_cache::Column;
364 use pretty_assertions::assert_eq;
365
366 fn create_test_table() -> Table {
367 let mut columns = IndexMap::new();
368 columns.insert(
369 "id".into(),
370 Column {
371 name: "id".into(),
372 description: None,
373 nullable: false,
374 data_type: "integer".into(),
375 nominal_type: "int4".into(),
376 max_len: None,
377 default: Some("nextval('users_id_seq')".into()),
378 enum_values: vec![],
379 is_pk: true,
380 position: 1,
381 },
382 );
383 columns.insert(
384 "name".into(),
385 Column {
386 name: "name".into(),
387 description: None,
388 nullable: false,
389 data_type: "text".into(),
390 nominal_type: "text".into(),
391 max_len: None,
392 default: None,
393 enum_values: vec![],
394 is_pk: false,
395 position: 2,
396 },
397 );
398 columns.insert(
399 "email".into(),
400 Column {
401 name: "email".into(),
402 description: None,
403 nullable: true,
404 data_type: "text".into(),
405 nominal_type: "text".into(),
406 max_len: None,
407 default: None,
408 enum_values: vec![],
409 is_pk: false,
410 position: 3,
411 },
412 );
413
414 Table {
415 schema: "public".into(),
416 name: "users".into(),
417 description: None,
418 is_view: false,
419 insertable: true,
420 updatable: true,
421 deletable: true,
422 pk_cols: vec!["id".into()],
423 columns,
424 }
425 }
426
427 #[test]
432 fn test_insert_args_default() {
433 let args = InsertArgs::new();
434 assert!(!args.has_objects());
435 assert_eq!(args.object_count(), 0);
436 }
437
438 #[test]
439 fn test_insert_args_with_object() {
440 let mut object = HashMap::new();
441 object.insert("name".to_string(), InputValue::String("Alice".to_string()));
442 object.insert(
443 "email".to_string(),
444 InputValue::String("alice@example.com".to_string()),
445 );
446
447 let args = InsertArgs::new().with_object(object);
448 assert!(args.has_objects());
449 assert_eq!(args.object_count(), 1);
450 }
451
452 #[test]
453 fn test_insert_args_with_multiple_objects() {
454 let obj1: HashMap<String, InputValue> =
455 [("name".to_string(), InputValue::String("Alice".to_string()))]
456 .into_iter()
457 .collect();
458 let obj2: HashMap<String, InputValue> =
459 [("name".to_string(), InputValue::String("Bob".to_string()))]
460 .into_iter()
461 .collect();
462
463 let args = InsertArgs::new().with_objects(vec![obj1, obj2]);
464 assert_eq!(args.object_count(), 2);
465 }
466
467 #[test]
468 fn test_insert_args_column_names() {
469 let object: HashMap<String, InputValue> = [
470 ("name".to_string(), InputValue::String("Alice".to_string())),
471 (
472 "email".to_string(),
473 InputValue::String("alice@example.com".to_string()),
474 ),
475 ]
476 .into_iter()
477 .collect();
478
479 let args = InsertArgs::new().with_object(object);
480 let columns = args.column_names();
481 assert_eq!(columns.len(), 2);
482 assert!(columns.contains(&"name".to_string()));
483 assert!(columns.contains(&"email".to_string()));
484 }
485
486 #[test]
487 fn test_insert_args_to_json_bytes() {
488 let object: HashMap<String, InputValue> =
489 [("name".to_string(), InputValue::String("Alice".to_string()))]
490 .into_iter()
491 .collect();
492
493 let args = InsertArgs::new().with_object(object);
494 let bytes = args.to_json_bytes().unwrap();
495 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
496 assert_eq!(json["name"], "Alice");
497 }
498
499 #[test]
500 fn test_insert_args_with_returning() {
501 let args = InsertArgs::new().with_returning(vec!["id".to_string(), "name".to_string()]);
502 assert_eq!(args.returning.len(), 2);
503 }
504
505 #[test]
506 fn test_insert_args_with_on_conflict() {
507 let on_conflict = OnConflictArgs::new(vec!["email".to_string()])
508 .with_update_columns(vec!["name".to_string()]);
509
510 let args = InsertArgs::new().with_on_conflict(on_conflict);
511 assert!(args.on_conflict.is_some());
512 }
513
514 #[test]
519 fn test_on_conflict_args() {
520 let args = OnConflictArgs::new(vec!["id".to_string()])
521 .with_update_columns(vec!["name".to_string(), "email".to_string()]);
522
523 assert_eq!(args.constraint, vec!["id".to_string()]);
524 assert_eq!(args.update_columns.len(), 2);
525 }
526
527 #[test]
532 fn test_update_args_default() {
533 let args = UpdateArgs::new();
534 assert!(!args.has_filter());
535 assert!(!args.has_set());
536 }
537
538 #[test]
539 fn test_update_args_with_set() {
540 let set: HashMap<String, InputValue> = [(
541 "name".to_string(),
542 InputValue::String("Updated".to_string()),
543 )]
544 .into_iter()
545 .collect();
546
547 let args = UpdateArgs::new().with_set(set);
548 assert!(args.has_set());
549 assert_eq!(args.column_names().len(), 1);
550 }
551
552 #[test]
553 fn test_update_args_with_filter() {
554 let filter = TableFilter::new().with_field(
555 "id",
556 FieldFilter::int(IntFilterInput {
557 eq: Some(1),
558 ..Default::default()
559 }),
560 );
561
562 let args = UpdateArgs::new().with_filter(filter);
563 assert!(args.has_filter());
564 }
565
566 #[test]
567 fn test_update_args_to_json_bytes() {
568 let set: HashMap<String, InputValue> = [
569 (
570 "name".to_string(),
571 InputValue::String("Updated".to_string()),
572 ),
573 ("active".to_string(), InputValue::Bool(true)),
574 ]
575 .into_iter()
576 .collect();
577
578 let args = UpdateArgs::new().with_set(set);
579 let bytes = args.to_json_bytes().unwrap();
580 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
581 assert_eq!(json["name"], "Updated");
582 assert_eq!(json["active"], true);
583 }
584
585 #[test]
590 fn test_delete_args_default() {
591 let args = DeleteArgs::new();
592 assert!(!args.has_filter());
593 }
594
595 #[test]
596 fn test_delete_args_with_filter() {
597 let filter = TableFilter::new().with_field(
598 "id",
599 FieldFilter::int(IntFilterInput {
600 eq: Some(1),
601 ..Default::default()
602 }),
603 );
604
605 let args = DeleteArgs::new().with_filter(filter);
606 assert!(args.has_filter());
607 }
608
609 #[test]
610 fn test_delete_args_with_returning() {
611 let args = DeleteArgs::new().with_returning(vec!["id".to_string(), "name".to_string()]);
612 assert_eq!(args.returning.len(), 2);
613 }
614
615 #[test]
620 fn test_input_value_to_json_null() {
621 let json = input_value_to_json(&InputValue::Null);
622 assert!(json.is_null());
623 }
624
625 #[test]
626 fn test_input_value_to_json_bool() {
627 let json = input_value_to_json(&InputValue::Bool(true));
628 assert_eq!(json, serde_json::Value::Bool(true));
629 }
630
631 #[test]
632 fn test_input_value_to_json_int() {
633 let json = input_value_to_json(&InputValue::Int(42));
634 assert_eq!(json, serde_json::json!(42));
635 }
636
637 #[test]
638 fn test_input_value_to_json_float() {
639 let json = input_value_to_json(&InputValue::Float(1.5));
640 assert_eq!(json, serde_json::json!(1.5));
641 }
642
643 #[test]
644 fn test_input_value_to_json_string() {
645 let json = input_value_to_json(&InputValue::String("hello".to_string()));
646 assert_eq!(json, serde_json::json!("hello"));
647 }
648
649 #[test]
650 fn test_input_value_to_json_array() {
651 let arr = vec![InputValue::Int(1), InputValue::Int(2), InputValue::Int(3)];
652 let json = input_value_to_json(&InputValue::Array(arr));
653 assert_eq!(json, serde_json::json!([1, 2, 3]));
654 }
655
656 #[test]
657 fn test_input_value_to_json_object() {
658 let obj: HashMap<String, InputValue> = [
659 ("name".to_string(), InputValue::String("test".to_string())),
660 ("count".to_string(), InputValue::Int(5)),
661 ]
662 .into_iter()
663 .collect();
664 let json = input_value_to_json(&InputValue::Object(obj));
665 assert_eq!(json["name"], "test");
666 assert_eq!(json["count"], 5);
667 }
668
669 #[test]
674 fn test_build_insert_plan_basic() {
675 let table = create_test_table();
676 let object: HashMap<String, InputValue> =
677 [("name".to_string(), InputValue::String("Alice".to_string()))]
678 .into_iter()
679 .collect();
680
681 let args = InsertArgs::new().with_object(object);
682 let plan = build_insert_plan(&args, &table);
683
684 match plan {
685 MutatePlan::Insert {
686 target,
687 body,
688 returning,
689 ..
690 } => {
691 assert_eq!(target.name, "users");
692 assert!(body.is_some());
693 assert_eq!(returning, vec!["id".to_string()]);
694 }
695 _ => panic!("Expected Insert plan"),
696 }
697 }
698
699 #[test]
700 fn test_build_insert_plan_with_returning() {
701 let table = create_test_table();
702 let object: HashMap<String, InputValue> =
703 [("name".to_string(), InputValue::String("Alice".to_string()))]
704 .into_iter()
705 .collect();
706
707 let args = InsertArgs::new()
708 .with_object(object)
709 .with_returning(vec!["id".to_string(), "name".to_string()]);
710 let plan = build_insert_plan(&args, &table);
711
712 match plan {
713 MutatePlan::Insert { returning, .. } => {
714 assert_eq!(returning.len(), 2);
715 }
716 _ => panic!("Expected Insert plan"),
717 }
718 }
719
720 #[test]
721 fn test_build_insert_plan_with_on_conflict() {
722 let table = create_test_table();
723 let object: HashMap<String, InputValue> =
724 [("name".to_string(), InputValue::String("Alice".to_string()))]
725 .into_iter()
726 .collect();
727
728 let on_conflict = OnConflictArgs::new(vec!["id".to_string()]);
729 let args = InsertArgs::new()
730 .with_object(object)
731 .with_on_conflict(on_conflict);
732 let plan = build_insert_plan(&args, &table);
733
734 match plan {
735 MutatePlan::Insert { on_conflict, .. } => {
736 assert!(on_conflict.is_some());
737 let (_, cols) = on_conflict.unwrap();
738 assert_eq!(cols, vec!["id".to_string()]);
739 }
740 _ => panic!("Expected Insert plan"),
741 }
742 }
743
744 #[test]
745 fn test_build_update_plan_basic() {
746 let table = create_test_table();
747 let set: HashMap<String, InputValue> = [(
748 "name".to_string(),
749 InputValue::String("Updated".to_string()),
750 )]
751 .into_iter()
752 .collect();
753
754 let filter = TableFilter::new().with_field(
755 "id",
756 FieldFilter::int(IntFilterInput {
757 eq: Some(1),
758 ..Default::default()
759 }),
760 );
761
762 let args = UpdateArgs::new().with_set(set).with_filter(filter);
763 let plan = build_update_plan(&args, &table);
764
765 match plan {
766 MutatePlan::Update {
767 target,
768 body,
769 where_clauses,
770 ..
771 } => {
772 assert_eq!(target.name, "users");
773 assert!(body.is_some());
774 assert!(!where_clauses.is_empty());
775 }
776 _ => panic!("Expected Update plan"),
777 }
778 }
779
780 #[test]
781 fn test_build_update_plan_with_returning() {
782 let table = create_test_table();
783 let set: HashMap<String, InputValue> = [(
784 "name".to_string(),
785 InputValue::String("Updated".to_string()),
786 )]
787 .into_iter()
788 .collect();
789
790 let args = UpdateArgs::new()
791 .with_set(set)
792 .with_returning(vec!["id".to_string(), "name".to_string()]);
793 let plan = build_update_plan(&args, &table);
794
795 match plan {
796 MutatePlan::Update { returning, .. } => {
797 assert_eq!(returning.len(), 2);
798 }
799 _ => panic!("Expected Update plan"),
800 }
801 }
802
803 #[test]
804 fn test_build_delete_plan_basic() {
805 let table = create_test_table();
806 let filter = TableFilter::new().with_field(
807 "id",
808 FieldFilter::int(IntFilterInput {
809 eq: Some(1),
810 ..Default::default()
811 }),
812 );
813
814 let args = DeleteArgs::new().with_filter(filter);
815 let plan = build_delete_plan(&args, &table);
816
817 match plan {
818 MutatePlan::Delete {
819 target,
820 where_clauses,
821 returning,
822 } => {
823 assert_eq!(target.name, "users");
824 assert!(!where_clauses.is_empty());
825 assert_eq!(returning, vec!["id".to_string()]);
826 }
827 _ => panic!("Expected Delete plan"),
828 }
829 }
830
831 #[test]
832 fn test_build_delete_plan_with_returning() {
833 let table = create_test_table();
834 let args = DeleteArgs::new().with_returning(vec![
835 "id".to_string(),
836 "name".to_string(),
837 "email".to_string(),
838 ]);
839 let plan = build_delete_plan(&args, &table);
840
841 match plan {
842 MutatePlan::Delete { returning, .. } => {
843 assert_eq!(returning.len(), 3);
844 }
845 _ => panic!("Expected Delete plan"),
846 }
847 }
848
849 #[test]
850 fn test_build_delete_plan_no_filter() {
851 let table = create_test_table();
852 let args = DeleteArgs::new();
853 let plan = build_delete_plan(&args, &table);
854
855 match plan {
856 MutatePlan::Delete { where_clauses, .. } => {
857 assert!(where_clauses.is_empty());
858 }
859 _ => panic!("Expected Delete plan"),
860 }
861 }
862}