From cef5164938e5e26e8d4f282860a04fb60cc6542f Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Thu, 12 Dec 2024 15:21:19 -0600 Subject: [PATCH 01/17] start of work to match cql2 against json --- Cargo.lock | 13 +++++ Cargo.toml | 1 + src/expr.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index bc68e7a..bcb1937 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "geo-types", "geojson", "geozero", + "json_dotpath", "lazy_static", "pest", "pest_derive", @@ -657,6 +658,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "json_dotpath" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbdcfef3cf5591f0cef62da413ae795e3d1f5a00936ccec0b2071499a32efd1a" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "lazy_static" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index d84ab7b..1d1455c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ boon = "0.6.0" geo-types = "0.7.13" geojson = "0.24.1" geozero = "0.14.0" +json_dotpath = "1.1.0" lazy_static = "1.5" pest = "2.7" pest_derive = { version = "2.7", features = ["grammar-extras"] } diff --git a/src/expr.rs b/src/expr.rs index 390ba9a..c994653 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -3,6 +3,7 @@ use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::str::FromStr; +use json_dotpath::DotPaths; /// A CQL2 expression. /// @@ -35,7 +36,157 @@ pub enum Expr { Geometry(Geometry), } +impl From for Expr{ + fn from(v: Value)-> Expr { + let e: Expr = serde_json::from_value(v).unwrap(); + e + } +} + +impl Into for Expr{ + fn into(self) -> Value { + let v: Value = serde_json::to_value(self).unwrap(); + v + } +} + +impl TryInto for Expr{ + type Error = (); + fn try_into(self) -> Result { + match self { + Expr::Float(v) => Ok(v), + Expr::Literal(v) => f64::from_str(&v).or(Err(())), + _ => Err(()) + } + } +} + impl Expr { + /// Insert values from properties from json + /// + /// # Examples + /// + /// ``` + /// use serde_json::{json, Value}; + /// use cql2::Expr; + /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z"}}); + /// let mut expr_json = json!( + /// { + /// "op": "+", + /// "args": [ + /// {"property": "eo:cloud_cover"}, + /// 10 + /// ] + /// } + /// ); + /// + /// let mut expr: Expr = serde_json::from_value(expr_json).unwrap(); + /// println!("Initial {:?}", expr); + /// expr.reduce(&item); + /// + /// let output: f64; + /// if let Expr::Float(v) = expr { + /// output = v; + /// } else { + /// assert!(false); + /// output = 0.0; + /// } + /// println!("Modified {:?}", expr); + /// + /// assert_eq!(20.0, output); + /// + /// ``` + pub fn reduce(&mut self, j:&Value) { + match self { + Expr::Property{property} => { + let mut prefixproperty: String = "properties.".to_string(); + prefixproperty.push_str(property); + + let propexpr: Option; + if j.dot_has(&property) { + propexpr = j.dot_get(&property).unwrap(); + } else { + let mut prefixproperty: String = "properties.".to_string(); + prefixproperty.push_str(property); + propexpr = j.dot_get(&prefixproperty).unwrap(); + } + if let Some(v) = propexpr { + *self = Expr::from(v); + } + + }, + Expr::Operation{op, args} => { + for arg in args.iter_mut() { + arg.reduce(j); + }; + + // binary operations + if args.len() == 2 { + // numerical binary operations + let left: Result = (*args[0].clone()).try_into(); + let right: Result = (*args[1].clone()).try_into(); + if let (Ok(l),Ok(r)) = (left, right) { + match op.as_str() { + "+" => {*self = Expr::Float(l + r);}, + "-" => {*self = Expr::Float(l - r);}, + "*" => {*self = Expr::Float(l * r);}, + "/" => {*self = Expr::Float(l / r);}, + "%" => {*self = Expr::Float(l % r);}, + "^" => {*self = Expr::Float(l.powf(r));}, + "=" => {*self = Expr::Bool(l == r);}, + "<=" => {*self = Expr::Bool(l <= r);}, + "<" => {*self = Expr::Bool(l < r);}, + ">=" => {*self = Expr::Bool(l >= r);}, + ">" => {*self = Expr::Bool(l > r);}, + "<>" => {*self = Expr::Bool(l != r);}, + _ => () + } + } + + } + }, + _ => () + } + } + /// Run CQL against a JSON Value + /// + /// # Examples + /// + /// ``` + /// use serde_json::{json, Value}; + /// use cql2::Expr; + /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z"}}); + /// let mut expr_json = json!( + /// { + /// "op" : ">", + /// "args" : [ + /// { + /// "op": "+", + /// "args": [ + /// {"property": "eo:cloud_cover"}, + /// 17 + /// ] + /// }, + /// 2 + /// ] + /// } + /// ); + /// + /// + /// let mut expr: Expr = serde_json::from_value(expr_json).unwrap(); + /// + /// + /// assert_eq!(true, expr.matches(&item).unwrap()); + /// + /// ``` + pub fn matches(&self, j: &Value) -> Result { + let mut e = self.clone(); + e.reduce(j); + match e { + Expr::Bool(v) => Ok(v), + _ => Err(()) + } + } /// Converts this expression to CQL2 text. /// /// # Examples From 8ac73fac6643cae2cfe208c46aba2d78e9c44250 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Thu, 12 Dec 2024 17:18:22 -0600 Subject: [PATCH 02/17] add boolean ops, add reduce option to cli --- Cargo.lock | 133 ++++++++++++++++++++++++++++++++------------- Cargo.toml | 1 + cli/src/lib.rs | 10 +++- src/expr.rs | 144 +++++++++++++++++++++++++++++++++++-------------- 4 files changed, 211 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bcb1937..d4fb1f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,10 +185,10 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -218,6 +218,7 @@ version = "0.3.2" dependencies = [ "assert-json-diff", "boon", + "derive_is_enum_variant", "geo-types", "geojson", "geozero", @@ -264,6 +265,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "derive_is_enum_variant" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ac8859845146979953797f03cc5b282fb4396891807cdb3d04929a88418197" +dependencies = [ + "heck 0.3.3", + "quote 0.3.15", + "syn 0.11.11", +] + [[package]] name = "digest" version = "0.10.7" @@ -281,8 +293,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -364,8 +376,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -475,6 +487,15 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.5.0" @@ -595,8 +616,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -767,8 +788,8 @@ dependencies = [ "pest", "pest_meta", "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -820,8 +841,8 @@ dependencies = [ "phf_generator", "phf_shared", "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -915,8 +936,8 @@ checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257" dependencies = [ "proc-macro2", "pyo3-macros-backend", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -925,11 +946,11 @@ version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "pyo3-build-config", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -942,6 +963,12 @@ dependencies = [ "serde", ] +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + [[package]] name = "quote" version = "1.0.37" @@ -1023,11 +1050,11 @@ dependencies = [ "glob", "proc-macro-crate", "proc-macro2", - "quote", + "quote 1.0.37", "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.90", "unicode-ident", ] @@ -1068,8 +1095,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1129,6 +1156,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid", +] + [[package]] name = "syn" version = "2.0.90" @@ -1136,10 +1174,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", - "quote", + "quote 1.0.37", "unicode-ident", ] +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -1147,8 +1194,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1182,8 +1229,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1193,8 +1240,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1272,6 +1319,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + [[package]] name = "unindent" version = "0.2.3" @@ -1444,8 +1503,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", "synstructure", ] @@ -1465,8 +1524,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] @@ -1485,8 +1544,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", "synstructure", ] @@ -1508,6 +1567,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", - "quote", - "syn", + "quote 1.0.37", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index 1d1455c..9af08b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ keywords = ["cql2"] [dependencies] boon = "0.6.0" +derive_is_enum_variant = "0.1.1" geo-types = "0.7.13" geojson = "0.24.1" geozero = "0.14.0" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b3bb6b0..0612d44 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use clap::{ArgAction, Parser, ValueEnum}; use cql2::{Expr, Validator}; +use serde_json::json; use std::io::Read; /// The CQL2 command-line interface. @@ -30,6 +31,10 @@ pub struct Cli { #[arg(long, default_value_t = true, action = ArgAction::Set)] validate: bool, + /// Reduce the CQL2 + #[arg(long, default_value_t = false, action = ArgAction::Set)] + reduce: bool, + /// Verbosity. /// /// Provide this argument several times to turn up the chatter. @@ -95,7 +100,7 @@ impl Cli { InputFormat::Text } }); - let expr: Expr = match input_format { + let mut expr: Expr = match input_format { InputFormat::Json => cql2::parse_json(&input)?, InputFormat::Text => match cql2::parse_text(&input) { Ok(expr) => expr, @@ -104,6 +109,9 @@ impl Cli { } }, }; + if self.reduce { + expr.reduce(&json!({})); + } if self.validate { let validator = Validator::new().unwrap(); let value = serde_json::to_value(&expr).unwrap(); diff --git a/src/expr.rs b/src/expr.rs index c994653..9f9c59f 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,9 +1,10 @@ use crate::{Error, Geometry, SqlQuery, Validator}; +use derive_is_enum_variant::is_enum_variant; +use json_dotpath::DotPaths; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::str::FromStr; -use json_dotpath::DotPaths; /// A CQL2 expression. /// @@ -19,7 +20,7 @@ use json_dotpath::DotPaths; /// /// Use [Expr::to_text], [Expr::to_json], and [Expr::to_sql] to use the CQL2, /// and use [Expr::is_valid] to check validity. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, is_enum_variant)] #[serde(untagged)] #[allow(missing_docs)] pub enum Expr { @@ -36,27 +37,38 @@ pub enum Expr { Geometry(Geometry), } -impl From for Expr{ - fn from(v: Value)-> Expr { +impl From for Expr { + fn from(v: Value) -> Expr { let e: Expr = serde_json::from_value(v).unwrap(); e } } -impl Into for Expr{ - fn into(self) -> Value { - let v: Value = serde_json::to_value(self).unwrap(); +impl From for Value { + fn from(v: Expr) -> Value { + let v: Value = serde_json::to_value(v).unwrap(); v } } -impl TryInto for Expr{ + +impl TryInto for Expr { type Error = (); fn try_into(self) -> Result { match self { Expr::Float(v) => Ok(v), Expr::Literal(v) => f64::from_str(&v).or(Err(())), - _ => Err(()) + _ => Err(()), + } + } +} + +impl TryInto for Expr { + type Error = (); + fn try_into(self) -> Result { + match self { + Expr::Bool(v) => Ok(v), + _ => Err(()), } } } @@ -69,7 +81,7 @@ impl Expr { /// ``` /// use serde_json::{json, Value}; /// use cql2::Expr; - /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z"}}); + /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); /// let mut expr_json = json!( /// { /// "op": "+", @@ -95,16 +107,17 @@ impl Expr { /// /// assert_eq!(20.0, output); /// + /// /// ``` - pub fn reduce(&mut self, j:&Value) { + pub fn reduce(&mut self, j: &Value) { match self { - Expr::Property{property} => { + Expr::Property { property } => { let mut prefixproperty: String = "properties.".to_string(); prefixproperty.push_str(property); - let propexpr: Option; - if j.dot_has(&property) { - propexpr = j.dot_get(&property).unwrap(); + let mut propexpr: Option = None; + if j.dot_has(property) { + propexpr = j.dot_get(property).unwrap(); } else { let mut prefixproperty: String = "properties.".to_string(); prefixproperty.push_str(property); @@ -113,39 +126,89 @@ impl Expr { if let Some(v) = propexpr { *self = Expr::from(v); } - - }, - Expr::Operation{op, args} => { + } + Expr::Operation { op, args } => { + let mut alltrue: bool = true; + let mut anytrue: bool = false; + let mut allbool: bool = true; for arg in args.iter_mut() { arg.reduce(j); - }; + let b: Result = arg.as_ref().clone().try_into(); + match b { + Ok(true) => anytrue = true, + Ok(false) => { + alltrue = false; + } + _ => { + alltrue = false; + allbool = false; + } + } + } + + // boolean operators + if allbool { + match op.as_str() { + "and" => { + *self = Expr::Bool(alltrue); + } + "or" => { + *self = Expr::Bool(anytrue); + } + _ => (), + } + return; + } // binary operations if args.len() == 2 { // numerical binary operations let left: Result = (*args[0].clone()).try_into(); let right: Result = (*args[1].clone()).try_into(); - if let (Ok(l),Ok(r)) = (left, right) { + if let (Ok(l), Ok(r)) = (left, right) { match op.as_str() { - "+" => {*self = Expr::Float(l + r);}, - "-" => {*self = Expr::Float(l - r);}, - "*" => {*self = Expr::Float(l * r);}, - "/" => {*self = Expr::Float(l / r);}, - "%" => {*self = Expr::Float(l % r);}, - "^" => {*self = Expr::Float(l.powf(r));}, - "=" => {*self = Expr::Bool(l == r);}, - "<=" => {*self = Expr::Bool(l <= r);}, - "<" => {*self = Expr::Bool(l < r);}, - ">=" => {*self = Expr::Bool(l >= r);}, - ">" => {*self = Expr::Bool(l > r);}, - "<>" => {*self = Expr::Bool(l != r);}, - _ => () + "+" => { + *self = Expr::Float(l + r); + } + "-" => { + *self = Expr::Float(l - r); + } + "*" => { + *self = Expr::Float(l * r); + } + "/" => { + *self = Expr::Float(l / r); + } + "%" => { + *self = Expr::Float(l % r); + } + "^" => { + *self = Expr::Float(l.powf(r)); + } + "=" => { + *self = Expr::Bool(l == r); + } + "<=" => { + *self = Expr::Bool(l <= r); + } + "<" => { + *self = Expr::Bool(l < r); + } + ">=" => { + *self = Expr::Bool(l >= r); + } + ">" => { + *self = Expr::Bool(l > r); + } + "<>" => { + *self = Expr::Bool(l != r); + } + _ => (), } } - } - }, - _ => () + } + _ => (), } } /// Run CQL against a JSON Value @@ -155,7 +218,7 @@ impl Expr { /// ``` /// use serde_json::{json, Value}; /// use cql2::Expr; - /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z"}}); + /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); /// let mut expr_json = json!( /// { /// "op" : ">", @@ -178,13 +241,16 @@ impl Expr { /// /// assert_eq!(true, expr.matches(&item).unwrap()); /// + /// + /// let mut expr2: Expr = "boolfield and 1 + 2 = 3".parse().unwrap(); + /// assert_eq!(true, expr2.matches(&item).unwrap()); /// ``` - pub fn matches(&self, j: &Value) -> Result { + pub fn matches(&self, j: &Value) -> Result { let mut e = self.clone(); e.reduce(j); match e { Expr::Bool(v) => Ok(v), - _ => Err(()) + _ => Err(()), } } /// Converts this expression to CQL2 text. From 93d7940d4592df3fefcfdc1488d4de4000a72fc5 Mon Sep 17 00:00:00 2001 From: David Bitner Date: Mon, 16 Dec 2024 09:19:13 -0600 Subject: [PATCH 03/17] Update src/expr.rs Co-authored-by: Pete Gadomski --- src/expr.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index 9f9c59f..bb36373 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -112,17 +112,7 @@ impl Expr { pub fn reduce(&mut self, j: &Value) { match self { Expr::Property { property } => { - let mut prefixproperty: String = "properties.".to_string(); - prefixproperty.push_str(property); - - let mut propexpr: Option = None; - if j.dot_has(property) { - propexpr = j.dot_get(property).unwrap(); - } else { - let mut prefixproperty: String = "properties.".to_string(); - prefixproperty.push_str(property); - propexpr = j.dot_get(&prefixproperty).unwrap(); - } + let propexpr = j.dot_get(property).or_else(|_| j.dot_get(&format!("properties.{}", property)))?; if let Some(v) = propexpr { *self = Expr::from(v); } From fa337c43d909576d3ac072729574a44a701aa142 Mon Sep 17 00:00:00 2001 From: David Bitner Date: Mon, 16 Dec 2024 09:21:34 -0600 Subject: [PATCH 04/17] Update src/expr.rs Co-authored-by: Pete Gadomski --- src/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expr.rs b/src/expr.rs index bb36373..6d6d734 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -74,7 +74,7 @@ impl TryInto for Expr { } impl Expr { - /// Insert values from properties from json + /// Update this expression with values from the `properties` attribute of a JSON object /// /// # Examples /// From bf6c285fc0e4671e4b45c5c2aa54dcd7848e764d Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 17 Dec 2024 12:39:14 -0600 Subject: [PATCH 05/17] add support for temporal and geospatial operators, add reduce tests --- tests/reduce_tests.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/reductions.txt | 24 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/reduce_tests.rs create mode 100644 tests/reductions.txt diff --git a/tests/reduce_tests.rs b/tests/reduce_tests.rs new file mode 100644 index 0000000..215f190 --- /dev/null +++ b/tests/reduce_tests.rs @@ -0,0 +1,42 @@ +use cql2::Expr; +use rstest::rstest; +use std::path::Path; +use serde_json::{Value, json}; + +fn read_lines(filename: impl AsRef) -> Vec { + std::fs::read_to_string(filename) + .unwrap() // panic on possible file-reading errors + .lines() // split the string into an iterator of string slices + .map(String::from) // make each slice into a string + .collect() // gather them together into a vector +} +fn validate_reduction(a: String, b: String){ + let properties: Value = json!( + { + "properties": { + "eo:cloud_cover": 10, + "boolfalse": false, + "booltrue": true, + "stringfield": "string", + "tsfield": {"timestamp": "2020-01-01 00:00:00Z"} + }, + "geometry": {"type": "Point", "coordinates": [-93.0, 45]}, + "datetime": "2020-01-01 00:00:00Z" + } + ); + let mut inexpr: Expr = a.parse().unwrap(); + inexpr.reduce(Some(&properties)); + let outexpr: Expr = b.parse().unwrap(); + assert_eq!(inexpr, outexpr); +} + +#[rstest] +fn validate_reduce_fixtures() { + let lines = read_lines("tests/reductions.txt"); + let a = lines.clone().into_iter().step_by(2); + let b = lines.clone().into_iter().skip(1).step_by(2); + let zipped = a.zip(b); + for (a,b) in zipped{ + validate_reduction(a, b); + } +} diff --git a/tests/reductions.txt b/tests/reductions.txt new file mode 100644 index 0000000..f48da18 --- /dev/null +++ b/tests/reductions.txt @@ -0,0 +1,24 @@ +1 + 1 +2 +1 + 2 = 4 +false +1 + 3 = 7 +false +true and false or true +true +"eo:cloud_cover" = 10 +true +eo:cloud_cover + 10 = 20 +true +booltrue +true +boolfalse +false +booltrue or boolfalse +true +2 > eo:cloud_cover +false +2 > eo:cloud_cover - 10 +true +S_EQUALS(POINT(-93.0 45.0), geometry) +true From f9a6c2267a45d4ecbb40df921512f020d41908b3 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 17 Dec 2024 12:39:43 -0600 Subject: [PATCH 06/17] mend --- Cargo.lock | 272 ++++++++++++++++++++++++++++---------------- Cargo.toml | 4 +- cli/src/lib.rs | 2 +- src/error.rs | 24 ++++ src/expr.rs | 296 +++++++++++++++++++++++++++--------------------- src/geometry.rs | 16 +++ src/lib.rs | 4 +- src/temporal.rs | 65 +++++++++++ 8 files changed, 458 insertions(+), 225 deletions(-) create mode 100644 src/temporal.rs diff --git a/Cargo.lock b/Cargo.lock index d4fb1f0..e1b663e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,6 +151,12 @@ dependencies = [ "url", ] +[[package]] +name = "c_vec" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd7a427adc0135366d99db65b36dae9237130997e560ed61118041fb72be6e8" + [[package]] name = "cfg-if" version = "1.0.0" @@ -185,10 +191,10 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -218,10 +224,12 @@ version = "0.3.2" dependencies = [ "assert-json-diff", "boon", - "derive_is_enum_variant", + "enum-as-inner", "geo-types", "geojson", + "geos", "geozero", + "jiff", "json_dotpath", "lazy_static", "pest", @@ -265,17 +273,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "derive_is_enum_variant" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ac8859845146979953797f03cc5b282fb4396891807cdb3d04929a88418197" -dependencies = [ - "heck 0.3.3", - "quote 0.3.15", - "syn 0.11.11", -] - [[package]] name = "digest" version = "0.10.7" @@ -293,8 +290,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -376,8 +385,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -450,6 +459,29 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "geos" +version = "9.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d199db00644057267a8a68ee72df92aa59a32036b487b2a2b76fd0b3fca32b" +dependencies = [ + "c_vec", + "geos-sys", + "libc", + "num", +] + +[[package]] +name = "geos-sys" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc873d24aefc72aa94c3c1c251afb82beb7be5926002746c0e1f585fef9854c" +dependencies = [ + "libc", + "pkg-config", + "semver", +] + [[package]] name = "geozero" version = "0.14.0" @@ -487,15 +519,6 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -616,8 +639,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -679,6 +702,31 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "jiff" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" +dependencies = [ + "jiff-tzdb-platform", + "windows-sys", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "json_dotpath" version = "1.1.0" @@ -736,6 +784,70 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -788,8 +900,8 @@ dependencies = [ "pest", "pest_meta", "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -841,8 +953,8 @@ dependencies = [ "phf_generator", "phf_shared", "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -866,6 +978,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "portable-atomic" version = "1.10.0" @@ -936,8 +1054,8 @@ checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257" dependencies = [ "proc-macro2", "pyo3-macros-backend", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -946,11 +1064,11 @@ version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "pyo3-build-config", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -963,12 +1081,6 @@ dependencies = [ "serde", ] -[[package]] -name = "quote" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" - [[package]] name = "quote" version = "1.0.37" @@ -1050,11 +1162,11 @@ dependencies = [ "glob", "proc-macro-crate", "proc-macro2", - "quote 1.0.37", + "quote", "regex", "relative-path", "rustc_version", - "syn 2.0.90", + "syn", "unicode-ident", ] @@ -1095,8 +1207,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -1156,17 +1268,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -dependencies = [ - "quote 0.3.15", - "synom", - "unicode-xid", -] - [[package]] name = "syn" version = "2.0.90" @@ -1174,19 +1275,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", - "quote 1.0.37", + "quote", "unicode-ident", ] -[[package]] -name = "synom" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -dependencies = [ - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.1" @@ -1194,8 +1286,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -1229,8 +1321,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -1240,8 +1332,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -1319,18 +1411,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" - [[package]] name = "unindent" version = "0.2.3" @@ -1503,8 +1583,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", "synstructure", ] @@ -1524,8 +1604,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] [[package]] @@ -1544,8 +1624,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", "synstructure", ] @@ -1567,6 +1647,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", - "quote 1.0.37", - "syn 2.0.90", + "quote", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 9af08b8..d7e9d4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,12 @@ keywords = ["cql2"] [dependencies] boon = "0.6.0" -derive_is_enum_variant = "0.1.1" +enum-as-inner = "0.6.1" geo-types = "0.7.13" geojson = "0.24.1" +geos = "9.1.1" geozero = "0.14.0" +jiff = "0.1.15" json_dotpath = "1.1.0" lazy_static = "1.5" pest = "2.7" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 0612d44..e9ad861 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -110,7 +110,7 @@ impl Cli { }, }; if self.reduce { - expr.reduce(&json!({})); + expr.reduce(Some(&json!({}))); } if self.validate { let validator = Validator::new().unwrap(); diff --git a/src/error.rs b/src/error.rs index b904c43..485f11c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,6 +12,10 @@ pub enum Error { #[error(transparent)] Geozero(#[from] geozero::error::GeozeroError), + /// [geos::Error] + #[error(transparent)] + Geos(#[from] geos::Error), + /// Invalid CQL2 text #[error("invalid cql2-text: {0}")] InvalidCql2Text(String), @@ -65,4 +69,24 @@ pub enum Error { /// validator's data. #[error("validation error")] Validation(serde_json::Value), + + /// Error Converting Expr to f64 + #[error("Could not convert Expression to f64")] + ExprToF64(), + + /// Error Converting Expr to bool + #[error("Could not convert Expression to bool")] + ExprToBool(), + + /// Error Converting Expr to geos geometry + #[error("Could not convert Expression to Geos Geometry")] + ExprToGeom(), + + /// Error Converting Expr to DateRange + #[error("Could not convert Expression to DateRange")] + ExprToDateRange(), + + /// Operator not implemented. + #[error("Operator {0} is not implemented for this type.")] + OpNotImplemented(&'static str), } diff --git a/src/expr.rs b/src/expr.rs index 6d6d734..b088806 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,5 +1,6 @@ -use crate::{Error, Geometry, SqlQuery, Validator}; -use derive_is_enum_variant::is_enum_variant; +use crate::{DateRange, Error, Geometry, SqlQuery, Validator, geometry::spatial_op, temporal::temporal_op}; +use enum_as_inner::EnumAsInner; +use geos::Geometry as GGeom; use json_dotpath::DotPaths; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; @@ -20,7 +21,7 @@ use std::str::FromStr; /// /// Use [Expr::to_text], [Expr::to_json], and [Expr::to_sql] to use the CQL2, /// and use [Expr::is_valid] to check validity. -#[derive(Debug, Serialize, Deserialize, Clone, is_enum_variant)] +#[derive(Debug, Serialize, Deserialize, Clone, EnumAsInner)] #[serde(untagged)] #[allow(missing_docs)] pub enum Expr { @@ -37,42 +38,95 @@ pub enum Expr { Geometry(Geometry), } -impl From for Expr { - fn from(v: Value) -> Expr { - let e: Expr = serde_json::from_value(v).unwrap(); - e +impl TryFrom for Expr { + type Error = Error; + fn try_from(v: Value) -> Result { + serde_json::from_value(v).map_err(Error::from) } } -impl From for Value { - fn from(v: Expr) -> Value { - let v: Value = serde_json::to_value(v).unwrap(); - v +impl TryFrom for Value { + type Error = Error; + fn try_from(v: Expr) -> Result { + serde_json::to_value(v).map_err(Error::from) } } - -impl TryInto for Expr { - type Error = (); - fn try_into(self) -> Result { - match self { +impl TryFrom for f64 { + type Error = Error; + fn try_from(v: Expr) -> Result { + match v { Expr::Float(v) => Ok(v), - Expr::Literal(v) => f64::from_str(&v).or(Err(())), - _ => Err(()), + Expr::Literal(v) => f64::from_str(&v).map_err(Error::from), + _ => Err(Error::ExprToF64()), } } } -impl TryInto for Expr { - type Error = (); - fn try_into(self) -> Result { - match self { +impl TryFrom for bool { + type Error = Error; + fn try_from(v: Expr) -> Result { + match v { Expr::Bool(v) => Ok(v), - _ => Err(()), + Expr::Literal(v) => bool::from_str(&v).map_err(Error::from), + _ => Err(Error::ExprToBool()), } } } +impl TryFrom for String { + type Error = Error; + fn try_from(v: Expr) -> Result { + match v { + Expr::Literal(v) => Ok(v), + Expr::Bool(v) => Ok(v.to_string()), + Expr::Float(v) => Ok(v.to_string()), + _ => Err(Error::ExprToBool()), + } + } +} + +impl TryFrom for GGeom { + type Error = Error; + fn try_from(v: Expr) -> Result { + match v { + Expr::Geometry(v) => Ok(GGeom::new_from_wkt(&v.to_wkt().unwrap()) + .expect("Failed to convert WKT to Geos Geometry")), + _ => Err(Error::ExprToGeom()), + } + } +} + +impl PartialEq for Expr { + fn eq(&self, other: &Self) -> bool { + self.to_text().unwrap() == other.to_text().unwrap() + } +} + +fn binary_bool(left: &T, right: &T, op: &str) -> Result { + match op { + "=" => Ok(left == right), + "<=" => Ok(left <= right), + "<" => Ok(left < right), + ">=" => Ok(left >= right), + ">" => Ok(left > right), + "<>" => Ok(left != right), + _ => Err(Error::OpNotImplemented("Binary Bool")), + } +} + +fn arith(left: &f64, right: &f64, op: &str) -> Result { + match op { + "+" => Ok(left + right), + "-" => Ok(left - right), + "*" => Ok(left * right), + "/" => Ok(left / right), + "%" => Ok(left % right), + "^" => Ok(left.powf(*right)), + _ => Err(Error::OpNotImplemented("Arith")) + } +} + impl Expr { /// Update this expression with values from the `properties` attribute of a JSON object /// @@ -81,40 +135,48 @@ impl Expr { /// ``` /// use serde_json::{json, Value}; /// use cql2::Expr; - /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); - /// let mut expr_json = json!( - /// { - /// "op": "+", - /// "args": [ - /// {"property": "eo:cloud_cover"}, - /// 10 - /// ] - /// } - /// ); + /// use std::str::FromStr; /// - /// let mut expr: Expr = serde_json::from_value(expr_json).unwrap(); - /// println!("Initial {:?}", expr); - /// expr.reduce(&item); - /// - /// let output: f64; - /// if let Expr::Float(v) = expr { - /// output = v; - /// } else { - /// assert!(false); - /// output = 0.0; - /// } - /// println!("Modified {:?}", expr); + /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); /// - /// assert_eq!(20.0, output); + /// let mut fromexpr: Expr = Expr::from_str("boolfield = true").unwrap(); + /// fromexpr.reduce(Some(&item)); + /// let mut toexpr: Expr = Expr::from_str("true").unwrap(); + /// assert_eq!(fromexpr, toexpr); /// + /// let mut fromexpr: Expr = Expr::from_str("\"eo:cloud_cover\" + 10").unwrap(); + /// fromexpr.reduce(Some(&item)); + /// let mut toexpr: Expr = Expr::from_str("20").unwrap(); + /// assert_eq!(fromexpr, toexpr); /// /// ``` - pub fn reduce(&mut self, j: &Value) { + pub fn reduce(&mut self, j: Option<&Value>) { match self { + Expr::Interval { interval } => { + for arg in interval.iter_mut() { + arg.reduce(j); + } + } + Expr::Timestamp { timestamp } => { + timestamp.reduce(j); + } + Expr::Date { date } => { + date.reduce(j); + } Expr::Property { property } => { - let propexpr = j.dot_get(property).or_else(|_| j.dot_get(&format!("properties.{}", property)))?; - if let Some(v) = propexpr { - *self = Expr::from(v); + if let Some(j) = j { + let propexpr: Option; + if j.dot_has(property) { + propexpr = j.dot_get(property).unwrap(); + } else { + propexpr = j.dot_get(&format!("properties.{}", property)).unwrap(); + } + + println!("j:{:?} property:{:?}", j, property); + println!("propexpr: {:?}", propexpr); + if let Some(v) = propexpr { + *self = Expr::try_from(v).unwrap(); + } } } Expr::Operation { op, args } => { @@ -123,16 +185,16 @@ impl Expr { let mut allbool: bool = true; for arg in args.iter_mut() { arg.reduce(j); - let b: Result = arg.as_ref().clone().try_into(); - match b { - Ok(true) => anytrue = true, - Ok(false) => { - alltrue = false; - } - _ => { + + if let Ok(bool) = arg.as_ref().clone().try_into() { + if bool { + anytrue = true; + } else { alltrue = false; - allbool = false; } + } else { + alltrue = false; + allbool = false; } } @@ -141,59 +203,63 @@ impl Expr { match op.as_str() { "and" => { *self = Expr::Bool(alltrue); + return } "or" => { *self = Expr::Bool(anytrue); + return } _ => (), } - return; } // binary operations if args.len() == 2 { - // numerical binary operations - let left: Result = (*args[0].clone()).try_into(); - let right: Result = (*args[1].clone()).try_into(); - if let (Ok(l), Ok(r)) = (left, right) { - match op.as_str() { - "+" => { - *self = Expr::Float(l + r); - } - "-" => { - *self = Expr::Float(l - r); - } - "*" => { - *self = Expr::Float(l * r); - } - "/" => { - *self = Expr::Float(l / r); - } - "%" => { - *self = Expr::Float(l % r); - } - "^" => { - *self = Expr::Float(l.powf(r)); - } - "=" => { - *self = Expr::Bool(l == r); - } - "<=" => { - *self = Expr::Bool(l <= r); - } - "<" => { - *self = Expr::Bool(l < r); - } - ">=" => { - *self = Expr::Bool(l >= r); - } - ">" => { - *self = Expr::Bool(l > r); - } - "<>" => { - *self = Expr::Bool(l != r); - } - _ => (), + let left: &Expr = args[0].as_ref(); + let right: &Expr = args[1].as_ref(); + + if let (Ok(l), Ok(r)) = + (f64::try_from(left.clone()), f64::try_from(right.clone())) + { + if let Ok(v) = arith(&l, &r, op) { + *self = Expr::Float(v); + return; + } + if let Ok(v) = binary_bool(&l, &r, op) { + *self = Expr::Bool(v); + return; + } + } else if let (Ok(l), Ok(r)) = + (bool::try_from(left.clone()), bool::try_from(right.clone())) + { + if let Ok(v) = binary_bool(&l, &r, op) { + *self = Expr::Bool(v); + return; + } + } else if let (Ok(l), Ok(r)) = ( + GGeom::try_from(left.clone()), + GGeom::try_from(right.clone()), + ) { + println!("Is Spatial Op. {:?} ({:?}, {:?})", op, left, right); + if let Ok(v) = spatial_op(&l, &r, op) { + *self = Expr::Bool(v); + return; + } + } else if let (Ok(l), Ok(r)) = ( + DateRange::try_from(left.clone()), + DateRange::try_from(right.clone()), + ) { + if let Ok(v) = temporal_op(&l, &r, op) { + *self = Expr::Bool(v); + return; + } + } else if let (Ok(l), Ok(r)) = ( + String::try_from(left.clone()), + String::try_from(right.clone()), + ) { + if let Ok(v) = binary_bool(&l, &r, op) { + *self = Expr::Bool(v); + return; } } } @@ -209,33 +275,11 @@ impl Expr { /// use serde_json::{json, Value}; /// use cql2::Expr; /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); - /// let mut expr_json = json!( - /// { - /// "op" : ">", - /// "args" : [ - /// { - /// "op": "+", - /// "args": [ - /// {"property": "eo:cloud_cover"}, - /// 17 - /// ] - /// }, - /// 2 - /// ] - /// } - /// ); - /// - /// - /// let mut expr: Expr = serde_json::from_value(expr_json).unwrap(); - /// - /// - /// assert_eq!(true, expr.matches(&item).unwrap()); - /// /// - /// let mut expr2: Expr = "boolfield and 1 + 2 = 3".parse().unwrap(); - /// assert_eq!(true, expr2.matches(&item).unwrap()); + /// let mut expr: Expr = "boolfield and 1 + 2 = 3".parse().unwrap(); + /// assert_eq!(true, expr.matches(Some(&item)).unwrap()); /// ``` - pub fn matches(&self, j: &Value) -> Result { + pub fn matches(&self, j: Option<&Value>) -> Result { let mut e = self.clone(); e.reduce(j); match e { diff --git a/src/geometry.rs b/src/geometry.rs index 03ccc3d..e9414fe 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,6 +1,7 @@ use crate::Error; use geozero::{wkt::Wkt, CoordDimensions, ToGeo, ToWkt}; use serde::{Deserialize, Serialize, Serializer}; +use geos::{Geometry as GGeom, Geom}; const DEFAULT_NDIM: usize = 2; @@ -80,3 +81,18 @@ fn geojson_ndims(geojson: &geojson::Geometry) -> usize { GeometryCollection(v) => v.first().map(geojson_ndims).unwrap_or(DEFAULT_NDIM), } } + +/// Run a spatial operation. +pub fn spatial_op(left: &GGeom, right: &GGeom, op: &str) -> Result { + match op { + "s_equals" => Ok(left == right), + "s_intersects" | "intersects" => left.intersects(right).map_err(Error::from), + "s_disjoint" => left.disjoint(right).map_err(Error::from), + "s_touches" => left.touches(right).map_err(Error::from), + "s_within" => left.within(right).map_err(Error::from), + "s_overlaps" => left.overlaps(right).map_err(Error::from), + "s_crosses" => left.crosses(right).map_err(Error::from), + "s_contains" => left.contains(right).map_err(Error::from), + _ => Err(Error::OpNotImplemented("Spatial")), + } +} diff --git a/src/lib.rs b/src/lib.rs index d630014..e15f1a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,14 +34,16 @@ mod error; mod expr; mod geometry; mod parser; +mod temporal; mod validator; pub use error::Error; pub use expr::Expr; -pub use geometry::Geometry; +pub use geometry::{Geometry, spatial_op}; pub use parser::parse_text; use serde_derive::{Deserialize, Serialize}; use std::{fs, path::Path}; +pub use temporal::{DateRange, temporal_op}; pub use validator::Validator; /// A SQL query, broken into the query and parameters. diff --git a/src/temporal.rs b/src/temporal.rs new file mode 100644 index 0000000..88e5678 --- /dev/null +++ b/src/temporal.rs @@ -0,0 +1,65 @@ +use crate::{Error, Expr}; +use jiff::{Timestamp, ToSpan}; + +/// Struct to hold a range of timestamps. +#[derive(Debug,Clone,PartialEq)] +pub struct DateRange { + start: Timestamp, + end: Timestamp, +} + +impl TryFrom for DateRange { + type Error = Error; + fn try_from(v: Expr) -> Result { + + match v { + Expr::Interval{interval} => { + let start_str: String = interval[0].to_text()?; + let end_str: String = interval[1].to_text()?; + let start: Timestamp = start_str.parse().unwrap(); + let end: Timestamp = end_str.parse().unwrap(); + Ok(DateRange{start, end}) + } + Expr::Timestamp{timestamp} => { + let start_str: String = timestamp.to_text()?; + let start: Timestamp = start_str.parse().unwrap(); + Ok(DateRange{start, end: start}) + } + Expr::Date{date} => { + let start_str: String = date.to_text()?; + let start: Timestamp = start_str.parse().unwrap(); + let end: Timestamp = start + 1.day() - 1.nanosecond(); + Ok(DateRange{start, end}) + } + Expr::Literal(v) => { + let start: Timestamp = v.parse().unwrap(); + Ok(DateRange{start, end: start}) + } + _ => Err(Error::ExprToDateRange()), + } + } +} + +/// Run a temporal operation. +pub fn temporal_op(left: &DateRange, right: &DateRange, op: &str) -> Result { + match op { + "t_before" => Ok(left.end < right.start), + "t_after" => temporal_op(right, left, "t_before"), + "t_meets" => Ok(left.end == right.start), + "t_metby" => temporal_op(right, left, "t_meets"), + "t_overlaps" => { + Ok(left.start < right.end && right.start < left.end && left.end < right.end) + } + "t_overlappedby" => temporal_op(right, left, "t_overlaps"), + "t_starts" => Ok(left.start == right.start && left.end < right.end), + "t_startedby" => temporal_op(right, left, "t_starts"), + "t_during" => Ok(left.start > right.start && left.end < right.end), + "t_contains" => temporal_op(right, left, "t_during"), + "t_finishes" => Ok(left.start > right.start && left.end == right.end), + "t_finishedby" => temporal_op(right, left, "t_finishes"), + "t_equals" => Ok(left.start == right.start && left.end == right.end), + "t_disjoint" => Ok(!(temporal_op(left, right, "t_intersects").unwrap())), + "t_intersects" | "anyinteracts" => Ok(left.start <= right.end && left.end >= right.start), + _ => Err(Error::OpNotImplemented("temporal")), + } +} From 7eff2828155b50f7c8def4cf25fdcd1f8cc9db29 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 17 Dec 2024 12:40:19 -0600 Subject: [PATCH 07/17] mend --- src/expr.rs | 10 ++++++---- src/geometry.rs | 2 +- src/lib.rs | 4 ++-- src/temporal.rs | 17 ++++++++--------- tests/reduce_tests.rs | 6 +++--- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index b088806..0901575 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,4 +1,6 @@ -use crate::{DateRange, Error, Geometry, SqlQuery, Validator, geometry::spatial_op, temporal::temporal_op}; +use crate::{ + geometry::spatial_op, temporal::temporal_op, DateRange, Error, Geometry, SqlQuery, Validator, +}; use enum_as_inner::EnumAsInner; use geos::Geometry as GGeom; use json_dotpath::DotPaths; @@ -123,7 +125,7 @@ fn arith(left: &f64, right: &f64, op: &str) -> Result { "/" => Ok(left / right), "%" => Ok(left % right), "^" => Ok(left.powf(*right)), - _ => Err(Error::OpNotImplemented("Arith")) + _ => Err(Error::OpNotImplemented("Arith")), } } @@ -203,11 +205,11 @@ impl Expr { match op.as_str() { "and" => { *self = Expr::Bool(alltrue); - return + return; } "or" => { *self = Expr::Bool(anytrue); - return + return; } _ => (), } diff --git a/src/geometry.rs b/src/geometry.rs index e9414fe..91a301e 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,7 +1,7 @@ use crate::Error; +use geos::{Geom, Geometry as GGeom}; use geozero::{wkt::Wkt, CoordDimensions, ToGeo, ToWkt}; use serde::{Deserialize, Serialize, Serializer}; -use geos::{Geometry as GGeom, Geom}; const DEFAULT_NDIM: usize = 2; diff --git a/src/lib.rs b/src/lib.rs index e15f1a6..3bf86a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,11 +39,11 @@ mod validator; pub use error::Error; pub use expr::Expr; -pub use geometry::{Geometry, spatial_op}; +pub use geometry::{spatial_op, Geometry}; pub use parser::parse_text; use serde_derive::{Deserialize, Serialize}; use std::{fs, path::Path}; -pub use temporal::{DateRange, temporal_op}; +pub use temporal::{temporal_op, DateRange}; pub use validator::Validator; /// A SQL query, broken into the query and parameters. diff --git a/src/temporal.rs b/src/temporal.rs index 88e5678..6c20cee 100644 --- a/src/temporal.rs +++ b/src/temporal.rs @@ -2,7 +2,7 @@ use crate::{Error, Expr}; use jiff::{Timestamp, ToSpan}; /// Struct to hold a range of timestamps. -#[derive(Debug,Clone,PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct DateRange { start: Timestamp, end: Timestamp, @@ -11,29 +11,28 @@ pub struct DateRange { impl TryFrom for DateRange { type Error = Error; fn try_from(v: Expr) -> Result { - match v { - Expr::Interval{interval} => { + Expr::Interval { interval } => { let start_str: String = interval[0].to_text()?; let end_str: String = interval[1].to_text()?; let start: Timestamp = start_str.parse().unwrap(); let end: Timestamp = end_str.parse().unwrap(); - Ok(DateRange{start, end}) + Ok(DateRange { start, end }) } - Expr::Timestamp{timestamp} => { + Expr::Timestamp { timestamp } => { let start_str: String = timestamp.to_text()?; let start: Timestamp = start_str.parse().unwrap(); - Ok(DateRange{start, end: start}) + Ok(DateRange { start, end: start }) } - Expr::Date{date} => { + Expr::Date { date } => { let start_str: String = date.to_text()?; let start: Timestamp = start_str.parse().unwrap(); let end: Timestamp = start + 1.day() - 1.nanosecond(); - Ok(DateRange{start, end}) + Ok(DateRange { start, end }) } Expr::Literal(v) => { let start: Timestamp = v.parse().unwrap(); - Ok(DateRange{start, end: start}) + Ok(DateRange { start, end: start }) } _ => Err(Error::ExprToDateRange()), } diff --git a/tests/reduce_tests.rs b/tests/reduce_tests.rs index 215f190..ae7ffc8 100644 --- a/tests/reduce_tests.rs +++ b/tests/reduce_tests.rs @@ -1,7 +1,7 @@ use cql2::Expr; use rstest::rstest; +use serde_json::{json, Value}; use std::path::Path; -use serde_json::{Value, json}; fn read_lines(filename: impl AsRef) -> Vec { std::fs::read_to_string(filename) @@ -10,7 +10,7 @@ fn read_lines(filename: impl AsRef) -> Vec { .map(String::from) // make each slice into a string .collect() // gather them together into a vector } -fn validate_reduction(a: String, b: String){ +fn validate_reduction(a: String, b: String) { let properties: Value = json!( { "properties": { @@ -36,7 +36,7 @@ fn validate_reduce_fixtures() { let a = lines.clone().into_iter().step_by(2); let b = lines.clone().into_iter().skip(1).step_by(2); let zipped = a.zip(b); - for (a,b) in zipped{ + for (a, b) in zipped { validate_reduction(a, b); } } From 56b56e61c29a4752f6f5f461d25b145ea3c95900 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 17 Dec 2024 12:46:12 -0600 Subject: [PATCH 08/17] mend --- src/error.rs | 4 ++++ src/expr.rs | 14 ++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index 485f11c..fb95400 100644 --- a/src/error.rs +++ b/src/error.rs @@ -89,4 +89,8 @@ pub enum Error { /// Operator not implemented. #[error("Operator {0} is not implemented for this type.")] OpNotImplemented(&'static str), + + /// Expression not reduced to boolean + #[error("Could not reduce expression to boolean")] + NonReduced(), } diff --git a/src/expr.rs b/src/expr.rs index 0901575..b5b8163 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -167,12 +167,11 @@ impl Expr { } Expr::Property { property } => { if let Some(j) = j { - let propexpr: Option; - if j.dot_has(property) { - propexpr = j.dot_get(property).unwrap(); + let propexpr: Option = if j.dot_has(property) { + j.dot_get(property).unwrap() } else { - propexpr = j.dot_get(&format!("properties.{}", property)).unwrap(); - } + j.dot_get(&format!("properties.{}", property)).unwrap() + }; println!("j:{:?} property:{:?}", j, property); println!("propexpr: {:?}", propexpr); @@ -229,7 +228,6 @@ impl Expr { } if let Ok(v) = binary_bool(&l, &r, op) { *self = Expr::Bool(v); - return; } } else if let (Ok(l), Ok(r)) = (bool::try_from(left.clone()), bool::try_from(right.clone())) @@ -281,12 +279,12 @@ impl Expr { /// let mut expr: Expr = "boolfield and 1 + 2 = 3".parse().unwrap(); /// assert_eq!(true, expr.matches(Some(&item)).unwrap()); /// ``` - pub fn matches(&self, j: Option<&Value>) -> Result { + pub fn matches(&self, j: Option<&Value>) -> Result { let mut e = self.clone(); e.reduce(j); match e { Expr::Bool(v) => Ok(v), - _ => Err(()), + _ => Err(Error::NonReduced()), } } /// Converts this expression to CQL2 text. From f5cce339adf29fd536ecaf18a23635cbd9b0a16a Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Thu, 16 Jan 2025 15:57:40 -0600 Subject: [PATCH 09/17] add support for all cql2 operators to reduce --- Cargo.lock | 310 ++++++++++++++++-------------------------- Cargo.toml | 11 +- cli/src/lib.rs | 2 +- src/error.rs | 4 + src/expr.rs | 236 ++++++++++++++++---------------- src/geometry.rs | 41 ++++-- src/temporal.rs | 37 +++-- tests/reduce_tests.rs | 9 +- tests/reductions.txt | 12 ++ 9 files changed, 326 insertions(+), 336 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1b663e..9c3d34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,19 +65,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "appendlist" @@ -112,15 +113,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "bitflags" -version = "1.3.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "block-buffer" @@ -133,15 +128,15 @@ dependencies = [ [[package]] name = "boon" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9672cb0edeadf721484e298c0ed4dd70b0eaa3acaed5b4fd0bd73ca32e51d814" +checksum = "baa187da765010b70370368c49f08244b1ae5cae1d5d33072f76c8cb7112fe3e" dependencies = [ "ahash", "appendlist", "base64", "fluent-uri", - "idna 0.5.0", + "idna", "once_cell", "percent-encoding", "regex", @@ -151,6 +146,12 @@ dependencies = [ "url", ] +[[package]] +name = "borrow-or-share" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" + [[package]] name = "c_vec" version = "2.0.0" @@ -165,9 +166,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.23" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", "clap_derive", @@ -175,9 +176,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", @@ -187,9 +188,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -224,7 +225,6 @@ version = "0.3.2" dependencies = [ "assert-json-diff", "boon", - "enum-as-inner", "geo-types", "geojson", "geos", @@ -239,7 +239,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.6", + "thiserror 2.0.11", ] [[package]] @@ -294,18 +294,6 @@ dependencies = [ "syn", ] -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -314,11 +302,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fluent-uri" -version = "0.1.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" dependencies = [ - "bitflags", + "borrow-or-share", + "ref-cast", ] [[package]] @@ -330,54 +319,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-macro" version = "0.3.31" @@ -389,12 +336,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - [[package]] name = "futures-task" version = "0.3.31" @@ -413,13 +354,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -437,9 +374,9 @@ dependencies = [ [[package]] name = "geo-types" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f47c611187777bbca61ea7aba780213f5f3441fd36294ab333e96cfa791b65" +checksum = "3bd1157f0f936bf0cd68dec91e8f7c311afe60295574d62b70d4861a1bfdf2d9" dependencies = [ "approx", "num-traits", @@ -509,9 +446,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "hashbrown" @@ -643,16 +580,6 @@ dependencies = [ "syn", ] -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -704,11 +631,15 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.15" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" +checksum = "7597657ea66d53f6e926a67d4cc3d125c4b57fa662f2d007a5476307de948453" dependencies = [ "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", "windows-sys", ] @@ -747,9 +678,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" @@ -765,9 +696,9 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -877,7 +808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.6", + "thiserror 2.0.11", "ucd-trie", ] @@ -926,9 +857,9 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -936,9 +867,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -946,9 +877,9 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", @@ -959,18 +890,18 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -990,6 +921,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -1001,18 +941,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" dependencies = [ "cfg-if", "indoc", @@ -1028,9 +968,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" dependencies = [ "once_cell", "target-lexicon", @@ -1038,9 +978,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" dependencies = [ "libc", "pyo3-build-config", @@ -1048,9 +988,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1060,9 +1000,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.23.3" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" dependencies = [ "heck", "proc-macro2", @@ -1083,9 +1023,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1105,6 +1045,26 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.11.1" @@ -1142,21 +1102,21 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rstest" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89" dependencies = [ - "futures", "futures-timer", + "futures-util", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" dependencies = [ "cfg-if", "glob", @@ -1187,24 +1147,24 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -1213,9 +1173,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "indexmap", "itoa", @@ -1237,9 +1197,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -1270,9 +1230,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1307,11 +1267,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.11", ] [[package]] @@ -1327,9 +1287,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -1346,21 +1306,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "toml_datetime" version = "0.6.8" @@ -1390,27 +1335,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - [[package]] name = "unindent" version = "0.2.3" @@ -1424,7 +1354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna", "percent-encoding", ] @@ -1533,9 +1463,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index d7e9d4c..9ce46be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,8 @@ license = { workspace = true } keywords = ["cql2"] [dependencies] -boon = "0.6.0" -enum-as-inner = "0.6.1" -geo-types = "0.7.13" +boon = "0.6.1" +geo-types = "0.7.15" geojson = "0.24.1" geos = "9.1.1" geozero = "0.14.0" @@ -36,13 +35,13 @@ pest = "2.7" pest_derive = { version = "2.7", features = ["grammar-extras"] } pg_escape = "0.1.1" serde = "1.0" -serde_derive = "1.0" -serde_json = { version = "1.0", features = ["preserve_order"] } +serde_derive = "1.0.217" +serde_json = { version = "1.0.135", features = ["preserve_order"] } thiserror = "2.0" [dev-dependencies] assert-json-diff = "2" -rstest = "0.23" +rstest = "0.24.0" [workspace] default-members = [".", "cli"] diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e9ad861..1f838cd 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -110,7 +110,7 @@ impl Cli { }, }; if self.reduce { - expr.reduce(Some(&json!({}))); + expr = expr.reduce(Some(&json!({}))); } if self.validate { let validator = Validator::new().unwrap(); diff --git a/src/error.rs b/src/error.rs index fb95400..b476561 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,4 +93,8 @@ pub enum Error { /// Expression not reduced to boolean #[error("Could not reduce expression to boolean")] NonReduced(), + + /// Could not run arith operation + #[error("Could not run operation.")] + OperationError(), } diff --git a/src/expr.rs b/src/expr.rs index b5b8163..f397bd2 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,13 +1,24 @@ use crate::{ - geometry::spatial_op, temporal::temporal_op, DateRange, Error, Geometry, SqlQuery, Validator, + geometry::spatial_op, temporal::temporal_op, Error, Geometry, SqlQuery, Validator, }; -use enum_as_inner::EnumAsInner; use geos::Geometry as GGeom; use json_dotpath::DotPaths; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::str::FromStr; +use std::collections::HashSet; + + +const BOOLOPS: &[&str] = &["and", "or"]; +const EQOPS: &[&str] = &["=", "<>"]; +const CMPOPS: &[&str] = &[">", ">=", "<", "<="]; +const SPATIALOPS: &[&str] = &["s_equals", "s_intersects","s_disjoint","s_touches","s_within","s_overlaps","s_crosses","s_contains"]; +const TEMPORALOPS: &[&str] = &["t_before","t_after","t_meets","t_metby","t_overlaps","t_overlappedby","t_starts","t_startedby","t_during","t_contains","t_finishes","to_finishedby","t_equals","t_disjoint","t_intersects"]; +const ARITHOPS: &[&str] = &["+","-","*","/","%","^","div"]; +const ARRAYOPS: &[&str] = &["a_equals","a_contains","a_containedby","a_overlaps"]; + +// todo: array ops, in, casei, accenti, between, not, like /// A CQL2 expression. /// @@ -23,7 +34,7 @@ use std::str::FromStr; /// /// Use [Expr::to_text], [Expr::to_json], and [Expr::to_sql] to use the CQL2, /// and use [Expr::is_valid] to check validity. -#[derive(Debug, Serialize, Deserialize, Clone, EnumAsInner)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, PartialOrd)] #[serde(untagged)] #[allow(missing_docs)] pub enum Expr { @@ -99,14 +110,25 @@ impl TryFrom for GGeom { } } -impl PartialEq for Expr { - fn eq(&self, other: &Self) -> bool { - self.to_text().unwrap() == other.to_text().unwrap() +impl TryFrom for HashSet { + type Error = Error; + fn try_from(v: Expr) -> Result, Error> { + match v { + Expr::Array(v) => { + let mut h = HashSet::new(); + for el in v { + let _ = h.insert(el.to_text()?); + } + Ok(h) + + } + _ => Err(Error::ExprToGeom()), + } } } -fn binary_bool(left: &T, right: &T, op: &str) -> Result { - match op { +fn cmp_op(left: T, right: T, op: &str) -> Result { + let out = match op { "=" => Ok(left == right), "<=" => Ok(left <= right), "<" => Ok(left < right), @@ -114,21 +136,49 @@ fn binary_bool(left: &T, right: &T, op: &str) -> Resu ">" => Ok(left > right), "<>" => Ok(left != right), _ => Err(Error::OpNotImplemented("Binary Bool")), + }; + match out { + Ok(v) => Ok(Expr::Bool(v)), + _ => Err(Error::OperationError()) } } -fn arith(left: &f64, right: &f64, op: &str) -> Result { - match op { +fn arith_op(left: Expr, right: Expr, op: &str) -> Result { + let left = f64::try_from(left)?; + let right = f64::try_from(right)?; + let out = match op { "+" => Ok(left + right), "-" => Ok(left - right), "*" => Ok(left * right), "/" => Ok(left / right), "%" => Ok(left % right), - "^" => Ok(left.powf(*right)), + "^" => Ok(left.powf(right)), + _ => Err(Error::OpNotImplemented("Arith")), + }; + match out { + Ok(v) => Ok(Expr::Float(v)), + _ => Err(Error::OperationError()) + } +} + +fn array_op(left: Expr, right: Expr, op: &str) -> Result { + let left: HashSet = left.try_into()?; + let right: HashSet = right.try_into()?; + let out = match op { + "a_equals" => Ok(left == right), + "a_contains" => Ok(left.is_superset(&right)), + "a_containedby" => Ok(left.is_subset(&right)), + "a_overlaps" => Ok(!left.is_disjoint(&right)), _ => Err(Error::OpNotImplemented("Arith")), + }; + match out { + Ok(v) => Ok(Expr::Bool(v)), + _ => Err(Error::OperationError()) } } + + impl Expr { /// Update this expression with values from the `properties` attribute of a JSON object /// @@ -141,30 +191,23 @@ impl Expr { /// /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); /// - /// let mut fromexpr: Expr = Expr::from_str("boolfield = true").unwrap(); - /// fromexpr.reduce(Some(&item)); - /// let mut toexpr: Expr = Expr::from_str("true").unwrap(); - /// assert_eq!(fromexpr, toexpr); + /// let fromexpr: Expr = Expr::from_str("boolfield = true").unwrap(); + /// let reduced = fromexpr.reduce(Some(&item)); + /// let toexpr: Expr = Expr::from_str("true").unwrap(); + /// assert_eq!(reduced, toexpr); /// - /// let mut fromexpr: Expr = Expr::from_str("\"eo:cloud_cover\" + 10").unwrap(); - /// fromexpr.reduce(Some(&item)); - /// let mut toexpr: Expr = Expr::from_str("20").unwrap(); - /// assert_eq!(fromexpr, toexpr); + /// let fromexpr: Expr = Expr::from_str("\"eo:cloud_cover\" + 10").unwrap(); + /// let reduced = fromexpr.reduce(Some(&item)); + /// let toexpr: Expr = Expr::from_str("20").unwrap(); + /// assert_eq!(reduced, toexpr); /// /// ``` - pub fn reduce(&mut self, j: Option<&Value>) { + pub fn reduce(&self, j: Option<&Value>) -> Expr { + fn reduce_args(args: &Vec>, j: Option<&Value>) -> Vec { + let mut outargs = args.clone(); + outargs.iter_mut().map(|arg| arg.reduce(j)).collect() + } match self { - Expr::Interval { interval } => { - for arg in interval.iter_mut() { - arg.reduce(j); - } - } - Expr::Timestamp { timestamp } => { - timestamp.reduce(j); - } - Expr::Date { date } => { - date.reduce(j); - } Expr::Property { property } => { if let Some(j) = j { let propexpr: Option = if j.dot_has(property) { @@ -172,99 +215,61 @@ impl Expr { } else { j.dot_get(&format!("properties.{}", property)).unwrap() }; - - println!("j:{:?} property:{:?}", j, property); - println!("propexpr: {:?}", propexpr); if let Some(v) = propexpr { - *self = Expr::try_from(v).unwrap(); + return Expr::try_from(v).unwrap() } } + return self.clone() } - Expr::Operation { op, args } => { - let mut alltrue: bool = true; - let mut anytrue: bool = false; - let mut allbool: bool = true; - for arg in args.iter_mut() { - arg.reduce(j); - - if let Ok(bool) = arg.as_ref().clone().try_into() { - if bool { - anytrue = true; - } else { - alltrue = false; + Expr::Operation {op, args } => { + let op = op.as_str(); + let args: Vec = reduce_args(args, j); + + if BOOLOPS.contains(&op){ + let bools: Result, Error> = args.into_iter().map(|x| bool::try_from(x)).collect(); + + if let Ok(bools) = bools { + match op { + "and" => { + return Expr::Bool(bools.into_iter().all(|x| x == true)) + } + "or" => { + return Expr::Bool(bools.into_iter().any(|x| x == true)) + }, + _ => return self.clone() } - } else { - alltrue = false; - allbool = false; - } + } else { return self.clone() } } - // boolean operators - if allbool { - match op.as_str() { - "and" => { - *self = Expr::Bool(alltrue); - return; - } - "or" => { - *self = Expr::Bool(anytrue); - return; - } - _ => (), - } + // no other operators should have arguments other than 2 + if args.len() != 2 { + return self.clone() } + let left = args[0].clone(); + let right = args[1].clone(); - // binary operations - if args.len() == 2 { - let left: &Expr = args[0].as_ref(); - let right: &Expr = args[1].as_ref(); - - if let (Ok(l), Ok(r)) = - (f64::try_from(left.clone()), f64::try_from(right.clone())) - { - if let Ok(v) = arith(&l, &r, op) { - *self = Expr::Float(v); - return; - } - if let Ok(v) = binary_bool(&l, &r, op) { - *self = Expr::Bool(v); - } - } else if let (Ok(l), Ok(r)) = - (bool::try_from(left.clone()), bool::try_from(right.clone())) - { - if let Ok(v) = binary_bool(&l, &r, op) { - *self = Expr::Bool(v); - return; - } - } else if let (Ok(l), Ok(r)) = ( - GGeom::try_from(left.clone()), - GGeom::try_from(right.clone()), - ) { - println!("Is Spatial Op. {:?} ({:?}, {:?})", op, left, right); - if let Ok(v) = spatial_op(&l, &r, op) { - *self = Expr::Bool(v); - return; - } - } else if let (Ok(l), Ok(r)) = ( - DateRange::try_from(left.clone()), - DateRange::try_from(right.clone()), - ) { - if let Ok(v) = temporal_op(&l, &r, op) { - *self = Expr::Bool(v); - return; - } - } else if let (Ok(l), Ok(r)) = ( - String::try_from(left.clone()), - String::try_from(right.clone()), - ) { - if let Ok(v) = binary_bool(&l, &r, op) { - *self = Expr::Bool(v); - return; - } - } + if SPATIALOPS.contains(&op) { + return spatial_op(left, right, op).unwrap_or(self.clone()) + } + + if TEMPORALOPS.contains(&op) { + return temporal_op(left, right, op).unwrap_or(self.clone()) + } + if ARITHOPS.contains(&op) { + return arith_op(left, right, op).unwrap_or(self.clone()) } + + if EQOPS.contains(&op) | CMPOPS.contains(&op) { + return cmp_op(left, right, op).unwrap_or(self.clone()) + } + + if ARRAYOPS.contains(&op) { + return array_op(left, right, op).unwrap_or(self.clone()) + } + return self.clone() } - _ => (), + + _ => self.clone(), } } /// Run CQL against a JSON Value @@ -280,9 +285,8 @@ impl Expr { /// assert_eq!(true, expr.matches(Some(&item)).unwrap()); /// ``` pub fn matches(&self, j: Option<&Value>) -> Result { - let mut e = self.clone(); - e.reduce(j); - match e { + let reduced = self.reduce(j); + match reduced { Expr::Bool(v) => Ok(v), _ => Err(Error::NonReduced()), } diff --git a/src/geometry.rs b/src/geometry.rs index 91a301e..0970564 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,4 +1,6 @@ -use crate::Error; +use std::cmp::Ordering; + +use crate::{Error, Expr}; use geos::{Geom, Geometry as GGeom}; use geozero::{wkt::Wkt, CoordDimensions, ToGeo, ToWkt}; use serde::{Deserialize, Serialize, Serializer}; @@ -46,6 +48,18 @@ impl Geometry { } } +impl PartialEq for Geometry { + fn eq(&self, other: &Self) -> bool { + self.to_wkt().unwrap() == other.to_wkt().unwrap() + } +} + +impl PartialOrd for Geometry { + fn partial_cmp(&self, _other: &Self) -> Option { + Some(Ordering::Equal) + } +} + fn to_geojson(wkt: &String, serializer: S) -> Result where S: Serializer, @@ -82,17 +96,24 @@ fn geojson_ndims(geojson: &geojson::Geometry) -> usize { } } + /// Run a spatial operation. -pub fn spatial_op(left: &GGeom, right: &GGeom, op: &str) -> Result { - match op { +pub fn spatial_op(left: Expr, right: Expr, op: &str) -> Result { + let left: GGeom = GGeom::try_from(left)?; + let right: GGeom = GGeom::try_from(right)?; + let out: Result = match op { "s_equals" => Ok(left == right), - "s_intersects" | "intersects" => left.intersects(right).map_err(Error::from), - "s_disjoint" => left.disjoint(right).map_err(Error::from), - "s_touches" => left.touches(right).map_err(Error::from), - "s_within" => left.within(right).map_err(Error::from), - "s_overlaps" => left.overlaps(right).map_err(Error::from), - "s_crosses" => left.crosses(right).map_err(Error::from), - "s_contains" => left.contains(right).map_err(Error::from), + "s_intersects" | "intersects" => left.intersects(&right).map_err(Error::from), + "s_disjoint" => left.disjoint(&right).map_err(Error::from), + "s_touches" => left.touches(&right).map_err(Error::from), + "s_within" => left.within(&right).map_err(Error::from), + "s_overlaps" => left.overlaps(&right).map_err(Error::from), + "s_crosses" => left.crosses(&right).map_err(Error::from), + "s_contains" => left.contains(&right).map_err(Error::from), _ => Err(Error::OpNotImplemented("Spatial")), + }; + match out { + Ok(v) => Ok(Expr::Bool(v)), + _ => Err(Error::OperationError()) } } diff --git a/src/temporal.rs b/src/temporal.rs index 6c20cee..e7db81a 100644 --- a/src/temporal.rs +++ b/src/temporal.rs @@ -40,25 +40,44 @@ impl TryFrom for DateRange { } /// Run a temporal operation. -pub fn temporal_op(left: &DateRange, right: &DateRange, op: &str) -> Result { - match op { +pub fn temporal_op(left_expr: Expr, right_expr: Expr, op: &str) -> Result { + let invop = match op { + "t_after" => "t_before", + "t_metby" => "t_meets", + "t_overlappedby" => "t_overlaps", + "t_startedby" => "t_starts", + "t_contains" => "t_during", + "t_finishedby" => "t_finishes", + _ => op + }; + + let left: DateRange; + let right: DateRange; + if invop != op { + left = DateRange::try_from(left_expr)?; + right = DateRange::try_from(right_expr)?; + } else { + right = DateRange::try_from(left_expr)?; + left = DateRange::try_from(right_expr)?; + } + + let out = match invop { "t_before" => Ok(left.end < right.start), - "t_after" => temporal_op(right, left, "t_before"), "t_meets" => Ok(left.end == right.start), - "t_metby" => temporal_op(right, left, "t_meets"), "t_overlaps" => { Ok(left.start < right.end && right.start < left.end && left.end < right.end) } - "t_overlappedby" => temporal_op(right, left, "t_overlaps"), "t_starts" => Ok(left.start == right.start && left.end < right.end), - "t_startedby" => temporal_op(right, left, "t_starts"), "t_during" => Ok(left.start > right.start && left.end < right.end), - "t_contains" => temporal_op(right, left, "t_during"), "t_finishes" => Ok(left.start > right.start && left.end == right.end), - "t_finishedby" => temporal_op(right, left, "t_finishes"), "t_equals" => Ok(left.start == right.start && left.end == right.end), - "t_disjoint" => Ok(!(temporal_op(left, right, "t_intersects").unwrap())), + "t_disjoint" => Ok(!(left.start <= right.end && left.end >= right.start)), "t_intersects" | "anyinteracts" => Ok(left.start <= right.end && left.end >= right.start), _ => Err(Error::OpNotImplemented("temporal")), + }; + + match out { + Ok(v) => Ok(Expr::Bool(v)), + _ => Err(Error::OperationError()) } } diff --git a/tests/reduce_tests.rs b/tests/reduce_tests.rs index ae7ffc8..cc42821 100644 --- a/tests/reduce_tests.rs +++ b/tests/reduce_tests.rs @@ -18,16 +18,17 @@ fn validate_reduction(a: String, b: String) { "boolfalse": false, "booltrue": true, "stringfield": "string", - "tsfield": {"timestamp": "2020-01-01 00:00:00Z"} + "tsfield": {"timestamp": "2020-01-01 00:00:00Z"}, + "tstarr": [1,2,3] }, "geometry": {"type": "Point", "coordinates": [-93.0, 45]}, "datetime": "2020-01-01 00:00:00Z" } ); - let mut inexpr: Expr = a.parse().unwrap(); - inexpr.reduce(Some(&properties)); + let inexpr: Expr = a.parse().unwrap(); + let reduced = inexpr.reduce(Some(&properties)); let outexpr: Expr = b.parse().unwrap(); - assert_eq!(inexpr, outexpr); + assert_eq!(reduced, outexpr); } #[rstest] diff --git a/tests/reductions.txt b/tests/reductions.txt index f48da18..fb01847 100644 --- a/tests/reductions.txt +++ b/tests/reductions.txt @@ -22,3 +22,15 @@ false true S_EQUALS(POINT(-93.0 45.0), geometry) true +A_EQUALS((1,2,3),(1,2,3)) +true +A_EQUALS((1,2,3),(1,2)) +false +A_OVERLAPS((1,2,3),(1,2)) +true +A_CONTAINS((1,2,3),(1,2)) +true +A_CONTAINEDBY((1,2),(1,2,3)) +true +A_EQUALS(tstarr,(1,2,3)) +true From 268b97ba02ceeac6a0d5d6b52b79fde1f64258ed Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Thu, 16 Jan 2025 16:12:43 -0600 Subject: [PATCH 10/17] lint --- src/expr.rs | 96 +++++++++++++++++++++++++++++-------------------- src/geometry.rs | 3 +- src/temporal.rs | 4 +-- 3 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index f397bd2..b46ef5f 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,22 +1,44 @@ -use crate::{ - geometry::spatial_op, temporal::temporal_op, Error, Geometry, SqlQuery, Validator, -}; +use crate::{geometry::spatial_op, temporal::temporal_op, Error, Geometry, SqlQuery, Validator}; use geos::Geometry as GGeom; use json_dotpath::DotPaths; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::str::FromStr; use std::collections::HashSet; - +use std::str::FromStr; const BOOLOPS: &[&str] = &["and", "or"]; const EQOPS: &[&str] = &["=", "<>"]; const CMPOPS: &[&str] = &[">", ">=", "<", "<="]; -const SPATIALOPS: &[&str] = &["s_equals", "s_intersects","s_disjoint","s_touches","s_within","s_overlaps","s_crosses","s_contains"]; -const TEMPORALOPS: &[&str] = &["t_before","t_after","t_meets","t_metby","t_overlaps","t_overlappedby","t_starts","t_startedby","t_during","t_contains","t_finishes","to_finishedby","t_equals","t_disjoint","t_intersects"]; -const ARITHOPS: &[&str] = &["+","-","*","/","%","^","div"]; -const ARRAYOPS: &[&str] = &["a_equals","a_contains","a_containedby","a_overlaps"]; +const SPATIALOPS: &[&str] = &[ + "s_equals", + "s_intersects", + "s_disjoint", + "s_touches", + "s_within", + "s_overlaps", + "s_crosses", + "s_contains", +]; +const TEMPORALOPS: &[&str] = &[ + "t_before", + "t_after", + "t_meets", + "t_metby", + "t_overlaps", + "t_overlappedby", + "t_starts", + "t_startedby", + "t_during", + "t_contains", + "t_finishes", + "to_finishedby", + "t_equals", + "t_disjoint", + "t_intersects", +]; +const ARITHOPS: &[&str] = &["+", "-", "*", "/", "%", "^", "div"]; +const ARRAYOPS: &[&str] = &["a_equals", "a_contains", "a_containedby", "a_overlaps"]; // todo: array ops, in, casei, accenti, between, not, like @@ -120,7 +142,6 @@ impl TryFrom for HashSet { let _ = h.insert(el.to_text()?); } Ok(h) - } _ => Err(Error::ExprToGeom()), } @@ -139,7 +160,7 @@ fn cmp_op(left: T, right: T, op: &str) -> Result Ok(Expr::Bool(v)), - _ => Err(Error::OperationError()) + _ => Err(Error::OperationError()), } } @@ -157,7 +178,7 @@ fn arith_op(left: Expr, right: Expr, op: &str) -> Result { }; match out { Ok(v) => Ok(Expr::Float(v)), - _ => Err(Error::OperationError()) + _ => Err(Error::OperationError()), } } @@ -167,18 +188,16 @@ fn array_op(left: Expr, right: Expr, op: &str) -> Result { let out = match op { "a_equals" => Ok(left == right), "a_contains" => Ok(left.is_superset(&right)), - "a_containedby" => Ok(left.is_subset(&right)), - "a_overlaps" => Ok(!left.is_disjoint(&right)), + "a_containedby" => Ok(left.is_subset(&right)), + "a_overlaps" => Ok(!left.is_disjoint(&right)), _ => Err(Error::OpNotImplemented("Arith")), }; match out { Ok(v) => Ok(Expr::Bool(v)), - _ => Err(Error::OperationError()) + _ => Err(Error::OperationError()), } } - - impl Expr { /// Update this expression with values from the `properties` attribute of a JSON object /// @@ -203,8 +222,8 @@ impl Expr { /// /// ``` pub fn reduce(&self, j: Option<&Value>) -> Expr { - fn reduce_args(args: &Vec>, j: Option<&Value>) -> Vec { - let mut outargs = args.clone(); + fn reduce_args(args: &[Box], j: Option<&Value>) -> Vec { + let mut outargs = args.to_owned(); outargs.iter_mut().map(|arg| arg.reduce(j)).collect() } match self { @@ -216,57 +235,56 @@ impl Expr { j.dot_get(&format!("properties.{}", property)).unwrap() }; if let Some(v) = propexpr { - return Expr::try_from(v).unwrap() + return Expr::try_from(v).unwrap(); } } - return self.clone() + self.clone() } - Expr::Operation {op, args } => { + Expr::Operation { op, args } => { let op = op.as_str(); let args: Vec = reduce_args(args, j); - if BOOLOPS.contains(&op){ - let bools: Result, Error> = args.into_iter().map(|x| bool::try_from(x)).collect(); + if BOOLOPS.contains(&op) { + let bools: Result, Error> = + args.into_iter().map(bool::try_from).collect(); if let Ok(bools) = bools { match op { - "and" => { - return Expr::Bool(bools.into_iter().all(|x| x == true)) - } - "or" => { - return Expr::Bool(bools.into_iter().any(|x| x == true)) - }, - _ => return self.clone() + "and" => return Expr::Bool(bools.into_iter().all(|x| x)), + "or" => return Expr::Bool(bools.into_iter().any(|x| x)), + _ => return self.clone(), } - } else { return self.clone() } + } else { + return self.clone(); + } } // no other operators should have arguments other than 2 if args.len() != 2 { - return self.clone() + return self.clone(); } let left = args[0].clone(); let right = args[1].clone(); if SPATIALOPS.contains(&op) { - return spatial_op(left, right, op).unwrap_or(self.clone()) + return spatial_op(left, right, op).unwrap_or(self.clone()); } if TEMPORALOPS.contains(&op) { - return temporal_op(left, right, op).unwrap_or(self.clone()) + return temporal_op(left, right, op).unwrap_or(self.clone()); } if ARITHOPS.contains(&op) { - return arith_op(left, right, op).unwrap_or(self.clone()) + return arith_op(left, right, op).unwrap_or(self.clone()); } if EQOPS.contains(&op) | CMPOPS.contains(&op) { - return cmp_op(left, right, op).unwrap_or(self.clone()) + return cmp_op(left, right, op).unwrap_or(self.clone()); } if ARRAYOPS.contains(&op) { - return array_op(left, right, op).unwrap_or(self.clone()) + return array_op(left, right, op).unwrap_or(self.clone()); } - return self.clone() + self.clone() } _ => self.clone(), diff --git a/src/geometry.rs b/src/geometry.rs index 0970564..435c641 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -96,7 +96,6 @@ fn geojson_ndims(geojson: &geojson::Geometry) -> usize { } } - /// Run a spatial operation. pub fn spatial_op(left: Expr, right: Expr, op: &str) -> Result { let left: GGeom = GGeom::try_from(left)?; @@ -114,6 +113,6 @@ pub fn spatial_op(left: Expr, right: Expr, op: &str) -> Result { }; match out { Ok(v) => Ok(Expr::Bool(v)), - _ => Err(Error::OperationError()) + _ => Err(Error::OperationError()), } } diff --git a/src/temporal.rs b/src/temporal.rs index e7db81a..cf679da 100644 --- a/src/temporal.rs +++ b/src/temporal.rs @@ -48,7 +48,7 @@ pub fn temporal_op(left_expr: Expr, right_expr: Expr, op: &str) -> Result "t_starts", "t_contains" => "t_during", "t_finishedby" => "t_finishes", - _ => op + _ => op, }; let left: DateRange; @@ -78,6 +78,6 @@ pub fn temporal_op(left_expr: Expr, right_expr: Expr, op: &str) -> Result Ok(Expr::Bool(v)), - _ => Err(Error::OperationError()) + _ => Err(Error::OperationError()), } } From 7a16f585eb8ea274f929e737bf3a78a069f9349a Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Thu, 16 Jan 2025 16:14:12 -0600 Subject: [PATCH 11/17] update dependencies --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c3d34f..6813471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,9 +631,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7597657ea66d53f6e926a67d4cc3d125c4b57fa662f2d007a5476307de948453" +checksum = "d2bb0c2e28117985a4d90e3bc70092bc8f226f434c7ec7e23dd9ff99c5c5721a" dependencies = [ "jiff-tzdb-platform", "log", @@ -645,15 +645,15 @@ dependencies = [ [[package]] name = "jiff-tzdb" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" +checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" [[package]] name = "jiff-tzdb-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" dependencies = [ "jiff-tzdb", ] diff --git a/Cargo.toml b/Cargo.toml index 9ce46be..4742a9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ geo-types = "0.7.15" geojson = "0.24.1" geos = "9.1.1" geozero = "0.14.0" -jiff = "0.1.15" +jiff = "0.1.24" json_dotpath = "1.1.0" lazy_static = "1.5" pest = "2.7" From 02466844e498822f805c27cc4efb0a02fabe28a2 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Thu, 16 Jan 2025 16:51:39 -0600 Subject: [PATCH 12/17] use geo rather than geos --- Cargo.lock | 341 ++++++++++++++++++++++++++++++++++-------------- Cargo.toml | 3 +- src/error.rs | 8 +- src/expr.rs | 7 +- src/geometry.rs | 29 ++-- 5 files changed, 264 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6813471..1419ec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "0.6.18" @@ -153,10 +159,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eeab4423108c5d7c744f4d234de88d18d636100093ae04caf4825134b9c3a32" [[package]] -name = "c_vec" -version = "2.0.0" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7a427adc0135366d99db65b36dae9237130997e560ed61118041fb72be6e8" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" @@ -225,9 +231,9 @@ version = "0.3.2" dependencies = [ "assert-json-diff", "boon", + "geo", "geo-types", "geojson", - "geos", "geozero", "jiff", "json_dotpath", @@ -240,6 +246,7 @@ dependencies = [ "serde_derive", "serde_json", "thiserror 2.0.11", + "wkt 0.12.0", ] [[package]] @@ -263,6 +270,31 @@ dependencies = [ "pythonize", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -294,12 +326,34 @@ dependencies = [ "syn", ] +[[package]] +name = "earcutr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" +dependencies = [ + "itertools", + "num-traits", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fluent-uri" version = "0.3.2" @@ -372,6 +426,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "geo" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f0e6e028c581e82e6822a68869514e94c25e7f8ea669a2d8595bdf7461ccc5" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "i_overlay", + "log", + "num-traits", + "robust", + "rstar", + "spade", +] + +[[package]] +name = "geo-traits" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b018fc19fa58202b03f1c809aebe654f7d70fd3887dace34c3d05c11aeb474b5" +dependencies = [ + "geo-types", +] + [[package]] name = "geo-types" version = "0.7.15" @@ -380,9 +461,20 @@ checksum = "3bd1157f0f936bf0cd68dec91e8f7c311afe60295574d62b70d4861a1bfdf2d9" dependencies = [ "approx", "num-traits", + "rayon", + "rstar", "serde", ] +[[package]] +name = "geographiclib-rs" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +dependencies = [ + "libm", +] + [[package]] name = "geojson" version = "0.24.1" @@ -396,29 +488,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "geos" -version = "9.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d199db00644057267a8a68ee72df92aa59a32036b487b2a2b76fd0b3fca32b" -dependencies = [ - "c_vec", - "geos-sys", - "libc", - "num", -] - -[[package]] -name = "geos-sys" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc873d24aefc72aa94c3c1c251afb82beb7be5926002746c0e1f585fef9854c" -dependencies = [ - "libc", - "pkg-config", - "semver", -] - [[package]] name = "geozero" version = "0.14.0" @@ -430,7 +499,7 @@ dependencies = [ "log", "serde_json", "thiserror 1.0.69", - "wkt", + "wkt 0.11.1", ] [[package]] @@ -450,18 +519,91 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "i_float" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775f9961a8d2f879725da8aff789bb20a3ddf297473e0c90af75e69313919490" +dependencies = [ + "serde", +] + +[[package]] +name = "i_key_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "347c253b4748a1a28baf94c9ce133b6b166f08573157e05afe718812bc599fcd" + +[[package]] +name = "i_overlay" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01882ce5ed786bf6e8f5167f171a4026cd129ce17d9ff5cbf1e6749b98628ece" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27dbe9e5238d6b9c694c08415bf00fb370b089949bd818ab01f41f8927b8774c" +dependencies = [ + "i_float", + "serde", +] + +[[package]] +name = "i_tree" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155181bc97d770181cf9477da51218a19ee92a8e5be642e796661aee2b601139" + [[package]] name = "icu_collections" version = "1.5.0" @@ -608,7 +750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -623,6 +765,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -715,70 +866,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -909,12 +996,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - [[package]] name = "portable-atomic" version = "1.10.0" @@ -1045,6 +1126,26 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "ref-cast" version = "1.0.23" @@ -1100,6 +1201,23 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "robust" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + [[package]] name = "rstest" version = "0.24.0" @@ -1216,6 +1334,18 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "spade" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f5ef1f863aca7d1d7dda7ccfc36a0a4279bd6d3c375176e5e0712e25cb4889" +dependencies = [ + "hashbrown 0.14.5", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1482,6 +1612,19 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "wkt" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c591649bd1c9d4e28459758bbb5fb5c0edc7a67060b52422f4761c94ffe961" +dependencies = [ + "geo-traits", + "geo-types", + "log", + "num-traits", + "thiserror 1.0.69", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 4742a9b..f0a6b21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,9 @@ keywords = ["cql2"] [dependencies] boon = "0.6.1" +geo = "0.29.3" geo-types = "0.7.15" geojson = "0.24.1" -geos = "9.1.1" geozero = "0.14.0" jiff = "0.1.24" json_dotpath = "1.1.0" @@ -38,6 +38,7 @@ serde = "1.0" serde_derive = "1.0.217" serde_json = { version = "1.0.135", features = ["preserve_order"] } thiserror = "2.0" +wkt = "0.12.0" [dev-dependencies] assert-json-diff = "2" diff --git a/src/error.rs b/src/error.rs index b476561..3919e1f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,10 +12,6 @@ pub enum Error { #[error(transparent)] Geozero(#[from] geozero::error::GeozeroError), - /// [geos::Error] - #[error(transparent)] - Geos(#[from] geos::Error), - /// Invalid CQL2 text #[error("invalid cql2-text: {0}")] InvalidCql2Text(String), @@ -78,8 +74,8 @@ pub enum Error { #[error("Could not convert Expression to bool")] ExprToBool(), - /// Error Converting Expr to geos geometry - #[error("Could not convert Expression to Geos Geometry")] + /// Error Converting Expr to geometry + #[error("Could not convert Expression to Geometry")] ExprToGeom(), /// Error Converting Expr to DateRange diff --git a/src/expr.rs b/src/expr.rs index b46ef5f..d954070 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,11 +1,12 @@ use crate::{geometry::spatial_op, temporal::temporal_op, Error, Geometry, SqlQuery, Validator}; -use geos::Geometry as GGeom; +use geo_types::Geometry as GGeom; use json_dotpath::DotPaths; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashSet; use std::str::FromStr; +use wkt::TryFromWkt; const BOOLOPS: &[&str] = &["and", "or"]; const EQOPS: &[&str] = &["=", "<>"]; @@ -125,8 +126,8 @@ impl TryFrom for GGeom { type Error = Error; fn try_from(v: Expr) -> Result { match v { - Expr::Geometry(v) => Ok(GGeom::new_from_wkt(&v.to_wkt().unwrap()) - .expect("Failed to convert WKT to Geos Geometry")), + Expr::Geometry(v) => Ok(GGeom::try_from_wkt_str(&v.to_wkt().unwrap()) + .expect("Failed to convert WKT to Geometry")), _ => Err(Error::ExprToGeom()), } } diff --git a/src/geometry.rs b/src/geometry.rs index 435c641..4133f4f 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -1,7 +1,8 @@ use std::cmp::Ordering; use crate::{Error, Expr}; -use geos::{Geom, Geometry as GGeom}; +use geo::*; +use geo_types::Geometry as GGeom; use geozero::{wkt::Wkt, CoordDimensions, ToGeo, ToWkt}; use serde::{Deserialize, Serialize, Serializer}; @@ -100,19 +101,17 @@ fn geojson_ndims(geojson: &geojson::Geometry) -> usize { pub fn spatial_op(left: Expr, right: Expr, op: &str) -> Result { let left: GGeom = GGeom::try_from(left)?; let right: GGeom = GGeom::try_from(right)?; - let out: Result = match op { - "s_equals" => Ok(left == right), - "s_intersects" | "intersects" => left.intersects(&right).map_err(Error::from), - "s_disjoint" => left.disjoint(&right).map_err(Error::from), - "s_touches" => left.touches(&right).map_err(Error::from), - "s_within" => left.within(&right).map_err(Error::from), - "s_overlaps" => left.overlaps(&right).map_err(Error::from), - "s_crosses" => left.crosses(&right).map_err(Error::from), - "s_contains" => left.contains(&right).map_err(Error::from), - _ => Err(Error::OpNotImplemented("Spatial")), + let rel = left.relate(&right); + let out = match op { + "s_equals" => rel.is_equal_topo(), + "s_intersects" | "intersects" => rel.is_intersects(), + "s_disjoint" => rel.is_disjoint(), + "s_touches" => rel.is_touches(), + "s_within" => rel.is_within(), + "s_overlaps" => rel.is_overlaps(), + "s_crosses" => rel.is_crosses(), + "s_contains" => rel.is_contains(), + &_ => todo!(), }; - match out { - Ok(v) => Ok(Expr::Bool(v)), - _ => Err(Error::OperationError()), - } + Ok(Expr::Bool(out)) } From 0170ce04abd2f172cb0a1c892c547fda7e5dc5e9 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 21 Jan 2025 10:11:28 -0600 Subject: [PATCH 13/17] add cast from bbox to geometry --- src/expr.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/expr.rs b/src/expr.rs index d954070..3255a90 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,5 +1,6 @@ use crate::{geometry::spatial_op, temporal::temporal_op, Error, Geometry, SqlQuery, Validator}; use geo_types::Geometry as GGeom; +use geo_types::{coord, Rect}; use json_dotpath::DotPaths; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; @@ -128,6 +129,26 @@ impl TryFrom for GGeom { match v { Expr::Geometry(v) => Ok(GGeom::try_from_wkt_str(&v.to_wkt().unwrap()) .expect("Failed to convert WKT to Geometry")), + Expr::BBox{bbox} => { + let minx: f64 = bbox[0].as_ref().clone().try_into()?; + let miny: f64 = bbox[1].as_ref().clone().try_into()?; + let maxx: f64; + let maxy: f64; + + match bbox.len() { + 4 => { + maxx = bbox[2].as_ref().clone().try_into()?; + maxy = bbox[3].as_ref().clone().try_into()?; + }, + 6 => { + maxx = bbox[3].as_ref().clone().try_into()?; + maxy = bbox[4].as_ref().clone().try_into()?; + }, + _ => return Err(Error::ExprToGeom()) + }; + let rec = Rect::new(coord!{x:minx, y:miny}, coord!{x:maxx,y:maxy}); + Ok(rec.into()) + }, _ => Err(Error::ExprToGeom()), } } From a7276bb0d94697e6c9875d269e44ad3b27c1a5e8 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 21 Jan 2025 11:20:57 -0600 Subject: [PATCH 14/17] add support for like, in, not, between --- Cargo.lock | 41 ++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/expr.rs | 52 +++++++++++++++++++++++++++++++++++++++++--- tests/reductions.txt | 22 +++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1419ec5..c826d6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "jiff", "json_dotpath", "lazy_static", + "like", "pest", "pest_derive", "pg_escape", @@ -246,6 +247,7 @@ dependencies = [ "serde_derive", "serde_json", "thiserror 2.0.11", + "unaccent", "wkt 0.12.0", ] @@ -839,6 +841,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "like" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7281e4b2b1a1fae03463a7c49dd21464de50251a450f6da9715c40c7b21a70" + [[package]] name = "litemap" version = "0.7.4" @@ -1436,6 +1444,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.6.8" @@ -1465,12 +1488,30 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unaccent" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a302f53d684e29e3d2ce7a83bb9bd2a0ee4a0793c0d4b6be325983b9b304e935" +dependencies = [ + "unicode-normalization", +] + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unindent" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index f0a6b21..d38ff2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ geozero = "0.14.0" jiff = "0.1.24" json_dotpath = "1.1.0" lazy_static = "1.5" +like = "0.3.1" pest = "2.7" pest_derive = { version = "2.7", features = ["grammar-extras"] } pg_escape = "0.1.1" @@ -38,6 +39,7 @@ serde = "1.0" serde_derive = "1.0.217" serde_json = { version = "1.0.135", features = ["preserve_order"] } thiserror = "2.0" +unaccent = "0.1.0" wkt = "0.12.0" [dev-dependencies] diff --git a/src/expr.rs b/src/expr.rs index 3255a90..c68ea75 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -8,6 +8,8 @@ use serde_json::Value; use std::collections::HashSet; use std::str::FromStr; use wkt::TryFromWkt; +use unaccent::unaccent; +use like::Like; const BOOLOPS: &[&str] = &["and", "or"]; const EQOPS: &[&str] = &["=", "<>"]; @@ -282,6 +284,35 @@ impl Expr { } // no other operators should have arguments other than 2 + + if op == "not" { + let out = match args[0]{ + Expr::Bool(v) => Expr::Bool(!v), + _ => self.clone() + }; + return out + } + + if op == "casei" { + let out = match &args[0] { + Expr::Literal(v) => Expr::Literal(v.to_lowercase()), + _ => self.clone() + }; + return out + } + + if op == "accenti" { + let out = match &args[0] { + Expr::Literal(v) => Expr::Literal(unaccent(v)), + _ => self.clone() + }; + return out + } + + if op == "between" { + return Expr::Bool(args[0] >= args[1] && args[0] <= args[2]); + } + if args.len() != 2 { return self.clone(); } @@ -306,10 +337,25 @@ impl Expr { if ARRAYOPS.contains(&op) { return array_op(left, right, op).unwrap_or(self.clone()); } - self.clone() - } - _ => self.clone(), + if op == "like" { + let l: String = String::try_from(left).expect("Could not convert left arg to string"); + let r: String = String::try_from(right).expect("Could not convert right arg to string"); + let m: bool = Like::::like(l.as_str(),r.as_str()).expect("Could not compare using like"); + return Expr::Bool(m); + } + if op == "in" { + let l: String = left.to_text().expect("Could not convert arg to string"); + let r: HashSet = right.try_into().expect("Could not convert arg to strings"); + let isin: bool = r.contains(&l); + return Expr::Bool(isin); + } + + + + self.clone() + }, + _ => self.clone() } } /// Run CQL against a JSON Value diff --git a/tests/reductions.txt b/tests/reductions.txt index fb01847..f5693ce 100644 --- a/tests/reductions.txt +++ b/tests/reductions.txt @@ -34,3 +34,25 @@ A_CONTAINEDBY((1,2),(1,2,3)) true A_EQUALS(tstarr,(1,2,3)) true +1 in (1,2,4) +true +'a' in ('a','b','c') +true +'a' in ('d','e','f') +false +'this' like 'th%' +true +not(true) +false +not(1+3=1) +true +casei('aardvarK') = casei('Aardvark') +true +accenti('Café') = accenti('Cafe') +true +1 between 1 and 2 +true +1 between 3 and 4 +false +1 is null +false From 2e2d117d78241b56369f89645b2c363f61835d14 Mon Sep 17 00:00:00 2001 From: David W Bitner Date: Tue, 21 Jan 2025 11:28:50 -0600 Subject: [PATCH 15/17] lint --- src/expr.rs | 48 +++++++++++++++++++++++--------------------- tests/reductions.txt | 2 -- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index c68ea75..2413e06 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -2,14 +2,14 @@ use crate::{geometry::spatial_op, temporal::temporal_op, Error, Geometry, SqlQue use geo_types::Geometry as GGeom; use geo_types::{coord, Rect}; use json_dotpath::DotPaths; +use like::Like; use pg_escape::{quote_identifier, quote_literal}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashSet; use std::str::FromStr; -use wkt::TryFromWkt; use unaccent::unaccent; -use like::Like; +use wkt::TryFromWkt; const BOOLOPS: &[&str] = &["and", "or"]; const EQOPS: &[&str] = &["=", "<>"]; @@ -131,7 +131,7 @@ impl TryFrom for GGeom { match v { Expr::Geometry(v) => Ok(GGeom::try_from_wkt_str(&v.to_wkt().unwrap()) .expect("Failed to convert WKT to Geometry")), - Expr::BBox{bbox} => { + Expr::BBox { bbox } => { let minx: f64 = bbox[0].as_ref().clone().try_into()?; let miny: f64 = bbox[1].as_ref().clone().try_into()?; let maxx: f64; @@ -141,16 +141,16 @@ impl TryFrom for GGeom { 4 => { maxx = bbox[2].as_ref().clone().try_into()?; maxy = bbox[3].as_ref().clone().try_into()?; - }, + } 6 => { maxx = bbox[3].as_ref().clone().try_into()?; maxy = bbox[4].as_ref().clone().try_into()?; - }, - _ => return Err(Error::ExprToGeom()) + } + _ => return Err(Error::ExprToGeom()), }; - let rec = Rect::new(coord!{x:minx, y:miny}, coord!{x:maxx,y:maxy}); + let rec = Rect::new(coord! {x:minx, y:miny}, coord! {x:maxx,y:maxy}); Ok(rec.into()) - }, + } _ => Err(Error::ExprToGeom()), } } @@ -286,27 +286,27 @@ impl Expr { // no other operators should have arguments other than 2 if op == "not" { - let out = match args[0]{ + let out = match args[0] { Expr::Bool(v) => Expr::Bool(!v), - _ => self.clone() + _ => self.clone(), }; - return out + return out; } if op == "casei" { let out = match &args[0] { Expr::Literal(v) => Expr::Literal(v.to_lowercase()), - _ => self.clone() + _ => self.clone(), }; - return out + return out; } if op == "accenti" { let out = match &args[0] { Expr::Literal(v) => Expr::Literal(unaccent(v)), - _ => self.clone() + _ => self.clone(), }; - return out + return out; } if op == "between" { @@ -339,23 +339,25 @@ impl Expr { } if op == "like" { - let l: String = String::try_from(left).expect("Could not convert left arg to string"); - let r: String = String::try_from(right).expect("Could not convert right arg to string"); - let m: bool = Like::::like(l.as_str(),r.as_str()).expect("Could not compare using like"); + let l: String = + String::try_from(left).expect("Could not convert left arg to string"); + let r: String = + String::try_from(right).expect("Could not convert right arg to string"); + let m: bool = Like::::like(l.as_str(), r.as_str()) + .expect("Could not compare using like"); return Expr::Bool(m); } if op == "in" { let l: String = left.to_text().expect("Could not convert arg to string"); - let r: HashSet = right.try_into().expect("Could not convert arg to strings"); + let r: HashSet = + right.try_into().expect("Could not convert arg to strings"); let isin: bool = r.contains(&l); return Expr::Bool(isin); } - - self.clone() - }, - _ => self.clone() + } + _ => self.clone(), } } /// Run CQL against a JSON Value diff --git a/tests/reductions.txt b/tests/reductions.txt index f5693ce..ebe3b4c 100644 --- a/tests/reductions.txt +++ b/tests/reductions.txt @@ -54,5 +54,3 @@ true true 1 between 3 and 4 false -1 is null -false From 2ee37496ac9d15ed61d4989db3ce29bcd4f52d46 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 21 Jan 2025 14:37:05 -0700 Subject: [PATCH 16/17] fix: owned reduce (#57) * fix: take ownership when reducing * fix: remove identical branches --- cli/src/lib.rs | 2 +- src/error.rs | 8 ++ src/expr.rs | 194 +++++++++++++++++++----------------------- tests/reduce_tests.rs | 2 +- 4 files changed, 98 insertions(+), 108 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 1f838cd..25f5a8e 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -110,7 +110,7 @@ impl Cli { }, }; if self.reduce { - expr = expr.reduce(Some(&json!({}))); + expr = expr.reduce(Some(&json!({})))?; } if self.validate { let validator = Validator::new().unwrap(); diff --git a/src/error.rs b/src/error.rs index 3919e1f..00de81a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,4 +93,12 @@ pub enum Error { /// Could not run arith operation #[error("Could not run operation.")] OperationError(), + + /// [json_dotpath::Error] + #[error(transparent)] + JsonDotpath(#[from] json_dotpath::Error), + + /// [like::Error] + #[error(transparent)] + Like(#[from] like::InvalidPatternError), } diff --git a/src/expr.rs b/src/expr.rs index 2413e06..6570fec 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -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::ops::Deref; use std::str::FromStr; use unaccent::unaccent; use wkt::TryFromWkt; @@ -102,12 +103,12 @@ impl TryFrom for f64 { } } -impl TryFrom for bool { +impl TryFrom<&Expr> for bool { type Error = Error; - fn try_from(v: Expr) -> Result { + fn try_from(v: &Expr) -> Result { match v { - Expr::Bool(v) => Ok(v), - Expr::Literal(v) => bool::from_str(&v).map_err(Error::from), + Expr::Bool(v) => Ok(*v), + Expr::Literal(v) => bool::from_str(v).map_err(Error::from), _ => Err(Error::ExprToBool()), } } @@ -235,131 +236,112 @@ impl Expr { /// let item = json!({"properties":{"eo:cloud_cover":10, "datetime": "2020-01-01 00:00:00Z", "boolfield": true}}); /// /// let fromexpr: Expr = Expr::from_str("boolfield = true").unwrap(); - /// let reduced = fromexpr.reduce(Some(&item)); + /// let reduced = fromexpr.reduce(Some(&item)).unwrap(); /// let toexpr: Expr = Expr::from_str("true").unwrap(); /// assert_eq!(reduced, toexpr); /// /// let fromexpr: Expr = Expr::from_str("\"eo:cloud_cover\" + 10").unwrap(); - /// let reduced = fromexpr.reduce(Some(&item)); + /// let reduced = fromexpr.reduce(Some(&item)).unwrap(); /// let toexpr: Expr = Expr::from_str("20").unwrap(); /// assert_eq!(reduced, toexpr); /// /// ``` - pub fn reduce(&self, j: Option<&Value>) -> Expr { - fn reduce_args(args: &[Box], j: Option<&Value>) -> Vec { - let mut outargs = args.to_owned(); - outargs.iter_mut().map(|arg| arg.reduce(j)).collect() - } + pub fn reduce(self, j: Option<&Value>) -> Result { match self { - Expr::Property { property } => { + Expr::Property { ref property } => { if let Some(j) = j { - let propexpr: Option = if j.dot_has(property) { - j.dot_get(property).unwrap() + if let Some(value) = j.dot_get::(property)? { + Expr::try_from(value) + } else if let Some(value) = + j.dot_get::(&format!("properties.{}", property))? + { + Expr::try_from(value) } else { - j.dot_get(&format!("properties.{}", property)).unwrap() - }; - if let Some(v) = propexpr { - return Expr::try_from(v).unwrap(); + Ok(self) } + } else { + Ok(self) } - self.clone() } Expr::Operation { op, args } => { - let op = op.as_str(); - let args: Vec = reduce_args(args, j); + let args: Vec> = args + .into_iter() + .map(|expr| expr.reduce(j).map(Box::new)) + .collect::>()?; - if BOOLOPS.contains(&op) { - let bools: Result, Error> = - args.into_iter().map(bool::try_from).collect(); + if BOOLOPS.contains(&op.as_str()) { + let bools: Result, Error> = args + .iter() + .map(|expr| bool::try_from(expr.as_ref())) + .collect(); if let Ok(bools) = bools { - match op { - "and" => return Expr::Bool(bools.into_iter().all(|x| x)), - "or" => return Expr::Bool(bools.into_iter().any(|x| x)), - _ => return self.clone(), + 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 }), } } else { - return self.clone(); + Ok(Expr::Operation { op, args }) + } + } else if op == "not" { + match args[0].deref() { + Expr::Bool(v) => Ok(Expr::Bool(!v)), + _ => Ok(Expr::Operation { op, args }), + } + } else if op == "casei" { + match args[0].as_ref() { + Expr::Literal(v) => Ok(Expr::Literal(v.to_lowercase())), + _ => Ok(Expr::Operation { op, args }), + } + } else if op == "accenti" { + match args[0].as_ref() { + Expr::Literal(v) => Ok(Expr::Literal(unaccent(v))), + _ => Ok(Expr::Operation { op, args }), + } + } else if op == "between" { + Ok(Expr::Bool(args[0] >= args[1] && args[0] <= args[2])) + } else if args.len() != 2 { + Ok(Expr::Operation { op, args }) + } else { + // Two-arg operations operations + 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::::like(l.as_str(), r.as_str())?; + Ok(Expr::Bool(m)) + } else if op == "in" { + let l: String = left.to_text()?; + let r: HashSet = right.try_into()?; + let isin: bool = r.contains(&l); + Ok(Expr::Bool(isin)) + } else { + Ok(Expr::Operation { op, args }) } } - - // no other operators should have arguments other than 2 - - if op == "not" { - let out = match args[0] { - Expr::Bool(v) => Expr::Bool(!v), - _ => self.clone(), - }; - return out; - } - - if op == "casei" { - let out = match &args[0] { - Expr::Literal(v) => Expr::Literal(v.to_lowercase()), - _ => self.clone(), - }; - return out; - } - - if op == "accenti" { - let out = match &args[0] { - Expr::Literal(v) => Expr::Literal(unaccent(v)), - _ => self.clone(), - }; - return out; - } - - if op == "between" { - return Expr::Bool(args[0] >= args[1] && args[0] <= args[2]); - } - - if args.len() != 2 { - return self.clone(); - } - let left = args[0].clone(); - let right = args[1].clone(); - - if SPATIALOPS.contains(&op) { - return spatial_op(left, right, op).unwrap_or(self.clone()); - } - - if TEMPORALOPS.contains(&op) { - return temporal_op(left, right, op).unwrap_or(self.clone()); - } - if ARITHOPS.contains(&op) { - return arith_op(left, right, op).unwrap_or(self.clone()); - } - - if EQOPS.contains(&op) | CMPOPS.contains(&op) { - return cmp_op(left, right, op).unwrap_or(self.clone()); - } - - if ARRAYOPS.contains(&op) { - return array_op(left, right, op).unwrap_or(self.clone()); - } - - if op == "like" { - let l: String = - String::try_from(left).expect("Could not convert left arg to string"); - let r: String = - String::try_from(right).expect("Could not convert right arg to string"); - let m: bool = Like::::like(l.as_str(), r.as_str()) - .expect("Could not compare using like"); - return Expr::Bool(m); - } - if op == "in" { - let l: String = left.to_text().expect("Could not convert arg to string"); - let r: HashSet = - right.try_into().expect("Could not convert arg to strings"); - let isin: bool = r.contains(&l); - return Expr::Bool(isin); - } - - self.clone() } - _ => self.clone(), + _ => Ok(self), } } + /// Run CQL against a JSON Value /// /// # Examples @@ -372,8 +354,8 @@ impl Expr { /// let mut expr: Expr = "boolfield and 1 + 2 = 3".parse().unwrap(); /// assert_eq!(true, expr.matches(Some(&item)).unwrap()); /// ``` - pub fn matches(&self, j: Option<&Value>) -> Result { - let reduced = self.reduce(j); + pub fn matches(self, j: Option<&Value>) -> Result { + let reduced = self.reduce(j)?; match reduced { Expr::Bool(v) => Ok(v), _ => Err(Error::NonReduced()), diff --git a/tests/reduce_tests.rs b/tests/reduce_tests.rs index cc42821..c88d1fd 100644 --- a/tests/reduce_tests.rs +++ b/tests/reduce_tests.rs @@ -26,7 +26,7 @@ fn validate_reduction(a: String, b: String) { } ); let inexpr: Expr = a.parse().unwrap(); - let reduced = inexpr.reduce(Some(&properties)); + let reduced = inexpr.reduce(Some(&properties)).unwrap(); let outexpr: Expr = b.parse().unwrap(); assert_eq!(reduced, outexpr); } From b8767acede5f96b52e094025c7fb7d1755168ae2 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 21 Jan 2025 14:44:54 -0700 Subject: [PATCH 17/17] feat: own errors --- src/error.rs | 9 +++++---- src/expr.rs | 14 +++++++------- src/temporal.rs | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/error.rs b/src/error.rs index 00de81a..30da851 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +use crate::Expr; use thiserror::Error; /// Crate-specific error enum. @@ -68,19 +69,19 @@ pub enum Error { /// Error Converting Expr to f64 #[error("Could not convert Expression to f64")] - ExprToF64(), + ExprToF64(Expr), /// Error Converting Expr to bool #[error("Could not convert Expression to bool")] - ExprToBool(), + ExprToBool(Expr), /// Error Converting Expr to geometry #[error("Could not convert Expression to Geometry")] - ExprToGeom(), + ExprToGeom(Expr), /// Error Converting Expr to DateRange #[error("Could not convert Expression to DateRange")] - ExprToDateRange(), + ExprToDateRange(Expr), /// Operator not implemented. #[error("Operator {0} is not implemented for this type.")] diff --git a/src/expr.rs b/src/expr.rs index 6570fec..b1211d0 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -98,7 +98,7 @@ impl TryFrom for f64 { match v { Expr::Float(v) => Ok(v), Expr::Literal(v) => f64::from_str(&v).map_err(Error::from), - _ => Err(Error::ExprToF64()), + _ => Err(Error::ExprToF64(v)), } } } @@ -109,7 +109,7 @@ impl TryFrom<&Expr> for bool { match v { Expr::Bool(v) => Ok(*v), Expr::Literal(v) => bool::from_str(v).map_err(Error::from), - _ => Err(Error::ExprToBool()), + _ => Err(Error::ExprToBool(v.clone())), } } } @@ -121,7 +121,7 @@ impl TryFrom for String { Expr::Literal(v) => Ok(v), Expr::Bool(v) => Ok(v.to_string()), Expr::Float(v) => Ok(v.to_string()), - _ => Err(Error::ExprToBool()), + _ => Err(Error::ExprToBool(v)), } } } @@ -132,7 +132,7 @@ impl TryFrom for GGeom { match v { Expr::Geometry(v) => Ok(GGeom::try_from_wkt_str(&v.to_wkt().unwrap()) .expect("Failed to convert WKT to Geometry")), - Expr::BBox { bbox } => { + Expr::BBox { ref bbox } => { let minx: f64 = bbox[0].as_ref().clone().try_into()?; let miny: f64 = bbox[1].as_ref().clone().try_into()?; let maxx: f64; @@ -147,12 +147,12 @@ impl TryFrom for GGeom { maxx = bbox[3].as_ref().clone().try_into()?; maxy = bbox[4].as_ref().clone().try_into()?; } - _ => return Err(Error::ExprToGeom()), + _ => return Err(Error::ExprToGeom(v.clone())), }; let rec = Rect::new(coord! {x:minx, y:miny}, coord! {x:maxx,y:maxy}); Ok(rec.into()) } - _ => Err(Error::ExprToGeom()), + _ => Err(Error::ExprToGeom(v)), } } } @@ -168,7 +168,7 @@ impl TryFrom for HashSet { } Ok(h) } - _ => Err(Error::ExprToGeom()), + _ => Err(Error::ExprToGeom(v)), } } } diff --git a/src/temporal.rs b/src/temporal.rs index cf679da..ab23fed 100644 --- a/src/temporal.rs +++ b/src/temporal.rs @@ -34,7 +34,7 @@ impl TryFrom for DateRange { let start: Timestamp = v.parse().unwrap(); Ok(DateRange { start, end: start }) } - _ => Err(Error::ExprToDateRange()), + _ => Err(Error::ExprToDateRange(v)), } } }