aegis_document/
query.rs

1//! Aegis Document Query
2//!
3//! Query language for document filtering and retrieval.
4//!
5//! @version 0.1.0
6//! @author AutomataNexus Development Team
7
8use crate::types::{Document, Value};
9use serde::{Deserialize, Serialize};
10
11// =============================================================================
12// Query
13// =============================================================================
14
15/// A query for filtering documents.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Query {
18    pub filters: Vec<Filter>,
19    pub sort: Option<Sort>,
20    pub skip: Option<usize>,
21    pub limit: Option<usize>,
22    pub projection: Option<Vec<String>>,
23}
24
25impl Query {
26    pub fn new() -> Self {
27        Self {
28            filters: Vec::new(),
29            sort: None,
30            skip: None,
31            limit: None,
32            projection: None,
33        }
34    }
35
36    /// Check if a document matches this query.
37    pub fn matches(&self, doc: &Document) -> bool {
38        self.filters.iter().all(|f| f.matches(doc))
39    }
40
41    /// Add a filter.
42    pub fn with_filter(mut self, filter: Filter) -> Self {
43        self.filters.push(filter);
44        self
45    }
46
47    /// Add sorting.
48    pub fn with_sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
49        self.sort = Some(Sort {
50            field: field.into(),
51            ascending,
52        });
53        self
54    }
55
56    /// Add skip.
57    pub fn with_skip(mut self, skip: usize) -> Self {
58        self.skip = Some(skip);
59        self
60    }
61
62    /// Add limit.
63    pub fn with_limit(mut self, limit: usize) -> Self {
64        self.limit = Some(limit);
65        self
66    }
67
68    /// Add projection.
69    pub fn with_projection(mut self, fields: Vec<String>) -> Self {
70        self.projection = Some(fields);
71        self
72    }
73}
74
75impl Default for Query {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81// =============================================================================
82// Filter
83// =============================================================================
84
85/// A filter condition.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub enum Filter {
88    Eq { field: String, value: Value },
89    Ne { field: String, value: Value },
90    Gt { field: String, value: Value },
91    Gte { field: String, value: Value },
92    Lt { field: String, value: Value },
93    Lte { field: String, value: Value },
94    In { field: String, values: Vec<Value> },
95    Nin { field: String, values: Vec<Value> },
96    Exists { field: String, exists: bool },
97    Regex { field: String, pattern: String },
98    Contains { field: String, value: String },
99    StartsWith { field: String, value: String },
100    EndsWith { field: String, value: String },
101    And(Vec<Filter>),
102    Or(Vec<Filter>),
103    Not(Box<Filter>),
104}
105
106impl Filter {
107    /// Check if a document matches this filter.
108    pub fn matches(&self, doc: &Document) -> bool {
109        match self {
110            Self::Eq { field, value } => {
111                doc.get(field).map(|v| v == value).unwrap_or(false)
112            }
113            Self::Ne { field, value } => {
114                doc.get(field).map(|v| v != value).unwrap_or(true)
115            }
116            Self::Gt { field, value } => {
117                doc.get(field)
118                    .map(|v| compare_values(v, value) == Some(std::cmp::Ordering::Greater))
119                    .unwrap_or(false)
120            }
121            Self::Gte { field, value } => {
122                doc.get(field)
123                    .map(|v| {
124                        matches!(
125                            compare_values(v, value),
126                            Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
127                        )
128                    })
129                    .unwrap_or(false)
130            }
131            Self::Lt { field, value } => {
132                doc.get(field)
133                    .map(|v| compare_values(v, value) == Some(std::cmp::Ordering::Less))
134                    .unwrap_or(false)
135            }
136            Self::Lte { field, value } => {
137                doc.get(field)
138                    .map(|v| {
139                        matches!(
140                            compare_values(v, value),
141                            Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
142                        )
143                    })
144                    .unwrap_or(false)
145            }
146            Self::In { field, values } => {
147                doc.get(field)
148                    .map(|v| values.contains(v))
149                    .unwrap_or(false)
150            }
151            Self::Nin { field, values } => {
152                doc.get(field)
153                    .map(|v| !values.contains(v))
154                    .unwrap_or(true)
155            }
156            Self::Exists { field, exists } => doc.contains(field) == *exists,
157            Self::Regex { field, pattern } => {
158                if let Some(Value::String(s)) = doc.get(field) {
159                    regex::Regex::new(pattern)
160                        .map(|re| re.is_match(s))
161                        .unwrap_or(false)
162                } else {
163                    false
164                }
165            }
166            Self::Contains { field, value } => {
167                if let Some(Value::String(s)) = doc.get(field) {
168                    s.contains(value)
169                } else {
170                    false
171                }
172            }
173            Self::StartsWith { field, value } => {
174                if let Some(Value::String(s)) = doc.get(field) {
175                    s.starts_with(value)
176                } else {
177                    false
178                }
179            }
180            Self::EndsWith { field, value } => {
181                if let Some(Value::String(s)) = doc.get(field) {
182                    s.ends_with(value)
183                } else {
184                    false
185                }
186            }
187            Self::And(filters) => filters.iter().all(|f| f.matches(doc)),
188            Self::Or(filters) => filters.iter().any(|f| f.matches(doc)),
189            Self::Not(filter) => !filter.matches(doc),
190        }
191    }
192}
193
194fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
195    match (a, b) {
196        (Value::Int(a), Value::Int(b)) => Some(a.cmp(b)),
197        (Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
198        (Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
199        (Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)),
200        (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
201        (Value::Bool(a), Value::Bool(b)) => Some(a.cmp(b)),
202        _ => None,
203    }
204}
205
206// =============================================================================
207// Sort
208// =============================================================================
209
210/// Sort specification.
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct Sort {
213    pub field: String,
214    pub ascending: bool,
215}
216
217// =============================================================================
218// Query Builder
219// =============================================================================
220
221/// Builder for constructing queries.
222pub struct QueryBuilder {
223    query: Query,
224}
225
226impl QueryBuilder {
227    pub fn new() -> Self {
228        Self {
229            query: Query::new(),
230        }
231    }
232
233    pub fn eq(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
234        self.query.filters.push(Filter::Eq {
235            field: field.into(),
236            value: value.into(),
237        });
238        self
239    }
240
241    pub fn ne(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
242        self.query.filters.push(Filter::Ne {
243            field: field.into(),
244            value: value.into(),
245        });
246        self
247    }
248
249    pub fn gt(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
250        self.query.filters.push(Filter::Gt {
251            field: field.into(),
252            value: value.into(),
253        });
254        self
255    }
256
257    pub fn gte(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
258        self.query.filters.push(Filter::Gte {
259            field: field.into(),
260            value: value.into(),
261        });
262        self
263    }
264
265    pub fn lt(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
266        self.query.filters.push(Filter::Lt {
267            field: field.into(),
268            value: value.into(),
269        });
270        self
271    }
272
273    pub fn lte(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
274        self.query.filters.push(Filter::Lte {
275            field: field.into(),
276            value: value.into(),
277        });
278        self
279    }
280
281    pub fn in_values(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
282        self.query.filters.push(Filter::In {
283            field: field.into(),
284            values,
285        });
286        self
287    }
288
289    pub fn exists(mut self, field: impl Into<String>, exists: bool) -> Self {
290        self.query.filters.push(Filter::Exists {
291            field: field.into(),
292            exists,
293        });
294        self
295    }
296
297    pub fn contains(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
298        self.query.filters.push(Filter::Contains {
299            field: field.into(),
300            value: value.into(),
301        });
302        self
303    }
304
305    pub fn starts_with(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
306        self.query.filters.push(Filter::StartsWith {
307            field: field.into(),
308            value: value.into(),
309        });
310        self
311    }
312
313    pub fn ends_with(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
314        self.query.filters.push(Filter::EndsWith {
315            field: field.into(),
316            value: value.into(),
317        });
318        self
319    }
320
321    pub fn regex(mut self, field: impl Into<String>, pattern: impl Into<String>) -> Self {
322        self.query.filters.push(Filter::Regex {
323            field: field.into(),
324            pattern: pattern.into(),
325        });
326        self
327    }
328
329    pub fn and(mut self, filters: Vec<Filter>) -> Self {
330        self.query.filters.push(Filter::And(filters));
331        self
332    }
333
334    pub fn or(mut self, filters: Vec<Filter>) -> Self {
335        self.query.filters.push(Filter::Or(filters));
336        self
337    }
338
339    pub fn sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
340        self.query.sort = Some(Sort {
341            field: field.into(),
342            ascending,
343        });
344        self
345    }
346
347    pub fn skip(mut self, skip: usize) -> Self {
348        self.query.skip = Some(skip);
349        self
350    }
351
352    pub fn limit(mut self, limit: usize) -> Self {
353        self.query.limit = Some(limit);
354        self
355    }
356
357    pub fn project(mut self, fields: Vec<String>) -> Self {
358        self.query.projection = Some(fields);
359        self
360    }
361
362    pub fn build(self) -> Query {
363        self.query
364    }
365}
366
367impl Default for QueryBuilder {
368    fn default() -> Self {
369        Self::new()
370    }
371}
372
373// =============================================================================
374// Query Result
375// =============================================================================
376
377/// Result of a document query.
378#[derive(Debug, Clone)]
379pub struct QueryResult {
380    pub documents: Vec<Document>,
381    pub total_scanned: usize,
382    pub execution_time_ms: u64,
383}
384
385impl QueryResult {
386    pub fn empty() -> Self {
387        Self {
388            documents: Vec::new(),
389            total_scanned: 0,
390            execution_time_ms: 0,
391        }
392    }
393
394    pub fn count(&self) -> usize {
395        self.documents.len()
396    }
397
398    pub fn is_empty(&self) -> bool {
399        self.documents.is_empty()
400    }
401
402    pub fn first(&self) -> Option<&Document> {
403        self.documents.first()
404    }
405}
406
407// =============================================================================
408// Tests
409// =============================================================================
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414
415    fn create_test_doc() -> Document {
416        let mut doc = Document::with_id("test");
417        doc.set("name", "Alice");
418        doc.set("age", 30i64);
419        doc.set("active", true);
420        doc.set("email", "alice@example.com");
421        doc
422    }
423
424    #[test]
425    fn test_eq_filter() {
426        let doc = create_test_doc();
427
428        let filter = Filter::Eq {
429            field: "name".to_string(),
430            value: Value::String("Alice".to_string()),
431        };
432        assert!(filter.matches(&doc));
433
434        let filter = Filter::Eq {
435            field: "name".to_string(),
436            value: Value::String("Bob".to_string()),
437        };
438        assert!(!filter.matches(&doc));
439    }
440
441    #[test]
442    fn test_comparison_filters() {
443        let doc = create_test_doc();
444
445        let filter = Filter::Gt {
446            field: "age".to_string(),
447            value: Value::Int(25),
448        };
449        assert!(filter.matches(&doc));
450
451        let filter = Filter::Lt {
452            field: "age".to_string(),
453            value: Value::Int(25),
454        };
455        assert!(!filter.matches(&doc));
456
457        let filter = Filter::Gte {
458            field: "age".to_string(),
459            value: Value::Int(30),
460        };
461        assert!(filter.matches(&doc));
462    }
463
464    #[test]
465    fn test_string_filters() {
466        let doc = create_test_doc();
467
468        let filter = Filter::Contains {
469            field: "email".to_string(),
470            value: "example".to_string(),
471        };
472        assert!(filter.matches(&doc));
473
474        let filter = Filter::StartsWith {
475            field: "email".to_string(),
476            value: "alice".to_string(),
477        };
478        assert!(filter.matches(&doc));
479
480        let filter = Filter::EndsWith {
481            field: "email".to_string(),
482            value: ".com".to_string(),
483        };
484        assert!(filter.matches(&doc));
485    }
486
487    #[test]
488    fn test_logical_filters() {
489        let doc = create_test_doc();
490
491        let filter = Filter::And(vec![
492            Filter::Eq {
493                field: "name".to_string(),
494                value: Value::String("Alice".to_string()),
495            },
496            Filter::Gt {
497                field: "age".to_string(),
498                value: Value::Int(20),
499            },
500        ]);
501        assert!(filter.matches(&doc));
502
503        let filter = Filter::Or(vec![
504            Filter::Eq {
505                field: "name".to_string(),
506                value: Value::String("Bob".to_string()),
507            },
508            Filter::Eq {
509                field: "active".to_string(),
510                value: Value::Bool(true),
511            },
512        ]);
513        assert!(filter.matches(&doc));
514    }
515
516    #[test]
517    fn test_query_builder() {
518        let doc = create_test_doc();
519
520        let query = QueryBuilder::new()
521            .eq("name", "Alice")
522            .gt("age", 25i64)
523            .build();
524
525        assert!(query.matches(&doc));
526
527        let query = QueryBuilder::new()
528            .eq("name", "Bob")
529            .build();
530
531        assert!(!query.matches(&doc));
532    }
533
534    #[test]
535    fn test_exists_filter() {
536        let doc = create_test_doc();
537
538        let filter = Filter::Exists {
539            field: "name".to_string(),
540            exists: true,
541        };
542        assert!(filter.matches(&doc));
543
544        let filter = Filter::Exists {
545            field: "missing".to_string(),
546            exists: false,
547        };
548        assert!(filter.matches(&doc));
549    }
550}