diff --git a/cozo-core/src/data/expr.rs b/cozo-core/src/data/expr.rs index 312d415e..100a9f4e 100644 --- a/cozo-core/src/data/expr.rs +++ b/cozo-core/src/data/expr.rs @@ -153,33 +153,42 @@ pub fn eval_bytecode( Ok(stack.pop().unwrap()) } +/// Expression can be evaluated to yield a DataValue #[derive(Clone, PartialEq, Eq, serde_derive::Serialize, serde_derive::Deserialize)] pub enum Expr { + /// Binding to variables Binding { + /// The variable name to bind var: Symbol, + /// When executing in the context of a tuple, the position of the binding within the tuple tuple_pos: Option, }, + /// Constant expression containing a value Const { + /// The value val: DataValue, + /// Source span #[serde(skip)] span: SourceSpan, }, + /// Function application Apply { + /// Op representing the function to apply op: &'static Op, + /// Arguments to the application args: Box<[Expr]>, + /// Source span #[serde(skip)] span: SourceSpan, }, + /// Conditional expressions Cond { + /// Conditional clauses, the first expression in each tuple should evaluate to a boolean clauses: Vec<(Expr, Expr)>, + /// Source span #[serde(skip)] span: SourceSpan, }, - // Try { - // clauses: Vec, - // #[serde(skip)] - // span: SourceSpan, - // }, } impl Debug for Expr { diff --git a/cozo-core/src/data/symb.rs b/cozo-core/src/data/symb.rs index b12206d6..531d9619 100644 --- a/cozo-core/src/data/symb.rs +++ b/cozo-core/src/data/symb.rs @@ -18,6 +18,7 @@ use thiserror::Error; use crate::parse::SourceSpan; +/// Names with associated source span #[derive(Clone, Deserialize, Serialize)] pub struct Symbol { pub(crate) name: SmartString, diff --git a/cozo-core/src/fixed_rule/mod.rs b/cozo-core/src/fixed_rule/mod.rs index a08dbb78..7cf44433 100644 --- a/cozo-core/src/fixed_rule/mod.rs +++ b/cozo-core/src/fixed_rule/mod.rs @@ -39,12 +39,14 @@ use crate::runtime::transact::SessionTx; pub(crate) mod algos; pub(crate) mod utilities; +/// Passed into implementation of fixed rule, can be used to obtain relation inputs and options pub struct FixedRulePayload<'a, 'b> { pub(crate) manifest: &'a MagicFixedRuleApply, pub(crate) stores: &'a BTreeMap, pub(crate) tx: &'a SessionTx<'b>, } +/// Represents an input relation during the execution of a fixed rule #[derive(Copy, Clone)] pub struct FixedRuleInputRelation<'a, 'b> { arg_manifest: &'a MagicFixedRuleRuleArg, @@ -53,9 +55,11 @@ pub struct FixedRuleInputRelation<'a, 'b> { } impl<'a, 'b> FixedRuleInputRelation<'a, 'b> { + /// The arity of the input relation pub fn arity(&self) -> Result { self.arg_manifest.arity(self.tx, self.stores) } + /// Ensure the input relation contains tuples of the given minimal length. pub fn ensure_min_len(self, len: usize) -> Result { #[derive(Error, Diagnostic, Debug)] #[error("Input relation to algorithm has insufficient arity")] @@ -70,9 +74,11 @@ impl<'a, 'b> FixedRuleInputRelation<'a, 'b> { ); Ok(self) } + /// Get the binding map of the input relation pub fn get_binding_map(&self, offset: usize) -> BTreeMap { self.arg_manifest.get_binding_map(offset) } + /// Iterate the input relation pub fn iter(&self) -> Result> { Ok(match &self.arg_manifest { MagicFixedRuleRuleArg::InMem { name, .. } => { @@ -91,6 +97,7 @@ impl<'a, 'b> FixedRuleInputRelation<'a, 'b> { } }) } + /// Iterate the relation with the given single-value prefix pub fn prefix_iter(&self, prefix: &DataValue) -> Result> { Ok(match self.arg_manifest { MagicFixedRuleRuleArg::InMem { name, .. } => { @@ -111,9 +118,16 @@ impl<'a, 'b> FixedRuleInputRelation<'a, 'b> { } }) } + /// Get the source span of the input relation. Useful for generating informative error messages. pub fn span(&self) -> SourceSpan { self.arg_manifest.span() } + /// Convert the input relation into a directed graph. + /// If `undirected` is true, then each edge in the input relation is treated as a pair + /// of edges, one for each direction. + /// + /// Returns the graph, the vertices in a vector with the index the same as used in the graph, + /// and the inverse vertex mapping. #[cfg(feature = "graph-algo")] pub fn as_directed_graph( &self, @@ -180,6 +194,12 @@ impl<'a, 'b> FixedRuleInputRelation<'a, 'b> { } Ok((graph, indices, inv_indices)) } + /// Convert the input relation into a directed weighted graph. + /// If `undirected` is true, then each edge in the input relation is treated as a pair + /// of edges, one for each direction. + /// + /// Returns the graph, the vertices in a vector with the index the same as used in the graph, + /// and the inverse vertex mapping. #[cfg(feature = "graph-algo")] pub fn as_directed_weighted_graph( &self, @@ -305,6 +325,7 @@ impl<'a, 'b> FixedRuleInputRelation<'a, 'b> { } impl<'a, 'b> FixedRulePayload<'a, 'b> { + /// Get the input relation at `idx`. pub fn get_input(&self, idx: usize) -> Result> { let arg_manifest = self.manifest.relation(idx)?; Ok(FixedRuleInputRelation { @@ -313,12 +334,15 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { tx: self.tx, }) } + /// Get the name of the current fixed rule pub fn name(&self) -> &str { &self.manifest.fixed_handle.name } + /// Get the source span of the payloads. Useful for generating informative errors. pub fn span(&self) -> SourceSpan { self.manifest.span } + /// Extract an expression option pub fn expr_option(&self, name: &str, default: Option) -> Result { match self.manifest.options.get(name) { Some(ex) => Ok(ex.clone()), @@ -334,6 +358,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { } } + /// Extract a string option pub fn string_option( &self, name: &str, @@ -362,6 +387,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { } } + /// Get the source span of the named option. Useful for generating informative error messages. pub fn option_span(&self, name: &str) -> Result { match self.manifest.options.get(name) { None => Err(FixedRuleOptionNotFoundError { @@ -373,7 +399,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { Some(v) => Ok(v.span()), } } - + /// Extract an integer option pub fn integer_option(&self, name: &str, default: Option) -> Result { match self.manifest.options.get(name) { Some(v) => match v.clone().eval_to_const() { @@ -405,7 +431,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { }, } } - + /// Extract a positive integer option pub fn pos_integer_option(&self, name: &str, default: Option) -> Result { let i = self.integer_option(name, default.map(|i| i as i64))?; ensure!( @@ -419,6 +445,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { ); Ok(i as usize) } + /// Extract a non-negative integer option pub fn non_neg_integer_option(&self, name: &str, default: Option) -> Result { let i = self.integer_option(name, default.map(|i| i as i64))?; ensure!( @@ -432,6 +459,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { ); Ok(i as usize) } + /// Extract a floating point option pub fn float_option(&self, name: &str, default: Option) -> Result { match self.manifest.options.get(name) { Some(v) => match v.clone().eval_to_const() { @@ -458,6 +486,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { }, } } + /// Extract a floating point option between 0. and 1. pub fn unit_interval_option(&self, name: &str, default: Option) -> Result { let f = self.float_option(name, default)?; ensure!( @@ -471,6 +500,7 @@ impl<'a, 'b> FixedRulePayload<'a, 'b> { ); Ok(f) } + /// Extract a boolean option pub fn bool_option(&self, name: &str, default: Option) -> Result { match self.manifest.options.get(name) { Some(v) => match v.clone().eval_to_const() { diff --git a/cozo-core/src/lib.rs b/cozo-core/src/lib.rs index 8d1df0e8..7c3bd2d1 100644 --- a/cozo-core/src/lib.rs +++ b/cozo-core/src/lib.rs @@ -47,7 +47,7 @@ use miette::{ use serde_json::json; pub use data::value::{DataValue, Num, RegexWrapper, UuidWrapper, Validity, ValidityTs}; -pub use fixed_rule::FixedRule; +pub use fixed_rule::{FixedRule, FixedRuleInputRelation, FixedRulePayload}; pub use runtime::db::Db; pub use runtime::db::NamedRows; pub use runtime::relation::decode_tuple_from_kv; @@ -63,11 +63,12 @@ pub use storage::sqlite::{new_cozo_sqlite, SqliteStorage}; pub use storage::tikv::{new_cozo_tikv, TiKvStorage}; pub use storage::{Storage, StoreTx}; +pub use crate::data::expr::Expr; use crate::data::json::JsonValue; -use crate::runtime::callback::CallbackOp; +pub use crate::data::symb::Symbol; +pub use crate::runtime::callback::CallbackOp; #[cfg(not(target_arch = "wasm32"))] - pub(crate) mod data; pub(crate) mod fixed_rule; pub(crate) mod parse; @@ -336,7 +337,11 @@ impl DbInstance { } } /// Dispatcher method. See [crate::Db::import_from_backup]. - pub fn import_from_backup(&self, in_file: impl AsRef, relations: &[String]) -> Result<()> { + pub fn import_from_backup( + &self, + in_file: impl AsRef, + relations: &[String], + ) -> Result<()> { match self { DbInstance::Mem(db) => db.import_from_backup(in_file, relations), #[cfg(feature = "storage-sqlite")] @@ -373,8 +378,9 @@ impl DbInstance { /// The returned ID can be used to unregister the callbacks. #[cfg(not(target_arch = "wasm32"))] pub fn register_callback(&self, callback: CB, dependent: &str) -> Result - where - CB: Fn(CallbackOp, NamedRows, NamedRows) + Send + Sync + 'static { + where + CB: Fn(CallbackOp, NamedRows, NamedRows) + Send + Sync + 'static, + { match self { DbInstance::Mem(db) => db.register_callback(callback, dependent), #[cfg(feature = "storage-sqlite")] @@ -387,6 +393,43 @@ impl DbInstance { DbInstance::TiKv(db) => db.register_callback(callback, dependent), } } + + /// Unregister callbacks to run when changes to relations are committed. + #[cfg(not(target_arch = "wasm32"))] + pub fn unregister_callback(&self, id: u32) -> bool { + match self { + DbInstance::Mem(db) => db.unregister_callback(id), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.unregister_callback(id), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.unregister_callback(id), + #[cfg(feature = "storage-sled")] + DbInstance::Sled(db) => db.unregister_callback(id), + #[cfg(feature = "storage-tikv")] + DbInstance::TiKv(db) => db.unregister_callback(id), + } + } + /// Register a custom fixed rule implementation. + /// + /// You must register fixed rules BEFORE you clone the database, + /// otherwise already cloned instances will not get the new fixed rule. + pub fn register_fixed_rule( + &mut self, + name: String, + rule_impl: Box, + ) -> Result<()> { + match self { + DbInstance::Mem(db) => db.register_fixed_rule(name, rule_impl), + #[cfg(feature = "storage-sqlite")] + DbInstance::Sqlite(db) => db.register_fixed_rule(name, rule_impl), + #[cfg(feature = "storage-rocksdb")] + DbInstance::RocksDb(db) => db.register_fixed_rule(name, rule_impl), + #[cfg(feature = "storage-sled")] + DbInstance::Sled(db) => db.register_fixed_rule(name, rule_impl), + #[cfg(feature = "storage-tikv")] + DbInstance::TiKv(db) => db.register_fixed_rule(name, rule_impl), + } + } } /// Convert error raised by the database into friendly JSON format diff --git a/cozo-core/src/runtime/callback.rs b/cozo-core/src/runtime/callback.rs index 5c6ebb66..c04f20e1 100644 --- a/cozo-core/src/runtime/callback.rs +++ b/cozo-core/src/runtime/callback.rs @@ -12,9 +12,12 @@ use smartstring::{LazyCompact, SmartString}; use crate::{Db, NamedRows, Storage}; +/// Represents the kind of operation that triggered the callback #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum CallbackOp { + /// Triggered by Put operations Put, + /// Triggered by Rm operations Rm, }