1use serde_json::Value;
2use tokio_stream::Stream;
3
4#[allow(
9 clippy::missing_docs_in_private_items,
10 reason = "fields are documented with ///"
11)]
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Query {
14 pub filters: Vec<Filter>,
16 pub sort: Option<(String, SortOrder)>,
18 pub limit: Option<usize>,
20 pub offset: Option<usize>,
22 pub projection: Option<Vec<String>>,
24}
25
26pub struct QueryResult {
28 pub documents: std::pin::Pin<Box<dyn Stream<Item = crate::Result<crate::Document>> + Send>>,
30 pub total_count: Option<usize>,
32 pub execution_time: std::time::Duration,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum SortOrder {
39 Ascending,
41 Descending,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum Filter {
48 Equals(String, Value),
50 GreaterThan(String, Value),
52 LessThan(String, Value),
54 GreaterOrEqual(String, Value),
56 LessOrEqual(String, Value),
58 Contains(String, String),
60 StartsWith(String, String),
62 EndsWith(String, String),
64 In(String, Vec<Value>),
66 Exists(String, bool),
68 And(Box<Self>, Box<Self>),
70 Or(Box<Self>, Box<Self>),
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum Operator {
77 Equals,
79 GreaterThan,
81 LessThan,
83 GreaterOrEqual,
85 LessOrEqual,
87 Contains,
89 StartsWith,
91 EndsWith,
93 In,
95 Exists,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct QueryBuilder {
102 filters: Vec<Filter>,
104 sort: Option<(String, SortOrder)>,
106 limit: Option<usize>,
108 offset: Option<usize>,
110 projection: Option<Vec<String>>,
112}
113
114impl Default for QueryBuilder {
115 fn default() -> Self { Self::new() }
116}
117
118impl QueryBuilder {
119 pub const fn new() -> Self {
121 Self {
122 filters: Vec::new(),
123 sort: None,
124 limit: None,
125 offset: None,
126 projection: None,
127 }
128 }
129
130 pub fn filter(mut self, field: &str, op: Operator, value: Value) -> Self {
153 let filter = match op {
154 Operator::Equals => Filter::Equals(field.to_owned(), value),
155 Operator::GreaterThan => Filter::GreaterThan(field.to_owned(), value),
156 Operator::LessThan => Filter::LessThan(field.to_owned(), value),
157 Operator::GreaterOrEqual => Filter::GreaterOrEqual(field.to_owned(), value),
158 Operator::LessOrEqual => Filter::LessOrEqual(field.to_owned(), value),
159 Operator::Contains => {
160 if let Value::String(s) = value {
161 Filter::Contains(field.to_owned(), s)
162 }
163 else {
164 return self;
166 }
167 },
168 Operator::StartsWith => {
169 if let Value::String(s) = value {
170 Filter::StartsWith(field.to_owned(), s)
171 }
172 else {
173 return self;
174 }
175 },
176 Operator::EndsWith => {
177 if let Value::String(s) = value {
178 Filter::EndsWith(field.to_owned(), s)
179 }
180 else {
181 return self;
182 }
183 },
184 Operator::In => {
185 if let Value::Array(arr) = value {
186 Filter::In(field.to_owned(), arr)
187 }
188 else {
189 return self;
190 }
191 },
192 Operator::Exists => {
193 let exists = match value {
194 Value::Bool(b) => b,
195 Value::Number(n) if n.as_i64() == Some(1) => true,
196 Value::Number(n) if n.as_i64() == Some(0) => false,
197 Value::Null | Value::Number(_) | Value::String(_) | Value::Array(_) | Value::Object(_) => true, };
199 Filter::Exists(field.to_owned(), exists)
200 },
201 };
202 self.filters.push(filter);
203 self
204 }
205
206 pub fn and(mut self, other: Filter) -> Self {
216 if let Some(last) = self.filters.pop() {
217 let combined = Filter::And(Box::new(last), Box::new(other));
218 self.filters.push(combined);
219 }
220 else {
221 self.filters.push(other);
222 }
223 self
224 }
225
226 pub fn or(mut self, other: Filter) -> Self {
236 if let Some(last) = self.filters.pop() {
237 let combined = Filter::Or(Box::new(last), Box::new(other));
238 self.filters.push(combined);
239 }
240 else {
241 self.filters.push(other);
242 }
243 self
244 }
245
246 pub fn sort(mut self, field: &str, order: SortOrder) -> Self {
265 self.sort = Some((field.to_owned(), order));
266 self
267 }
268
269 pub const fn limit(mut self, limit: usize) -> Self {
279 self.limit = Some(limit);
280 self
281 }
282
283 pub const fn offset(mut self, offset: usize) -> Self {
293 self.offset = Some(offset);
294 self
295 }
296
297 pub fn projection(mut self, fields: Vec<&str>) -> Self {
318 self.projection = Some(fields.into_iter().map(|s| s.to_owned()).collect());
319 self
320 }
321
322 pub fn build(self) -> Query {
328 Query {
329 filters: self.filters,
330 sort: self.sort,
331 limit: self.limit,
332 offset: self.offset,
333 projection: self.projection,
334 }
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
340pub enum Aggregation {
341 Count,
343 Sum(String),
345 Avg(String),
347 Min(String),
349 Max(String),
351}
352
353#[cfg(test)]
354mod tests {
355 use serde_json::json;
356
357 use super::*;
358
359 #[test]
360 fn test_query_builder_new() {
361 let qb = QueryBuilder::new();
362 assert!(qb.filters.is_empty());
363 assert!(qb.sort.is_none());
364 assert!(qb.limit.is_none());
365 assert!(qb.offset.is_none());
366 assert!(qb.projection.is_none());
367 }
368
369 #[test]
370 fn test_query_builder_default() {
371 let qb = QueryBuilder::default();
372 assert!(qb.filters.is_empty());
373 assert!(qb.sort.is_none());
374 assert!(qb.limit.is_none());
375 assert!(qb.offset.is_none());
376 assert!(qb.projection.is_none());
377 }
378
379 #[test]
380 fn test_query_builder_filter_equals() {
381 let qb = QueryBuilder::new().filter("name", Operator::Equals, json!("Alice"));
382 assert_eq!(qb.filters.len(), 1);
383 match &qb.filters[0] {
384 Filter::Equals(field, value) => {
385 assert_eq!(field, "name");
386 assert_eq!(value, &json!("Alice"));
387 },
388 _ => panic!("Expected Equals filter"),
389 }
390 }
391
392 #[test]
393 fn test_query_builder_filter_greater_than() {
394 let qb = QueryBuilder::new().filter("age", Operator::GreaterThan, json!(18));
395 assert_eq!(qb.filters.len(), 1);
396 match &qb.filters[0] {
397 Filter::GreaterThan(field, value) => {
398 assert_eq!(field, "age");
399 assert_eq!(value, &json!(18));
400 },
401 _ => panic!("Expected GreaterThan filter"),
402 }
403 }
404
405 #[test]
406 fn test_query_builder_filter_less_than() {
407 let qb = QueryBuilder::new().filter("age", Operator::LessThan, json!(65));
408 assert_eq!(qb.filters.len(), 1);
409 match &qb.filters[0] {
410 Filter::LessThan(field, value) => {
411 assert_eq!(field, "age");
412 assert_eq!(value, &json!(65));
413 },
414 _ => panic!("Expected LessThan filter"),
415 }
416 }
417
418 #[test]
419 fn test_query_builder_filter_greater_or_equal() {
420 let qb = QueryBuilder::new().filter("age", Operator::GreaterOrEqual, json!(18));
421 assert_eq!(qb.filters.len(), 1);
422 match &qb.filters[0] {
423 Filter::GreaterOrEqual(field, value) => {
424 assert_eq!(field, "age");
425 assert_eq!(value, &json!(18));
426 },
427 _ => panic!("Expected GreaterOrEqual filter"),
428 }
429 }
430
431 #[test]
432 fn test_query_builder_filter_less_or_equal() {
433 let qb = QueryBuilder::new().filter("age", Operator::LessOrEqual, json!(65));
434 assert_eq!(qb.filters.len(), 1);
435 match &qb.filters[0] {
436 Filter::LessOrEqual(field, value) => {
437 assert_eq!(field, "age");
438 assert_eq!(value, &json!(65));
439 },
440 _ => panic!("Expected LessOrEqual filter"),
441 }
442 }
443
444 #[test]
445 fn test_query_builder_filter_contains_valid() {
446 let qb = QueryBuilder::new().filter("name", Operator::Contains, json!("Ali"));
447 assert_eq!(qb.filters.len(), 1);
448 match &qb.filters[0] {
449 Filter::Contains(field, value) => {
450 assert_eq!(field, "name");
451 assert_eq!(value, "Ali");
452 },
453 _ => panic!("Expected Contains filter"),
454 }
455 }
456
457 #[test]
458 fn test_query_builder_filter_contains_invalid() {
459 let qb = QueryBuilder::new().filter("name", Operator::Contains, json!(123));
460 assert!(qb.filters.is_empty());
461 }
462
463 #[test]
464 fn test_query_builder_filter_starts_with_valid() {
465 let qb = QueryBuilder::new().filter("name", Operator::StartsWith, json!("Ali"));
466 assert_eq!(qb.filters.len(), 1);
467 match &qb.filters[0] {
468 Filter::StartsWith(field, value) => {
469 assert_eq!(field, "name");
470 assert_eq!(value, "Ali");
471 },
472 _ => panic!("Expected StartsWith filter"),
473 }
474 }
475
476 #[test]
477 fn test_query_builder_filter_starts_with_invalid() {
478 let qb = QueryBuilder::new().filter("name", Operator::StartsWith, json!(123));
479 assert!(qb.filters.is_empty());
480 }
481
482 #[test]
483 fn test_query_builder_filter_ends_with_valid() {
484 let qb = QueryBuilder::new().filter("name", Operator::EndsWith, json!("ice"));
485 assert_eq!(qb.filters.len(), 1);
486 match &qb.filters[0] {
487 Filter::EndsWith(field, value) => {
488 assert_eq!(field, "name");
489 assert_eq!(value, "ice");
490 },
491 _ => panic!("Expected EndsWith filter"),
492 }
493 }
494
495 #[test]
496 fn test_query_builder_filter_ends_with_invalid() {
497 let qb = QueryBuilder::new().filter("name", Operator::EndsWith, json!(123));
498 assert!(qb.filters.is_empty());
499 }
500
501 #[test]
502 fn test_query_builder_filter_in_valid() {
503 let qb = QueryBuilder::new().filter("status", Operator::In, json!(["active", "inactive"]));
504 assert_eq!(qb.filters.len(), 1);
505 match &qb.filters[0] {
506 Filter::In(field, values) => {
507 assert_eq!(field, "status");
508 assert_eq!(values, &vec![json!("active"), json!("inactive")]);
509 },
510 _ => panic!("Expected In filter"),
511 }
512 }
513
514 #[test]
515 fn test_query_builder_filter_in_invalid() {
516 let qb = QueryBuilder::new().filter("status", Operator::In, json!("active"));
517 assert!(qb.filters.is_empty());
518 }
519
520 #[test]
521 fn test_query_builder_filter_exists_bool() {
522 let qb = QueryBuilder::new().filter("name", Operator::Exists, json!(true));
523 assert_eq!(qb.filters.len(), 1);
524 match &qb.filters[0] {
525 Filter::Exists(field, exists) => {
526 assert_eq!(field, "name");
527 assert!(*exists);
528 },
529 _ => panic!("Expected Exists filter"),
530 }
531 }
532
533 #[test]
534 fn test_query_builder_filter_exists_number() {
535 let qb = QueryBuilder::new().filter("name", Operator::Exists, json!(1));
536 assert_eq!(qb.filters.len(), 1);
537 match &qb.filters[0] {
538 Filter::Exists(field, exists) => {
539 assert_eq!(field, "name");
540 assert!(*exists);
541 },
542 _ => panic!("Expected Exists filter"),
543 }
544 }
545
546 #[test]
547 fn test_query_builder_filter_exists_false() {
548 let qb = QueryBuilder::new().filter("name", Operator::Exists, json!(false));
549 assert_eq!(qb.filters.len(), 1);
550 match &qb.filters[0] {
551 Filter::Exists(field, exists) => {
552 assert_eq!(field, "name");
553 assert!(!*exists);
554 },
555 _ => panic!("Expected Exists filter"),
556 }
557 }
558
559 #[test]
560 fn test_query_builder_filter_exists_string() {
561 let qb = QueryBuilder::new().filter("name", Operator::Exists, json!("yes"));
562 assert_eq!(qb.filters.len(), 1);
563 match &qb.filters[0] {
564 Filter::Exists(field, exists) => {
565 assert_eq!(field, "name");
566 assert!(*exists); },
568 _ => panic!("Expected Exists filter"),
569 }
570 }
571
572 #[test]
573 fn test_query_builder_sort() {
574 let qb = QueryBuilder::new().sort("age", SortOrder::Descending);
575 assert_eq!(qb.sort, Some(("age".to_string(), SortOrder::Descending)));
576 }
577
578 #[test]
579 fn test_query_builder_limit() {
580 let qb = QueryBuilder::new().limit(10);
581 assert_eq!(qb.limit, Some(10));
582 }
583
584 #[test]
585 fn test_query_builder_offset() {
586 let qb = QueryBuilder::new().offset(5);
587 assert_eq!(qb.offset, Some(5));
588 }
589
590 #[test]
591 fn test_query_builder_projection() {
592 let qb = QueryBuilder::new().projection(vec!["name", "age"]);
593 assert_eq!(
594 qb.projection,
595 Some(vec!["name".to_string(), "age".to_string()])
596 );
597 }
598
599 #[test]
600 fn test_query_builder_and() {
601 let qb = QueryBuilder::new()
602 .filter("age", Operator::GreaterThan, json!(18))
603 .and(Filter::Equals("status".to_string(), json!("active")));
604 assert_eq!(qb.filters.len(), 1);
605 match &qb.filters[0] {
606 Filter::And(left, right) => {
607 match **left {
608 Filter::GreaterThan(ref field, _) => assert_eq!(field, "age"),
609 _ => panic!("Expected GreaterThan in left"),
610 }
611 match **right {
612 Filter::Equals(ref field, _) => assert_eq!(field, "status"),
613 _ => panic!("Expected Equals in right"),
614 }
615 },
616 _ => panic!("Expected And filter"),
617 }
618 }
619
620 #[test]
621 fn test_query_builder_or() {
622 let qb = QueryBuilder::new()
623 .filter("age", Operator::GreaterThan, json!(18))
624 .or(Filter::Equals("status".to_string(), json!("active")));
625 assert_eq!(qb.filters.len(), 1);
626 match &qb.filters[0] {
627 Filter::Or(left, right) => {
628 match **left {
629 Filter::GreaterThan(ref field, _) => assert_eq!(field, "age"),
630 _ => panic!("Expected GreaterThan in left"),
631 }
632 match **right {
633 Filter::Equals(ref field, _) => assert_eq!(field, "status"),
634 _ => panic!("Expected Equals in right"),
635 }
636 },
637 _ => panic!("Expected Or filter"),
638 }
639 }
640
641 #[test]
642 fn test_query_builder_build() {
643 let query = QueryBuilder::new()
644 .filter("age", Operator::GreaterThan, json!(18))
645 .sort("name", SortOrder::Ascending)
646 .limit(10)
647 .offset(5)
648 .projection(vec!["name", "age"])
649 .build();
650
651 assert_eq!(query.filters.len(), 1);
652 assert_eq!(query.sort, Some(("name".to_string(), SortOrder::Ascending)));
653 assert_eq!(query.limit, Some(10));
654 assert_eq!(query.offset, Some(5));
655 assert_eq!(
656 query.projection,
657 Some(vec!["name".to_string(), "age".to_string()])
658 );
659 }
660
661 #[test]
662 fn test_query_builder_filter_exists_number_zero() {
663 let qb = QueryBuilder::new().filter("name", Operator::Exists, json!(0));
664 assert_eq!(qb.filters.len(), 1);
665 match &qb.filters[0] {
666 Filter::Exists(field, exists) => {
667 assert_eq!(field, "name");
668 assert!(!*exists);
669 },
670 _ => panic!("Expected Exists filter"),
671 }
672 }
673
674 #[test]
675 fn test_query_builder_and_empty() {
676 let qb = QueryBuilder::new().and(Filter::Equals("status".to_string(), json!("active")));
677 assert_eq!(qb.filters.len(), 1);
678 match &qb.filters[0] {
679 Filter::Equals(field, _) => assert_eq!(field, "status"),
680 _ => panic!("Expected Equals filter"),
681 }
682 }
683
684 #[test]
685 fn test_query_builder_or_empty() {
686 let qb = QueryBuilder::new().or(Filter::Equals("status".to_string(), json!("active")));
687 assert_eq!(qb.filters.len(), 1);
688 match &qb.filters[0] {
689 Filter::Equals(field, _) => assert_eq!(field, "status"),
690 _ => panic!("Expected Equals filter"),
691 }
692 }
693}