diff --git a/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval.rs b/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval.rs index 6ff5bc199b37..2b27f09c1751 100644 --- a/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval.rs +++ b/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval.rs @@ -178,13 +178,21 @@ impl Context<()> for NoneContext {} impl TryFrom for Evaluator { type Error = ParsePbError; - fn try_from(suffix_tree: common_pb::Expression) -> ParsePbResult + fn try_from(expression: common_pb::Expression) -> ParsePbResult where Self: Sized, { - let mut inner_tree: Vec = Vec::with_capacity(suffix_tree.operators.len()); - let suffix_oprs = to_suffix_expr(suffix_tree.operators) + let suffix_oprs = to_suffix_expr(expression.operators) .map_err(|err| ParsePbError::ParseError(format!("{:?}", err)))?; + suffix_oprs.try_into() + } +} + +impl TryFrom> for Evaluator { + type Error = ParsePbError; + + fn try_from(suffix_oprs: Vec) -> ParsePbResult { + let mut inner_tree: Vec = Vec::with_capacity(suffix_oprs.len()); for unit in suffix_oprs { inner_tree.push(InnerOpr::try_from(unit)?); } @@ -825,7 +833,7 @@ impl InnerOpr { mod tests { use ahash::HashMap; use dyn_type::DateTimeFormats; - use ir_common::expr_parse::str_to_expr_pb; + use ir_common::{expr_parse::str_to_expr_pb, generated::physical::physical_opr::operator}; use super::*; use crate::apis::{DynDetails, Vertex}; @@ -1345,6 +1353,66 @@ mod tests { } } + #[test] + fn test_eval_extract_02() { + // expression: 0.date1.day >= 9, fall back to general evaluator + // date1: "2020-08-08" + // datetime2: "2020-08-09 10:11:12.100" + let mut operators1 = str_to_expr_pb("@0.date1".to_string()) + .unwrap() + .operators; + let mut operators2 = str_to_expr_pb("@0.datetime2".to_string()) + .unwrap() + .operators; + let extract_opr = common_pb::ExprOpr { + node_type: None, + item: Some(common_pb::expr_opr::Item::Extract(common_pb::Extract { + interval: common_pb::extract::Interval::Day as i32, + })), + }; + operators1.push(extract_opr.clone()); + operators2.push(extract_opr); + let cmp_opr = common_pb::ExprOpr { + node_type: None, + item: Some(common_pb::expr_opr::Item::Logical(common_pb::Logical::Ge as i32)), + }; + operators1.push(cmp_opr.clone()); + operators2.push(cmp_opr); + let right = common_pb::ExprOpr { + node_type: None, + item: Some(common_pb::expr_opr::Item::Const(common_pb::Value { + item: Some(common_pb::value::Item::I32(9)), + })), + }; + operators1.push(right.clone()); + operators2.push(right); + let expr1 = common_pb::Expression { operators: operators1 }; + let expr2 = common_pb::Expression { operators: operators2 }; + + let eval1 = PEvaluator::try_from(expr1).unwrap(); + let eva2 = PEvaluator::try_from(expr2).unwrap(); + match eval1 { + PEvaluator::Predicates(_) => panic!("should fall back to general evaluator"), + PEvaluator::General(_) => assert!(true), + } + match eva2 { + PEvaluator::Predicates(_) => panic!("should fall back to general evaluator"), + PEvaluator::General(_) => assert!(true), + } + let ctxt = prepare_context_with_date(); + assert_eq!( + eval1 + .eval_bool::<_, Vertices>(Some(&ctxt)) + .unwrap(), + false + ); + assert_eq!( + eva2.eval_bool::<_, Vertices>(Some(&ctxt)) + .unwrap(), + true + ); + } + fn gen_regex_expression(to_match: &str, pattern: &str) -> common_pb::Expression { let mut regex_expr = common_pb::Expression { operators: vec![] }; let left = common_pb::ExprOpr { @@ -1375,8 +1443,14 @@ mod tests { // So use gen_regex_expression() to help generate expression let cases: Vec<(&str, &str)> = vec![ ("Josh", r"^J"), // startWith, true + ("Josh", r"^J.*"), // startWith, true + ("josh", r"^J.*"), // startWith, false + ("BJosh", r"^J.*"), // startWith, false ("Josh", r"J.*"), // true ("Josh", r"h$"), // endWith, true + ("Josh", r".*h$"), // endWith, true + ("JosH", r".*h$"), // endWith, false + ("JoshB", r".*h$"), // endWith, false ("Josh", r".*h"), // true ("Josh", r"os"), // true ("Josh", r"A.*"), // false @@ -1387,9 +1461,15 @@ mod tests { (r"I categorically deny having triskaidekaphobia.", r"\b\w{13}\b"), //true ]; let expected: Vec = vec![ + object!(true), + object!(true), + object!(false), + object!(false), object!(true), object!(true), object!(true), + object!(false), + object!(false), object!(true), object!(true), object!(false), diff --git a/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval_pred.rs b/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval_pred.rs index 4c628159eb8d..710dc0725dc7 100644 --- a/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval_pred.rs +++ b/interactive_engine/executor/ir/graph_proxy/src/utils/expr/eval_pred.rs @@ -17,8 +17,8 @@ use std::convert::{TryFrom, TryInto}; use dyn_type::{BorrowObject, Object}; -use ir_common::error::ParsePbError; -use ir_common::expr_parse::error::{ExprError, ExprResult}; +use ir_common::error::{ParsePbError, ParsePbResult}; +use ir_common::expr_parse::to_suffix_expr; use ir_common::generated::algebra as pb; use ir_common::generated::common as common_pb; @@ -44,125 +44,6 @@ pub struct Predicate { pub(crate) right: Operand, } -#[allow(dead_code)] -#[derive(Debug, Clone)] -enum Partial { - // a single item can represent an operand (if only left is_some()), a unary operator (if both left and cmp is_some()), - // or a binary operator (if all left, cmp and right is_some()) - SingleItem { left: Option, cmp: Option, right: Option }, - Predicates(Predicates), -} - -impl Default for Partial { - fn default() -> Self { - Self::SingleItem { left: None, cmp: None, right: None } - } -} - -#[allow(dead_code)] -impl Partial { - pub fn get_left(&self) -> Option<&Operand> { - match self { - Partial::SingleItem { left, cmp: _, right: _ } => left.as_ref(), - Partial::Predicates(_) => None, - } - } - - pub fn left(&mut self, item: Operand) -> ExprResult<()> { - match self { - Self::SingleItem { left, cmp: _, right: _ } => { - *left = Some(item); - Ok(()) - } - Self::Predicates(_) => { - Err(ExprError::OtherErr(format!("invalid predicate: {:?}, {:?}", self, item))) - } - } - } - - pub fn cmp(&mut self, logical: common_pb::Logical) -> ExprResult<()> { - match self { - Partial::SingleItem { left: _, cmp, right: _ } => { - if cmp.is_some() { - Err(ExprError::OtherErr(format!("invalid predicate: {:?}, {:?}", self, logical))) - } else { - *cmp = Some(logical); - Ok(()) - } - } - Partial::Predicates(_) => Ok(()), - } - } - - pub fn right(&mut self, item: Operand) -> ExprResult<()> { - match self { - Partial::SingleItem { left: _, cmp, right } => { - if cmp.is_none() || right.is_some() { - Err(ExprError::OtherErr(format!("invalid predicate: {:?}, {:?}", self, item))) - } else { - *right = Some(item); - Ok(()) - } - } - Partial::Predicates(_) => Ok(()), - } - } - - pub fn predicates(&mut self, pred: Predicates) -> ExprResult<()> { - match self { - Partial::SingleItem { left, cmp: _, right: _ } => { - if !left.is_none() { - return Err(ExprError::OtherErr(format!("invalid predicate: {:?}, {:?}", self, pred))); - } - } - Partial::Predicates(_) => {} - } - *self = Self::Predicates(pred); - - Ok(()) - } -} - -impl TryFrom for Predicates { - type Error = ParsePbError; - fn try_from(partial: Partial) -> Result { - let partical_old = partial.clone(); - let predicate = match partial { - Partial::SingleItem { left, cmp, right } => { - if left.is_none() { - None - } else if cmp.is_none() { - // the case of operand - Some(Predicates::SingleItem(left.unwrap())) - } else if right.is_none() { - // the case of unary operator - if !cmp.unwrap().is_unary() { - None - } else { - Some(Predicates::Unary(UnaryPredicate { - operand: left.unwrap(), - cmp: cmp.unwrap(), - })) - } - } else { - // the case of binary operator - if !cmp.unwrap().is_binary() { - None - } else { - Some(Predicates::Binary(Predicate { - left: left.unwrap(), - cmp: cmp.unwrap(), - right: right.unwrap(), - })) - } - } - } - Partial::Predicates(pred) => Some(pred), - }; - predicate.ok_or_else(|| ParsePbError::ParseError(format!("invalid predicate: {:?}", partical_old))) - } -} - #[derive(Debug, Clone, PartialEq)] pub enum Predicates { Init, @@ -195,16 +76,19 @@ impl TryFrom for Predicates { } else { None }; - let partial = Partial::SingleItem { + let predicate = Predicate { left: triplet .key .map(|var| var.try_into()) - .transpose()?, - cmp: Some(unsafe { std::mem::transmute(triplet.cmp) }), - right: value.map(|val| val.try_into()).transpose()?, + .transpose()? + .ok_or_else(|| ParsePbError::ParseError("missing key in `Triplet`".to_string()))?, + cmp: unsafe { std::mem::transmute(triplet.cmp) }, + right: value + .map(|val| val.try_into()) + .transpose()? + .ok_or_else(|| ParsePbError::ParseError("missing value in `Triplet`".to_string()))?, }; - - partial.try_into() + Ok(Predicates::Binary(predicate)) } } @@ -436,121 +320,124 @@ impl Predicates { } } - fn merge_partial( - self, curr_cmp: Option, partial: Partial, is_not: bool, - ) -> ExprResult { - use common_pb::Logical; - let mut new_pred = Predicates::try_from(partial)?; - if is_not { - new_pred = new_pred.not() - }; - if let Some(cmp) = curr_cmp { - match cmp { - Logical::And => Ok(self.and(new_pred)), - Logical::Or => Ok(self.or(new_pred)), - _ => unreachable!(), - } - } else { - Ok(new_pred) + fn take_operand(self) -> Option { + match self { + Predicates::SingleItem(op) => Some(op), + _ => None, } } } #[allow(dead_code)] -fn process_predicates( - iter: &mut dyn Iterator, -) -> ExprResult> { +fn process_predicates(rpn_tokens: Vec) -> ParsePbResult { use common_pb::expr_opr::Item; use common_pb::Logical; - let mut partial = Partial::default(); - let mut predicates = Predicates::default(); - let mut curr_cmp: Option = None; - let mut is_not = false; - let mut left_brace_count = 0; - let mut container: Vec = Vec::new(); - - while let Some(opr) = iter.next() { + let mut stack: Vec = Vec::new(); + + for opr in rpn_tokens { if let Some(item) = &opr.item { match item { Item::Logical(l) => { - if left_brace_count == 0 { - let logical = unsafe { std::mem::transmute::(*l) }; - match logical { - Logical::Eq - | Logical::Ne - | Logical::Lt - | Logical::Le - | Logical::Gt - | Logical::Ge - | Logical::Within - | Logical::Without - | Logical::Startswith - | Logical::Endswith - | Logical::Regex - | Logical::Isnull => partial.cmp(logical)?, - Logical::Not => is_not = true, - Logical::And | Logical::Or => { - predicates = predicates.merge_partial(curr_cmp, partial, is_not)?; - if is_not { - is_not = false; - } - partial = Partial::default(); - curr_cmp = Some(logical); - } + let logical = unsafe { std::mem::transmute::(*l) }; + match logical { + Logical::Eq + | Logical::Ne + | Logical::Lt + | Logical::Le + | Logical::Gt + | Logical::Ge + | Logical::Within + | Logical::Without + | Logical::Startswith + | Logical::Endswith + | Logical::Regex => { + // binary operator + let rhs = stack + .pop() + .ok_or_else(|| { + ParsePbError::ParseError( + "missing right for binary operator".to_string(), + ) + })? + .take_operand() + .ok_or_else(|| { + ParsePbError::ParseError( + "missing right operand for binary operator".to_string(), + ) + })?; + let lhs = stack + .pop() + .ok_or_else(|| { + ParsePbError::ParseError("missing left for binary operator".to_string()) + })? + .take_operand() + .ok_or_else(|| { + ParsePbError::ParseError( + "missing left operand for binary operator".to_string(), + ) + })?; + stack.push(Predicates::Binary(Predicate { + left: lhs, + cmp: logical, + right: rhs, + })); } - } else { - container.push(opr.clone()); - } - } - Item::Const(_) | Item::Var(_) | Item::Vars(_) | Item::VarMap(_) | Item::Map(_) => { - if left_brace_count == 0 { - if partial.get_left().is_none() { - partial.left(opr.clone().try_into()?)?; - } else { - partial.right(opr.clone().try_into()?)?; + Logical::Isnull => { + // unary operator + let operand = stack + .pop() + .ok_or_else(|| { + ParsePbError::ParseError( + "missing operand for unary operator".to_string(), + ) + })? + .take_operand() + .ok_or_else(|| { + ParsePbError::ParseError( + "missing operand for unary operator".to_string(), + ) + })?; + stack.push(Predicates::Unary(UnaryPredicate { operand, cmp: logical })); } - } else { - container.push(opr.clone()); - } - } - Item::Brace(brace) => { - if *brace == 0 { - if left_brace_count != 0 { - container.push(opr.clone()); + Logical::Not => { + let pred = stack.pop().ok_or_else(|| { + ParsePbError::ParseError( + "missing predicates for unary operator".to_string(), + ) + })?; + stack.push(Predicates::Not(Box::new(pred))); } - left_brace_count += 1; - } else { - left_brace_count -= 1; - if left_brace_count == 0 { - let mut iter = container.iter(); - if let Some(p) = process_predicates(&mut iter)? { - partial.predicates(p)?; - container.clear(); - } else { - return Ok(None); - } - } else { - container.push(opr.clone()); + Logical::And => { + let rhs = stack.pop().ok_or_else(|| { + ParsePbError::ParseError("missing right for And operator".to_string()) + })?; + let lhs = stack.pop().ok_or_else(|| { + ParsePbError::ParseError("missing left for And operator".to_string()) + })?; + stack.push(Predicates::And((Box::new(lhs), Box::new(rhs)))); + } + Logical::Or => { + let rhs = stack.pop().ok_or_else(|| { + ParsePbError::ParseError("missing right for Or operator".to_string()) + })?; + let lhs = stack.pop().ok_or_else(|| { + ParsePbError::ParseError("missing left for Or operator".to_string()) + })?; + stack.push(Predicates::Or((Box::new(lhs), Box::new(rhs)))); } } } - Item::Arith(_) => return Ok(None), - Item::Param(param) => { - return Err(ExprError::unsupported(format!("Dynamic Param {:?}", param))) - } - Item::Case(case) => return Err(ExprError::unsupported(format!("Case When {:?}", case))), - Item::Extract(extract) => { - return Err(ExprError::unsupported(format!("Extract {:?}", extract))) + Item::Const(_) | Item::Var(_) | Item::Vars(_) | Item::VarMap(_) | Item::Map(_) => { + stack.push(Predicates::SingleItem(opr.try_into()?)); } - _ => return Err(ExprError::unsupported(format!("Item {:?}", item))), + _ => return Err(ParsePbError::Unsupported(format!("Item {:?}", item))), } } } - - if left_brace_count == 0 { - Ok(Some(predicates.merge_partial(curr_cmp, partial, is_not)?)) + if stack.len() != 1 { + Err(ParsePbError::ParseError(format!("{:?} in predicate parsing", stack))) } else { - Err(ExprError::UnmatchedLRBraces) + Ok(stack.pop().unwrap()) } } @@ -580,13 +467,21 @@ impl TryFrom for PEvaluator { type Error = ParsePbError; fn try_from(expr: common_pb::Expression) -> Result { - let mut iter = expr.operators.iter(); - if let Some(pred) = - process_predicates(&mut iter).map_err(|err| ParsePbError::ParseError(format!("{:?}", err)))? - { - Ok(Self::Predicates(pred)) - } else { - Ok(Self::General(expr.try_into()?)) + let suffix_oprs = to_suffix_expr(expr.operators.clone()) + .map_err(|err| ParsePbError::ParseError(format!("{:?}", err)))?; + let pred_res = process_predicates(suffix_oprs.clone()); + match pred_res { + Ok(pred) => { + return Ok(Self::Predicates(pred)); + } + Err(err) => match err { + ParsePbError::Unsupported(_) => { + // fall back to general evaluator + let eval = Evaluator::try_from(expr)?; + Ok(Self::General(eval)) + } + _ => Err(err), + }, } } } @@ -984,12 +879,18 @@ mod tests { .eval_bool::<_, Vertices>(Some(&context)) .unwrap()); + let expr = str_to_expr_pb("true || true && false".to_string()).unwrap(); + let p_eval = PEvaluator::try_from(expr).unwrap(); + assert!(p_eval + .eval_bool::<_, Vertices>(Some(&context)) + .unwrap()); + let expr = str_to_expr_pb( "@0.name == \"John\" && @0.age > 27 || @1.~label == 11 && @1.name == \"Alien\"".to_string(), ) .unwrap(); let p_eval = PEvaluator::try_from(expr).unwrap(); - assert!(!p_eval + assert!(p_eval .eval_bool::<_, Vertices>(Some(&context)) .unwrap()); @@ -1072,8 +973,14 @@ mod tests { // So use gen_regex_expression() to help generate expression let cases: Vec<(&str, &str)> = vec![ ("@0.name", r"^J"), // startWith, true + ("@0.name", r"^J.*"), // startWith, true + ("@0.name", r"^j.*"), // startWith, false + ("@0.name", r"^B.*"), // startWith, false ("@0.name", r"J.*"), // true ("@0.name", r"n$"), // endWith, true + ("@0.name", r".*n$"), // endWith, true + ("@0.name", r".*s$"), // endWith, false + ("@0.name", r".*N$"), // endWith, false ("@0.name", r".*n"), // true ("@0.name", r"oh"), // true ("@0.name", r"A.*"), // false @@ -1082,7 +989,10 @@ mod tests { ("@0.name", r"John.+"), // false ("@0.str_birthday", r"^\d{4}-\d{2}-\d{2}$"), // true ]; - let expected: Vec = vec![true, true, true, true, true, false, false, false, false, true]; + let expected: Vec = vec![ + true, true, false, false, true, true, true, false, false, true, true, false, false, false, + false, true, + ]; for ((to_match, pattern), expected) in cases.into_iter().zip(expected.into_iter()) { let eval = PEvaluator::try_from(gen_regex_expression(to_match, pattern)).unwrap();