1use crate::input::filter::{
6 combine_with_and, filters_to_logic_tree, BooleanFilterInput, FloatFilterInput, IntFilterInput,
7 StringFilterInput, UuidFilterInput,
8};
9use crate::input::order::{OrderByField, PaginationInput};
10use postrust_core::api_request::{Filter, LogicTree, Range};
11use postrust_core::plan::{CoercibleLogicTree, CoercibleOrderTerm, CoercibleSelectField, ReadPlan};
12use postrust_core::schema_cache::Table;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16#[derive(Debug, Clone, Default)]
18pub struct QueryArgs {
19 pub select: Vec<String>,
21 pub filter: Option<TableFilter>,
23 pub order_by: Vec<OrderByField>,
25 pub limit: Option<i64>,
27 pub offset: Option<i64>,
29 pub relations: HashMap<String, QueryArgs>,
31}
32
33impl QueryArgs {
34 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn with_select(mut self, fields: Vec<String>) -> Self {
41 self.select = fields;
42 self
43 }
44
45 pub fn with_filter(mut self, filter: TableFilter) -> Self {
47 self.filter = Some(filter);
48 self
49 }
50
51 pub fn with_order_by(mut self, order_by: Vec<OrderByField>) -> Self {
53 self.order_by = order_by;
54 self
55 }
56
57 pub fn with_pagination(mut self, pagination: PaginationInput) -> Self {
59 self.limit = pagination.limit;
60 self.offset = pagination.offset;
61 self
62 }
63
64 pub fn with_limit(mut self, limit: i64) -> Self {
66 self.limit = Some(limit);
67 self
68 }
69
70 pub fn with_offset(mut self, offset: i64) -> Self {
72 self.offset = Some(offset);
73 self
74 }
75
76 pub fn with_relation(mut self, name: String, args: QueryArgs) -> Self {
78 self.relations.insert(name, args);
79 self
80 }
81
82 pub fn to_range(&self) -> Range {
84 Range {
85 offset: self.offset.unwrap_or(0),
86 limit: self.limit,
87 offset_explicit: self.offset.is_some(),
91 }
92 }
93
94 pub fn has_select(&self) -> bool {
96 !self.select.is_empty()
97 }
98
99 pub fn has_filter(&self) -> bool {
101 self.filter.is_some()
102 }
103
104 pub fn has_order_by(&self) -> bool {
106 !self.order_by.is_empty()
107 }
108
109 pub fn has_pagination(&self) -> bool {
111 self.limit.is_some() || self.offset.is_some()
112 }
113}
114
115#[derive(Debug, Clone, Default, Serialize, Deserialize)]
117pub struct TableFilter {
118 #[serde(flatten)]
120 pub fields: HashMap<String, FieldFilter>,
121 #[serde(rename = "_and")]
123 pub and: Option<Vec<TableFilter>>,
124 #[serde(rename = "_or")]
126 pub or: Option<Vec<TableFilter>>,
127 #[serde(rename = "_not")]
129 pub not: Option<Box<TableFilter>>,
130}
131
132impl TableFilter {
133 pub fn new() -> Self {
135 Self::default()
136 }
137
138 pub fn with_field(mut self, name: impl Into<String>, filter: FieldFilter) -> Self {
140 self.fields.insert(name.into(), filter);
141 self
142 }
143
144 pub fn with_and(mut self, filters: Vec<TableFilter>) -> Self {
146 self.and = Some(filters);
147 self
148 }
149
150 pub fn with_or(mut self, filters: Vec<TableFilter>) -> Self {
152 self.or = Some(filters);
153 self
154 }
155
156 pub fn with_not(mut self, filter: TableFilter) -> Self {
158 self.not = Some(Box::new(filter));
159 self
160 }
161
162 pub fn is_empty(&self) -> bool {
164 self.fields.is_empty() && self.and.is_none() && self.or.is_none() && self.not.is_none()
165 }
166
167 pub fn to_logic_tree(&self) -> Option<LogicTree> {
169 let mut trees = Vec::new();
170
171 for (field_name, field_filter) in &self.fields {
173 let filters = field_filter.to_filters(field_name);
174 if let Some(tree) = filters_to_logic_tree(filters) {
175 trees.push(tree);
176 }
177 }
178
179 if let Some(and_filters) = &self.and {
181 let and_trees: Vec<LogicTree> = and_filters
182 .iter()
183 .filter_map(|f| f.to_logic_tree())
184 .collect();
185 if let Some(tree) = combine_with_and(and_trees) {
186 trees.push(tree);
187 }
188 }
189
190 if let Some(or_filters) = &self.or {
192 let or_trees: Vec<LogicTree> = or_filters
193 .iter()
194 .filter_map(|f| f.to_logic_tree())
195 .collect();
196 if !or_trees.is_empty() {
197 trees.push(LogicTree::or(or_trees));
198 }
199 }
200
201 if let Some(not_filter) = &self.not {
203 if let Some(tree) = not_filter.to_logic_tree() {
204 let negated = match tree {
205 LogicTree::Expr { op, children, .. } => LogicTree::Expr {
206 negated: true,
207 op,
208 children,
209 },
210 LogicTree::Stmt(filter) => {
211 let negated_expr = postrust_core::api_request::OpExpr {
212 negated: !filter.op_expr.negated,
213 operation: filter.op_expr.operation,
214 };
215 LogicTree::Stmt(Filter::new(filter.field, negated_expr))
216 }
217 };
218 trees.push(negated);
219 }
220 }
221
222 combine_with_and(trees)
223 }
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228#[serde(untagged)]
229pub enum FieldFilter {
230 String(StringFilterInput),
232 Int(IntFilterInput),
234 Float(FloatFilterInput),
236 Boolean(BooleanFilterInput),
238 Uuid(UuidFilterInput),
240}
241
242impl FieldFilter {
243 pub fn string(filter: StringFilterInput) -> Self {
245 Self::String(filter)
246 }
247
248 pub fn int(filter: IntFilterInput) -> Self {
250 Self::Int(filter)
251 }
252
253 pub fn float(filter: FloatFilterInput) -> Self {
255 Self::Float(filter)
256 }
257
258 pub fn boolean(filter: BooleanFilterInput) -> Self {
260 Self::Boolean(filter)
261 }
262
263 pub fn uuid(filter: UuidFilterInput) -> Self {
265 Self::Uuid(filter)
266 }
267
268 pub fn to_filters(&self, field_name: &str) -> Vec<Filter> {
270 match self {
271 Self::String(f) => f.to_filters(field_name),
272 Self::Int(f) => f.to_filters(field_name),
273 Self::Float(f) => f.to_filters(field_name),
274 Self::Boolean(f) => f.to_filters(field_name),
275 Self::Uuid(f) => f.to_filters(field_name),
276 }
277 }
278}
279
280pub fn build_select_fields(columns: &[String], table: &Table) -> Vec<CoercibleSelectField> {
282 if columns.is_empty() {
283 return table
285 .columns
286 .iter()
287 .map(|(name, col)| CoercibleSelectField::simple(name, &col.data_type))
288 .collect();
289 }
290
291 columns
292 .iter()
293 .filter_map(|name| {
294 table
295 .columns
296 .get(name)
297 .map(|col| CoercibleSelectField::simple(name, &col.data_type))
298 })
299 .collect()
300}
301
302pub fn build_order_terms(order_by: &[OrderByField], table: &Table) -> Vec<CoercibleOrderTerm> {
304 order_by
305 .iter()
306 .filter_map(|ob| {
307 table.columns.get(&ob.field).map(|col| {
308 let order_term = ob.to_order_term();
309 CoercibleOrderTerm::from_order_term(&order_term, &col.data_type)
310 })
311 })
312 .collect()
313}
314
315pub fn build_where_clauses(filter: &Option<TableFilter>, table: &Table) -> Vec<CoercibleLogicTree> {
317 let Some(filter) = filter else {
318 return vec![];
319 };
320
321 let type_resolver = |name: &str| -> String {
322 table
323 .get_column(name)
324 .map(|c| c.data_type.clone())
325 .unwrap_or_else(|| "text".to_string())
326 };
327
328 filter
329 .to_logic_tree()
330 .map(|tree| vec![CoercibleLogicTree::from_logic_tree(&tree, type_resolver)])
331 .unwrap_or_default()
332}
333
334pub fn build_read_plan(args: &QueryArgs, table: &Table) -> ReadPlan {
336 let select = build_select_fields(&args.select, table);
337 let order = build_order_terms(&args.order_by, table);
338 let where_clauses = build_where_clauses(&args.filter, table);
339
340 ReadPlan {
341 select,
342 from: table.qualified_identifier(),
343 from_alias: None,
344 where_clauses,
345 order,
346 range: args.to_range(),
347 rel_name: table.name.clone(),
348 rel_to_parent: None,
349 rel_join_conds: vec![],
350 rel_join_type: None,
351 rel_select: vec![],
352 depth: 0,
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::input::filter::IntFilterInput;
360 use indexmap::IndexMap;
361 use postrust_core::schema_cache::Column;
362 use pretty_assertions::assert_eq;
363
364 fn create_test_table() -> Table {
365 let mut columns = IndexMap::new();
366 columns.insert(
367 "id".into(),
368 Column {
369 name: "id".into(),
370 description: None,
371 nullable: false,
372 data_type: "integer".into(),
373 nominal_type: "int4".into(),
374 max_len: None,
375 default: Some("nextval('users_id_seq')".into()),
376 enum_values: vec![],
377 is_pk: true,
378 position: 1,
379 always_generated: false,
380 domain_type: None,
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 always_generated: false,
397 domain_type: None,
398 },
399 );
400 columns.insert(
401 "email".into(),
402 Column {
403 name: "email".into(),
404 description: None,
405 nullable: true,
406 data_type: "text".into(),
407 nominal_type: "text".into(),
408 max_len: None,
409 default: None,
410 enum_values: vec![],
411 is_pk: false,
412 position: 3,
413 always_generated: false,
414 domain_type: None,
415 },
416 );
417 columns.insert(
418 "age".into(),
419 Column {
420 name: "age".into(),
421 description: None,
422 nullable: true,
423 data_type: "integer".into(),
424 nominal_type: "int4".into(),
425 max_len: None,
426 default: None,
427 enum_values: vec![],
428 is_pk: false,
429 position: 4,
430 always_generated: false,
431 domain_type: None,
432 },
433 );
434
435 Table {
436 schema: "public".into(),
437 name: "users".into(),
438 description: None,
439 is_view: false,
440 insertable: true,
441 updatable: true,
442 deletable: true,
443 pk_cols: vec!["id".into()],
444 unique_constraints: Vec::new(),
445 columns,
446 computed_columns: Default::default(),
447 is_partitioned: false,
448 }
449 }
450
451 #[test]
456 fn test_query_args_default() {
457 let args = QueryArgs::new();
458 assert!(!args.has_select());
459 assert!(!args.has_filter());
460 assert!(!args.has_order_by());
461 assert!(!args.has_pagination());
462 }
463
464 #[test]
465 fn test_query_args_with_select() {
466 let args = QueryArgs::new().with_select(vec!["id".to_string(), "name".to_string()]);
467 assert!(args.has_select());
468 assert_eq!(args.select.len(), 2);
469 }
470
471 #[test]
472 fn test_query_args_with_filter() {
473 let filter = TableFilter::new().with_field(
474 "name",
475 FieldFilter::string(StringFilterInput {
476 eq: Some("test".to_string()),
477 ..Default::default()
478 }),
479 );
480 let args = QueryArgs::new().with_filter(filter);
481 assert!(args.has_filter());
482 }
483
484 #[test]
485 fn test_query_args_with_order_by() {
486 let args = QueryArgs::new().with_order_by(vec![OrderByField::desc("created_at")]);
487 assert!(args.has_order_by());
488 assert_eq!(args.order_by.len(), 1);
489 }
490
491 #[test]
492 fn test_query_args_with_pagination() {
493 let args = QueryArgs::new().with_limit(10).with_offset(20);
494 assert!(args.has_pagination());
495 assert_eq!(args.limit, Some(10));
496 assert_eq!(args.offset, Some(20));
497 }
498
499 #[test]
500 fn test_query_args_to_range() {
501 let args = QueryArgs::new().with_limit(10).with_offset(5);
502 let range = args.to_range();
503 assert_eq!(range.offset, 5);
504 assert_eq!(range.limit, Some(10));
505 }
506
507 #[test]
508 fn test_query_args_with_relation() {
509 let child_args = QueryArgs::new().with_limit(5);
510 let args = QueryArgs::new().with_relation("orders".to_string(), child_args);
511 assert!(args.relations.contains_key("orders"));
512 }
513
514 #[test]
519 fn test_table_filter_empty() {
520 let filter = TableFilter::new();
521 assert!(filter.is_empty());
522 assert!(filter.to_logic_tree().is_none());
523 }
524
525 #[test]
526 fn test_table_filter_single_field() {
527 let filter = TableFilter::new().with_field(
528 "name",
529 FieldFilter::string(StringFilterInput {
530 eq: Some("Alice".to_string()),
531 ..Default::default()
532 }),
533 );
534 assert!(!filter.is_empty());
535 assert!(filter.to_logic_tree().is_some());
536 }
537
538 #[test]
539 fn test_table_filter_multiple_fields() {
540 let filter = TableFilter::new()
541 .with_field(
542 "name",
543 FieldFilter::string(StringFilterInput {
544 eq: Some("Alice".to_string()),
545 ..Default::default()
546 }),
547 )
548 .with_field(
549 "age",
550 FieldFilter::int(IntFilterInput {
551 gt: Some(18),
552 ..Default::default()
553 }),
554 );
555
556 let tree = filter.to_logic_tree().unwrap();
557 match tree {
558 LogicTree::Expr { children, .. } => {
559 assert_eq!(children.len(), 2);
560 }
561 _ => panic!("Expected Expr for multiple fields"),
562 }
563 }
564
565 #[test]
566 fn test_table_filter_with_and() {
567 let filter1 = TableFilter::new().with_field(
568 "a",
569 FieldFilter::int(IntFilterInput {
570 eq: Some(1),
571 ..Default::default()
572 }),
573 );
574 let filter2 = TableFilter::new().with_field(
575 "b",
576 FieldFilter::int(IntFilterInput {
577 eq: Some(2),
578 ..Default::default()
579 }),
580 );
581
582 let combined = TableFilter::new().with_and(vec![filter1, filter2]);
583 let tree = combined.to_logic_tree().unwrap();
584 assert!(matches!(tree, LogicTree::Expr { .. }));
585 }
586
587 #[test]
588 fn test_table_filter_with_or() {
589 let filter1 = TableFilter::new().with_field(
590 "status",
591 FieldFilter::string(StringFilterInput {
592 eq: Some("active".to_string()),
593 ..Default::default()
594 }),
595 );
596 let filter2 = TableFilter::new().with_field(
597 "status",
598 FieldFilter::string(StringFilterInput {
599 eq: Some("pending".to_string()),
600 ..Default::default()
601 }),
602 );
603
604 let combined = TableFilter::new().with_or(vec![filter1, filter2]);
605 let tree = combined.to_logic_tree().unwrap();
606
607 match tree {
608 LogicTree::Expr {
609 op: postrust_core::api_request::LogicOperator::Or,
610 ..
611 } => {}
612 _ => panic!("Expected OR expression"),
613 }
614 }
615
616 #[test]
617 fn test_table_filter_with_not() {
618 let inner = TableFilter::new().with_field(
619 "deleted",
620 FieldFilter::boolean(BooleanFilterInput {
621 eq: Some(true),
622 ..Default::default()
623 }),
624 );
625
626 let filter = TableFilter::new().with_not(inner);
627 let tree = filter.to_logic_tree().unwrap();
628
629 match tree {
630 LogicTree::Expr { negated: true, .. } | LogicTree::Stmt(_) => {}
631 _ => panic!("Expected negated expression"),
632 }
633 }
634
635 #[test]
640 fn test_field_filter_string() {
641 let filter = FieldFilter::string(StringFilterInput {
642 eq: Some("test".to_string()),
643 ..Default::default()
644 });
645 let filters = filter.to_filters("name");
646 assert_eq!(filters.len(), 1);
647 }
648
649 #[test]
650 fn test_field_filter_int() {
651 let filter = FieldFilter::int(IntFilterInput {
652 gte: Some(18),
653 lte: Some(65),
654 ..Default::default()
655 });
656 let filters = filter.to_filters("age");
657 assert_eq!(filters.len(), 2); }
659
660 #[test]
661 fn test_field_filter_boolean() {
662 let filter = FieldFilter::boolean(BooleanFilterInput {
663 eq: Some(true),
664 ..Default::default()
665 });
666 let filters = filter.to_filters("active");
667 assert_eq!(filters.len(), 1);
668 }
669
670 #[test]
675 fn test_build_select_fields_empty() {
676 let table = create_test_table();
677 let fields = build_select_fields(&[], &table);
678 assert_eq!(fields.len(), 4); }
680
681 #[test]
682 fn test_build_select_fields_specific() {
683 let table = create_test_table();
684 let fields = build_select_fields(&["id".to_string(), "name".to_string()], &table);
685 assert_eq!(fields.len(), 2);
686 }
687
688 #[test]
689 fn test_build_select_fields_invalid_column() {
690 let table = create_test_table();
691 let fields = build_select_fields(&["nonexistent".to_string()], &table);
692 assert_eq!(fields.len(), 0); }
694
695 #[test]
696 fn test_build_order_terms() {
697 let table = create_test_table();
698 let order_by = vec![OrderByField::desc("name"), OrderByField::asc("id")];
699 let terms = build_order_terms(&order_by, &table);
700 assert_eq!(terms.len(), 2);
701 }
702
703 #[test]
704 fn test_build_order_terms_invalid_column() {
705 let table = create_test_table();
706 let order_by = vec![OrderByField::desc("nonexistent")];
707 let terms = build_order_terms(&order_by, &table);
708 assert_eq!(terms.len(), 0); }
710
711 #[test]
712 fn test_build_where_clauses_none() {
713 let table = create_test_table();
714 let clauses = build_where_clauses(&None, &table);
715 assert!(clauses.is_empty());
716 }
717
718 #[test]
719 fn test_build_where_clauses_with_filter() {
720 let table = create_test_table();
721 let filter = TableFilter::new().with_field(
722 "name",
723 FieldFilter::string(StringFilterInput {
724 eq: Some("test".to_string()),
725 ..Default::default()
726 }),
727 );
728 let clauses = build_where_clauses(&Some(filter), &table);
729 assert_eq!(clauses.len(), 1);
730 }
731
732 #[test]
737 fn test_build_read_plan_basic() {
738 let table = create_test_table();
739 let args = QueryArgs::new();
740 let plan = build_read_plan(&args, &table);
741
742 assert_eq!(plan.from.name, "users");
743 assert_eq!(plan.select.len(), 4); assert!(plan.where_clauses.is_empty());
745 assert!(plan.order.is_empty());
746 }
747
748 #[test]
749 fn test_build_read_plan_with_select() {
750 let table = create_test_table();
751 let args = QueryArgs::new().with_select(vec!["id".to_string(), "name".to_string()]);
752 let plan = build_read_plan(&args, &table);
753
754 assert_eq!(plan.select.len(), 2);
755 }
756
757 #[test]
758 fn test_build_read_plan_with_filter() {
759 let table = create_test_table();
760 let filter = TableFilter::new().with_field(
761 "age",
762 FieldFilter::int(IntFilterInput {
763 gte: Some(18),
764 ..Default::default()
765 }),
766 );
767 let args = QueryArgs::new().with_filter(filter);
768 let plan = build_read_plan(&args, &table);
769
770 assert!(!plan.where_clauses.is_empty());
771 }
772
773 #[test]
774 fn test_build_read_plan_with_order() {
775 let table = create_test_table();
776 let args = QueryArgs::new().with_order_by(vec![OrderByField::desc("name")]);
777 let plan = build_read_plan(&args, &table);
778
779 assert_eq!(plan.order.len(), 1);
780 }
781
782 #[test]
783 fn test_build_read_plan_with_pagination() {
784 let table = create_test_table();
785 let args = QueryArgs::new().with_limit(10).with_offset(20);
786 let plan = build_read_plan(&args, &table);
787
788 assert_eq!(plan.range.limit, Some(10));
789 assert_eq!(plan.range.offset, 20);
790 }
791
792 #[test]
793 fn test_build_read_plan_full() {
794 let table = create_test_table();
795 let filter = TableFilter::new()
796 .with_field(
797 "name",
798 FieldFilter::string(StringFilterInput {
799 like: Some("%John%".to_string()),
800 ..Default::default()
801 }),
802 )
803 .with_field(
804 "age",
805 FieldFilter::int(IntFilterInput {
806 gte: Some(21),
807 ..Default::default()
808 }),
809 );
810
811 let args = QueryArgs::new()
812 .with_select(vec![
813 "id".to_string(),
814 "name".to_string(),
815 "email".to_string(),
816 ])
817 .with_filter(filter)
818 .with_order_by(vec![OrderByField::asc("name")])
819 .with_limit(50)
820 .with_offset(0);
821
822 let plan = build_read_plan(&args, &table);
823
824 assert_eq!(plan.select.len(), 3);
825 assert!(!plan.where_clauses.is_empty());
826 assert_eq!(plan.order.len(), 1);
827 assert_eq!(plan.range.limit, Some(50));
828 assert_eq!(plan.range.offset, 0);
829 }
830}