aurora_db/
query.rs

1//! # Aurora Query System
2//! 
3//! This module provides a powerful, fluent query interface for filtering, sorting,
4//! and retrieving documents from Aurora collections.
5//! 
6//! ## Examples
7//! 
8//! ```rust
9//! // Get all active users over 21
10//! let users = db.query("users")
11//!     .filter(|f| f.eq("status", "active") && f.gt("age", 21))
12//!     .order_by("last_login", false)
13//!     .limit(20)
14//!     .collect()
15//!     .await?;
16//! ```
17
18use crate::types::{Document, Value};
19use crate::Aurora;
20use crate::error::Result;
21use crate::error::AuroraError;
22use std::collections::HashMap;
23use serde::{Deserialize, Serialize};
24
25/// Trait for objects that can filter documents.
26///
27/// This trait is implemented for closures that take a reference to a 
28/// `FilterBuilder` and return a boolean, allowing for a natural filter syntax.
29pub trait Queryable {
30    fn matches(&self, doc: &Document) -> bool;
31}
32
33impl<F> Queryable for F
34where
35    F: Fn(&Document) -> bool
36{
37    fn matches(&self, doc: &Document) -> bool {
38        self(doc)
39    }
40} 
41
42/// Builder for creating and executing document queries.
43///
44/// QueryBuilder uses a fluent interface pattern to construct
45/// and execute queries against Aurora collections.
46///
47/// # Examples
48///
49/// ```
50/// // Query for active premium users
51/// let premium_users = db.query("users")
52///     .filter(|f| f.eq("status", "active") && f.eq("account_type", "premium"))
53///     .order_by("created_at", false)
54///     .limit(10)
55///     .collect()
56///     .await?;
57/// ```
58pub struct QueryBuilder<'a> {
59    db: &'a Aurora,
60    collection: String,
61    filters: Vec<Box<dyn Fn(&Document) -> bool + 'a>>,
62    order_by: Option<(String, bool)>,
63    limit: Option<usize>,
64    offset: Option<usize>,
65    fields: Option<Vec<String>>,
66}
67
68/// Builder for constructing document filter expressions.
69///
70/// This struct provides methods for comparing document fields
71/// with values to create filter conditions.
72///
73/// # Examples
74///
75/// ```
76/// // Combine multiple filter conditions
77/// db.query("products")
78///     .filter(|f| {
79///         f.gte("price", 10.0) && 
80///         f.lte("price", 50.0) && 
81///         f.contains("name", "widget")
82///     })
83///     .collect()
84///     .await?;
85/// ```
86pub struct FilterBuilder<'a, 'b> {
87    doc: &'b Document,
88    _marker: std::marker::PhantomData<&'a ()>,
89}
90
91impl<'a, 'b> FilterBuilder<'a, 'b> {
92    /// Create a new filter builder for the given document
93    pub fn new(doc: &'b Document) -> Self {
94        Self {
95            doc,
96            _marker: std::marker::PhantomData,
97        }
98    }
99
100    /// Check if a field equals a value
101    ///
102    /// # Examples
103    /// ```
104    /// .filter(|f| f.eq("status", "active"))
105    /// ```
106    pub fn eq<T: Into<Value>>(&self, field: &str, value: T) -> bool {
107        let value = value.into();
108        self.doc.data.get(field).map_or(false, |v| v == &value)
109    }
110
111    /// Check if a field is greater than a value
112    ///
113    /// # Examples
114    /// ```
115    /// .filter(|f| f.gt("age", 18))
116    /// ```
117    pub fn gt<T: Into<Value>>(&self, field: &str, value: T) -> bool {
118        let value = value.into();
119        self.doc.data.get(field).map_or(false, |v| v > &value)
120    }
121
122    /// Check if a field is less than a value
123    ///
124    /// # Examples
125    /// ```
126    /// .filter(|f| f.lt("age", 18))
127    /// ```
128    pub fn lt<T: Into<Value>>(&self, field: &str, value: T) -> bool {
129        let value = value.into();
130        self.doc.data.get(field).map_or(false, |v| v < &value)
131    }
132
133    /// Check if a field contains a value
134    ///
135    /// # Examples
136    /// ```
137    /// .filter(|f| f.contains("name", "widget"))
138    /// ```
139    pub fn contains(&self, field: &str, value: &str) -> bool {
140        self.doc.data.get(field).map_or(false, |v| {
141            match v {
142                Value::String(s) => s.contains(value),
143                Value::Array(arr) => arr.contains(&Value::String(value.to_string())),
144                _ => false,
145            }
146        })
147    }
148
149    /// Check if a field is in a list of values
150    ///
151    /// # Examples
152    /// ```
153    /// .filter(|f| f.in_values("status", &["active", "inactive"]))
154    /// ```
155    pub fn in_values<T: Into<Value> + Clone>(&self, field: &str, values: &[T]) -> bool {
156        let values: Vec<Value> = values.iter().map(|v| v.clone().into()).collect();
157        self.doc.data.get(field).map_or(false, |v| values.contains(v))
158    }
159
160    /// Check if a field is between two values (inclusive)
161    ///
162    /// # Examples
163    /// ```
164    /// .filter(|f| f.between("age", 18, 65))
165    /// ```
166    pub fn between<T: Into<Value> + Clone>(&self, field: &str, min: T, max: T) -> bool {
167        let min_val = min.into();
168        let max_val = max.into();
169        self.doc.data.get(field).map_or(false, |v| {
170            v >= &min_val && v <= &max_val
171        })
172    }
173
174    /// Check if a field exists and is not null
175    ///
176    /// # Examples
177    /// ```
178    /// .filter(|f| f.exists("email"))
179    /// ```
180    pub fn exists(&self, field: &str) -> bool {
181        self.doc.data.get(field).map_or(false, |v| !matches!(v, Value::Null))
182    }
183
184    /// Check if a field doesn't exist or is null
185    ///
186    /// # Examples
187    /// ```
188    /// .filter(|f| f.is_null("email"))
189    /// ```
190    pub fn is_null(&self, field: &str) -> bool {
191        self.doc.data.get(field).map_or(true, |v| matches!(v, Value::Null))
192    }
193
194    /// Check if a field starts with a prefix
195    ///
196    /// # Examples
197    /// ```
198    /// .filter(|f| f.starts_with("name", "John"))
199    /// ```
200    pub fn starts_with(&self, field: &str, prefix: &str) -> bool {
201        self.doc.data.get(field).map_or(false, |v| {
202            match v {
203                Value::String(s) => s.starts_with(prefix),
204                _ => false,
205            }
206        })
207    }
208
209    /// Check if a field ends with a suffix
210    ///
211    /// # Examples
212    /// ```
213    /// .filter(|f| f.ends_with("name", "son"))
214    /// ```
215    pub fn ends_with(&self, field: &str, suffix: &str) -> bool {
216        self.doc.data.get(field).map_or(false, |v| {
217            match v {
218                Value::String(s) => s.ends_with(suffix),
219                _ => false,
220            }
221        })
222    }
223
224    /// Check if a field is in an array
225    ///
226    /// # Examples
227    /// ```
228    /// .filter(|f| f.array_contains("status", "active"))
229    /// ```
230    pub fn array_contains(&self, field: &str, value: impl Into<Value>) -> bool {
231        let value = value.into();
232        self.doc.data.get(field).map_or(false, |v| {
233            match v {
234                Value::Array(arr) => arr.contains(&value),
235                _ => false,
236            }
237        })
238    }
239
240    /// Check if an array has a specific length
241    ///
242    /// # Examples
243    /// ```
244    /// .filter(|f| f.array_len_eq("status", 2))
245    /// ```
246    pub fn array_len_eq(&self, field: &str, len: usize) -> bool {
247        self.doc.data.get(field).map_or(false, |v| {
248            match v {
249                Value::Array(arr) => arr.len() == len,
250                _ => false,
251            }
252        })
253    }
254
255    /// Access a nested field using dot notation
256    ///
257    /// # Examples
258    /// ```
259    /// .filter(|f| f.get_nested_value("user.address.city") == Some(&Value::String("New York")))
260    /// ```
261    pub fn get_nested_value(&self, path: &str) -> Option<&Value> {
262        let parts: Vec<&str> = path.split('.').collect();
263        let mut current = self.doc.data.get(parts[0])?;
264        
265        for &part in &parts[1..] {
266            if let Value::Object(map) = current {
267                current = map.get(part)?;
268            } else {
269                return None;
270            }
271        }
272        
273        Some(current)
274    }
275    
276    /// Check if a nested field equals a value
277    ///
278    /// # Examples
279    /// ```
280    /// .filter(|f| f.nested_eq("user.address.city", "New York"))
281    /// ```
282    pub fn nested_eq<T: Into<Value>>(&self, path: &str, value: T) -> bool {
283        let value = value.into();
284        self.get_nested_value(path).map_or(false, |v| v == &value)
285    }
286    
287    /// Check if a field matches a regular expression
288    ///
289    /// # Examples
290    /// ```
291    /// .filter(|f| f.matches_regex("email", r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"))
292    /// ```
293    pub fn matches_regex(&self, field: &str, pattern: &str) -> bool {
294        use regex::Regex;
295        
296        if let Ok(re) = Regex::new(pattern) {
297            self.doc.data.get(field).map_or(false, |v| {
298                match v {
299                    Value::String(s) => re.is_match(s),
300                    _ => false
301                }
302            })
303        } else {
304            false
305        }
306    }
307}
308
309impl<'a> QueryBuilder<'a> {
310    /// Create a new query builder for the specified collection
311    ///
312    /// # Examples
313    /// ```
314    /// let query = db.query("users");
315    /// ```
316    pub fn new(db: &'a Aurora, collection: &str) -> Self {
317        Self {
318            db,
319            collection: collection.to_string(),
320            filters: Vec::new(),
321            order_by: None,
322            limit: None,
323            offset: None,
324            fields: None,
325        }
326    }
327
328    /// Add a filter function to the query
329    ///
330    /// # Examples
331    /// ```
332    /// let active_users = db.query("users")
333    ///     .filter(|f| f.eq("status", "active"))
334    ///     .collect()
335    ///     .await?;
336    /// ```
337    pub fn filter<F>(mut self, filter_fn: F) -> Self
338    where
339        F: Fn(&FilterBuilder) -> bool + 'a,
340    {
341        self.filters.push(Box::new(move |doc| {
342            let filter_builder = FilterBuilder::new(doc);
343            filter_fn(&filter_builder)
344        }));
345        self
346    }
347
348    /// Sort results by a field (ascending or descending)
349    ///
350    /// # Parameters
351    /// * `field` - The field to sort by
352    /// * `ascending` - `true` for ascending order, `false` for descending
353    ///
354    /// # Examples
355    /// ```
356    /// // Sort by age ascending
357    /// .order_by("age", true)
358    ///
359    /// // Sort by creation date descending (newest first)
360    /// .order_by("created_at", false)
361    /// ```
362    pub fn order_by(mut self, field: &str, ascending: bool) -> Self {
363        self.order_by = Some((field.to_string(), ascending));
364        self
365    }
366
367    /// Limit the number of results returned
368    ///
369    /// # Examples
370    /// ```
371    /// // Get at most 10 results
372    /// .limit(10)
373    /// ```
374    pub fn limit(mut self, limit: usize) -> Self {
375        self.limit = Some(limit);
376        self
377    }
378
379    /// Skip a number of results (for pagination)
380    ///
381    /// # Examples
382    /// ```
383    /// // For pagination: skip the first 20 results and get the next 10
384    /// .offset(20).limit(10)
385    /// ```
386    pub fn offset(mut self, offset: usize) -> Self {
387        self.offset = Some(offset);
388        self
389    }
390
391    /// Select only specific fields to return
392    ///
393    /// # Examples
394    /// ```
395    /// // Only return name and email fields
396    /// .select(&["name", "email"])
397    /// ```
398    pub fn select(mut self, fields: &[&str]) -> Self {
399        self.fields = Some(fields.iter().map(|s| s.to_string()).collect());
400        self
401    }
402
403    /// Execute the query and collect the results
404    ///
405    /// # Returns
406    /// A vector of documents matching the query criteria
407    ///
408    /// # Examples
409    /// ```
410    /// let results = db.query("products")
411    ///     .filter(|f| f.lt("price", 100))
412    ///     .collect()
413    ///     .await?;
414    /// ```
415    pub async fn collect(self) -> Result<Vec<Document>> {
416        let mut docs = self.db.get_all_collection(&self.collection).await?;
417        
418        // Apply filters
419        docs.retain(|doc| self.filters.iter().all(|f| f(doc)));
420
421        // Apply ordering
422        if let Some((field, ascending)) = self.order_by {
423            docs.sort_by(|a, b| {
424                match (a.data.get(&field), b.data.get(&field)) {
425                    (Some(v1), Some(v2)) => {
426                        let cmp = v1.cmp(v2);
427                        if ascending { cmp } else { cmp.reverse() }
428                    },
429                    (None, Some(_)) => std::cmp::Ordering::Less,
430                    (Some(_), None) => std::cmp::Ordering::Greater,
431                    (None, None) => std::cmp::Ordering::Equal,
432                }
433            });
434        }
435
436        // Apply field selection if specified
437        if let Some(fields) = self.fields {
438            // Create new documents with only selected fields
439            docs = docs.into_iter().map(|doc| {
440                let mut new_data = HashMap::new();
441                // Always include the ID
442                for field in &fields {
443                    if let Some(value) = doc.data.get(field) {
444                        new_data.insert(field.clone(), value.clone());
445                    }
446                }
447                Document {
448                    id: doc.id,
449                    data: new_data,
450                }
451            }).collect();
452        }
453
454        // Apply offset and limit safely
455        let start = self.offset.unwrap_or(0);
456        let end = self.limit
457            .map(|l| start.saturating_add(l))
458            .unwrap_or(docs.len());
459        
460        // Ensure we don't go out of bounds
461        let end = end.min(docs.len());
462        Ok(docs.get(start..end).unwrap_or(&[]).to_vec())
463    }
464
465    /// Get only the first matching document or None if no matches
466    ///
467    /// # Examples
468    /// ```
469    /// let user = db.query("users")
470    ///     .filter(|f| f.eq("email", "jane@example.com"))
471    ///     .first_one()
472    ///     .await?;
473    /// ```
474    pub async fn first_one(self) -> Result<Option<Document>> {
475        self.limit(1).collect().await.map(|mut docs| docs.pop())
476    }
477
478    /// Count the number of documents matching the query
479    ///
480    /// # Examples
481    /// ```
482    /// let active_count = db.query("users")
483    ///     .filter(|f| f.eq("status", "active"))
484    ///     .count()
485    ///     .await?;
486    /// ```
487    pub async fn count(self) -> Result<usize> {
488        self.collect().await.map(|docs| docs.len())
489    }
490
491    /// Update documents matching the query with new field values
492    ///
493    /// # Returns
494    /// The number of documents updated
495    ///
496    /// # Examples
497    /// ```
498    /// let updated = db.query("products")
499    ///     .filter(|f| f.lt("stock", 5))
500    ///     .update([
501    ///         ("status", Value::String("low_stock".to_string())),
502    ///         ("needs_reorder", Value::Bool(true))
503    ///     ].into_iter().collect())
504    ///     .await?;
505    /// ```
506    pub async fn update(self, updates: HashMap<&str, Value>) -> Result<usize> {
507        // Store a reference to the db and collection before consuming self
508        let db = self.db;
509        let collection = self.collection.clone();
510        
511        let docs = self.collect().await?;
512        let mut updated_count = 0;
513        
514        for doc in docs {
515            let mut updated_doc = doc.clone();
516            let mut changed = false;
517            
518            for (field, value) in &updates {
519                updated_doc.data.insert(field.to_string(), value.clone());
520                changed = true;
521            }
522            
523            if changed {
524                // Update document in the database
525                db.put(
526                    format!("{}:{}", collection, updated_doc.id),
527                    serde_json::to_vec(&updated_doc)?,
528                    None
529                )?;
530                updated_count += 1;
531            }
532        }
533        
534        Ok(updated_count)
535    }
536}
537
538/// Builder for performing full-text search operations
539///
540/// # Examples
541/// ```
542/// let results = db.search("products")
543///     .field("description")
544///     .matching("wireless headphones")
545///     .fuzzy(true)
546///     .collect()
547///     .await?;
548/// ```
549pub struct SearchBuilder<'a> {
550    db: &'a Aurora,
551    collection: String,
552    field: Option<String>,
553    query: Option<String>,
554    fuzzy: bool,
555}
556
557impl<'a> SearchBuilder<'a> {
558    /// Create a new search builder for the specified collection
559    pub fn new(db: &'a Aurora, collection: &str) -> Self {
560        Self {
561            db,
562            collection: collection.to_string(),
563            field: None,
564            query: None,
565            fuzzy: false,
566        }
567    }
568
569    /// Specify the field to search in
570    ///
571    /// # Examples
572    /// ```
573    /// .field("description")
574    /// ```
575    pub fn field(mut self, field: &str) -> Self {
576        self.field = Some(field.to_string());
577        self
578    }
579
580    /// Specify the search query text
581    ///
582    /// # Examples
583    /// ```
584    /// .matching("wireless headphones")
585    /// ```
586    pub fn matching(mut self, query: &str) -> Self {
587        self.query = Some(query.to_string());
588        self
589    }
590
591    /// Enable or disable fuzzy matching (for typo tolerance)
592    ///
593    /// # Examples
594    /// ```
595    /// .fuzzy(true)  // Enable fuzzy matching
596    /// ```
597    pub fn fuzzy(mut self, enable: bool) -> Self {
598        self.fuzzy = enable;
599        self
600    }
601
602    /// Execute the search and collect the results
603    ///
604    /// # Returns
605    /// A vector of documents matching the search criteria
606    ///
607    /// # Examples
608    /// ```
609    /// let results = db.search("articles")
610    ///     .field("content")
611    ///     .matching("quantum computing")
612    ///     .collect()
613    ///     .await?;
614    /// ```
615    pub async fn collect(self) -> Result<Vec<Document>> {
616        let field = self.field.ok_or_else(|| AuroraError::InvalidOperation("Search field not specified".into()))?;
617        let query = self.query.ok_or_else(|| AuroraError::InvalidOperation("Search query not specified".into()))?;
618        
619        self.db.search_text(&self.collection, &field, &query).await
620    }
621}
622
623#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
624pub struct SimpleQueryBuilder {
625    pub collection: String,
626    pub filters: Vec<Filter>,
627}
628
629impl SimpleQueryBuilder {
630    pub fn new(collection: String) -> Self {
631        Self {
632            collection,
633            filters: Vec::new(),
634        }
635    }
636
637    pub fn filter(mut self, filter: Filter) -> Self {
638        self.filters.push(filter);
639        self
640    }
641
642    pub fn eq(self, field: &str, value: Value) -> Self {
643        self.filter(Filter::Eq(field.to_string(), value))
644    }
645
646    pub fn gt(self, field: &str, value: Value) -> Self {
647        self.filter(Filter::Gt(field.to_string(), value))
648    }
649
650    pub fn lt(self, field: &str, value: Value) -> Self {
651        self.filter(Filter::Lt(field.to_string(), value))
652    }
653
654    pub fn contains(self, field: &str, value: &str) -> Self {
655        self.filter(Filter::Contains(field.to_string(), value.to_string()))
656    }
657}
658
659#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
660pub enum Filter {
661    Eq(String, Value),
662    Gt(String, Value),
663    Lt(String, Value),
664    Contains(String, String),
665}