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::Aurora;
19use crate::error::AuroraError;
20use crate::error::Result;
21use crate::types::{Document, Value};
22use serde::{Deserialize, Serialize};
23use std::collections::HashMap;
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/// Type alias for document filter functions
43type DocumentFilter<'a> = Box<dyn Fn(&Document) -> bool + Send + Sync + 'a>;
44
45/// Builder for creating and executing document queries.
46///
47/// QueryBuilder uses a fluent interface pattern to construct
48/// and execute queries against Aurora collections.
49///
50/// # Examples
51///
52/// ```
53/// // Query for active premium users
54/// let premium_users = db.query("users")
55///     .filter(|f| f.eq("status", "active") && f.eq("account_type", "premium"))
56///     .order_by("created_at", false)
57///     .limit(10)
58///     .collect()
59///     .await?;
60/// ```
61pub struct QueryBuilder<'a> {
62    db: &'a Aurora,
63    collection: String,
64    filters: Vec<DocumentFilter<'a>>,
65    order_by: Option<(String, bool)>,
66    limit: Option<usize>,
67    offset: Option<usize>,
68    fields: Option<Vec<String>>,
69}
70
71/// Builder for constructing document filter expressions.
72///
73/// This struct provides methods for comparing document fields
74/// with values to create filter conditions.
75///
76/// # Examples
77///
78/// ```
79/// // Combine multiple filter conditions
80/// db.query("products")
81///     .filter(|f| {
82///         f.gte("price", 10.0) &&
83///         f.lte("price", 50.0) &&
84///         f.contains("name", "widget")
85///     })
86///     .collect()
87///     .await?;
88/// ```
89pub struct FilterBuilder<'a, 'b> {
90    doc: &'b Document,
91    _marker: std::marker::PhantomData<&'a ()>,
92}
93
94impl<'a, 'b> FilterBuilder<'a, 'b> {
95    /// Create a new filter builder for the given document
96    pub fn new(doc: &'b Document) -> Self {
97        Self {
98            doc,
99            _marker: std::marker::PhantomData,
100        }
101    }
102
103    /// Check if a field equals a value
104    ///
105    /// # Examples
106    /// ```
107    /// .filter(|f| f.eq("status", "active"))
108    /// ```
109    pub fn eq<T: Into<Value>>(&self, field: &str, value: T) -> bool {
110        let value = value.into();
111        self.doc.data.get(field) == Some(&value)
112    }
113
114    /// Check if a field is greater than a value
115    ///
116    /// # Examples
117    /// ```
118    /// .filter(|f| f.gt("age", 21))
119    /// ```
120    pub fn gt<T: Into<Value>>(&self, field: &str, value: T) -> bool {
121        let value = value.into();
122        self.doc.data.get(field).is_some_and(|v| v > &value)
123    }
124
125    /// Check if a field is greater than or equal to a value
126    ///
127    /// # Examples
128    /// ```
129    /// .filter(|f| f.gte("age", 21))
130    /// ```
131    pub fn gte<T: Into<Value>>(&self, field: &str, value: T) -> bool {
132        let value = value.into();
133        self.doc.data.get(field).is_some_and(|v| v >= &value)
134    }
135
136    /// Check if a field is less than a value
137    ///
138    /// # Examples
139    /// ```
140    /// .filter(|f| f.lt("age", 65))
141    /// ```
142    pub fn lt<T: Into<Value>>(&self, field: &str, value: T) -> bool {
143        let value = value.into();
144        self.doc.data.get(field).is_some_and(|v| v < &value)
145    }
146
147    /// Check if a field is less than or equal to a value
148    ///
149    /// # Examples
150    /// ```
151    /// .filter(|f| f.lte("age", 65))
152    /// ```
153    pub fn lte<T: Into<Value>>(&self, field: &str, value: T) -> bool {
154        let value = value.into();
155        self.doc.data.get(field).is_some_and(|v| v <= &value)
156    }
157
158    /// Check if a field contains a value
159    ///
160    /// # Examples
161    /// ```
162    /// .filter(|f| f.contains("name", "widget"))
163    /// ```
164    pub fn contains(&self, field: &str, value: &str) -> bool {
165        self.doc.data.get(field).is_some_and(|v| match v {
166            Value::String(s) => s.contains(value),
167            Value::Array(arr) => arr.contains(&Value::String(value.to_string())),
168            _ => false,
169        })
170    }
171
172    /// Check if a field is in a list of values
173    ///
174    /// # Examples
175    /// ```
176    /// .filter(|f| f.in_values("status", &["active", "inactive"]))
177    /// ```
178    pub fn in_values<T: Into<Value> + Clone>(&self, field: &str, values: &[T]) -> bool {
179        let values: Vec<Value> = values.iter().map(|v| v.clone().into()).collect();
180        self.doc
181            .data
182            .get(field)
183            .is_some_and(|v| values.contains(v))
184    }
185
186    /// Check if a field is between two values (inclusive)
187    ///
188    /// # Examples
189    /// ```
190    /// .filter(|f| f.between("age", 18, 65))
191    /// ```
192    pub fn between<T: Into<Value> + Clone>(&self, field: &str, min: T, max: T) -> bool {
193        self.gte(field, min) && self.lte(field, max)
194    }
195
196    /// Check if a field exists and is not null
197    ///
198    /// # Examples
199    /// ```
200    /// .filter(|f| f.exists("email"))
201    /// ```
202    pub fn exists(&self, field: &str) -> bool {
203        self.doc
204            .data
205            .get(field)
206            .is_some_and(|v| !matches!(v, Value::Null))
207    }
208
209    /// Check if a field doesn't exist or is null
210    ///
211    /// # Examples
212    /// ```
213    /// .filter(|f| f.is_null("email"))
214    /// ```
215    pub fn is_null(&self, field: &str) -> bool {
216        self.doc
217            .data
218            .get(field)
219            .is_none_or(|v| matches!(v, Value::Null))
220    }
221
222    /// Check if a field starts with a prefix
223    ///
224    /// # Examples
225    /// ```
226    /// .filter(|f| f.starts_with("name", "John"))
227    /// ```
228    pub fn starts_with(&self, field: &str, prefix: &str) -> bool {
229        self.doc.data.get(field).is_some_and(|v| match v {
230            Value::String(s) => s.starts_with(prefix),
231            _ => false,
232        })
233    }
234
235    /// Check if a field ends with a suffix
236    ///
237    /// # Examples
238    /// ```
239    /// .filter(|f| f.ends_with("name", "son"))
240    /// ```
241    pub fn ends_with(&self, field: &str, suffix: &str) -> bool {
242        self.doc.data.get(field).is_some_and(|v| match v {
243            Value::String(s) => s.ends_with(suffix),
244            _ => false,
245        })
246    }
247
248    /// Check if a field is in an array
249    ///
250    /// # Examples
251    /// ```
252    /// .filter(|f| f.array_contains("status", "active"))
253    /// ```
254    pub fn array_contains(&self, field: &str, value: impl Into<Value>) -> bool {
255        let value = value.into();
256        self.doc.data.get(field).is_some_and(|v| match v {
257            Value::Array(arr) => arr.contains(&value),
258            _ => false,
259        })
260    }
261
262    /// Check if an array has a specific length
263    ///
264    /// # Examples
265    /// ```
266    /// .filter(|f| f.array_len_eq("status", 2))
267    /// ```
268    pub fn array_len_eq(&self, field: &str, len: usize) -> bool {
269        self.doc.data.get(field).is_some_and(|v| match v {
270            Value::Array(arr) => arr.len() == len,
271            _ => false,
272        })
273    }
274
275    /// Access a nested field using dot notation
276    ///
277    /// # Examples
278    /// ```
279    /// .filter(|f| f.get_nested_value("user.address.city") == Some(&Value::String("New York")))
280    /// ```
281    pub fn get_nested_value(&self, path: &str) -> Option<&Value> {
282        let parts: Vec<&str> = path.split('.').collect();
283        let mut current = self.doc.data.get(parts[0])?;
284
285        for &part in &parts[1..] {
286            if let Value::Object(map) = current {
287                current = map.get(part)?;
288            } else {
289                return None;
290            }
291        }
292
293        Some(current)
294    }
295
296    /// Check if a nested field equals a value
297    ///
298    /// # Examples
299    /// ```
300    /// .filter(|f| f.nested_eq("user.address.city", "New York"))
301    /// ```
302    pub fn nested_eq<T: Into<Value>>(&self, path: &str, value: T) -> bool {
303        let value = value.into();
304        self.get_nested_value(path) == Some(&value)
305    }
306
307    /// Check if a field matches a regular expression
308    ///
309    /// # Examples
310    /// ```
311    /// .filter(|f| f.matches_regex("email", r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"))
312    /// ```
313    pub fn matches_regex(&self, field: &str, pattern: &str) -> bool {
314        use regex::Regex;
315
316        if let Ok(re) = Regex::new(pattern) {
317            self.doc.data.get(field).is_some_and(|v| match v {
318                Value::String(s) => re.is_match(s),
319                _ => false,
320            })
321        } else {
322            false
323        }
324    }
325}
326
327impl<'a> QueryBuilder<'a> {
328    /// Create a new query builder for the specified collection
329    ///
330    /// # Examples
331    /// ```
332    /// let query = db.query("users");
333    /// ```
334    pub fn new(db: &'a Aurora, collection: &str) -> Self {
335        Self {
336            db,
337            collection: collection.to_string(),
338            filters: Vec::new(),
339            order_by: None,
340            limit: None,
341            offset: None,
342            fields: None,
343        }
344    }
345
346    /// Add a filter function to the query
347    ///
348    /// # Examples
349    /// ```
350    /// let active_users = db.query("users")
351    ///     .filter(|f| f.eq("status", "active"))
352    ///     .collect()
353    ///     .await?;
354    /// ```
355    pub fn filter<F>(mut self, filter_fn: F) -> Self
356    where
357        // CHANGE 2: Require the closure `F` to be `Send + Sync`.
358        F: Fn(&FilterBuilder) -> bool + Send + Sync + 'a,
359    {
360        self.filters.push(Box::new(move |doc| {
361            let filter_builder = FilterBuilder::new(doc);
362            filter_fn(&filter_builder)
363        }));
364        self
365    }
366
367    /// Sort results by a field (ascending or descending)
368    ///
369    /// # Parameters
370    /// * `field` - The field to sort by
371    /// * `ascending` - `true` for ascending order, `false` for descending
372    ///
373    /// # Examples
374    /// ```
375    /// // Sort by age ascending
376    /// .order_by("age", true)
377    ///
378    /// // Sort by creation date descending (newest first)
379    /// .order_by("created_at", false)
380    /// ```
381    pub fn order_by(mut self, field: &str, ascending: bool) -> Self {
382        self.order_by = Some((field.to_string(), ascending));
383        self
384    }
385
386    /// Limit the number of results returned
387    ///
388    /// # Examples
389    /// ```
390    /// // Get at most 10 results
391    /// .limit(10)
392    /// ```
393    pub fn limit(mut self, limit: usize) -> Self {
394        self.limit = Some(limit);
395        self
396    }
397
398    /// Skip a number of results (for pagination)
399    ///
400    /// # Examples
401    /// ```
402    /// // For pagination: skip the first 20 results and get the next 10
403    /// .offset(20).limit(10)
404    /// ```
405    pub fn offset(mut self, offset: usize) -> Self {
406        self.offset = Some(offset);
407        self
408    }
409
410    /// Select only specific fields to return
411    ///
412    /// # Examples
413    /// ```
414    /// // Only return name and email fields
415    /// .select(&["name", "email"])
416    /// ```
417    pub fn select(mut self, fields: &[&str]) -> Self {
418        self.fields = Some(fields.iter().map(|s| s.to_string()).collect());
419        self
420    }
421
422    /// Execute the query and collect the results
423    ///
424    /// # Returns
425    /// A vector of documents matching the query criteria
426    ///
427    /// # Examples
428    /// ```
429    /// let results = db.query("products")
430    ///     .filter(|f| f.lt("price", 100))
431    ///     .collect()
432    ///     .await?;
433    /// ```
434    pub async fn collect(self) -> Result<Vec<Document>> {
435        // Ensure indices are initialized
436        self.db.ensure_indices_initialized().await?;
437
438        // Optimization: Use early termination for queries with LIMIT but no ORDER BY
439        let mut docs = if self.order_by.is_none() && self.limit.is_some() {
440            // Early termination path - scan only until we have enough results
441            let target = self.limit.unwrap() + self.offset.unwrap_or(0);
442            let filter = |doc: &Document| self.filters.iter().all(|f| f(doc));
443            self.db.scan_and_filter(&self.collection, filter, Some(target))?
444        } else {
445            // Standard path - need all matching docs (for sorting or no limit)
446            let mut docs = self.db.get_all_collection(&self.collection).await?;
447            docs.retain(|doc| self.filters.iter().all(|f| f(doc)));
448            docs
449        };
450
451        // Apply ordering
452        if let Some((field, ascending)) = self.order_by {
453            docs.sort_by(|a, b| match (a.data.get(&field), b.data.get(&field)) {
454                (Some(v1), Some(v2)) => {
455                    let cmp = v1.cmp(v2);
456                    if ascending { cmp } else { cmp.reverse() }
457                }
458                (None, Some(_)) => std::cmp::Ordering::Less,
459                (Some(_), None) => std::cmp::Ordering::Greater,
460                (None, None) => std::cmp::Ordering::Equal,
461            });
462        }
463
464        // Apply field selection if specified
465        if let Some(fields) = self.fields {
466            // Create new documents with only selected fields
467            docs = docs
468                .into_iter()
469                .map(|doc| {
470                    let mut new_data = HashMap::new();
471                    // Always include the ID
472                    for field in &fields {
473                        if let Some(value) = doc.data.get(field) {
474                            new_data.insert(field.clone(), value.clone());
475                        }
476                    }
477                    Document {
478                        id: doc.id,
479                        data: new_data,
480                    }
481                })
482                .collect();
483        }
484
485        // Apply offset and limit safely
486        let start = self.offset.unwrap_or(0);
487        let end = self
488            .limit
489            .map(|l| start.saturating_add(l))
490            .unwrap_or(docs.len());
491
492        // Ensure we don't go out of bounds
493        let end = end.min(docs.len());
494        Ok(docs.get(start..end).unwrap_or(&[]).to_vec())
495    }
496
497    /// Watch the query for real-time updates
498    ///
499    /// Returns a QueryWatcher that streams live updates when documents are added,
500    /// removed, or modified in ways that affect the query results. Perfect for
501    /// building reactive UIs, live dashboards, and real-time applications.
502    ///
503    /// # Performance
504    /// - Zero overhead for queries without watchers
505    /// - Updates delivered asynchronously via channels
506    /// - Automatic filtering - only matching changes are emitted
507    /// - Memory efficient - only tracks matching documents
508    ///
509    /// # Requirements
510    /// This method requires the QueryBuilder to have a 'static lifetime,
511    /// which means the database reference must also be 'static (e.g., Arc<Aurora>).
512    ///
513    /// # Examples
514    ///
515    /// ```
516    /// use aurora_db::{Aurora, types::Value};
517    /// use std::sync::Arc;
518    ///
519    /// let db = Arc::new(Aurora::open("mydb.db")?);
520    ///
521    /// // Basic reactive query - watch active users
522    /// let mut watcher = db.query("users")
523    ///     .filter(|f| f.eq("active", Value::Bool(true)))
524    ///     .watch()
525    ///     .await?;
526    ///
527    /// // Receive updates in real-time
528    /// while let Some(update) = watcher.next().await {
529    ///     match update {
530    ///         QueryUpdate::Added(doc) => {
531    ///             println!("New active user: {}", doc.id);
532    ///         },
533    ///         QueryUpdate::Removed(doc) => {
534    ///             println!("User deactivated: {}", doc.id);
535    ///         },
536    ///         QueryUpdate::Modified { old, new } => {
537    ///             println!("User updated: {} -> {}", old.id, new.id);
538    ///         },
539    ///     }
540    /// }
541    /// ```
542    ///
543    /// # Real-World Use Cases
544    ///
545    /// **Live Leaderboard:**
546    /// ```
547    /// // Watch top players by score
548    /// let mut leaderboard = db.query("players")
549    ///     .filter(|f| f.gte("score", Value::Int(1000)))
550    ///     .watch()
551    ///     .await?;
552    ///
553    /// tokio::spawn(async move {
554    ///     while let Some(update) = leaderboard.next().await {
555    ///         // Update UI with new rankings
556    ///         broadcast_to_clients(&update).await;
557    ///     }
558    /// });
559    /// ```
560    ///
561    /// **Activity Feed:**
562    /// ```
563    /// // Watch recent posts for a user's feed
564    /// let mut feed = db.query("posts")
565    ///     .filter(|f| f.eq("author_id", user_id))
566    ///     .watch()
567    ///     .await?;
568    ///
569    /// // Stream updates to WebSocket
570    /// while let Some(update) = feed.next().await {
571    ///     match update {
572    ///         QueryUpdate::Added(post) => {
573    ///             websocket.send(json!({"type": "new_post", "post": post})).await?;
574    ///         },
575    ///         _ => {}
576    ///     }
577    /// }
578    /// ```
579    ///
580    /// **Real-Time Dashboard:**
581    /// ```
582    /// // Watch critical metrics
583    /// let mut alerts = db.query("metrics")
584    ///     .filter(|f| f.gt("cpu_usage", Value::Float(80.0)))
585    ///     .watch()
586    ///     .await?;
587    ///
588    /// tokio::spawn(async move {
589    ///     while let Some(update) = alerts.next().await {
590    ///         if let QueryUpdate::Added(metric) = update {
591    ///             // Alert on high CPU usage
592    ///             send_alert(format!("High CPU: {:?}", metric)).await;
593    ///         }
594    ///     }
595    /// });
596    /// ```
597    ///
598    /// **Collaborative Editing:**
599    /// ```
600    /// // Watch document for changes from other users
601    /// let doc_id = "doc-123";
602    /// let mut changes = db.query("documents")
603    ///     .filter(|f| f.eq("id", doc_id))
604    ///     .watch()
605    ///     .await?;
606    ///
607    /// tokio::spawn(async move {
608    ///     while let Some(update) = changes.next().await {
609    ///         if let QueryUpdate::Modified { old, new } = update {
610    ///             // Merge changes from other editors
611    ///             apply_remote_changes(&old, &new).await;
612    ///         }
613    ///     }
614    /// });
615    /// ```
616    ///
617    /// **Stock Ticker:**
618    /// ```
619    /// // Watch price changes
620    /// let mut price_watcher = db.query("stocks")
621    ///     .filter(|f| f.eq("symbol", "AAPL"))
622    ///     .watch()
623    ///     .await?;
624    ///
625    /// while let Some(update) = price_watcher.next().await {
626    ///     if let QueryUpdate::Modified { old, new } = update {
627    ///         if let (Some(old_price), Some(new_price)) =
628    ///             (old.data.get("price"), new.data.get("price")) {
629    ///             println!("AAPL: {} -> {}", old_price, new_price);
630    ///         }
631    ///     }
632    /// }
633    /// ```
634    ///
635    /// # Multiple Watchers Pattern
636    ///
637    /// ```
638    /// // Watch multiple queries concurrently
639    /// let mut high_priority = db.query("tasks")
640    ///     .filter(|f| f.eq("priority", Value::String("high".into())))
641    ///     .watch()
642    ///     .await?;
643    ///
644    /// let mut urgent = db.query("tasks")
645    ///     .filter(|f| f.eq("status", Value::String("urgent".into())))
646    ///     .watch()
647    ///     .await?;
648    ///
649    /// tokio::spawn(async move {
650    ///     loop {
651    ///         tokio::select! {
652    ///             Some(update) = high_priority.next() => {
653    ///                 println!("High priority: {:?}", update);
654    ///             },
655    ///             Some(update) = urgent.next() => {
656    ///                 println!("Urgent: {:?}", update);
657    ///             },
658    ///         }
659    ///     }
660    /// });
661    /// ```
662    ///
663    /// # Important Notes
664    /// - Requires Arc<Aurora> for 'static lifetime
665    /// - Updates are delivered asynchronously
666    /// - Watcher keeps running until dropped
667    /// - Only matching documents trigger updates
668    /// - Use tokio::spawn to process updates in background
669    ///
670    /// # See Also
671    /// - `Aurora::listen()` for collection-level change notifications
672    /// - `QueryWatcher::next()` to receive the next update
673    /// - `QueryWatcher::try_next()` for non-blocking checks
674    pub async fn watch(mut self) -> Result<crate::reactive::QueryWatcher>
675    where
676        'a: 'static,
677    {
678        use crate::reactive::{QueryWatcher, ReactiveQueryState};
679        use std::sync::Arc;
680
681        // Extract the filters before consuming self
682        let collection = self.collection.clone();
683        let db = self.db;
684        let filters = std::mem::take(&mut self.filters);
685
686        // Get initial results
687        let docs = self.collect().await?;
688
689        // Create a listener for this collection
690        let listener = db.listen(&collection);
691
692        // Create filter closure that combines all the query filters
693        let filter_fn = move |doc: &Document| -> bool { filters.iter().all(|f| f(doc)) };
694
695        // Create reactive state
696        let state = Arc::new(ReactiveQueryState::new(filter_fn));
697
698        // Create and return watcher
699        Ok(QueryWatcher::new(collection, listener, state, docs))
700    }
701
702    /// Get only the first matching document or None if no matches
703    ///
704    /// # Examples
705    /// ```
706    /// let user = db.query("users")
707    ///     .filter(|f| f.eq("email", "jane@example.com"))
708    ///     .first_one()
709    ///     .await?;
710    /// ```
711    pub async fn first_one(self) -> Result<Option<Document>> {
712        self.limit(1).collect().await.map(|mut docs| docs.pop())
713    }
714
715    /// Count the number of documents matching the query
716    ///
717    /// # Examples
718    /// ```
719    /// let active_count = db.query("users")
720    ///     .filter(|f| f.eq("status", "active"))
721    ///     .count()
722    ///     .await?;
723    /// ```
724    pub async fn count(self) -> Result<usize> {
725        self.collect().await.map(|docs| docs.len())
726    }
727
728    /// Update documents matching the query with new field values
729    ///
730    /// # Returns
731    /// The number of documents updated
732    ///
733    /// # Examples
734    /// ```
735    /// let updated = db.query("products")
736    ///     .filter(|f| f.lt("stock", 5))
737    ///     .update([
738    ///         ("status", Value::String("low_stock".to_string())),
739    ///         ("needs_reorder", Value::Bool(true))
740    ///     ].into_iter().collect())
741    ///     .await?;
742    /// ```
743    pub async fn update(self, updates: HashMap<&str, Value>) -> Result<usize> {
744        // Store a reference to the db and collection before consuming self
745        let db = self.db;
746        let collection = self.collection.clone();
747
748        let docs = self.collect().await?;
749        let mut updated_count = 0;
750
751        for doc in docs {
752            let mut updated_doc = doc.clone();
753            let mut changed = false;
754
755            for (field, value) in &updates {
756                updated_doc.data.insert(field.to_string(), value.clone());
757                changed = true;
758            }
759
760            if changed {
761                // Update document in the database
762                db.put(
763                    format!("{}:{}", collection, updated_doc.id),
764                    serde_json::to_vec(&updated_doc)?,
765                    None,
766                )?;
767                updated_count += 1;
768            }
769        }
770
771        Ok(updated_count)
772    }
773}
774
775/// Builder for performing full-text search operations
776///
777/// # Examples
778/// ```
779/// let results = db.search("products")
780///     .field("description")
781///     .matching("wireless headphones")
782///     .fuzzy(true)
783///     .collect()
784///     .await?;
785/// ```
786pub struct SearchBuilder<'a> {
787    db: &'a Aurora,
788    collection: String,
789    field: Option<String>,
790    query: Option<String>,
791    fuzzy: bool,
792}
793
794impl<'a> SearchBuilder<'a> {
795    /// Create a new search builder for the specified collection
796    pub fn new(db: &'a Aurora, collection: &str) -> Self {
797        Self {
798            db,
799            collection: collection.to_string(),
800            field: None,
801            query: None,
802            fuzzy: false,
803        }
804    }
805
806    /// Specify the field to search in
807    ///
808    /// # Examples
809    /// ```
810    /// .field("description")
811    /// ```
812    pub fn field(mut self, field: &str) -> Self {
813        self.field = Some(field.to_string());
814        self
815    }
816
817    /// Specify the search query text
818    ///
819    /// # Examples
820    /// ```
821    /// .matching("wireless headphones")
822    /// ```
823    pub fn matching(mut self, query: &str) -> Self {
824        self.query = Some(query.to_string());
825        self
826    }
827
828    /// Enable or disable fuzzy matching (for typo tolerance)
829    ///
830    /// # Examples
831    /// ```
832    /// .fuzzy(true)  // Enable fuzzy matching
833    /// ```
834    pub fn fuzzy(mut self, enable: bool) -> Self {
835        self.fuzzy = enable;
836        self
837    }
838
839    /// Execute the search and collect the results
840    ///
841    /// # Returns
842    /// A vector of documents matching the search criteria
843    ///
844    /// # Examples
845    /// ```
846    /// let results = db.search("articles")
847    ///     .field("content")
848    ///     .matching("quantum computing")
849    ///     .collect()
850    ///     .await?;
851    /// ```
852    pub async fn collect(self) -> Result<Vec<Document>> {
853        let field = self
854            .field
855            .ok_or_else(|| AuroraError::InvalidOperation("Search field not specified".into()))?;
856        let query = self
857            .query
858            .ok_or_else(|| AuroraError::InvalidOperation("Search query not specified".into()))?;
859
860        self.db.search_text(&self.collection, &field, &query).await
861    }
862}
863
864#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
865pub struct SimpleQueryBuilder {
866    pub collection: String,
867    pub filters: Vec<Filter>,
868    pub order_by: Option<(String, bool)>,
869    pub limit: Option<usize>,
870    pub offset: Option<usize>,
871}
872
873impl SimpleQueryBuilder {
874    pub fn new(collection: String) -> Self {
875        Self {
876            collection,
877            filters: Vec::new(),
878            order_by: None,
879            limit: None,
880            offset: None,
881        }
882    }
883
884    pub fn filter(mut self, filter: Filter) -> Self {
885        self.filters.push(filter);
886        self
887    }
888
889    /// Filter for exact equality
890    ///
891    /// Uses secondary index if the field is indexed (O(1) lookup).
892    /// Falls back to full collection scan if not indexed (O(n)).
893    ///
894    /// # Arguments
895    /// * `field` - The field name to filter on
896    /// * `value` - The exact value to match
897    ///
898    /// # Examples
899    ///
900    /// ```
901    /// use aurora_db::{Aurora, types::Value};
902    ///
903    /// let db = Aurora::open("mydb.db")?;
904    ///
905    /// // Find active users
906    /// let active_users = db.query("users")
907    ///     .filter(|f| f.eq("status", Value::String("active".into())))
908    ///     .collect()
909    ///     .await?;
910    ///
911    /// // Multiple equality filters (AND logic)
912    /// let premium_active = db.query("users")
913    ///     .filter(|f| f.eq("tier", Value::String("premium".into())))
914    ///     .filter(|f| f.eq("active", Value::Bool(true)))
915    ///     .collect()
916    ///     .await?;
917    ///
918    /// // Numeric equality
919    /// let age_30 = db.query("users")
920    ///     .filter(|f| f.eq("age", Value::Int(30)))
921    ///     .collect()
922    ///     .await?;
923    /// ```
924    pub fn eq(self, field: &str, value: Value) -> Self {
925        self.filter(Filter::Eq(field.to_string(), value))
926    }
927
928    /// Filter for greater than
929    ///
930    /// Finds all documents where the field value is strictly greater than
931    /// the provided value. With LIMIT, uses early termination for performance.
932    ///
933    /// # Arguments
934    /// * `field` - The field name to compare
935    /// * `value` - The minimum value (exclusive)
936    ///
937    /// # Performance
938    /// - Without LIMIT: O(n) - scans all documents
939    /// - With LIMIT: O(k) where k = limit + offset (early termination)
940    /// - No index support yet (planned for future)
941    ///
942    /// # Examples
943    ///
944    /// ```
945    /// use aurora_db::{Aurora, types::Value};
946    ///
947    /// let db = Aurora::open("mydb.db")?;
948    ///
949    /// // Find high scorers (with early termination)
950    /// let high_scorers = db.query("users")
951    ///     .filter(|f| f.gt("score", Value::Int(1000)))
952    ///     .limit(100)  // Stops after finding 100 matches
953    ///     .collect()
954    ///     .await?;
955    ///
956    /// // Price range queries
957    /// let expensive = db.query("products")
958    ///     .filter(|f| f.gt("price", Value::Float(99.99)))
959    ///     .order_by("price", false)  // Descending
960    ///     .collect()
961    ///     .await?;
962    ///
963    /// // Date filtering (timestamps as integers)
964    /// let recent = db.query("events")
965    ///     .filter(|f| f.gt("timestamp", Value::Int(1609459200)))  // After Jan 1, 2021
966    ///     .collect()
967    ///     .await?;
968    /// ```
969    pub fn gt(self, field: &str, value: Value) -> Self {
970        self.filter(Filter::Gt(field.to_string(), value))
971    }
972
973    /// Filter for greater than or equal to
974    ///
975    /// Finds all documents where the field value is greater than or equal to
976    /// the provided value. Inclusive version of `gt()`.
977    ///
978    /// # Arguments
979    /// * `field` - The field name to compare
980    /// * `value` - The minimum value (inclusive)
981    ///
982    /// # Examples
983    ///
984    /// ```
985    /// use aurora_db::{Aurora, types::Value};
986    ///
987    /// let db = Aurora::open("mydb.db")?;
988    ///
989    /// // Minimum age requirement (inclusive)
990    /// let adults = db.query("users")
991    ///     .filter(|f| f.gte("age", Value::Int(18)))
992    ///     .collect()
993    ///     .await?;
994    ///
995    /// // Inventory management
996    /// let in_stock = db.query("products")
997    ///     .filter(|f| f.gte("stock", Value::Int(1)))
998    ///     .collect()
999    ///     .await?;
1000    /// ```
1001    pub fn gte(self, field: &str, value: Value) -> Self {
1002        self.filter(Filter::Gte(field.to_string(), value))
1003    }
1004
1005    /// Filter for less than
1006    ///
1007    /// Finds all documents where the field value is strictly less than
1008    /// the provided value.
1009    ///
1010    /// # Arguments
1011    /// * `field` - The field name to compare
1012    /// * `value` - The maximum value (exclusive)
1013    ///
1014    /// # Examples
1015    ///
1016    /// ```
1017    /// use aurora_db::{Aurora, types::Value};
1018    ///
1019    /// let db = Aurora::open("mydb.db")?;
1020    ///
1021    /// // Low balance accounts
1022    /// let low_balance = db.query("accounts")
1023    ///     .filter(|f| f.lt("balance", Value::Float(10.0)))
1024    ///     .collect()
1025    ///     .await?;
1026    ///
1027    /// // Budget products
1028    /// let budget = db.query("products")
1029    ///     .filter(|f| f.lt("price", Value::Float(50.0)))
1030    ///     .order_by("price", true)  // Ascending
1031    ///     .collect()
1032    ///     .await?;
1033    /// ```
1034    pub fn lt(self, field: &str, value: Value) -> Self {
1035        self.filter(Filter::Lt(field.to_string(), value))
1036    }
1037
1038    /// Filter for less than or equal to
1039    ///
1040    /// Finds all documents where the field value is less than or equal to
1041    /// the provided value. Inclusive version of `lt()`.
1042    ///
1043    /// # Arguments
1044    /// * `field` - The field name to compare
1045    /// * `value` - The maximum value (inclusive)
1046    ///
1047    /// # Examples
1048    ///
1049    /// ```
1050    /// use aurora_db::{Aurora, types::Value};
1051    ///
1052    /// let db = Aurora::open("mydb.db")?;
1053    ///
1054    /// // Senior discount eligibility
1055    /// let seniors = db.query("users")
1056    ///     .filter(|f| f.lte("age", Value::Int(65)))
1057    ///     .collect()
1058    ///     .await?;
1059    ///
1060    /// // Clearance items
1061    /// let clearance = db.query("products")
1062    ///     .filter(|f| f.lte("price", Value::Float(20.0)))
1063    ///     .collect()
1064    ///     .await?;
1065    /// ```
1066    pub fn lte(self, field: &str, value: Value) -> Self {
1067        self.filter(Filter::Lte(field.to_string(), value))
1068    }
1069
1070    /// Filter for substring containment
1071    ///
1072    /// Finds all documents where the field value contains the specified substring.
1073    /// Case-sensitive matching. For text search, consider using the `search()` API instead.
1074    ///
1075    /// # Arguments
1076    /// * `field` - The field name to search in (must be a string field)
1077    /// * `value` - The substring to search for
1078    ///
1079    /// # Performance
1080    /// - Always O(n) - scans all documents
1081    /// - Case-sensitive string matching
1082    /// - For full-text search, use `db.search()` instead
1083    ///
1084    /// # Examples
1085    ///
1086    /// ```
1087    /// use aurora_db::Aurora;
1088    ///
1089    /// let db = Aurora::open("mydb.db")?;
1090    ///
1091    /// // Find articles about Rust
1092    /// let rust_articles = db.query("articles")
1093    ///     .filter(|f| f.contains("title", "Rust"))
1094    ///     .collect()
1095    ///     .await?;
1096    ///
1097    /// // Email domain filtering
1098    /// let gmail_users = db.query("users")
1099    ///     .filter(|f| f.contains("email", "@gmail.com"))
1100    ///     .collect()
1101    ///     .await?;
1102    ///
1103    /// // Tag searching
1104    /// let rust_posts = db.query("posts")
1105    ///     .filter(|f| f.contains("tags", "rust"))
1106    ///     .collect()
1107    ///     .await?;
1108    /// ```
1109    ///
1110    /// # Note
1111    /// For case-insensitive search or more advanced text matching,
1112    /// use the full-text search API: `db.search(collection).query(text)`
1113    pub fn contains(self, field: &str, value: &str) -> Self {
1114        self.filter(Filter::Contains(field.to_string(), value.to_string()))
1115    }
1116
1117    /// Convenience method for range queries
1118    pub fn between(self, field: &str, min: Value, max: Value) -> Self {
1119        self.filter(Filter::Gte(field.to_string(), min))
1120            .filter(Filter::Lte(field.to_string(), max))
1121    }
1122
1123    /// Sort results by a field (ascending or descending)
1124    pub fn order_by(mut self, field: &str, ascending: bool) -> Self {
1125        self.order_by = Some((field.to_string(), ascending));
1126        self
1127    }
1128
1129    /// Limit the number of results returned
1130    pub fn limit(mut self, limit: usize) -> Self {
1131        self.limit = Some(limit);
1132        self
1133    }
1134
1135    /// Skip a number of results (for pagination)
1136    pub fn offset(mut self, offset: usize) -> Self {
1137        self.offset = Some(offset);
1138        self
1139    }
1140}
1141
1142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1143pub enum Filter {
1144    Eq(String, Value),
1145    Gt(String, Value),
1146    Gte(String, Value),
1147    Lt(String, Value),
1148    Lte(String, Value),
1149    Contains(String, String),
1150}