diff --git a/cozo-core/src/cozoscript.pest b/cozo-core/src/cozoscript.pest index c34eeb35..b2da94a1 100644 --- a/cozo-core/src/cozoscript.pest +++ b/cozo-core/src/cozoscript.pest @@ -58,14 +58,16 @@ fixed_arg = _{fixed_rel | fixed_opt_pair} fixed_opt_pair = {ident ~ ":" ~ expr} fixed_rel = {fixed_rule_rel | fixed_relation_rel | fixed_named_relation_rel } fixed_rule_rel = {ident ~ "[" ~ (var ~ ",")* ~ var? ~ "]"} -fixed_relation_rel = {relation_ident ~ "[" ~ (var ~ ",")* ~ var? ~ "]"} -fixed_named_relation_rel = {relation_ident ~ "{" ~ (fixed_named_relation_arg_pair ~ ",")* ~ fixed_named_relation_arg_pair? ~ "}"} +fixed_relation_rel = {relation_ident ~ "[" ~ (var ~ ",")* ~ var? ~ validity_clause? ~ "]"} +fixed_named_relation_rel = {relation_ident ~ "{" ~ (fixed_named_relation_arg_pair ~ ",")* ~ fixed_named_relation_arg_pair? ~ validity_clause? ~ "}"} fixed_named_relation_arg_pair = {ident ~ (":" ~ ident)?} +validity_clause = {"@" ~ expr} + rule_body = {(disjunction ~ ",")* ~ disjunction?} rule_apply = {underscore_ident ~ "[" ~ apply_args ~ "]"} -relation_named_apply = {relation_ident ~ "{" ~ named_apply_args ~ "}"} -relation_apply = {relation_ident ~ "[" ~ apply_args ~ "]"} +relation_named_apply = {relation_ident ~ "{" ~ named_apply_args ~ validity_clause? ~ "}"} +relation_apply = {relation_ident ~ "[" ~ apply_args ~ validity_clause? ~ "]"} disjunction = {(atom ~ "or" )* ~ atom} atom = _{ negation | relation_named_apply | relation_apply | rule_apply | unify_multi | unify | expr | grouped} diff --git a/cozo-core/src/data/functions.rs b/cozo-core/src/data/functions.rs index a1190eda..9bb2e2ce 100644 --- a/cozo-core/src/data/functions.rs +++ b/cozo-core/src/data/functions.rs @@ -1271,6 +1271,7 @@ pub(crate) fn op_to_bool(args: &[DataValue]) -> Result { DataValue::Regex(r) => !r.0.as_str().is_empty(), DataValue::List(l) => !l.is_empty(), DataValue::Set(s) => !s.is_empty(), + DataValue::Validity(vld) => vld.is_assert, DataValue::Bot => false, })) } @@ -1288,6 +1289,7 @@ pub(crate) fn op_to_unity(args: &[DataValue]) -> Result { DataValue::Regex(r) => if r.0.as_str().is_empty() {0 } else { 1}, DataValue::List(l) => if l.is_empty() {0} else {1}, DataValue::Set(s) => if s.is_empty() {0} else {1}, + DataValue::Validity(vld) => if vld.is_assert {1} else {0}, DataValue::Bot => 0, })) } diff --git a/cozo-core/src/data/json.rs b/cozo-core/src/data/json.rs index e16e265d..f9a782a1 100644 --- a/cozo-core/src/data/json.rs +++ b/cozo-core/src/data/json.rs @@ -29,7 +29,9 @@ impl From for DataValue { JsonValue::Object(d) => DataValue::List( d.into_iter() .map(|(k, v)| { - DataValue::List([DataValue::Str(SmartString::from(k)), DataValue::from(v)].into()) + DataValue::List( + [DataValue::Str(SmartString::from(k)), DataValue::from(v)].into(), + ) }) .collect(), ), @@ -98,6 +100,9 @@ impl From for JsonValue { DataValue::Uuid(u) => { json!(u.0) } + DataValue::Validity(v) => { + json!([v.timestamp.0, v.is_assert]) + } } } } diff --git a/cozo-core/src/data/memcmp.rs b/cozo-core/src/data/memcmp.rs index 5e195a0a..d3d8c0a4 100644 --- a/cozo-core/src/data/memcmp.rs +++ b/cozo-core/src/data/memcmp.rs @@ -6,6 +6,7 @@ * You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::cmp::Reverse; use std::collections::BTreeSet; use std::io::Write; use std::str::FromStr; @@ -13,7 +14,7 @@ use std::str::FromStr; use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use regex::Regex; -use crate::data::value::{DataValue, Num, RegexWrapper, UuidWrapper}; +use crate::data::value::{DataValue, Num, RegexWrapper, UuidWrapper, Validity}; const INIT_TAG: u8 = 0x00; const NULL_TAG: u8 = 0x01; @@ -26,6 +27,7 @@ const UUID_TAG: u8 = 0x08; const REGEX_TAG: u8 = 0x09; const LIST_TAG: u8 = 0x0A; const SET_TAG: u8 = 0x0B; +const VLD_TAG: u8 = 0x0C; const BOT_TAG: u8 = 0xFF; const IS_FLOAT: u8 = 0b00010000; @@ -78,6 +80,14 @@ pub(crate) trait MemCmpEncoder: Write { } self.write_u8(INIT_TAG).unwrap() } + DataValue::Validity(vld) => { + let ts = vld.timestamp.0; + let ts_u64 = order_encode_i64(ts); + let ts_flipped = !ts_u64; + self.write_u8(VLD_TAG).unwrap(); + self.write_u64::(ts_flipped).unwrap(); + self.write_u8(vld.is_assert as u8).unwrap(); + } DataValue::Bot => self.write_u8(BOT_TAG).unwrap(), } } @@ -269,6 +279,21 @@ impl DataValue { } (DataValue::Set(collected), &remaining[1..]) } + VLD_TAG => { + let (ts_flipped_bytes, rest) = remaining.split_at(8); + let ts_flipped = BigEndian::read_u64(ts_flipped_bytes); + let ts_u64 = !ts_flipped; + let ts = order_decode_i64(ts_u64); + let (is_assert_byte, rest) = rest.split_first().unwrap(); + let is_assert = *is_assert_byte != 0; + ( + DataValue::Validity(Validity { + timestamp: Reverse(ts), + is_assert, + }), + rest, + ) + } BOT_TAG => (DataValue::Bot, remaining), _ => unreachable!("{:?}", bs), } diff --git a/cozo-core/src/data/program.rs b/cozo-core/src/data/program.rs index 94615a4c..60967ec7 100644 --- a/cozo-core/src/data/program.rs +++ b/cozo-core/src/data/program.rs @@ -324,11 +324,13 @@ pub(crate) enum FixedRuleArg { Stored { name: Symbol, bindings: Vec, + // valid_at: Option, span: SourceSpan, }, NamedStored { name: Symbol, bindings: BTreeMap, Symbol>, + // valid_at: Option, span: SourceSpan, }, } @@ -998,6 +1000,7 @@ pub(crate) struct InputNamedFieldRelationApplyAtom { pub(crate) struct InputRelationApplyAtom { pub(crate) name: Symbol, pub(crate) args: Vec, + // pub(crate) valid_at: Option, pub(crate) span: SourceSpan, } diff --git a/cozo-core/src/data/value.rs b/cozo-core/src/data/value.rs index 2c3305c1..61a07225 100644 --- a/cozo-core/src/data/value.rs +++ b/cozo-core/src/data/value.rs @@ -6,7 +6,7 @@ * You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cmp::Ordering; +use std::cmp::{Ordering, Reverse}; use std::collections::BTreeSet; use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; @@ -84,6 +84,22 @@ impl PartialOrd for RegexWrapper { } } +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + serde_derive::Deserialize, + serde_derive::Serialize, + Hash, +)] +pub struct Validity { + pub(crate) timestamp: Reverse, + pub(crate) is_assert: bool, +} + #[derive( Clone, PartialEq, Eq, PartialOrd, Ord, serde_derive::Deserialize, serde_derive::Serialize, Hash, )] @@ -98,6 +114,7 @@ pub enum DataValue { Regex(RegexWrapper), List(Vec), Set(BTreeSet), + Validity(Validity), Bot, } @@ -245,6 +262,11 @@ impl Display for DataValue { DataValue::List(ls) => f.debug_list().entries(ls).finish(), DataValue::Set(s) => f.debug_list().entries(s).finish(), DataValue::Bot => write!(f, "null"), + DataValue::Validity(v) => f + .debug_struct("Validity") + .field("timestamp", &v.timestamp.0) + .field("retracted", &v.is_assert) + .finish(), } } } diff --git a/cozo-core/src/parse/query.rs b/cozo-core/src/parse/query.rs index 755b0735..6b9711b7 100644 --- a/cozo-core/src/parse/query.rs +++ b/cozo-core/src/parse/query.rs @@ -560,6 +560,10 @@ fn parse_atom(src: Pair<'_>, param_pool: &BTreeMap) -> Result .into_inner() .map(|v| build_expr(v, param_pool)) .try_collect()?; + // let validity = match src.next() { + // None => None, + // Some(vld_clause) => todo!() + // }; InputAtom::Relation { inner: InputRelationApplyAtom { name: Symbol::new(&name.as_str()[1..], name.extract_span()), @@ -591,6 +595,10 @@ fn parse_atom(src: Pair<'_>, param_pool: &BTreeMap) -> Result Ok((name, arg)) }) .try_collect()?; + // let validity = match src.next() { + // None => None, + // Some(vld_clause) => todo!() + // }; InputAtom::NamedFieldRelation { inner: InputNamedFieldRelationApplyAtom { name, args, span }, } @@ -698,9 +706,16 @@ fn parse_fixed_rule( Rule::fixed_relation_rel => { let mut els = inner.into_inner(); let name = els.next().unwrap(); - let bindings = els - .map(|v| Symbol::new(v.as_str(), v.extract_span())) - .collect_vec(); + let mut bindings = vec![]; + for v in els { + match v.as_rule() { + Rule::var => { + bindings.push(Symbol::new(v.as_str(), v.extract_span())) + } + Rule::validity_clause => todo!(), + _ => unreachable!(), + } + } rule_args.push(FixedRuleArg::Stored { name: Symbol::new( name.as_str().strip_prefix('*').unwrap(), @@ -713,18 +728,23 @@ fn parse_fixed_rule( Rule::fixed_named_relation_rel => { let mut els = inner.into_inner(); let name = els.next().unwrap(); - let bindings = els - .map(|v| { - let mut vs = v.into_inner(); - let kp = vs.next().unwrap(); - let k = SmartString::from(kp.as_str()); - let v = match vs.next() { - Some(vp) => Symbol::new(vp.as_str(), vp.extract_span()), - None => Symbol::new(k.clone(), kp.extract_span()), - }; - (k, v) - }) - .collect(); + let mut bindings = BTreeMap::new(); + for p in els { + match p.as_rule() { + Rule::fixed_named_relation_arg_pair => { + let mut vs = p.into_inner(); + let kp = vs.next().unwrap(); + let k = SmartString::from(kp.as_str()); + let v = match vs.next() { + Some(vp) => Symbol::new(vp.as_str(), vp.extract_span()), + None => Symbol::new(k.clone(), kp.extract_span()), + }; + bindings.insert(k, v); + } + Rule::validity_clause => todo!(), + _ => unreachable!(), + } + } rule_args.push(FixedRuleArg::NamedStored { name: Symbol::new(