use crate::helpers;
use bson;
use bson::Bson;
use std::fmt;
enum SubQuery {
MatchAll,
Eq(String, Bson),
Gt(String, Bson),
Gte(String, Bson),
Lt(String, Bson),
Lte(String, Bson),
Ne(String, Bson),
And(Vec<SubQuery>),
Not(Box<SubQuery>),
Or(Vec<SubQuery>),
Exists(String, bool),
}
impl SubQuery {
fn parse_root(query: &bson::Bson) -> Result<SubQuery, String> {
let mut subqueries = Vec::new();
match query {
Bson::Document(doc) => {
for (operator, argument) in doc {
subqueries.push(SubQuery::parse_outer(operator, argument)?);
}
}
_ => {
return Err(format!(
"Queries need to be Objects, but received {}",
query
));
}
}
match subqueries.len() {
0 => Ok(SubQuery::MatchAll),
1 => Ok(subqueries.pop().unwrap()),
_ => Ok(SubQuery::And(subqueries)),
}
}
fn parse_outer(operator: &str, argument: &bson::Bson) -> Result<SubQuery, String> {
match operator {
"$and" => match argument {
Bson::Array(raw_subqueries) => {
let mut subqueries = Vec::new();
for val in raw_subqueries {
subqueries.push(SubQuery::parse_root(val)?);
}
Ok(SubQuery::And(subqueries))
}
_ => Err(format!(
"The $and operator needs an array as argument, got {:?}",
argument
)),
},
"$not" => Ok(SubQuery::Not(Box::new(SubQuery::parse_root(argument)?))),
"$or" => match argument {
Bson::Array(raw_subqueries) => {
let mut subqueries = Vec::new();
for val in raw_subqueries {
subqueries.push(SubQuery::parse_root(val)?);
}
Ok(SubQuery::Or(subqueries))
}
_ => Err(format!(
"The $and filter needs an array as argument, got {:?}",
argument
)),
},
value => {
if value.starts_with("$") {
Err(format!(
"This operator cannot be placed here, or is unsupported: {}",
value
))
} else {
SubQuery::parse_inner(value, argument)
}
}
}
}
fn parse_inner(field: &str, argument: &bson::Bson) -> Result<SubQuery, String> {
let (operator, argument) = match argument {
Bson::Document(doc) => {
if doc.len() != 1 {
return Err(format!(
"Operators need to be documents with one key, but got {:?}",
doc
));
}
doc.iter().next().unwrap()
}
value => return Ok(SubQuery::Eq(field.to_string(), value.clone())),
};
match operator.as_str() {
"$lt" => Ok(SubQuery::Lt(field.to_string(), argument.clone())),
"$lte" => Ok(SubQuery::Lte(field.to_string(), argument.clone())),
"$eq" => Ok(SubQuery::Eq(field.to_string(), argument.clone())),
"$ne" => Ok(SubQuery::Ne(field.to_string(), argument.clone())),
"$gt" => Ok(SubQuery::Gt(field.to_string(), argument.clone())),
"$gte" => Ok(SubQuery::Gte(field.to_string(), argument.clone())),
"$exists" => match argument {
Bson::Boolean(value) => Ok(SubQuery::Exists(field.to_string(), *value)),
_ => Err(format!(
"Expected boolean value for $exists filter, got {:?}",
argument
)),
},
op_name => Err(format!(
"This operator cannot be placed here, or is unsupported: {}",
op_name
)),
}
}
fn matches_document(&self, doc: &bson::Document) -> bool {
let comparison_or_return_if_not_exists =
|field: &str, value: &Bson, ordering: std::cmp::Ordering, negate: bool| -> bool {
let field_value = match doc.get(field) {
Some(doc_value) => doc_value,
None => return false,
};
match field_value {
Bson::Array(array) => {
match ordering {
std::cmp::Ordering::Equal => {
for array_value in array {
if helpers::compare_bson(array_value, value)
== std::cmp::Ordering::Equal
{
return !negate;
}
}
}
_ => {}
}
}
_ => {}
}
let order_is = helpers::compare_bson(field_value, value);
(order_is == ordering) ^ negate
};
match self {
SubQuery::MatchAll => true,
SubQuery::And(subqueries) => {
for subquery in subqueries {
if !subquery.matches_document(doc) {
return false;
}
}
true
}
SubQuery::Not(subquery) => !subquery.matches_document(doc),
SubQuery::Or(subqueries) => {
for subquery in subqueries {
if subquery.matches_document(doc) {
return true;
}
}
false
}
SubQuery::Lt(field, value) => {
comparison_or_return_if_not_exists(field, value, std::cmp::Ordering::Less, false)
}
SubQuery::Lte(field, value) => {
comparison_or_return_if_not_exists(field, value, std::cmp::Ordering::Greater, true)
}
SubQuery::Eq(field, value) => {
comparison_or_return_if_not_exists(field, value, std::cmp::Ordering::Equal, false)
}
SubQuery::Ne(field, value) => {
comparison_or_return_if_not_exists(field, value, std::cmp::Ordering::Equal, true)
}
SubQuery::Gt(field, value) => {
comparison_or_return_if_not_exists(field, value, std::cmp::Ordering::Greater, false)
}
SubQuery::Gte(field, value) => {
comparison_or_return_if_not_exists(field, value, std::cmp::Ordering::Less, true)
}
SubQuery::Exists(field, should_exist) => {
let does_exist = matches!(doc.get(field), Some(_));
*should_exist == does_exist
}
}
}
fn to_bson(&self) -> bson::Document {
match self {
SubQuery::MatchAll => bson::Document::new(),
SubQuery::And(subqueries) => bson::doc! {
"$and": subqueries.iter().map(|subquery| subquery.to_bson()).collect::<Vec<_>>()
},
SubQuery::Not(subquery) => bson::doc! {
"$not": subquery.to_bson()
},
SubQuery::Or(subqueries) => bson::doc! {
"$or": subqueries.iter().map(|subquery| subquery.to_bson()).collect::<Vec<_>>()
},
SubQuery::Exists(field, should_exist) => bson::doc! {
"$exists": {field: *should_exist}
},
SubQuery::Eq(field, value) => bson::doc! {
"$eq": {field: value}
},
SubQuery::Gt(field, value) => bson::doc! {
"$gt": {field: value}
},
SubQuery::Gte(field, value) => bson::doc! {
"$gte": {field: value}
},
SubQuery::Lt(field, value) => bson::doc! {
"$lt": {field: value}
},
SubQuery::Lte(field, value) => bson::doc! {
"$lte": {field: value}
},
SubQuery::Ne(field, value) => bson::doc! {
"$ne": {field: value}
},
}
}
}
pub struct FindQuery {
root_query: SubQuery,
}
impl FindQuery {
pub fn new_all() -> FindQuery {
FindQuery {
root_query: SubQuery::MatchAll,
}
}
pub fn parse_bson<B: Into<Bson>>(query: B) -> Result<FindQuery, String> {
let query: Bson = query.into();
Ok(FindQuery {
root_query: SubQuery::parse_root(&query)?,
})
}
pub fn to_bson(&self) -> bson::Document {
self.root_query.to_bson()
}
pub(crate) fn matches(&self, value: &bson::Document) -> bool {
self.root_query.matches_document(value)
}
}
impl fmt::Display for FindQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let as_bson = self.to_bson();
write!(
f,
"{}",
helpers::dump_to_json_string(as_bson.into(), true, true).unwrap()
)
}
}