From cdac4f84cd43e0a24103eed4c327bca8205ee4d5 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Sat, 25 Mar 2023 19:44:03 +0000 Subject: [PATCH] Add support for custom SQL functions Closes #247 --- lib/src/doc/document.rs | 4 +- lib/src/err/mod.rs | 66 ++++++--- lib/src/key/fc.rs | 64 +++++++++ lib/src/key/mod.rs | 1 + lib/src/kvs/cache.rs | 24 ++-- lib/src/kvs/tx.rs | 225 +++++++++++++++++++++++++++---- lib/src/sql/function.rs | 80 ++++++++++- lib/src/sql/ident.rs | 5 + lib/src/sql/statements/define.rs | 95 ++++++++++++- lib/src/sql/statements/info.rs | 6 + lib/src/sql/statements/mod.rs | 6 +- lib/src/sql/statements/remove.rs | 69 +++++++++- lib/src/sql/value/value.rs | 8 +- lib/tests/define.rs | 40 +++++- lib/tests/param.rs | 1 + lib/tests/strict.rs | 85 ++++++++++-- 16 files changed, 688 insertions(+), 91 deletions(-) create mode 100644 lib/src/key/fc.rs diff --git a/lib/src/doc/document.rs b/lib/src/doc/document.rs index 38e529cc..915218df 100644 --- a/lib/src/doc/document.rs +++ b/lib/src/doc/document.rs @@ -70,7 +70,9 @@ impl<'a> Document<'a> { // Return the table or attempt to define it match tb { // The table doesn't exist - Err(Error::TbNotFound) => match opt.auth.check(Level::Db) { + Err(Error::TbNotFound { + value: _, + }) => match opt.auth.check(Level::Db) { // We can create the table automatically true => { run.add_and_cache_ns(opt.ns(), opt.strict).await?; diff --git a/lib/src/err/mod.rs b/lib/src/err/mod.rs index 1cd80f92..b70bf7ce 100644 --- a/lib/src/err/mod.rs +++ b/lib/src/err/mod.rs @@ -171,44 +171,70 @@ pub enum Error { }, /// The requested namespace does not exist - #[error("The namespace does not exist")] - NsNotFound, + #[error("The namespace '{value}' does not exist")] + NsNotFound { + value: String, + }, /// The requested namespace token does not exist - #[error("The namespace token does not exist")] - NtNotFound, + #[error("The namespace token '{value}' does not exist")] + NtNotFound { + value: String, + }, /// The requested namespace login does not exist - #[error("The namespace login does not exist")] - NlNotFound, + #[error("The namespace login '{value}' does not exist")] + NlNotFound { + value: String, + }, /// The requested database does not exist - #[error("The database does not exist")] - DbNotFound, + #[error("The database '{value}' does not exist")] + DbNotFound { + value: String, + }, /// The requested database token does not exist - #[error("The database token does not exist")] - DtNotFound, + #[error("The database token '{value}' does not exist")] + DtNotFound { + value: String, + }, /// The requested database login does not exist - #[error("The database login does not exist")] - DlNotFound, + #[error("The database login '{value}' does not exist")] + DlNotFound { + value: String, + }, + + /// The requested function does not exist + #[error("The function 'fn::{value}' does not exist")] + FcNotFound { + value: String, + }, /// The requested scope does not exist - #[error("The scope does not exist")] - ScNotFound, + #[error("The scope '{value}' does not exist")] + ScNotFound { + value: String, + }, /// The requested scope token does not exist - #[error("The scope token does not exist")] - StNotFound, + #[error("The scope token '{value}' does not exist")] + StNotFound { + value: String, + }, /// The requested param does not exist - #[error("The param does not exist")] - PaNotFound, + #[error("The param '${value}' does not exist")] + PaNotFound { + value: String, + }, /// The requested table does not exist - #[error("The table does not exist")] - TbNotFound, + #[error("The table '{value}' does not exist")] + TbNotFound { + value: String, + }, /// Unable to perform the realtime query #[error("Unable to perform the realtime query")] diff --git a/lib/src/key/fc.rs b/lib/src/key/fc.rs new file mode 100644 index 00000000..da42643e --- /dev/null +++ b/lib/src/key/fc.rs @@ -0,0 +1,64 @@ +use derive::Key; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)] +pub struct Fc { + __: u8, + _a: u8, + pub ns: String, + _b: u8, + pub db: String, + _c: u8, + _d: u8, + _e: u8, + pub fc: String, +} + +pub fn new(ns: &str, db: &str, fc: &str) -> Fc { + Fc::new(ns.to_string(), db.to_string(), fc.to_string()) +} + +pub fn prefix(ns: &str, db: &str) -> Vec { + let mut k = super::database::new(ns, db).encode().unwrap(); + k.extend_from_slice(&[0x21, 0x66, 0x6e, 0x00]); + k +} + +pub fn suffix(ns: &str, db: &str) -> Vec { + let mut k = super::database::new(ns, db).encode().unwrap(); + k.extend_from_slice(&[0x21, 0x66, 0x6e, 0xff]); + k +} + +impl Fc { + pub fn new(ns: String, db: String, fc: String) -> Fc { + Fc { + __: 0x2f, // / + _a: 0x2a, // * + ns, + _b: 0x2a, // * + db, + _c: 0x21, // ! + _d: 0x66, // f + _e: 0x6e, // n + fc, + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn key() { + use super::*; + #[rustfmt::skip] + let val = Fc::new( + "test".to_string(), + "test".to_string(), + "test".to_string(), + ); + let enc = Fc::encode(&val).unwrap(); + let dec = Fc::decode(&enc).unwrap(); + assert_eq!(val, dec); + } +} diff --git a/lib/src/key/mod.rs b/lib/src/key/mod.rs index d554a66f..c197633b 100644 --- a/lib/src/key/mod.rs +++ b/lib/src/key/mod.rs @@ -36,6 +36,7 @@ pub mod db; // Stores a DEFINE DATABASE config definition pub mod dl; // Stores a DEFINE LOGIN ON DATABASE config definition pub mod dt; // Stores a DEFINE LOGIN ON DATABASE config definition pub mod ev; // Stores a DEFINE EVENT config definition +pub mod fc; // Stores a DEFINE FUNCTION config definition pub mod fd; // Stores a DEFINE FIELD config definition pub mod ft; // Stores a DEFINE TABLE AS config definition pub mod graph; // Stores a graph edge pointer diff --git a/lib/src/kvs/cache.rs b/lib/src/kvs/cache.rs index 0017bc06..57802395 100644 --- a/lib/src/kvs/cache.rs +++ b/lib/src/kvs/cache.rs @@ -2,6 +2,7 @@ use crate::kvs::kv::Key; use crate::sql::statements::DefineDatabaseStatement; use crate::sql::statements::DefineEventStatement; use crate::sql::statements::DefineFieldStatement; +use crate::sql::statements::DefineFunctionStatement; use crate::sql::statements::DefineIndexStatement; use crate::sql::statements::DefineLoginStatement; use crate::sql::statements::DefineNamespaceStatement; @@ -15,24 +16,27 @@ use std::sync::Arc; #[derive(Clone)] pub enum Entry { - Ns(Arc), + // Single definitions Db(Arc), + Ns(Arc), Tb(Arc), - Nss(Arc<[DefineNamespaceStatement]>), - Nls(Arc<[DefineLoginStatement]>), - Nts(Arc<[DefineTokenStatement]>), + // Multi definitions Dbs(Arc<[DefineDatabaseStatement]>), Dls(Arc<[DefineLoginStatement]>), Dts(Arc<[DefineTokenStatement]>), + Evs(Arc<[DefineEventStatement]>), + Fcs(Arc<[DefineFunctionStatement]>), + Fds(Arc<[DefineFieldStatement]>), + Fts(Arc<[DefineTableStatement]>), + Ixs(Arc<[DefineIndexStatement]>), + Lvs(Arc<[LiveStatement]>), + Nls(Arc<[DefineLoginStatement]>), + Nss(Arc<[DefineNamespaceStatement]>), + Nts(Arc<[DefineTokenStatement]>), + Pas(Arc<[DefineParamStatement]>), Scs(Arc<[DefineScopeStatement]>), Sts(Arc<[DefineTokenStatement]>), - Pas(Arc<[DefineParamStatement]>), Tbs(Arc<[DefineTableStatement]>), - Evs(Arc<[DefineEventStatement]>), - Fds(Arc<[DefineFieldStatement]>), - Ixs(Arc<[DefineIndexStatement]>), - Fts(Arc<[DefineTableStatement]>), - Lvs(Arc<[LiveStatement]>), } #[derive(Default)] diff --git a/lib/src/kvs/tx.rs b/lib/src/kvs/tx.rs index a58cef11..1bc1dd0c 100644 --- a/lib/src/kvs/tx.rs +++ b/lib/src/kvs/tx.rs @@ -17,6 +17,7 @@ use sql::permission::Permissions; use sql::statements::DefineDatabaseStatement; use sql::statements::DefineEventStatement; use sql::statements::DefineFieldStatement; +use sql::statements::DefineFunctionStatement; use sql::statements::DefineIndexStatement; use sql::statements::DefineLoginStatement; use sql::statements::DefineNamespaceStatement; @@ -53,6 +54,10 @@ pub(super) enum Inner { } impl Transaction { + // -------------------------------------------------- + // Integral methods + // -------------------------------------------------- + /// Check if transactions is finished. /// /// If the transaction has been cancelled or committed, @@ -92,6 +97,7 @@ impl Transaction { _ => unreachable!(), } } + /// Cancel a transaction. /// /// This reverses all changes made within the transaction. @@ -128,6 +134,7 @@ impl Transaction { _ => unreachable!(), } } + /// Commit a transaction. /// /// This attempts to commit all changes made within the transaction. @@ -164,6 +171,7 @@ impl Transaction { _ => unreachable!(), } } + /// Delete a key from the datastore. #[allow(unused_variables)] pub async fn del(&mut self, key: K) -> Result<(), Error> @@ -202,6 +210,7 @@ impl Transaction { _ => unreachable!(), } } + /// Check if a key exists in the datastore. #[allow(unused_variables)] pub async fn exi(&mut self, key: K) -> Result @@ -240,6 +249,7 @@ impl Transaction { _ => unreachable!(), } } + /// Fetch a key from the datastore. #[allow(unused_variables)] pub async fn get(&mut self, key: K) -> Result, Error> @@ -278,6 +288,7 @@ impl Transaction { _ => unreachable!(), } } + /// Insert or update a key in the datastore. #[allow(unused_variables)] pub async fn set(&mut self, key: K, val: V) -> Result<(), Error> @@ -317,6 +328,7 @@ impl Transaction { _ => unreachable!(), } } + /// Insert a key if it doesn't exist in the datastore. #[allow(unused_variables)] pub async fn put(&mut self, key: K, val: V) -> Result<(), Error> @@ -356,6 +368,7 @@ impl Transaction { _ => unreachable!(), } } + /// Retrieve a specific range of keys from the datastore. /// /// This function fetches the full range of key-value pairs, in a single request to the underlying datastore. @@ -396,6 +409,7 @@ impl Transaction { _ => unreachable!(), } } + /// Update a key in the datastore if the current value matches a condition. #[allow(unused_variables)] pub async fn putc(&mut self, key: K, val: V, chk: Option) -> Result<(), Error> @@ -435,6 +449,7 @@ impl Transaction { _ => unreachable!(), } } + /// Delete a key from the datastore if the current value matches a condition. #[allow(unused_variables)] pub async fn delc(&mut self, key: K, chk: Option) -> Result<(), Error> @@ -474,6 +489,11 @@ impl Transaction { _ => unreachable!(), } } + + // -------------------------------------------------- + // Superjacent methods + // -------------------------------------------------- + /// Retrieve a specific range of keys from the datastore. /// /// This function fetches key-value pairs from the underlying datastore in batches of 1000. @@ -672,6 +692,11 @@ impl Transaction { } Ok(()) } + + // -------------------------------------------------- + // Superimposed methods + // -------------------------------------------------- + /// Clear any cache entry for the specified key. pub async fn clr(&mut self, key: K) -> Result<(), Error> where @@ -681,6 +706,7 @@ impl Transaction { self.cache.del(&key); Ok(()) } + /// Retrieve all namespace definitions in a datastore. pub async fn all_ns(&mut self) -> Result, Error> { let key = crate::key::ns::prefix(); @@ -699,6 +725,7 @@ impl Transaction { } } } + /// Retrieve all namespace login definitions for a specific namespace. pub async fn all_nl(&mut self, ns: &str) -> Result, Error> { let key = crate::key::nl::prefix(ns); @@ -717,6 +744,7 @@ impl Transaction { } } } + /// Retrieve all namespace token definitions for a specific namespace. pub async fn all_nt(&mut self, ns: &str) -> Result, Error> { let key = crate::key::nt::prefix(ns); @@ -735,6 +763,7 @@ impl Transaction { } } } + /// Retrieve all database definitions for a specific namespace. pub async fn all_db(&mut self, ns: &str) -> Result, Error> { let key = crate::key::db::prefix(ns); @@ -753,6 +782,7 @@ impl Transaction { } } } + /// Retrieve all database login definitions for a specific database. pub async fn all_dl( &mut self, @@ -775,6 +805,7 @@ impl Transaction { } } } + /// Retrieve all database token definitions for a specific database. pub async fn all_dt( &mut self, @@ -797,6 +828,30 @@ impl Transaction { } } } + + /// Retrieve all function definitions for a specific database. + pub async fn all_fc( + &mut self, + ns: &str, + db: &str, + ) -> Result, Error> { + let key = crate::key::fc::prefix(ns, db); + match self.cache.exi(&key) { + true => match self.cache.get(&key) { + Some(Entry::Fcs(v)) => Ok(v), + _ => unreachable!(), + }, + _ => { + let beg = crate::key::fc::prefix(ns, db); + let end = crate::key::fc::suffix(ns, db); + let val = self.getr(beg..end, u32::MAX).await?; + let val = val.convert().into(); + self.cache.set(key, Entry::Fcs(Arc::clone(&val))); + Ok(val) + } + } + } + /// Retrieve all scope definitions for a specific database. pub async fn all_sc( &mut self, @@ -819,6 +874,7 @@ impl Transaction { } } } + /// Retrieve all scope token definitions for a scope. pub async fn all_st( &mut self, @@ -842,6 +898,7 @@ impl Transaction { } } } + /// Retrieve all scope definitions for a specific database. pub async fn all_pa( &mut self, @@ -864,6 +921,7 @@ impl Transaction { } } } + /// Retrieve all table definitions for a specific database. pub async fn all_tb( &mut self, @@ -886,6 +944,7 @@ impl Transaction { } } } + /// Retrieve all event definitions for a specific table. pub async fn all_ev( &mut self, @@ -909,6 +968,7 @@ impl Transaction { } } } + /// Retrieve all field definitions for a specific table. pub async fn all_fd( &mut self, @@ -932,6 +992,7 @@ impl Transaction { } } } + /// Retrieve all index definitions for a specific table. pub async fn all_ix( &mut self, @@ -955,6 +1016,7 @@ impl Transaction { } } } + /// Retrieve all view definitions for a specific table. pub async fn all_ft( &mut self, @@ -978,6 +1040,7 @@ impl Transaction { } } } + /// Retrieve all live definitions for a specific table. pub async fn all_lv( &mut self, @@ -1001,30 +1064,43 @@ impl Transaction { } } } + /// Retrieve a specific namespace definition. pub async fn get_ns(&mut self, ns: &str) -> Result { let key = crate::key::ns::new(ns); - let val = self.get(key).await?.ok_or(Error::NsNotFound)?; + let val = self.get(key).await?.ok_or(Error::NsNotFound { + value: ns.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific namespace login definition. pub async fn get_nl(&mut self, ns: &str, nl: &str) -> Result { let key = crate::key::nl::new(ns, nl); - let val = self.get(key).await?.ok_or(Error::NlNotFound)?; + let val = self.get(key).await?.ok_or(Error::NlNotFound { + value: nl.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific namespace token definition. pub async fn get_nt(&mut self, ns: &str, nt: &str) -> Result { let key = crate::key::nt::new(ns, nt); - let val = self.get(key).await?.ok_or(Error::NtNotFound)?; + let val = self.get(key).await?.ok_or(Error::NtNotFound { + value: nt.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific database definition. pub async fn get_db(&mut self, ns: &str, db: &str) -> Result { let key = crate::key::db::new(ns, db); - let val = self.get(key).await?.ok_or(Error::DbNotFound)?; + let val = self.get(key).await?.ok_or(Error::DbNotFound { + value: db.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific database login definition. pub async fn get_dl( &mut self, @@ -1033,9 +1109,12 @@ impl Transaction { dl: &str, ) -> Result { let key = crate::key::dl::new(ns, db, dl); - let val = self.get(key).await?.ok_or(Error::DlNotFound)?; + let val = self.get(key).await?.ok_or(Error::DlNotFound { + value: dl.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific database token definition. pub async fn get_dt( &mut self, @@ -1044,9 +1123,12 @@ impl Transaction { dt: &str, ) -> Result { let key = crate::key::dt::new(ns, db, dt); - let val = self.get(key).await?.ok_or(Error::DtNotFound)?; + let val = self.get(key).await?.ok_or(Error::DtNotFound { + value: dt.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific scope definition. pub async fn get_sc( &mut self, @@ -1055,9 +1137,12 @@ impl Transaction { sc: &str, ) -> Result { let key = crate::key::sc::new(ns, db, sc); - let val = self.get(key).await?.ok_or(Error::ScNotFound)?; + let val = self.get(key).await?.ok_or(Error::ScNotFound { + value: sc.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific scope token definition. pub async fn get_st( &mut self, @@ -1067,9 +1152,26 @@ impl Transaction { st: &str, ) -> Result { let key = crate::key::st::new(ns, db, sc, st); - let val = self.get(key).await?.ok_or(Error::StNotFound)?; + let val = self.get(key).await?.ok_or(Error::StNotFound { + value: st.to_owned(), + })?; Ok(val.into()) } + + /// Retrieve a specific function definition. + pub async fn get_fc( + &mut self, + ns: &str, + db: &str, + fc: &str, + ) -> Result { + let key = crate::key::fc::new(ns, db, fc); + let val = self.get(key).await?.ok_or(Error::FcNotFound { + value: fc.to_owned(), + })?; + Ok(val.into()) + } + /// Retrieve a specific param definition. pub async fn get_pa( &mut self, @@ -1078,9 +1180,12 @@ impl Transaction { pa: &str, ) -> Result { let key = crate::key::pa::new(ns, db, pa); - let val = self.get(key).await?.ok_or(Error::PaNotFound)?; + let val = self.get(key).await?.ok_or(Error::PaNotFound { + value: pa.to_owned(), + })?; Ok(val.into()) } + /// Retrieve a specific table definition. pub async fn get_tb( &mut self, @@ -1089,9 +1194,12 @@ impl Transaction { tb: &str, ) -> Result { let key = crate::key::tb::new(ns, db, tb); - let val = self.get(key).await?.ok_or(Error::TbNotFound)?; + let val = self.get(key).await?.ok_or(Error::TbNotFound { + value: tb.to_owned(), + })?; Ok(val.into()) } + /// Add a namespace with a default configuration, only if we are in dynamic mode. pub async fn add_ns( &mut self, @@ -1099,7 +1207,9 @@ impl Transaction { strict: bool, ) -> Result { match self.get_ns(ns).await { - Err(Error::NsNotFound) => match strict { + Err(Error::NsNotFound { + value, + }) => match strict { false => { let key = crate::key::ns::new(ns); let val = DefineNamespaceStatement { @@ -1108,12 +1218,15 @@ impl Transaction { self.put(key, &val).await?; Ok(val) } - true => Err(Error::NsNotFound), + true => Err(Error::NsNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Add a database with a default configuration, only if we are in dynamic mode. pub async fn add_db( &mut self, @@ -1122,7 +1235,9 @@ impl Transaction { strict: bool, ) -> Result { match self.get_db(ns, db).await { - Err(Error::DbNotFound) => match strict { + Err(Error::DbNotFound { + value, + }) => match strict { false => { let key = crate::key::db::new(ns, db); let val = DefineDatabaseStatement { @@ -1131,12 +1246,15 @@ impl Transaction { self.put(key, &val).await?; Ok(val) } - true => Err(Error::DbNotFound), + true => Err(Error::DbNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Add a scope with a default configuration, only if we are in dynamic mode. pub async fn add_sc( &mut self, @@ -1146,7 +1264,9 @@ impl Transaction { strict: bool, ) -> Result { match self.get_sc(ns, db, sc).await { - Err(Error::ScNotFound) => match strict { + Err(Error::ScNotFound { + value, + }) => match strict { false => { let key = crate::key::sc::new(ns, db, sc); let val = DefineScopeStatement { @@ -1156,12 +1276,15 @@ impl Transaction { self.put(key, &val).await?; Ok(val) } - true => Err(Error::ScNotFound), + true => Err(Error::ScNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Add a table with a default configuration, only if we are in dynamic mode. pub async fn add_tb( &mut self, @@ -1171,7 +1294,9 @@ impl Transaction { strict: bool, ) -> Result { match self.get_tb(ns, db, tb).await { - Err(Error::TbNotFound) => match strict { + Err(Error::TbNotFound { + value, + }) => match strict { false => { let key = crate::key::tb::new(ns, db, tb); let val = DefineTableStatement { @@ -1182,12 +1307,15 @@ impl Transaction { self.put(key, &val).await?; Ok(val) } - true => Err(Error::TbNotFound), + true => Err(Error::TbNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Retrieve and cache a specific namespace definition. pub async fn get_and_cache_ns( &mut self, @@ -1200,13 +1328,16 @@ impl Transaction { _ => unreachable!(), }, _ => { - let val = self.get(key.clone()).await?.ok_or(Error::NsNotFound)?; + let val = self.get(key.clone()).await?.ok_or(Error::NsNotFound { + value: ns.to_owned(), + })?; let val: Arc = Arc::new(val.into()); self.cache.set(key, Entry::Ns(Arc::clone(&val))); Ok(val) } } } + /// Retrieve and cache a specific database definition. pub async fn get_and_cache_db( &mut self, @@ -1220,13 +1351,16 @@ impl Transaction { _ => unreachable!(), }, _ => { - let val = self.get(key.clone()).await?.ok_or(Error::DbNotFound)?; + let val = self.get(key.clone()).await?.ok_or(Error::DbNotFound { + value: db.to_owned(), + })?; let val: Arc = Arc::new(val.into()); self.cache.set(key, Entry::Db(Arc::clone(&val))); Ok(val) } } } + /// Retrieve and cache a specific table definition. pub async fn get_and_cache_tb( &mut self, @@ -1241,13 +1375,16 @@ impl Transaction { _ => unreachable!(), }, _ => { - let val = self.get(key.clone()).await?.ok_or(Error::TbNotFound)?; + let val = self.get(key.clone()).await?.ok_or(Error::TbNotFound { + value: tb.to_owned(), + })?; let val: Arc = Arc::new(val.into()); self.cache.set(key, Entry::Tb(Arc::clone(&val))); Ok(val) } } } + /// Add a namespace with a default configuration, only if we are in dynamic mode. pub async fn add_and_cache_ns( &mut self, @@ -1255,7 +1392,9 @@ impl Transaction { strict: bool, ) -> Result, Error> { match self.get_and_cache_ns(ns).await { - Err(Error::NsNotFound) => match strict { + Err(Error::NsNotFound { + value, + }) => match strict { false => { let key = crate::key::ns::new(ns); let val = DefineNamespaceStatement { @@ -1264,12 +1403,15 @@ impl Transaction { self.put(key, &val).await?; Ok(Arc::new(val)) } - true => Err(Error::NsNotFound), + true => Err(Error::NsNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Add a database with a default configuration, only if we are in dynamic mode. pub async fn add_and_cache_db( &mut self, @@ -1278,7 +1420,9 @@ impl Transaction { strict: bool, ) -> Result, Error> { match self.get_and_cache_db(ns, db).await { - Err(Error::DbNotFound) => match strict { + Err(Error::DbNotFound { + value, + }) => match strict { false => { let key = crate::key::db::new(ns, db); let val = DefineDatabaseStatement { @@ -1287,12 +1431,15 @@ impl Transaction { self.put(key, &val).await?; Ok(Arc::new(val)) } - true => Err(Error::DbNotFound), + true => Err(Error::DbNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Add a table with a default configuration, only if we are in dynamic mode. pub async fn add_and_cache_tb( &mut self, @@ -1302,7 +1449,9 @@ impl Transaction { strict: bool, ) -> Result, Error> { match self.get_and_cache_tb(ns, db, tb).await { - Err(Error::TbNotFound) => match strict { + Err(Error::TbNotFound { + value, + }) => match strict { false => { let key = crate::key::tb::new(ns, db, tb); let val = DefineTableStatement { @@ -1313,12 +1462,15 @@ impl Transaction { self.put(key, &val).await?; Ok(Arc::new(val)) } - true => Err(Error::TbNotFound), + true => Err(Error::TbNotFound { + value, + }), }, Err(e) => Err(e), Ok(v) => Ok(v), } } + /// Retrieve and cache a specific table definition. pub async fn check_ns_db_tb( &mut self, @@ -1339,6 +1491,11 @@ impl Transaction { } } } + + // -------------------------------------------------- + // Additional methods + // -------------------------------------------------- + /// Writes the full database contents as binary SQL. pub async fn export(&mut self, ns: &str, db: &str, chn: Sender>) -> Result<(), Error> { // Output OPTIONS @@ -1350,6 +1507,20 @@ impl Transaction { chn.send(bytes!("OPTION IMPORT;")).await?; chn.send(bytes!("")).await?; } + // Output FUNCTIONS + { + let fcs = self.all_fc(ns, db).await?; + if !fcs.is_empty() { + chn.send(bytes!("-- ------------------------------")).await?; + chn.send(bytes!("-- FUNCTIONS")).await?; + chn.send(bytes!("-- ------------------------------")).await?; + chn.send(bytes!("")).await?; + for fc in fcs.iter() { + chn.send(bytes!(format!("{fc};"))).await?; + } + chn.send(bytes!("")).await?; + } + } // Output LOGINS { let dls = self.all_dl(ns, db).await?; diff --git a/lib/src/sql/function.rs b/lib/src/sql/function.rs index 567a25d7..ef17e3b1 100644 --- a/lib/src/sql/function.rs +++ b/lib/src/sql/function.rs @@ -5,13 +5,17 @@ use crate::err::Error; use crate::fnc; use crate::sql::comment::mightbespace; use crate::sql::common::commas; +use crate::sql::common::val_char; use crate::sql::error::IResult; use crate::sql::fmt::Fmt; +use crate::sql::idiom::Idiom; use crate::sql::script::{script as func, Script}; use crate::sql::value::{single, value, Value}; +use async_recursion::async_recursion; use futures::future::try_join_all; use nom::branch::alt; use nom::bytes::complete::tag; +use nom::bytes::complete::take_while1; use nom::character::complete::char; use nom::multi::separated_list0; use serde::{Deserialize, Serialize}; @@ -22,6 +26,7 @@ use std::fmt; pub enum Function { Cast(String, Value), Normal(String, Vec), + Custom(String, Vec), Script(Script, Vec), } @@ -37,6 +42,7 @@ impl Function { pub fn name(&self) -> &str { match self { Self::Normal(n, _) => n.as_str(), + Self::Custom(n, _) => n.as_str(), _ => unreachable!(), } } @@ -44,9 +50,19 @@ impl Function { pub fn args(&self) -> &[Value] { match self { Self::Normal(_, a) => a, + Self::Custom(_, a) => a, _ => &[], } } + /// Convert function call to a field name + pub fn to_idiom(&self) -> Idiom { + match self { + Self::Script(_, _) => "function".to_string().into(), + Self::Normal(f, _) => f.to_owned().into(), + Self::Custom(f, _) => format!("fn::{f}").into(), + Self::Cast(_, v) => v.to_idiom(), + } + } /// Convert this function to an aggregate pub fn aggregate(&self, val: Value) -> Self { match self { @@ -104,12 +120,14 @@ impl Function { } impl Function { + #[cfg_attr(not(target_arch = "wasm32"), async_recursion)] + #[cfg_attr(target_arch = "wasm32", async_recursion(?Send))] pub(crate) async fn compute( &self, ctx: &Context<'_>, opt: &Options, txn: &Transaction, - doc: Option<&Value>, + doc: Option<&'async_recursion Value>, ) -> Result { // Prevent long function chains let opt = &opt.dive(1)?; @@ -129,6 +147,44 @@ impl Function { // Run the normal function fnc::run(ctx, s, a).await } + Self::Custom(s, x) => { + // Get the function definition + let val = { + // Clone transaction + let run = txn.clone(); + // Claim transaction + let mut run = run.lock().await; + // Get the function definition + run.get_fc(opt.ns(), opt.db(), s).await? + }; + // Check the function arguments + if x.len() != val.args.len() { + return Err(Error::InvalidArguments { + name: format!("fn::{}", val.name), + message: match val.args.len() { + 1 => String::from("The function expects 1 argument."), + l => format!("The function expects {l} arguments."), + }, + }); + } + // Compute the function arguments + let a = try_join_all(x.iter().map(|v| v.compute(ctx, opt, txn, doc))).await?; + // Duplicate context + let mut ctx = Context::new(ctx); + // Process the function arguments + for (val, (name, kind)) in a.into_iter().zip(val.args) { + ctx.add_value( + name.to_raw(), + match val { + Value::None => val, + Value::Null => val, + _ => val.convert_to(&kind), + }, + ); + } + // Run the custom function + val.block.compute(&ctx, opt, txn, doc).await + } #[allow(unused_variables)] Self::Script(s, x) => { #[cfg(feature = "scripting")] @@ -152,17 +208,16 @@ impl Function { impl fmt::Display for Function { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::Cast(ref s, ref e) => write!(f, "<{s}> {e}"), - Self::Script(ref s, ref e) => { - write!(f, "function({}) {{{s}}}", Fmt::comma_separated(e)) - } - Self::Normal(ref s, ref e) => write!(f, "{s}({})", Fmt::comma_separated(e)), + Self::Cast(s, e) => write!(f, "<{s}> {e}"), + Self::Normal(s, e) => write!(f, "{s}({})", Fmt::comma_separated(e)), + Self::Custom(s, e) => write!(f, "fn::{s}({})", Fmt::comma_separated(e)), + Self::Script(s, e) => write!(f, "function({}) {{{s}}}", Fmt::comma_separated(e)), } } } pub fn function(i: &str) -> IResult<&str, Function> { - alt((normal, script, cast))(i) + alt((normal, custom, script, cast))(i) } fn normal(i: &str) -> IResult<&str, Function> { @@ -175,6 +230,17 @@ fn normal(i: &str) -> IResult<&str, Function> { Ok((i, Function::Normal(s.to_string(), a))) } +fn custom(i: &str) -> IResult<&str, Function> { + let (i, _) = tag("fn::")(i)?; + let (i, s) = take_while1(val_char)(i)?; + let (i, _) = char('(')(i)?; + let (i, _) = mightbespace(i)?; + let (i, a) = separated_list0(commas, value)(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(')')(i)?; + Ok((i, Function::Custom(s.to_string(), a))) +} + fn script(i: &str) -> IResult<&str, Function> { let (i, _) = alt((tag("fn::script"), tag("fn"), tag("function")))(i)?; let (i, _) = mightbespace(i)?; diff --git a/lib/src/sql/ident.rs b/lib/src/sql/ident.rs index 85b7d81f..ee3e66fd 100644 --- a/lib/src/sql/ident.rs +++ b/lib/src/sql/ident.rs @@ -64,6 +64,11 @@ pub fn ident(i: &str) -> IResult<&str, Ident> { Ok((i, Ident::from(v))) } +pub fn plain(i: &str) -> IResult<&str, Ident> { + let (i, v) = ident_default(i)?; + Ok((i, Ident::from(v))) +} + pub fn ident_raw(i: &str) -> IResult<&str, String> { let (i, v) = alt((ident_default, ident_backtick, ident_brackets))(i)?; Ok((i, v)) diff --git a/lib/src/sql/statements/define.rs b/lib/src/sql/statements/define.rs index 50acfe9c..a75bf43f 100644 --- a/lib/src/sql/statements/define.rs +++ b/lib/src/sql/statements/define.rs @@ -5,12 +5,15 @@ use crate::dbs::Transaction; use crate::err::Error; use crate::sql::algorithm::{algorithm, Algorithm}; use crate::sql::base::{base, base_or_scope, Base}; -use crate::sql::comment::shouldbespace; +use crate::sql::block::{block, Block}; +use crate::sql::comment::{mightbespace, shouldbespace}; +use crate::sql::common::commas; use crate::sql::duration::{duration, Duration}; use crate::sql::error::IResult; use crate::sql::escape::escape_str; use crate::sql::fmt::is_pretty; use crate::sql::fmt::pretty_indent; +use crate::sql::ident; use crate::sql::ident::{ident, Ident}; use crate::sql::idiom; use crate::sql::idiom::{Idiom, Idioms}; @@ -24,10 +27,12 @@ use argon2::password_hash::{PasswordHasher, SaltString}; use argon2::Argon2; use derive::Store; use nom::branch::alt; +use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; use nom::combinator::{map, opt}; use nom::multi::many0; +use nom::multi::separated_list0; use nom::sequence::tuple; use rand::distributions::Alphanumeric; use rand::rngs::OsRng; @@ -39,6 +44,7 @@ use std::fmt::{self, Display, Write}; pub enum DefineStatement { Namespace(DefineNamespaceStatement), Database(DefineDatabaseStatement), + Function(DefineFunctionStatement), Login(DefineLoginStatement), Token(DefineTokenStatement), Scope(DefineScopeStatement), @@ -60,6 +66,7 @@ impl DefineStatement { match self { Self::Namespace(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Database(ref v) => v.compute(ctx, opt, txn, doc).await, + Self::Function(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Login(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Token(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Scope(ref v) => v.compute(ctx, opt, txn, doc).await, @@ -77,6 +84,7 @@ impl fmt::Display for DefineStatement { match self { Self::Namespace(v) => Display::fmt(v, f), Self::Database(v) => Display::fmt(v, f), + Self::Function(v) => Display::fmt(v, f), Self::Login(v) => Display::fmt(v, f), Self::Token(v) => Display::fmt(v, f), Self::Scope(v) => Display::fmt(v, f), @@ -93,6 +101,7 @@ pub fn define(i: &str) -> IResult<&str, DefineStatement> { alt(( map(namespace, DefineStatement::Namespace), map(database, DefineStatement::Database), + map(function, DefineStatement::Function), map(login, DefineStatement::Login), map(token, DefineStatement::Token), map(scope, DefineStatement::Scope), @@ -211,6 +220,90 @@ fn database(i: &str) -> IResult<&str, DefineDatabaseStatement> { // -------------------------------------------------- // -------------------------------------------------- +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +pub struct DefineFunctionStatement { + pub name: Ident, + pub args: Vec<(Ident, Kind)>, + pub block: Block, +} + +impl DefineFunctionStatement { + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&Value>, + ) -> Result { + // Selected DB? + opt.needs(Level::Db)?; + // Allowed to run? + opt.check(Level::Db)?; + // Clone transaction + let run = txn.clone(); + // Claim transaction + let mut run = run.lock().await; + // Process the statement + let key = crate::key::fc::new(opt.ns(), opt.db(), &self.name); + run.add_ns(opt.ns(), opt.strict).await?; + run.add_db(opt.ns(), opt.db(), opt.strict).await?; + run.set(key, self).await?; + // Ok all good + Ok(Value::None) + } +} + +impl fmt::Display for DefineFunctionStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE FUNCTION fn::{}(", self.name)?; + for (i, (name, kind)) in self.args.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + write!(f, "${name}: {kind}")?; + } + f.write_str(") ")?; + Display::fmt(&self.block, f) + } +} + +fn function(i: &str) -> IResult<&str, DefineFunctionStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("FUNCTION")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag("fn::")(i)?; + let (i, name) = ident::plain(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char('(')(i)?; + let (i, _) = mightbespace(i)?; + let (i, args) = separated_list0(commas, |i| { + let (i, _) = char('$')(i)?; + let (i, name) = ident(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(':')(i)?; + let (i, _) = mightbespace(i)?; + let (i, kind) = kind(i)?; + Ok((i, (name, kind))) + })(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(')')(i)?; + let (i, _) = mightbespace(i)?; + let (i, block) = block(i)?; + Ok(( + i, + DefineFunctionStatement { + name, + args, + block, + }, + )) +} + +// -------------------------------------------------- +// -------------------------------------------------- +// -------------------------------------------------- + #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] pub struct DefineLoginStatement { pub name: Ident, diff --git a/lib/src/sql/statements/info.rs b/lib/src/sql/statements/info.rs index 0a27c463..2602515e 100644 --- a/lib/src/sql/statements/info.rs +++ b/lib/src/sql/statements/info.rs @@ -108,6 +108,12 @@ impl InfoStatement { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("dt".to_owned(), tmp.into()); + // Process the functions + let mut tmp = Object::default(); + for v in run.all_fc(opt.ns(), opt.db()).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("fc".to_owned(), tmp.into()); // Process the params let mut tmp = Object::default(); for v in run.all_pa(opt.ns(), opt.db()).await?.iter() { diff --git a/lib/src/sql/statements/mod.rs b/lib/src/sql/statements/mod.rs index ee0c91a8..5c6d4871 100644 --- a/lib/src/sql/statements/mod.rs +++ b/lib/src/sql/statements/mod.rs @@ -39,23 +39,21 @@ pub use self::yuse::UseStatement; pub use self::define::DefineDatabaseStatement; pub use self::define::DefineEventStatement; -pub use self::define::DefineFieldOption; pub use self::define::DefineFieldStatement; +pub use self::define::DefineFunctionStatement; pub use self::define::DefineIndexStatement; -pub use self::define::DefineLoginOption; pub use self::define::DefineLoginStatement; pub use self::define::DefineNamespaceStatement; pub use self::define::DefineParamStatement; -pub use self::define::DefineScopeOption; pub use self::define::DefineScopeStatement; pub use self::define::DefineStatement; -pub use self::define::DefineTableOption; pub use self::define::DefineTableStatement; pub use self::define::DefineTokenStatement; pub use self::remove::RemoveDatabaseStatement; pub use self::remove::RemoveEventStatement; pub use self::remove::RemoveFieldStatement; +pub use self::remove::RemoveFunctionStatement; pub use self::remove::RemoveIndexStatement; pub use self::remove::RemoveLoginStatement; pub use self::remove::RemoveNamespaceStatement; diff --git a/lib/src/sql/statements/remove.rs b/lib/src/sql/statements/remove.rs index 50f399b6..7a46e93b 100644 --- a/lib/src/sql/statements/remove.rs +++ b/lib/src/sql/statements/remove.rs @@ -4,14 +4,16 @@ use crate::dbs::Options; use crate::dbs::Transaction; use crate::err::Error; use crate::sql::base::{base, base_or_scope, Base}; -use crate::sql::comment::shouldbespace; +use crate::sql::comment::{mightbespace, shouldbespace}; use crate::sql::error::IResult; +use crate::sql::ident; use crate::sql::ident::{ident, Ident}; use crate::sql::idiom; use crate::sql::idiom::Idiom; use crate::sql::value::Value; use derive::Store; use nom::branch::alt; +use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::char; use nom::combinator::{map, opt}; @@ -23,6 +25,7 @@ use std::fmt::{self, Display, Formatter}; pub enum RemoveStatement { Namespace(RemoveNamespaceStatement), Database(RemoveDatabaseStatement), + Function(RemoveFunctionStatement), Login(RemoveLoginStatement), Token(RemoveTokenStatement), Scope(RemoveScopeStatement), @@ -44,6 +47,7 @@ impl RemoveStatement { match self { Self::Namespace(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Database(ref v) => v.compute(ctx, opt, txn, doc).await, + Self::Function(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Login(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Token(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Scope(ref v) => v.compute(ctx, opt, txn, doc).await, @@ -61,6 +65,7 @@ impl Display for RemoveStatement { match self { Self::Namespace(v) => Display::fmt(v, f), Self::Database(v) => Display::fmt(v, f), + Self::Function(v) => Display::fmt(v, f), Self::Login(v) => Display::fmt(v, f), Self::Token(v) => Display::fmt(v, f), Self::Scope(v) => Display::fmt(v, f), @@ -77,6 +82,7 @@ pub fn remove(i: &str) -> IResult<&str, RemoveStatement> { alt(( map(namespace, RemoveStatement::Namespace), map(database, RemoveStatement::Database), + map(function, RemoveStatement::Function), map(login, RemoveStatement::Login), map(token, RemoveStatement::Token), map(scope, RemoveStatement::Scope), @@ -204,6 +210,67 @@ fn database(i: &str) -> IResult<&str, RemoveDatabaseStatement> { // -------------------------------------------------- // -------------------------------------------------- +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +pub struct RemoveFunctionStatement { + pub name: Ident, +} + +impl RemoveFunctionStatement { + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&Value>, + ) -> Result { + // Selected DB? + opt.needs(Level::Db)?; + // Allowed to run? + opt.check(Level::Db)?; + // Clone transaction + let run = txn.clone(); + // Claim transaction + let mut run = run.lock().await; + // Delete the definition + let key = crate::key::fc::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl fmt::Display for RemoveFunctionStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "REMOVE FUNCTION fn::{}", self.name) + } +} + +fn function(i: &str) -> IResult<&str, RemoveFunctionStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("FUNCTION")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag("fn::")(i)?; + let (i, name) = ident::plain(i)?; + let (i, _) = opt(|i| { + let (i, _) = mightbespace(i)?; + let (i, _) = char('(')(i)?; + let (i, _) = mightbespace(i)?; + let (i, _) = char(')')(i)?; + Ok((i, ())) + })(i)?; + Ok(( + i, + RemoveFunctionStatement { + name, + }, + )) +} + +// -------------------------------------------------- +// -------------------------------------------------- +// -------------------------------------------------- + #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] pub struct RemoveLoginStatement { pub name: Ident, diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index 44249cea..ac38105f 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -964,12 +964,8 @@ impl Value { Value::Idiom(v) => v.simplify(), Value::Strand(v) => v.0.to_string().into(), Value::Datetime(v) => v.0.to_string().into(), - Value::Future(_) => "fn::future".to_string().into(), - Value::Function(v) => match v.as_ref() { - Function::Script(_, _) => "fn::script".to_string().into(), - Function::Normal(f, _) => f.to_string().into(), - Function::Cast(_, v) => v.to_idiom(), - }, + Value::Future(_) => "future".to_string().into(), + Value::Function(v) => v.to_idiom(), _ => self.to_string().into(), } } diff --git a/lib/tests/define.rs b/lib/tests/define.rs index e689718f..3a944d8f 100644 --- a/lib/tests/define.rs +++ b/lib/tests/define.rs @@ -57,6 +57,40 @@ async fn define_statement_database() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn define_statement_function() -> Result<(), Error> { + let sql = " + DEFINE FUNCTION fn::test($first: string, $last: string) { + RETURN $first + $last; + }; + INFO FOR DB; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 2); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + dl: {}, + dt: {}, + fc: { test: 'DEFINE FUNCTION fn::test($first: string, $last: string) { RETURN $first + $last; }' }, + pa: {}, + sc: {}, + pa: {}, + sc: {}, + tb: {}, + }", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + #[tokio::test] async fn define_statement_table_drop() -> Result<(), Error> { let sql = " @@ -76,8 +110,7 @@ async fn define_statement_table_drop() -> Result<(), Error> { "{ dl: {}, dt: {}, - pa: {}, - sc: {}, + fc: {}, pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test DROP SCHEMALESS' }, @@ -107,6 +140,7 @@ async fn define_statement_table_schemaless() -> Result<(), Error> { "{ dl: {}, dt: {}, + fc: {}, pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMALESS' }, @@ -140,6 +174,7 @@ async fn define_statement_table_schemafull() -> Result<(), Error> { "{ dl: {}, dt: {}, + fc: {}, pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMAFULL' }, @@ -169,6 +204,7 @@ async fn define_statement_table_schemaful() -> Result<(), Error> { "{ dl: {}, dt: {}, + fc: {}, pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMAFULL' }, diff --git a/lib/tests/param.rs b/lib/tests/param.rs index 02c1fac9..b28f2a4c 100644 --- a/lib/tests/param.rs +++ b/lib/tests/param.rs @@ -27,6 +27,7 @@ async fn define_global_param() -> Result<(), Error> { "{ dl: {}, dt: {}, + fc: {}, pa: { test: 'DEFINE PARAM $test VALUE 12345' }, sc: {}, tb: {}, diff --git a/lib/tests/strict.rs b/lib/tests/strict.rs index 29288d16..079ec89a 100644 --- a/lib/tests/strict.rs +++ b/lib/tests/strict.rs @@ -21,19 +21,44 @@ async fn strict_mode_no_namespace() -> Result<(), Error> { assert_eq!(res.len(), 5); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::NsNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::NsNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::NsNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::NsNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::NsNotFound { + value: _ + }) + )); // Ok(()) } @@ -57,16 +82,36 @@ async fn strict_mode_no_database() -> Result<(), Error> { assert!(tmp.is_ok()); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::DbNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::DbNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::DbNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::DbNotFound { + value: _ + }) + )); // Ok(()) } @@ -93,13 +138,28 @@ async fn strict_mode_no_table() -> Result<(), Error> { assert!(tmp.is_ok()); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::TbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::TbNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::TbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::TbNotFound { + value: _ + }) + )); // let tmp = res.remove(0).result; - assert!(matches!(tmp.err(), Some(Error::TbNotFound))); + assert!(matches!( + tmp.err(), + Some(Error::TbNotFound { + value: _ + }) + )); // Ok(()) } @@ -192,6 +252,7 @@ async fn loose_mode_all_ok() -> Result<(), Error> { "{ dl: {}, dt: {}, + fc: {}, pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMALESS PERMISSIONS NONE' },