diff --git a/cozo-core/benches/pokec.rs b/cozo-core/benches/pokec.rs index 8c7eabfd..3e5143e8 100644 --- a/cozo-core/benches/pokec.rs +++ b/cozo-core/benches/pokec.rs @@ -300,7 +300,7 @@ fn aggregation_count() { fn aggregation_filter() { TEST_DB .run_script( - "?[age, count(age)] := *user{age}, coalesce(age, 0) >= 18", + "?[age, count(age)] := *user{age}, age ~ 0 >= 18", Default::default(), ) .unwrap(); @@ -331,7 +331,7 @@ fn expansion_1_filter() { let i = rng.gen_range(1..SIZES.0); TEST_DB .run_script( - "?[to] := *friends{fr: $id, to}, *user{uid: to, age}, coalesce(age, 0) >= 18", + "?[to] := *friends{fr: $id, to}, *user{uid: to, age}, age ~ 0 >= 18", BTreeMap::from([("id".to_string(), json!(i))]), ) .unwrap(); @@ -353,7 +353,7 @@ fn expansion_2_filter() { let i = rng.gen_range(1..SIZES.0); TEST_DB .run_script( - "?[to] := *friends{fr: $id, to: a}, *friends{fr: a, to}, *user{uid: to, age}, coalesce(age, 0) >= 18", + "?[to] := *friends{fr: $id, to: a}, *friends{fr: a, to}, *user{uid: to, age}, age ~ 0 >= 18", BTreeMap::from([("id".to_string(), json!(i))]), ) .unwrap(); @@ -381,7 +381,7 @@ fn expansion_3_filter() { r#" l1[to] := *friends{fr: $id, to} l2[to] := l1[fr], *friends{fr, to} - ?[to] := l2[fr], *friends{fr, to}, *user{uid: to, age}, coalesce(age, 0) >= 18 + ?[to] := l2[fr], *friends{fr, to}, *user{uid: to, age}, age ~ 0 >= 18 "#, BTreeMap::from([("id".to_string(), json!(i))]), ) @@ -440,8 +440,8 @@ fn neighbours_2_filter() { .run_script( r#" l1[to] := *friends{fr: $id, to} - ?[to] := l1[to], *user{uid: to, age}, coalesce(age, 0) >= 18 - ?[to] := l1[fr], *friends{fr, to}, *user{uid: to, age}, coalesce(age, 0) >= 18 + ?[to] := l1[to], *user{uid: to, age}, age ~ 0 >= 18 + ?[to] := l1[fr], *friends{fr, to}, *user{uid: to, age}, age ~ 0 >= 18 "#, BTreeMap::from([("id".to_string(), json!(i))]), ) @@ -470,8 +470,8 @@ fn neighbours_2_filter_data() { .run_script( r#" l1[to] := *friends{fr: $id, to} - ?[to] := l1[to], *user{uid: to, age, cmpl_pct, gender}, coalesce(age, 0) >= 18 - ?[to] := l1[fr], *friends{fr, to}, *user{uid: to, age, cmpl_pct, gender}, coalesce(age, 0) >= 18 + ?[to] := l1[to], *user{uid: to, age, cmpl_pct, gender}, age ~ 0 >= 18 + ?[to] := l1[fr], *friends{fr, to}, *user{uid: to, age, cmpl_pct, gender}, age ~ 0 >= 18 "#, BTreeMap::from([("id".to_string(), json!(i))]), ) diff --git a/cozo-core/src/cozoscript.pest b/cozo-core/src/cozoscript.pest index 2ebf5b53..39269165 100644 --- a/cozo-core/src/cozoscript.pest +++ b/cozo-core/src/cozoscript.pest @@ -80,7 +80,7 @@ grouped = _{"(" ~ rule_body ~ ")"} expr = {unary_op* ~ term ~ (operation ~ unary_op* ~ term)*} operation = _{ (op_and | op_or | op_pow | op_concat | op_add | op_sub | op_mul | op_div | op_mod | - op_ge | op_le | op_gt | op_lt | op_eq | op_ne)} + op_ge | op_le | op_gt | op_lt | op_eq | op_ne | op_coalesce )} op_or = { "||" } op_and = { "&&" } op_concat = { "++" } @@ -96,6 +96,7 @@ op_lt = { "<" } op_ge = { ">=" } op_le = { "<=" } op_pow = { "^" } +op_coalesce = { "~" } unary_op = _{ minus | negate } minus = { "-" } negate = { "!" } diff --git a/cozo-core/src/data/aggr.rs b/cozo-core/src/data/aggr.rs index 2275f4b6..b12cc603 100644 --- a/cozo-core/src/data/aggr.rs +++ b/cozo-core/src/data/aggr.rs @@ -716,51 +716,6 @@ impl MeetAggrObj for MeetAggrMax { } } -define_aggr!(AGGR_CHOICE_LAST, true); - -pub(crate) struct AggrChoiceLast { - found: DataValue, -} - -impl Default for AggrChoiceLast { - fn default() -> Self { - Self { - found: DataValue::Null, - } - } -} - -impl NormalAggrObj for AggrChoiceLast { - fn set(&mut self, value: &DataValue) -> Result<()> { - self.found = value.clone(); - Ok(()) - } - - fn get(&self) -> Result { - Ok(self.found.clone()) - } -} - -pub(crate) struct MeetAggrChoiceLast; - -impl MeetAggrObj for MeetAggrChoiceLast { - fn init_val(&self) -> DataValue { - DataValue::Null - } - - fn update(&self, left: &mut DataValue, right: &DataValue) -> Result { - if *right == DataValue::Null { - return Ok(false); - } - Ok(if *left == *right { - false - } else { - *left = right.clone(); - true - }) - } -} - define_aggr!(AGGR_LATEST_BY, false); pub(crate) struct AggrLatestBy { @@ -1178,7 +1133,6 @@ pub(crate) fn parse_aggr(name: &str) -> Option<&'static Aggregation> { "max" => &AGGR_MAX, "mean" => &AGGR_MEAN, "choice" => &AGGR_CHOICE, - "choice_last" => &AGGR_CHOICE_LAST, "collect" => &AGGR_COLLECT, "shortest" => &AGGR_SHORTEST, "min_cost" => &AGGR_MIN_COST, @@ -1199,7 +1153,6 @@ impl Aggregation { name if name == AGGR_MIN.name => Box::new(MeetAggrMin), name if name == AGGR_MAX.name => Box::new(MeetAggrMax), name if name == AGGR_CHOICE.name => Box::new(MeetAggrChoice), - name if name == AGGR_CHOICE_LAST.name => Box::new(MeetAggrChoiceLast), name if name == AGGR_BIT_AND.name => Box::new(MeetAggrBitAnd), name if name == AGGR_BIT_OR.name => Box::new(MeetAggrBitOr), name if name == AGGR_UNION.name => Box::new(MeetAggrUnion), @@ -1225,7 +1178,6 @@ impl Aggregation { name if name == AGGR_VARIANCE.name => Box::new(AggrVariance::default()), name if name == AGGR_STD_DEV.name => Box::new(AggrStdDev::default()), name if name == AGGR_CHOICE.name => Box::new(AggrChoice::default()), - name if name == AGGR_CHOICE_LAST.name => Box::new(AggrChoiceLast::default()), name if name == AGGR_BIT_AND.name => Box::new(AggrBitAnd::default()), name if name == AGGR_BIT_OR.name => Box::new(AggrBitOr::default()), name if name == AGGR_BIT_XOR.name => Box::new(AggrBitXor::default()), diff --git a/cozo-core/src/data/tests/aggrs.rs b/cozo-core/src/data/tests/aggrs.rs index 385f7784..b5ff0233 100644 --- a/cozo-core/src/data/tests/aggrs.rs +++ b/cozo-core/src/data/tests/aggrs.rs @@ -361,26 +361,6 @@ fn test_max() { assert_eq!(v, DataValue::from(10)); } -#[test] -fn test_choice_last() { - let mut aggr = parse_aggr("choice_last").unwrap().clone(); - aggr.normal_init(&[]).unwrap(); - aggr.meet_init(&[]).unwrap(); - - let mut choice_aggr = aggr.normal_op.unwrap(); - choice_aggr.set(&DataValue::from(1)).unwrap(); - choice_aggr.set(&DataValue::from(2)).unwrap(); - choice_aggr.set(&DataValue::from(3)).unwrap(); - assert_eq!(choice_aggr.get().unwrap(), DataValue::from(3)); - - let m_choice_aggr = aggr.meet_op.unwrap(); - let mut v = DataValue::from(5); - m_choice_aggr.update(&mut v, &DataValue::from(1)).unwrap(); - m_choice_aggr.update(&mut v, &DataValue::from(2)).unwrap(); - m_choice_aggr.update(&mut v, &DataValue::from(3)).unwrap(); - assert_eq!(v, DataValue::from(3)); -} - #[test] fn test_choice_rand() { let mut aggr = parse_aggr("choice_rand").unwrap().clone(); diff --git a/cozo-core/src/data/tests/functions.rs b/cozo-core/src/data/tests/functions.rs index a6327f71..95b746bd 100644 --- a/cozo-core/src/data/tests/functions.rs +++ b/cozo-core/src/data/tests/functions.rs @@ -9,10 +9,12 @@ use approx::AbsDiffEq; use num_traits::FloatConst; use regex::Regex; +use serde_json::json; use smartstring::SmartString; use crate::data::functions::*; use crate::data::value::{DataValue, RegexWrapper}; +use crate::new_cozo_mem; #[test] fn test_add() { @@ -1439,3 +1441,14 @@ fn test_to_bool() { DataValue::Bool(true) ); } + +#[test] +fn test_coalesce() { + let db = new_cozo_mem().unwrap(); + let res = db.run_script("?[a] := a = null ~ 1 ~ 2", Default::default()).unwrap().rows; + assert_eq!(res[0][0], json!(1)); + let res = db.run_script("?[a] := a = null ~ null ~ null", Default::default()).unwrap().rows; + assert_eq!(res[0][0], json!(null)); + let res = db.run_script("?[a] := a = 2 ~ null ~ 1", Default::default()).unwrap().rows; + assert_eq!(res[0][0], json!(2)); +} \ No newline at end of file diff --git a/cozo-core/src/parse/expr.rs b/cozo-core/src/parse/expr.rs index dff50760..f94d0950 100644 --- a/cozo-core/src/parse/expr.rs +++ b/cozo-core/src/parse/expr.rs @@ -17,8 +17,8 @@ use thiserror::Error; use crate::data::expr::{get_op, Expr}; use crate::data::functions::{ - OP_ADD, OP_AND, OP_CONCAT, OP_DIV, OP_EQ, OP_GE, OP_GT, OP_LE, OP_LIST, OP_LT, OP_MINUS, - OP_MOD, OP_MUL, OP_NEGATE, OP_NEQ, OP_OR, OP_POW, OP_SUB, + OP_ADD, OP_AND, OP_COALESCE, OP_CONCAT, OP_DIV, OP_EQ, OP_GE, OP_GT, OP_LE, OP_LIST, OP_LT, + OP_MINUS, OP_MOD, OP_MUL, OP_NEGATE, OP_NEQ, OP_OR, OP_POW, OP_SUB, }; use crate::data::symb::Symbol; use crate::data::value::DataValue; @@ -42,6 +42,7 @@ lazy_static! { | Op::infix(Rule::op_concat, Left)) .op(Op::infix(Rule::op_mul, Left) | Op::infix(Rule::op_div, Left)) .op(Op::infix(Rule::op_pow, Right)) + .op(Op::infix(Rule::op_coalesce, Left)) .op(Op::prefix(Rule::minus)) .op(Op::prefix(Rule::negate)) }; @@ -99,6 +100,7 @@ fn build_expr_infix(lhs: Result, op: Pair<'_>, rhs: Result) -> Resul Rule::op_concat => &OP_CONCAT, Rule::op_or => &OP_OR, Rule::op_and => &OP_AND, + Rule::op_coalesce => &OP_COALESCE, _ => unreachable!(), }; let start = args[0].span().0;