use crate::types::{Document, Value};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Query {
pub filters: Vec<Filter>,
pub sort: Option<Sort>,
pub skip: Option<usize>,
pub limit: Option<usize>,
pub projection: Option<Vec<String>>,
}
impl Query {
pub fn new() -> Self {
Self {
filters: Vec::new(),
sort: None,
skip: None,
limit: None,
projection: None,
}
}
pub fn matches(&self, doc: &Document) -> bool {
self.filters.iter().all(|f| f.matches(doc))
}
pub fn with_filter(mut self, filter: Filter) -> Self {
self.filters.push(filter);
self
}
pub fn with_sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
self.sort = Some(Sort {
field: field.into(),
ascending,
});
self
}
pub fn with_skip(mut self, skip: usize) -> Self {
self.skip = Some(skip);
self
}
pub fn with_limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn with_projection(mut self, fields: Vec<String>) -> Self {
self.projection = Some(fields);
self
}
}
impl Default for Query {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Filter {
Eq { field: String, value: Value },
Ne { field: String, value: Value },
Gt { field: String, value: Value },
Gte { field: String, value: Value },
Lt { field: String, value: Value },
Lte { field: String, value: Value },
In { field: String, values: Vec<Value> },
Nin { field: String, values: Vec<Value> },
Exists { field: String, exists: bool },
Regex { field: String, pattern: String },
Contains { field: String, value: String },
StartsWith { field: String, value: String },
EndsWith { field: String, value: String },
And(Vec<Filter>),
Or(Vec<Filter>),
Not(Box<Filter>),
}
impl Filter {
pub fn matches(&self, doc: &Document) -> bool {
match self {
Self::Eq { field, value } => doc.get(field).map(|v| v == value).unwrap_or(false),
Self::Ne { field, value } => doc.get(field).map(|v| v != value).unwrap_or(true),
Self::Gt { field, value } => doc
.get(field)
.map(|v| compare_values(v, value) == Some(std::cmp::Ordering::Greater))
.unwrap_or(false),
Self::Gte { field, value } => doc
.get(field)
.map(|v| {
matches!(
compare_values(v, value),
Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
)
})
.unwrap_or(false),
Self::Lt { field, value } => doc
.get(field)
.map(|v| compare_values(v, value) == Some(std::cmp::Ordering::Less))
.unwrap_or(false),
Self::Lte { field, value } => doc
.get(field)
.map(|v| {
matches!(
compare_values(v, value),
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
)
})
.unwrap_or(false),
Self::In { field, values } => {
doc.get(field).map(|v| values.contains(v)).unwrap_or(false)
}
Self::Nin { field, values } => {
doc.get(field).map(|v| !values.contains(v)).unwrap_or(true)
}
Self::Exists { field, exists } => doc.contains(field) == *exists,
Self::Regex { field, pattern } => {
if let Some(Value::String(s)) = doc.get(field) {
regex::Regex::new(pattern)
.map(|re| re.is_match(s))
.unwrap_or(false)
} else {
false
}
}
Self::Contains { field, value } => {
if let Some(Value::String(s)) = doc.get(field) {
s.contains(value)
} else {
false
}
}
Self::StartsWith { field, value } => {
if let Some(Value::String(s)) = doc.get(field) {
s.starts_with(value)
} else {
false
}
}
Self::EndsWith { field, value } => {
if let Some(Value::String(s)) = doc.get(field) {
s.ends_with(value)
} else {
false
}
}
Self::And(filters) => filters.iter().all(|f| f.matches(doc)),
Self::Or(filters) => filters.iter().any(|f| f.matches(doc)),
Self::Not(filter) => !filter.matches(doc),
}
}
}
fn compare_values(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
match (a, b) {
(Value::Int(a), Value::Int(b)) => Some(a.cmp(b)),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
(Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
(Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)),
(Value::String(a), Value::String(b)) => Some(a.cmp(b)),
(Value::Bool(a), Value::Bool(b)) => Some(a.cmp(b)),
_ => None,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sort {
pub field: String,
pub ascending: bool,
}
pub struct QueryBuilder {
query: Query,
}
impl QueryBuilder {
pub fn new() -> Self {
Self {
query: Query::new(),
}
}
pub fn eq(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
self.query.filters.push(Filter::Eq {
field: field.into(),
value: value.into(),
});
self
}
pub fn ne(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
self.query.filters.push(Filter::Ne {
field: field.into(),
value: value.into(),
});
self
}
pub fn gt(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
self.query.filters.push(Filter::Gt {
field: field.into(),
value: value.into(),
});
self
}
pub fn gte(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
self.query.filters.push(Filter::Gte {
field: field.into(),
value: value.into(),
});
self
}
pub fn lt(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
self.query.filters.push(Filter::Lt {
field: field.into(),
value: value.into(),
});
self
}
pub fn lte(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
self.query.filters.push(Filter::Lte {
field: field.into(),
value: value.into(),
});
self
}
pub fn in_values(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
self.query.filters.push(Filter::In {
field: field.into(),
values,
});
self
}
pub fn exists(mut self, field: impl Into<String>, exists: bool) -> Self {
self.query.filters.push(Filter::Exists {
field: field.into(),
exists,
});
self
}
pub fn contains(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.query.filters.push(Filter::Contains {
field: field.into(),
value: value.into(),
});
self
}
pub fn starts_with(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.query.filters.push(Filter::StartsWith {
field: field.into(),
value: value.into(),
});
self
}
pub fn ends_with(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
self.query.filters.push(Filter::EndsWith {
field: field.into(),
value: value.into(),
});
self
}
pub fn regex(mut self, field: impl Into<String>, pattern: impl Into<String>) -> Self {
self.query.filters.push(Filter::Regex {
field: field.into(),
pattern: pattern.into(),
});
self
}
pub fn and(mut self, filters: Vec<Filter>) -> Self {
self.query.filters.push(Filter::And(filters));
self
}
pub fn or(mut self, filters: Vec<Filter>) -> Self {
self.query.filters.push(Filter::Or(filters));
self
}
pub fn sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
self.query.sort = Some(Sort {
field: field.into(),
ascending,
});
self
}
pub fn skip(mut self, skip: usize) -> Self {
self.query.skip = Some(skip);
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.query.limit = Some(limit);
self
}
pub fn project(mut self, fields: Vec<String>) -> Self {
self.query.projection = Some(fields);
self
}
pub fn build(self) -> Query {
self.query
}
}
impl Default for QueryBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub documents: Vec<Document>,
pub total_scanned: usize,
pub execution_time_ms: u64,
}
impl QueryResult {
pub fn empty() -> Self {
Self {
documents: Vec::new(),
total_scanned: 0,
execution_time_ms: 0,
}
}
pub fn count(&self) -> usize {
self.documents.len()
}
pub fn is_empty(&self) -> bool {
self.documents.is_empty()
}
pub fn first(&self) -> Option<&Document> {
self.documents.first()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_doc() -> Document {
let mut doc = Document::with_id("test");
doc.set("name", "Alice");
doc.set("age", 30i64);
doc.set("active", true);
doc.set("email", "alice@example.com");
doc
}
#[test]
fn test_eq_filter() {
let doc = create_test_doc();
let filter = Filter::Eq {
field: "name".to_string(),
value: Value::String("Alice".to_string()),
};
assert!(filter.matches(&doc));
let filter = Filter::Eq {
field: "name".to_string(),
value: Value::String("Bob".to_string()),
};
assert!(!filter.matches(&doc));
}
#[test]
fn test_comparison_filters() {
let doc = create_test_doc();
let filter = Filter::Gt {
field: "age".to_string(),
value: Value::Int(25),
};
assert!(filter.matches(&doc));
let filter = Filter::Lt {
field: "age".to_string(),
value: Value::Int(25),
};
assert!(!filter.matches(&doc));
let filter = Filter::Gte {
field: "age".to_string(),
value: Value::Int(30),
};
assert!(filter.matches(&doc));
}
#[test]
fn test_string_filters() {
let doc = create_test_doc();
let filter = Filter::Contains {
field: "email".to_string(),
value: "example".to_string(),
};
assert!(filter.matches(&doc));
let filter = Filter::StartsWith {
field: "email".to_string(),
value: "alice".to_string(),
};
assert!(filter.matches(&doc));
let filter = Filter::EndsWith {
field: "email".to_string(),
value: ".com".to_string(),
};
assert!(filter.matches(&doc));
}
#[test]
fn test_logical_filters() {
let doc = create_test_doc();
let filter = Filter::And(vec![
Filter::Eq {
field: "name".to_string(),
value: Value::String("Alice".to_string()),
},
Filter::Gt {
field: "age".to_string(),
value: Value::Int(20),
},
]);
assert!(filter.matches(&doc));
let filter = Filter::Or(vec![
Filter::Eq {
field: "name".to_string(),
value: Value::String("Bob".to_string()),
},
Filter::Eq {
field: "active".to_string(),
value: Value::Bool(true),
},
]);
assert!(filter.matches(&doc));
}
#[test]
fn test_query_builder() {
let doc = create_test_doc();
let query = QueryBuilder::new()
.eq("name", "Alice")
.gt("age", 25i64)
.build();
assert!(query.matches(&doc));
let query = QueryBuilder::new().eq("name", "Bob").build();
assert!(!query.matches(&doc));
}
#[test]
fn test_exists_filter() {
let doc = create_test_doc();
let filter = Filter::Exists {
field: "name".to_string(),
exists: true,
};
assert!(filter.matches(&doc));
let filter = Filter::Exists {
field: "missing".to_string(),
exists: false,
};
assert!(filter.matches(&doc));
}
}