Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 77 additions & 31 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use pg_escape::{quote_identifier, quote_literal};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashSet;
use std::fmt::Debug;
use std::ops::{Add, Deref};
use std::str::FromStr;
use unaccent::unaccent;
Expand Down Expand Up @@ -245,6 +246,11 @@ impl Expr {
/// let toexpr: Expr = Expr::from_str("20").unwrap();
/// assert_eq!(reduced, toexpr);
///
/// let fromexpr: Expr = Expr::from_str("(bork=1) and (bork=1) and (bork=1 and true)").unwrap();
/// let reduced = fromexpr.reduce(Some(&item)).unwrap();
/// let toexpr: Expr = Expr::from_str("bork=1").unwrap();
/// assert_eq!(reduced, toexpr);
///
/// ```
pub fn reduce(self, j: Option<&Value>) -> Result<Expr, Error> {
match self {
Expand All @@ -270,19 +276,55 @@ impl Expr {
.collect::<Result<_, _>>()?;

if BOOLOPS.contains(&op.as_str()) {
let bools: Result<Vec<bool>, Error> = args
.iter()
.map(|expr| bool::try_from(expr.as_ref()))
.collect();

if let Ok(bools) = bools {
match op.as_str() {
"and" => Ok(Expr::Bool(bools.into_iter().all(|x| x))),
"or" => Ok(Expr::Bool(bools.into_iter().any(|x| x))),
_ => Ok(Expr::Operation { op, args }),
let curop = op.clone();
let mut dedupargs: Vec<Box<Expr>> = vec![];
let mut nestedargs: Vec<Box<Expr>> = vec![];
for a in args {
match *a {
Expr::Operation { op, args } if op == curop => {
nestedargs.append(&mut args.clone());
}
_ => {
dedupargs.push(a.clone());
}
}
}
dedupargs.append(&mut nestedargs);
dedupargs.sort_by(|a, b| a.partial_cmp(b).unwrap());
dedupargs.dedup();

let mut anytrue: bool = false;
let mut anyfalse: bool = false;
let mut anyexp: bool = false;

for a in dedupargs.iter() {
let b = bool::try_from(a.as_ref());
match b {
Ok(true) => {
anytrue = true;
}
Ok(false) => {
anyfalse = true;
}
_ => {
anyexp = true;
}
}
}
if op == "and" && anytrue {
dedupargs.retain(|x| !bool::try_from(x.as_ref()).unwrap_or(false));
}
if dedupargs.len() == 1 {
Ok(dedupargs.pop().unwrap().as_ref().clone())
} else if (op == "and" && anyfalse) || (op == "or" && !anytrue && !anyexp) {
Ok(Expr::Bool(false))
} else if (op == "and" && !anyfalse && !anyexp) || (op == "or" && anytrue) {
Ok(Expr::Bool(true))
} else {
Ok(Expr::Operation { op, args })
Ok(Expr::Operation {
op,
args: dedupargs.clone(),
})
}
} else if op == "not" {
match args[0].deref() {
Expand All @@ -308,26 +350,30 @@ impl Expr {
let left = args[0].deref().clone();
let right = args[1].deref().clone();

if SPATIALOPS.contains(&op.as_str()) {
Ok(spatial_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if TEMPORALOPS.contains(&op.as_str()) {
Ok(temporal_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if ARITHOPS.contains(&op.as_str()) {
Ok(arith_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if EQOPS.contains(&op.as_str()) || CMPOPS.contains(&op.as_str()) {
Ok(cmp_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if ARRAYOPS.contains(&op.as_str()) {
Ok(array_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if op == "like" {
let l: String = left.try_into()?;
let r: String = right.try_into()?;
let m: bool = Like::<true>::like(l.as_str(), r.as_str())?;
Ok(Expr::Bool(m))
if std::mem::discriminant(&left) == std::mem::discriminant(&right) {
if SPATIALOPS.contains(&op.as_str()) {
Ok(spatial_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if TEMPORALOPS.contains(&op.as_str()) {
Ok(temporal_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if ARITHOPS.contains(&op.as_str()) {
Ok(arith_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if EQOPS.contains(&op.as_str()) || CMPOPS.contains(&op.as_str()) {
Ok(cmp_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if ARRAYOPS.contains(&op.as_str()) {
Ok(array_op(left, right, &op)
.unwrap_or_else(|_| Expr::Operation { op, args }))
} else if op == "like" {
let l: String = left.try_into()?;
let r: String = right.try_into()?;
let m: bool = Like::<true>::like(l.as_str(), r.as_str())?;
Ok(Expr::Bool(m))
} else {
Ok(Expr::Operation { op, args })
}
} else if op == "in" {
let l: String = left.to_text()?;
let r: HashSet<String> = right.try_into()?;
Expand Down
10 changes: 10 additions & 0 deletions tests/reductions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,13 @@ POINT(0 0) = POINT(0 0)
true
POINT(0 0) = POINT(1 0)
false
a and b and c and a and a
a and b and c
true or b = false
true
true or false or c=3
true
false or c=3 or c=3
c=3 or false
true and c=3
c=3
6 changes: 3 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.