1mod parser;
48
49pub use parser::ParseError;
50
51#[derive(Debug, Clone, PartialEq)]
53pub enum QueryValue {
54 String(String),
56 Int(i64),
58 Float(f64),
60 Bool(bool),
62 Date(String),
64 Null,
66}
67
68impl QueryValue {
69 pub fn string(s: impl Into<String>) -> Self {
71 Self::String(s.into())
72 }
73
74 pub fn int(n: i64) -> Self {
76 Self::Int(n)
77 }
78
79 pub fn float(n: f64) -> Self {
81 Self::Float(n)
82 }
83
84 pub fn bool(b: bool) -> Self {
86 Self::Bool(b)
87 }
88
89 pub fn contains(&self, needle: &str) -> bool {
91 match self {
92 Self::String(s) => {
93 let needle_lower = needle.to_lowercase();
95 s.to_lowercase().contains(&needle_lower)
96 }
97 Self::Int(n) => n.to_string().contains(needle),
98 Self::Float(n) => n.to_string().contains(needle),
99 Self::Bool(b) => b.to_string() == needle.to_lowercase(),
100 Self::Date(d) => d.contains(needle),
101 Self::Null => false,
102 }
103 }
104
105 pub fn equals_str(&self, other: &str) -> bool {
107 match self {
108 Self::String(s) => {
109 let other_lower = other.to_lowercase();
111 s.to_lowercase() == other_lower
112 }
113 Self::Int(n) => other.parse::<i64>().map(|o| *n == o).unwrap_or(false),
114 Self::Float(n) => other
115 .parse::<f64>()
116 .map(|o| (*n - o).abs() < f64::EPSILON)
117 .unwrap_or(false),
118 Self::Bool(b) => {
119 let other_lower = other.to_lowercase();
120 (*b && (other_lower == "true" || other_lower == "yes" || other_lower == "1"))
121 || (!*b
122 && (other_lower == "false" || other_lower == "no" || other_lower == "0"))
123 }
124 Self::Date(d) => d == other,
125 Self::Null => other.to_lowercase() == "null" || other.is_empty(),
126 }
127 }
128
129 pub fn compare(&self, other: &str) -> Option<std::cmp::Ordering> {
131 match self {
132 Self::Int(n) => other.parse::<i64>().ok().map(|o| n.cmp(&o)),
133 Self::Float(n) => other.parse::<f64>().ok().and_then(|o| n.partial_cmp(&o)),
134 Self::String(s) => Some(s.cmp(&other.to_string())),
135 Self::Date(d) => Some(d.cmp(&other.to_string())),
136 _ => None,
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum Operator {
144 Eq,
146 Ne,
148 Gt,
150 Lt,
152 Ge,
154 Le,
156 Contains,
158}
159
160#[derive(Debug, Clone)]
162pub enum Filter {
163 Text(String),
165 Field {
167 name: String,
169 op: Operator,
171 value: String,
173 },
174 And(Box<Filter>, Box<Filter>),
176 Or(Box<Filter>, Box<Filter>),
178 Not(Box<Filter>),
180}
181
182impl Filter {
183 pub fn text(s: impl Into<String>) -> Self {
185 Self::Text(s.into())
186 }
187
188 pub fn eq(field: impl Into<String>, value: impl Into<String>) -> Self {
190 Self::Field {
191 name: field.into(),
192 op: Operator::Eq,
193 value: value.into(),
194 }
195 }
196
197 pub fn ne(field: impl Into<String>, value: impl Into<String>) -> Self {
199 Self::Field {
200 name: field.into(),
201 op: Operator::Ne,
202 value: value.into(),
203 }
204 }
205
206 pub fn contains(field: impl Into<String>, value: impl Into<String>) -> Self {
208 Self::Field {
209 name: field.into(),
210 op: Operator::Contains,
211 value: value.into(),
212 }
213 }
214
215 pub fn gt(field: impl Into<String>, value: impl Into<String>) -> Self {
217 Self::Field {
218 name: field.into(),
219 op: Operator::Gt,
220 value: value.into(),
221 }
222 }
223
224 pub fn lt(field: impl Into<String>, value: impl Into<String>) -> Self {
226 Self::Field {
227 name: field.into(),
228 op: Operator::Lt,
229 value: value.into(),
230 }
231 }
232
233 pub fn and(self, other: Filter) -> Self {
235 Self::And(Box::new(self), Box::new(other))
236 }
237
238 pub fn or(self, other: Filter) -> Self {
240 Self::Or(Box::new(self), Box::new(other))
241 }
242
243 pub fn negate(self) -> Self {
245 Self::Not(Box::new(self))
246 }
247
248 pub fn matches<T: Queryable>(&self, item: &T) -> bool {
250 match self {
251 Self::Text(text) => {
252 let full_text = item.full_text();
253 let full_text_lower = full_text.to_lowercase();
255 text.split_whitespace()
256 .all(|word| full_text_lower.contains(&word.to_lowercase()))
257 }
258 Self::Field { name, op, value } => {
259 if let Some(field_value) = item.field_value(name) {
260 match op {
261 Operator::Eq => field_value.equals_str(value),
262 Operator::Ne => !field_value.equals_str(value),
263 Operator::Contains => field_value.contains(value),
264 Operator::Gt => {
265 field_value.compare(value) == Some(std::cmp::Ordering::Greater)
266 }
267 Operator::Lt => {
268 field_value.compare(value) == Some(std::cmp::Ordering::Less)
269 }
270 Operator::Ge => matches!(
271 field_value.compare(value),
272 Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
273 ),
274 Operator::Le => matches!(
275 field_value.compare(value),
276 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
277 ),
278 }
279 } else {
280 false
281 }
282 }
283 Self::And(a, b) => a.matches(item) && b.matches(item),
284 Self::Or(a, b) => a.matches(item) || b.matches(item),
285 Self::Not(f) => !f.matches(item),
286 }
287 }
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
292pub enum SortDirection {
293 #[default]
295 Ascending,
296 Descending,
298}
299
300#[derive(Debug, Clone)]
302pub struct SortBy {
303 pub field: String,
305 pub direction: SortDirection,
307}
308
309impl SortBy {
310 pub fn asc(field: impl Into<String>) -> Self {
312 Self {
313 field: field.into(),
314 direction: SortDirection::Ascending,
315 }
316 }
317
318 pub fn desc(field: impl Into<String>) -> Self {
320 Self {
321 field: field.into(),
322 direction: SortDirection::Descending,
323 }
324 }
325}
326
327#[derive(Debug, Clone, Default)]
329pub struct Query {
330 pub filters: Vec<Filter>,
332 pub sort: Option<SortBy>,
334 pub limit: Option<usize>,
336 pub offset: Option<usize>,
338}
339
340impl Query {
341 pub fn new() -> Self {
343 Self::default()
344 }
345
346 pub fn parse(input: &str) -> Result<Self, ParseError> {
355 parser::parse(input)
356 }
357
358 pub fn filter(mut self, filter: Filter) -> Self {
360 self.filters.push(filter);
361 self
362 }
363
364 pub fn text(self, text: impl Into<String>) -> Self {
366 self.filter(Filter::text(text))
367 }
368
369 pub fn field_eq(self, field: impl Into<String>, value: impl Into<String>) -> Self {
371 self.filter(Filter::eq(field, value))
372 }
373
374 pub fn field_contains(self, field: impl Into<String>, value: impl Into<String>) -> Self {
376 self.filter(Filter::contains(field, value))
377 }
378
379 pub fn sort_by(mut self, sort: SortBy) -> Self {
381 self.sort = Some(sort);
382 self
383 }
384
385 pub fn sort_asc(self, field: impl Into<String>) -> Self {
387 self.sort_by(SortBy::asc(field))
388 }
389
390 pub fn sort_desc(self, field: impl Into<String>) -> Self {
392 self.sort_by(SortBy::desc(field))
393 }
394
395 pub fn limit(mut self, limit: usize) -> Self {
397 self.limit = Some(limit);
398 self
399 }
400
401 pub fn offset(mut self, offset: usize) -> Self {
403 self.offset = Some(offset);
404 self
405 }
406
407 pub fn matches<T: Queryable>(&self, item: &T) -> bool {
409 if self.filters.is_empty() {
410 return true;
411 }
412 self.filters.iter().all(|f| f.matches(item))
413 }
414
415 pub fn filter_items<'a, T: Queryable>(&self, items: &'a [T]) -> Vec<&'a T> {
417 let mut result: Vec<_> = items.iter().filter(|item| self.matches(*item)).collect();
418
419 if let Some(ref sort) = self.sort {
421 result.sort_by(|a, b| {
422 let a_val = a.field_value(&sort.field);
423 let b_val = b.field_value(&sort.field);
424
425 let ordering = match (&a_val, &b_val) {
426 (Some(QueryValue::String(a)), Some(QueryValue::String(b))) => a.cmp(b),
427 (Some(QueryValue::Int(a)), Some(QueryValue::Int(b))) => a.cmp(b),
428 (Some(QueryValue::Float(a)), Some(QueryValue::Float(b))) => {
429 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
430 }
431 (Some(QueryValue::Date(a)), Some(QueryValue::Date(b))) => a.cmp(b),
432 (Some(_), None) => std::cmp::Ordering::Less,
433 (None, Some(_)) => std::cmp::Ordering::Greater,
434 _ => std::cmp::Ordering::Equal,
435 };
436
437 match sort.direction {
438 SortDirection::Ascending => ordering,
439 SortDirection::Descending => ordering.reverse(),
440 }
441 });
442 }
443
444 if let Some(offset) = self.offset {
446 result = result.into_iter().skip(offset).collect();
447 }
448
449 if let Some(limit) = self.limit {
451 result.truncate(limit);
452 }
453
454 result
455 }
456
457 pub fn is_empty(&self) -> bool {
459 self.filters.is_empty() && self.sort.is_none() && self.limit.is_none()
460 }
461}
462
463pub trait Queryable {
465 fn field_value(&self, field: &str) -> Option<QueryValue>;
467
468 fn full_text(&self) -> String;
470}
471
472#[macro_export]
474macro_rules! impl_queryable {
475 ($type:ty, full_text: $full_text:expr, fields: { $($field:literal => $accessor:expr),* $(,)? }) => {
476 impl $crate::query::Queryable for $type {
477 fn field_value(&self, field: &str) -> Option<$crate::query::QueryValue> {
478 match field {
479 $($field => Some($accessor(self)),)*
480 _ => None,
481 }
482 }
483
484 fn full_text(&self) -> String {
485 $full_text(self)
486 }
487 }
488 };
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 struct TestItem {
496 name: String,
497 age: i64,
498 active: bool,
499 }
500
501 impl Queryable for TestItem {
502 fn field_value(&self, field: &str) -> Option<QueryValue> {
503 match field {
504 "name" => Some(QueryValue::String(self.name.clone())),
505 "age" => Some(QueryValue::Int(self.age)),
506 "active" => Some(QueryValue::Bool(self.active)),
507 _ => None,
508 }
509 }
510
511 fn full_text(&self) -> String {
512 self.name.clone()
513 }
514 }
515
516 #[test]
517 fn test_text_filter() {
518 let item = TestItem {
519 name: "John Doe".into(),
520 age: 30,
521 active: true,
522 };
523
524 assert!(Filter::text("john").matches(&item));
525 assert!(Filter::text("doe").matches(&item));
526 assert!(Filter::text("john doe").matches(&item));
527 assert!(!Filter::text("jane").matches(&item));
528 }
529
530 #[test]
531 fn test_field_eq() {
532 let item = TestItem {
533 name: "John".into(),
534 age: 30,
535 active: true,
536 };
537
538 assert!(Filter::eq("name", "john").matches(&item));
539 assert!(Filter::eq("age", "30").matches(&item));
540 assert!(Filter::eq("active", "true").matches(&item));
541 assert!(!Filter::eq("name", "jane").matches(&item));
542 }
543
544 #[test]
545 fn test_field_ne() {
546 let item = TestItem {
547 name: "John".into(),
548 age: 30,
549 active: true,
550 };
551
552 assert!(Filter::ne("name", "jane").matches(&item));
553 assert!(!Filter::ne("name", "john").matches(&item));
554 }
555
556 #[test]
557 fn test_field_contains() {
558 let item = TestItem {
559 name: "John Doe".into(),
560 age: 30,
561 active: true,
562 };
563
564 assert!(Filter::contains("name", "oh").matches(&item));
565 assert!(!Filter::contains("name", "xyz").matches(&item));
566 }
567
568 #[test]
569 fn test_field_comparison() {
570 let item = TestItem {
571 name: "John".into(),
572 age: 30,
573 active: true,
574 };
575
576 assert!(Filter::gt("age", "20").matches(&item));
577 assert!(!Filter::gt("age", "40").matches(&item));
578 assert!(Filter::lt("age", "40").matches(&item));
579 assert!(!Filter::lt("age", "20").matches(&item));
580 }
581
582 #[test]
583 fn test_and_or() {
584 let item = TestItem {
585 name: "John".into(),
586 age: 30,
587 active: true,
588 };
589
590 let and_filter = Filter::eq("name", "john").and(Filter::eq("active", "true"));
591 assert!(and_filter.matches(&item));
592
593 let or_filter = Filter::eq("name", "jane").or(Filter::eq("name", "john"));
594 assert!(or_filter.matches(&item));
595 }
596
597 #[test]
598 fn test_not() {
599 let item = TestItem {
600 name: "John".into(),
601 age: 30,
602 active: true,
603 };
604
605 assert!(Filter::eq("name", "jane").negate().matches(&item));
606 assert!(!Filter::eq("name", "john").negate().matches(&item));
607 }
608
609 #[test]
610 fn test_query() {
611 let items = vec![
612 TestItem {
613 name: "Alice".into(),
614 age: 25,
615 active: true,
616 },
617 TestItem {
618 name: "Bob".into(),
619 age: 30,
620 active: false,
621 },
622 TestItem {
623 name: "Charlie".into(),
624 age: 35,
625 active: true,
626 },
627 ];
628
629 let query = Query::new().filter(Filter::eq("active", "true"));
630 let result = query.filter_items(&items);
631 assert_eq!(result.len(), 2);
632
633 let query = Query::new().filter(Filter::gt("age", "27"));
634 let result = query.filter_items(&items);
635 assert_eq!(result.len(), 2);
636 }
637
638 #[test]
639 fn test_query_sort() {
640 let items = vec![
641 TestItem {
642 name: "Charlie".into(),
643 age: 35,
644 active: true,
645 },
646 TestItem {
647 name: "Alice".into(),
648 age: 25,
649 active: true,
650 },
651 TestItem {
652 name: "Bob".into(),
653 age: 30,
654 active: false,
655 },
656 ];
657
658 let query = Query::new().sort_asc("age");
659 let result = query.filter_items(&items);
660 assert_eq!(result[0].name, "Alice");
661 assert_eq!(result[1].name, "Bob");
662 assert_eq!(result[2].name, "Charlie");
663
664 let query = Query::new().sort_desc("age");
665 let result = query.filter_items(&items);
666 assert_eq!(result[0].name, "Charlie");
667 }
668
669 #[test]
670 fn test_query_limit_offset() {
671 let items = vec![
672 TestItem {
673 name: "A".into(),
674 age: 1,
675 active: true,
676 },
677 TestItem {
678 name: "B".into(),
679 age: 2,
680 active: true,
681 },
682 TestItem {
683 name: "C".into(),
684 age: 3,
685 active: true,
686 },
687 TestItem {
688 name: "D".into(),
689 age: 4,
690 active: true,
691 },
692 ];
693
694 let query = Query::new().limit(2);
695 let result = query.filter_items(&items);
696 assert_eq!(result.len(), 2);
697
698 let query = Query::new().offset(1).limit(2);
699 let result = query.filter_items(&items);
700 assert_eq!(result.len(), 2);
701 assert_eq!(result[0].name, "B");
702 }
703
704 #[test]
706 fn test_query_value_string() {
707 let val = QueryValue::string("hello");
708 assert_eq!(val, QueryValue::String("hello".to_string()));
709 }
710
711 #[test]
712 fn test_query_value_int() {
713 let val = QueryValue::int(42);
714 assert_eq!(val, QueryValue::Int(42));
715 }
716
717 #[test]
718 fn test_query_value_float() {
719 let val = QueryValue::float(3.14);
720 assert_eq!(val, QueryValue::Float(3.14));
721 }
722
723 #[test]
724 fn test_query_value_bool() {
725 let val = QueryValue::bool(true);
726 assert_eq!(val, QueryValue::Bool(true));
727 }
728
729 #[test]
730 fn test_query_value_contains_string() {
731 let val = QueryValue::string("Hello World");
732 assert!(val.contains("hello"));
733 assert!(val.contains("WORLD"));
734 assert!(!val.contains("xyz"));
735 }
736
737 #[test]
738 fn test_query_value_contains_int() {
739 let val = QueryValue::int(12345);
740 assert!(val.contains("123"));
741 assert!(!val.contains("999"));
742 }
743
744 #[test]
745 fn test_query_value_contains_float() {
746 let val = QueryValue::float(3.14);
747 assert!(val.contains("3.14"));
748 assert!(val.contains("3"));
749 assert!(!val.contains("999"));
750 }
751
752 #[test]
753 fn test_query_value_contains_bool() {
754 let val = QueryValue::bool(true);
755 assert!(val.contains("true"));
756 assert!(val.contains("TRUE"));
757 assert!(!val.contains("false"));
758
759 let val = QueryValue::bool(false);
760 assert!(val.contains("false"));
761 assert!(!val.contains("true"));
762 }
763
764 #[test]
765 fn test_query_value_contains_null() {
766 let val = QueryValue::Null;
767 assert!(!val.contains("anything"));
768 }
769
770 #[test]
771 fn test_query_value_equals_str_string() {
772 let val = QueryValue::string("Hello");
773 assert!(val.equals_str("hello"));
774 assert!(val.equals_str("HELLO"));
775 assert!(!val.equals_str("world"));
776 }
777
778 #[test]
779 fn test_query_value_equals_str_int() {
780 let val = QueryValue::int(42);
781 assert!(val.equals_str("42"));
782 assert!(!val.equals_str("999"));
783 }
784
785 #[test]
786 fn test_query_value_equals_str_bool() {
787 let val = QueryValue::bool(true);
788 assert!(val.equals_str("true"));
789 assert!(!val.equals_str("false"));
790 }
791
792 #[test]
793 fn test_query_value_equals_str_null() {
794 let val = QueryValue::Null;
795 assert!(val.equals_str("null"));
796 assert!(val.equals_str(""));
797 assert!(!val.equals_str("value"));
798 }
799
800 #[test]
801 fn test_query_value_compare_int() {
802 let val = QueryValue::int(50);
803 assert_eq!(val.compare("30"), Some(std::cmp::Ordering::Greater));
804 assert_eq!(val.compare("50"), Some(std::cmp::Ordering::Equal));
805 assert_eq!(val.compare("70"), Some(std::cmp::Ordering::Less));
806 }
807
808 #[test]
809 fn test_query_value_compare_float() {
810 let val = QueryValue::float(3.5);
811 assert_eq!(val.compare("2.5"), Some(std::cmp::Ordering::Greater));
812 assert_eq!(val.compare("3.5"), Some(std::cmp::Ordering::Equal));
813 assert_eq!(val.compare("4.5"), Some(std::cmp::Ordering::Less));
814 }
815}