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}