use crate::database::{Database, Iter};
use crate::error::JasonError;
use crate::sources::Source;
use crate::util::indexing;
use humphrey_json::prelude::*;
pub use humphrey_json::Value;
use std::fmt::Debug;
use std::ops::{BitAnd, BitOr};
#[derive(Debug, PartialEq)]
pub struct Query {
pub(crate) predicates: Vec<Predicate>,
pub(crate) predicate_combination: PredicateCombination,
}
#[derive(Debug, PartialEq)]
pub enum Predicate {
Gt(String, f64),
Gte(String, f64),
Lt(String, f64),
Lte(String, f64),
Eq(String, Value),
Ne(String, Value),
Closure(String, PredicateClosure),
}
#[derive(Debug, PartialEq, Eq)]
pub enum PredicateCombination {
And,
Or,
}
pub struct PredicateClosure {
pub closure: Box<dyn Fn(&Value) -> bool>,
}
impl Query {
pub fn execute<'a, T, S>(
&self,
database: &'a mut Database<T, S>,
) -> Result<Iter<'a, T, S>, JasonError>
where
T: IntoJson + FromJson,
S: Source,
{
if self.is_optimisable(database) {
self.execute_optimised(database)
} else {
self.execute_unoptimised(database)
}
}
fn is_optimisable<T, S>(&self, database: &Database<T, S>) -> bool
where
T: IntoJson + FromJson,
S: Source,
{
match self.predicate_combination {
PredicateCombination::And => self.predicates.iter().any(|p| p.is_indexed(database)),
PredicateCombination::Or => self.predicates.iter().all(|p| p.is_indexed(database)),
}
}
pub(crate) fn execute_optimised<'a, T, S>(
&self,
database: &'a mut Database<T, S>,
) -> Result<Iter<'a, T, S>, JasonError>
where
T: IntoJson + FromJson,
S: Source,
{
let mut indexes = Vec::new();
let optimisable_predicates = self
.predicates
.iter()
.filter(|p| database.secondary_indexes.contains_key(p.key()))
.collect::<Vec<_>>();
let unoptimisable_predicates = self
.predicates
.iter()
.filter(|p| !database.secondary_indexes.contains_key(p.key()))
.collect::<Vec<_>>();
for predicate in &optimisable_predicates {
let index = database.secondary_indexes.get(predicate.key()).unwrap();
for (v, i) in index {
if predicate.matches_direct(v)? {
indexes.extend(i.iter());
}
}
}
let include: Box<dyn Fn(usize) -> bool> = match self.predicate_combination {
PredicateCombination::And => Box::new(|n: usize| n == optimisable_predicates.len()),
PredicateCombination::Or => Box::new(|n: usize| n > 0),
};
let mut combined_indexes = Vec::new();
let mut count = 0;
let mut last = 1;
#[allow(clippy::stable_sort_primitive)]
indexes.sort();
for index in indexes {
if last != index {
if include(count) {
combined_indexes.push(last);
}
last = index;
count = 1;
} else {
count += 1;
}
}
if include(count) {
combined_indexes.push(last);
}
if unoptimisable_predicates.is_empty() {
Ok(Iter {
database,
keys: combined_indexes.into_iter(),
})
} else {
let mut filtered_indexes = Vec::with_capacity(combined_indexes.len());
'outer: for index in combined_indexes {
let (_, v) = database.get_at_index(index)?;
for predicate in &unoptimisable_predicates {
if !predicate.matches(&v.to_json())? {
continue 'outer;
}
}
filtered_indexes.push(index);
}
Ok(Iter {
database,
keys: filtered_indexes.into_iter(),
})
}
}
pub(crate) fn execute_unoptimised<'a, T, S>(
&self,
database: &'a mut Database<T, S>,
) -> Result<Iter<'a, T, S>, JasonError>
where
T: IntoJson + FromJson,
S: Source,
{
let mut indexes = Vec::new();
let keys = database
.primary_indexes
.values()
.cloned()
.collect::<Vec<_>>();
for key in &keys {
let (_, v) = database.get_at_index(*key)?;
if self.matches(&v.to_json())? {
indexes.push(*key);
}
}
Ok(Iter {
database,
keys: indexes.into_iter(),
})
}
pub(crate) fn matches(&self, json: &Value) -> Result<bool, JasonError> {
match self.predicate_combination {
PredicateCombination::And => {
for predicate in &self.predicates {
if !predicate.matches(json)? {
return Ok(false);
}
}
Ok(true)
}
PredicateCombination::Or => {
for predicate in &self.predicates {
if predicate.matches(json)? {
return Ok(true);
}
}
Ok(false)
}
}
}
}
impl Predicate {
fn is_indexed<T, S>(&self, database: &Database<T, S>) -> bool
where
T: IntoJson + FromJson,
S: Source,
{
database.secondary_indexes.contains_key(self.key())
}
pub(crate) fn matches(&self, json: &Value) -> Result<bool, JasonError> {
match self {
Self::Gt(index, right) => {
let left = indexing::get_number(index, json)?;
Ok(left > *right)
}
Self::Gte(index, right) => {
let left = indexing::get_number(index, json)?;
Ok(left >= *right)
}
Self::Lt(index, right) => {
let left = indexing::get_number(index, json)?;
Ok(left < *right)
}
Self::Lte(index, right) => {
let left = indexing::get_number(index, json)?;
Ok(left <= *right)
}
Self::Eq(index, right) => {
let left = indexing::get_value(index, json);
Ok(left == *right)
}
Self::Ne(index, right) => {
let left = indexing::get_value(index, json);
Ok(left != *right)
}
Self::Closure(index, closure) => {
let left = indexing::get_value(index, json);
Ok((closure.closure)(&left))
}
}
}
pub(crate) fn matches_direct(&self, json: &Value) -> Result<bool, JasonError> {
match self {
Self::Gt(_, right) => {
let left = json.as_number().ok_or(JasonError::JsonError)?;
Ok(left > *right)
}
Self::Gte(_, right) => {
let left = json.as_number().ok_or(JasonError::JsonError)?;
Ok(left >= *right)
}
Self::Lt(_, right) => {
let left = json.as_number().ok_or(JasonError::JsonError)?;
Ok(left < *right)
}
Self::Lte(_, right) => {
let left = json.as_number().ok_or(JasonError::JsonError)?;
Ok(left <= *right)
}
Self::Eq(_, right) => Ok(*json == *right),
Self::Ne(_, right) => Ok(*json != *right),
Self::Closure(_, closure) => Ok((closure.closure)(json)),
}
}
pub(crate) fn key(&self) -> &str {
match self {
Self::Gt(key, _) => key,
Self::Gte(key, _) => key,
Self::Lt(key, _) => key,
Self::Lte(key, _) => key,
Self::Eq(key, _) => key,
Self::Ne(key, _) => key,
Self::Closure(key, _) => key,
}
}
}
impl From<Predicate> for Query {
fn from(predicate: Predicate) -> Self {
Self {
predicates: vec![predicate],
predicate_combination: PredicateCombination::And,
}
}
}
impl BitAnd for Query {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self {
predicates: self.predicates.into_iter().chain(rhs.predicates).collect(),
predicate_combination: PredicateCombination::And,
}
}
}
impl BitOr for Query {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self {
predicates: self.predicates.into_iter().chain(rhs.predicates).collect(),
predicate_combination: PredicateCombination::Or,
}
}
}
impl Debug for PredicateClosure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PredicateClosure").finish()
}
}
impl PartialEq for PredicateClosure {
fn eq(&self, _: &Self) -> bool {
false
}
}
#[macro_export]
macro_rules! query {
($($field:ident).+ > $value:expr) => {
$crate::query::Query::from($crate::query::Predicate::Gt(
stringify!($($field).+).to_string(),
f64::from($value),
))
};
($($field:ident).+ >= $value:expr) => {
$crate::query::Query::from($crate::query::Predicate::Gte(
stringify!($($field).+).to_string(),
f64::from($value),
))
};
($($field:ident).+ < $value:expr) => {
$crate::query::Query::from($crate::query::Predicate::Lt(
stringify!($($field).+).to_string(),
f64::from($value),
))
};
($($field:ident).+ <= $value:expr) => {
$crate::query::Query::from($crate::query::Predicate::Lte(
stringify!($($field).+).to_string(),
f64::from($value),
))
};
($($field:ident).+ == null) => {
$crate::query::Query::from($crate::query::Predicate::Eq(
stringify!($($field).+).to_string(),
$crate::query::Value::Null,
))
};
($($field:ident).+ != null) => {
$crate::query::Query::from($crate::query::Predicate::Ne(
stringify!($($field).+).to_string(),
$crate::query::Value::Null,
))
};
($($field:ident).+ == $value:expr) => {
$crate::query::Query::from($crate::query::Predicate::Eq(
stringify!($($field).+).to_string(),
$crate::query::Value::from($value),
))
};
($($field:ident).+ != $value:expr) => {
$crate::query::Query::from($crate::query::Predicate::Ne(
stringify!($($field).+).to_string(),
$crate::query::Value::from($value),
))
};
($($field:ident).+) => {
$crate::query::Query::from($crate::query::Predicate::Eq(
stringify!($($field).+).to_string(),
$crate::query::Value::Bool(true),
))
};
($($field:ident).+, $closure:expr) => {
$crate::query::Query::from($crate::query::Predicate::Closure(
stringify!($($field).+).to_string(),
$crate::query::PredicateClosure {
closure: Box::new($closure),
},
))
};
}
#[macro_export]
macro_rules! field {
($($field:ident).+) => {
stringify!($($field).+).to_string()
}
}