Add support for custom SQL functions

Closes #247
This commit is contained in:
Tobie Morgan Hitchcock 2023-03-25 19:44:03 +00:00
parent a64ebdb4a2
commit cdac4f84cd
16 changed files with 688 additions and 91 deletions

View file

@ -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?;

View file

@ -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")]

64
lib/src/key/fc.rs Normal file
View file

@ -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<u8> {
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<u8> {
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);
}
}

View file

@ -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

View file

@ -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<DefineNamespaceStatement>),
// Single definitions
Db(Arc<DefineDatabaseStatement>),
Ns(Arc<DefineNamespaceStatement>),
Tb(Arc<DefineTableStatement>),
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)]

View file

@ -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<K>(&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<K>(&mut self, key: K) -> Result<bool, Error>
@ -240,6 +249,7 @@ impl Transaction {
_ => unreachable!(),
}
}
/// Fetch a key from the datastore.
#[allow(unused_variables)]
pub async fn get<K>(&mut self, key: K) -> Result<Option<Val>, Error>
@ -278,6 +288,7 @@ impl Transaction {
_ => unreachable!(),
}
}
/// Insert or update a key in the datastore.
#[allow(unused_variables)]
pub async fn set<K, V>(&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<K, V>(&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<K, V>(&mut self, key: K, val: V, chk: Option<V>) -> 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<K, V>(&mut self, key: K, chk: Option<V>) -> 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<K>(&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<Arc<[DefineNamespaceStatement]>, 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<Arc<[DefineLoginStatement]>, 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<Arc<[DefineTokenStatement]>, 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<Arc<[DefineDatabaseStatement]>, 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<Arc<[DefineFunctionStatement]>, 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<DefineNamespaceStatement, Error> {
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<DefineLoginStatement, Error> {
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<DefineTokenStatement, Error> {
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<DefineDatabaseStatement, Error> {
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<DefineLoginStatement, Error> {
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<DefineTokenStatement, Error> {
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<DefineScopeStatement, Error> {
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<DefineTokenStatement, Error> {
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<DefineFunctionStatement, Error> {
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<DefineParamStatement, Error> {
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<DefineTableStatement, Error> {
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<DefineNamespaceStatement, Error> {
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<DefineDatabaseStatement, Error> {
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<DefineScopeStatement, Error> {
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<DefineTableStatement, Error> {
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<DefineNamespaceStatement> = 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<DefineDatabaseStatement> = 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<DefineTableStatement> = 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<Arc<DefineNamespaceStatement>, 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<Arc<DefineDatabaseStatement>, 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<Arc<DefineTableStatement>, 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<Vec<u8>>) -> 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?;

View file

@ -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<Value>),
Custom(String, Vec<Value>),
Script(Script, Vec<Value>),
}
@ -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<Value, Error> {
// 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)?;

View file

@ -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))

View file

@ -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<Value, Error> {
// 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,

View file

@ -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() {

View file

@ -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;

View file

@ -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<Value, Error> {
// 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,

View file

@ -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(),
}
}

View file

@ -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' },

View file

@ -27,6 +27,7 @@ async fn define_global_param() -> Result<(), Error> {
"{
dl: {},
dt: {},
fc: {},
pa: { test: 'DEFINE PARAM $test VALUE 12345' },
sc: {},
tb: {},

View file

@ -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' },