diff --git a/lib/src/err/mod.rs b/lib/src/err/mod.rs index 78cc44a4..1cd80f92 100644 --- a/lib/src/err/mod.rs +++ b/lib/src/err/mod.rs @@ -202,6 +202,10 @@ pub enum Error { #[error("The scope token does not exist")] StNotFound, + /// The requested param does not exist + #[error("The param does not exist")] + PaNotFound, + /// The requested table does not exist #[error("The table does not exist")] TbNotFound, diff --git a/lib/src/key/mod.rs b/lib/src/key/mod.rs index bd73a793..1e628170 100644 --- a/lib/src/key/mod.rs +++ b/lib/src/key/mod.rs @@ -10,6 +10,7 @@ /// DL /*{ns}*{db}!dl{us} /// DT /*{ns}*{db}!dt{tk} /// SC /*{ns}*{db}!sc{sc} +/// PA /*{ns}*{db}!pa{pa} /// TB /*{ns}*{db}!tb{tb} /// LQ /*{ns}*{db}!lq{lq} /// @@ -46,6 +47,7 @@ pub mod namespace; pub mod nl; pub mod ns; pub mod nt; +pub mod pa; pub mod sc; pub mod scope; pub mod st; diff --git a/lib/src/key/pa.rs b/lib/src/key/pa.rs new file mode 100644 index 00000000..f887d168 --- /dev/null +++ b/lib/src/key/pa.rs @@ -0,0 +1,64 @@ +use derive::Key; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)] +pub struct Pa { + __: u8, + _a: u8, + pub ns: String, + _b: u8, + pub db: String, + _c: u8, + _d: u8, + _e: u8, + pub pa: String, +} + +pub fn new(ns: &str, db: &str, pa: &str) -> Pa { + Pa::new(ns.to_string(), db.to_string(), pa.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, 0x70, 0x61, 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, 0x70, 0x61, 0xff]); + k +} + +impl Pa { + pub fn new(ns: String, db: String, pa: String) -> Pa { + Pa { + __: 0x2f, // / + _a: 0x2a, // * + ns, + _b: 0x2a, // * + db, + _c: 0x21, // ! + _d: 0x70, // p + _e: 0x61, // a + pa, + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn key() { + use super::*; + #[rustfmt::skip] + let val = Pa::new( + "test".to_string(), + "test".to_string(), + "test".to_string(), + ); + let enc = Pa::encode(&val).unwrap(); + let dec = Pa::decode(&enc).unwrap(); + assert_eq!(val, dec); + } +} diff --git a/lib/src/kvs/cache.rs b/lib/src/kvs/cache.rs index fe102e9b..0017bc06 100644 --- a/lib/src/kvs/cache.rs +++ b/lib/src/kvs/cache.rs @@ -5,6 +5,7 @@ use crate::sql::statements::DefineFieldStatement; use crate::sql::statements::DefineIndexStatement; use crate::sql::statements::DefineLoginStatement; use crate::sql::statements::DefineNamespaceStatement; +use crate::sql::statements::DefineParamStatement; use crate::sql::statements::DefineScopeStatement; use crate::sql::statements::DefineTableStatement; use crate::sql::statements::DefineTokenStatement; @@ -25,6 +26,7 @@ pub enum Entry { Dts(Arc<[DefineTokenStatement]>), Scs(Arc<[DefineScopeStatement]>), Sts(Arc<[DefineTokenStatement]>), + Pas(Arc<[DefineParamStatement]>), Tbs(Arc<[DefineTableStatement]>), Evs(Arc<[DefineEventStatement]>), Fds(Arc<[DefineFieldStatement]>), diff --git a/lib/src/kvs/tx.rs b/lib/src/kvs/tx.rs index cf0652e7..6841fc4e 100644 --- a/lib/src/kvs/tx.rs +++ b/lib/src/kvs/tx.rs @@ -16,6 +16,7 @@ use sql::statements::DefineFieldStatement; use sql::statements::DefineIndexStatement; use sql::statements::DefineLoginStatement; use sql::statements::DefineNamespaceStatement; +use sql::statements::DefineParamStatement; use sql::statements::DefineScopeStatement; use sql::statements::DefineTableStatement; use sql::statements::DefineTokenStatement; @@ -812,6 +813,28 @@ impl Transaction { } } } + /// Retrieve all scope definitions for a specific database. + pub async fn all_pa( + &mut self, + ns: &str, + db: &str, + ) -> Result, Error> { + let key = crate::key::pa::prefix(ns, db); + match self.cache.exi(&key) { + true => match self.cache.get(&key) { + Some(Entry::Pas(v)) => Ok(v), + _ => unreachable!(), + }, + _ => { + let beg = crate::key::pa::prefix(ns, db); + let end = crate::key::pa::suffix(ns, db); + let val = self.getr(beg..end, u32::MAX).await?; + let val = val.convert().into(); + self.cache.set(key, Entry::Pas(Arc::clone(&val))); + Ok(val) + } + } + } /// Retrieve all table definitions for a specific database. pub async fn all_tb( &mut self, @@ -1018,6 +1041,17 @@ impl Transaction { let val = self.get(key).await?.ok_or(Error::StNotFound)?; Ok(val.into()) } + /// Retrieve a specific param definition. + pub async fn get_pa( + &mut self, + ns: &str, + db: &str, + pa: &str, + ) -> Result { + let key = crate::key::pa::new(ns, db, pa); + let val = self.get(key).await?.ok_or(Error::PaNotFound)?; + Ok(val.into()) + } /// Retrieve a specific table definition. pub async fn get_tb( &mut self, @@ -1329,6 +1363,20 @@ impl Transaction { chn.send(bytes!("")).await?; } } + // Output PARAMS + { + let pas = self.all_pa(ns, db).await?; + if !pas.is_empty() { + chn.send(bytes!("-- ------------------------------")).await?; + chn.send(bytes!("-- PARAMS")).await?; + chn.send(bytes!("-- ------------------------------")).await?; + chn.send(bytes!("")).await?; + for pa in pas.iter() { + chn.send(bytes!(format!("{};", pa))).await?; + } + chn.send(bytes!("")).await?; + } + } // Output TABLES { let tbs = self.all_tb(ns, db).await?; diff --git a/lib/src/sql/param.rs b/lib/src/sql/param.rs index 57dde85a..354be616 100644 --- a/lib/src/sql/param.rs +++ b/lib/src/sql/param.rs @@ -42,6 +42,7 @@ impl Param { match self.first() { // The first part will be a field Some(Part::Field(v)) => match v.as_str() { + // This is a special param "this" | "self" => match doc { // The base document exists Some(v) => { @@ -55,8 +56,9 @@ impl Param { // The base document does not exist None => Ok(Value::None), }, + // This is a normal param _ => match ctx.value(v) { - // The base variable exists + // The param has been set locally Some(v) => { // Get the path parts let pth: &[Part] = self; @@ -65,8 +67,22 @@ impl Param { // Return the desired field res.get(ctx, opt, txn, pth.next()).await } - // The base variable does not exist - None => Ok(Value::None), + // The param has not been set locally + None => { + // Clone transaction + let run = txn.clone(); + // Claim transaction + let mut run = run.lock().await; + // Get the param definition + let val = run.get_pa(opt.ns(), opt.db(), v).await; + // Check if the param has been set globally + match val { + // The param has been set globally + Ok(v) => Ok(v.value), + // The param has not been set globally + Err(_) => Ok(Value::None), + } + } }, }, _ => unreachable!(), diff --git a/lib/src/sql/statements/define.rs b/lib/src/sql/statements/define.rs index 5fb3aa10..4fdd5d00 100644 --- a/lib/src/sql/statements/define.rs +++ b/lib/src/sql/statements/define.rs @@ -23,6 +23,7 @@ use argon2::Argon2; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::character::complete::char; use nom::combinator::{map, opt}; use nom::multi::many0; use nom::sequence::tuple; @@ -40,6 +41,7 @@ pub enum DefineStatement { Login(DefineLoginStatement), Token(DefineTokenStatement), Scope(DefineScopeStatement), + Param(DefineParamStatement), Table(DefineTableStatement), Event(DefineEventStatement), Field(DefineFieldStatement), @@ -60,6 +62,7 @@ impl DefineStatement { 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, + Self::Param(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Table(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Event(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Field(ref v) => v.compute(ctx, opt, txn, doc).await, @@ -76,6 +79,7 @@ impl fmt::Display for DefineStatement { Self::Login(v) => Display::fmt(v, f), Self::Token(v) => Display::fmt(v, f), Self::Scope(v) => Display::fmt(v, f), + Self::Param(v) => Display::fmt(v, f), Self::Table(v) => Display::fmt(v, f), Self::Event(v) => Display::fmt(v, f), Self::Field(v) => Display::fmt(v, f), @@ -91,6 +95,7 @@ pub fn define(i: &str) -> IResult<&str, DefineStatement> { map(login, DefineStatement::Login), map(token, DefineStatement::Token), map(scope, DefineStatement::Scope), + map(param, DefineStatement::Param), map(table, DefineStatement::Table), map(event, DefineStatement::Event), map(field, DefineStatement::Field), @@ -569,6 +574,68 @@ fn scope_signin(i: &str) -> IResult<&str, DefineScopeOption> { // -------------------------------------------------- // -------------------------------------------------- +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +pub struct DefineParamStatement { + pub name: Ident, + pub value: Value, +} + +impl DefineParamStatement { + 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::pa::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 DefineParamStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE PARAM ${} VALUE {}", self.name, self.value) + } +} + +fn param(i: &str) -> IResult<&str, DefineParamStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("PARAM")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = char('$')(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("VALUE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, value) = value(i)?; + Ok(( + i, + DefineParamStatement { + name, + value, + }, + )) +} + +// -------------------------------------------------- +// -------------------------------------------------- +// -------------------------------------------------- + #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] pub struct DefineTableStatement { pub name: Ident, diff --git a/lib/src/sql/statements/info.rs b/lib/src/sql/statements/info.rs index e66ed97e..3f70d662 100644 --- a/lib/src/sql/statements/info.rs +++ b/lib/src/sql/statements/info.rs @@ -70,18 +70,18 @@ impl InfoStatement { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("db".to_owned(), tmp.into()); - // Process the tokens - let mut tmp = Object::default(); - for v in run.all_nt(opt.ns()).await?.iter() { - tmp.insert(v.name.to_string(), v.to_string().into()); - } - res.insert("nt".to_owned(), tmp.into()); // Process the logins let mut tmp = Object::default(); for v in run.all_nl(opt.ns()).await?.iter() { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("nl".to_owned(), tmp.into()); + // Process the tokens + let mut tmp = Object::default(); + for v in run.all_nt(opt.ns()).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("nt".to_owned(), tmp.into()); // Ok all good Value::from(res).ok() } @@ -96,30 +96,36 @@ impl InfoStatement { let mut run = run.lock().await; // Create the result set let mut res = Object::default(); - // Process the tables - let mut tmp = Object::default(); - for v in run.all_tb(opt.ns(), opt.db()).await?.iter() { - tmp.insert(v.name.to_string(), v.to_string().into()); - } - res.insert("tb".to_owned(), tmp.into()); - // Process the scopes - let mut tmp = Object::default(); - for v in run.all_sc(opt.ns(), opt.db()).await?.iter() { - tmp.insert(v.name.to_string(), v.to_string().into()); - } - res.insert("sc".to_owned(), tmp.into()); - // Process the tokens - let mut tmp = Object::default(); - for v in run.all_dt(opt.ns(), opt.db()).await?.iter() { - tmp.insert(v.name.to_string(), v.to_string().into()); - } - res.insert("dt".to_owned(), tmp.into()); // Process the logins let mut tmp = Object::default(); for v in run.all_dl(opt.ns(), opt.db()).await?.iter() { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("dl".to_owned(), tmp.into()); + // Process the tokens + let mut tmp = Object::default(); + for v in run.all_dt(opt.ns(), opt.db()).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("dt".to_owned(), tmp.into()); + // Process the params + let mut tmp = Object::default(); + for v in run.all_pa(opt.ns(), opt.db()).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("pa".to_owned(), tmp.into()); + // Process the scopes + let mut tmp = Object::default(); + for v in run.all_sc(opt.ns(), opt.db()).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("sc".to_owned(), tmp.into()); + // Process the tables + let mut tmp = Object::default(); + for v in run.all_tb(opt.ns(), opt.db()).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("tb".to_owned(), tmp.into()); // Ok all good Value::from(res).ok() } @@ -166,18 +172,18 @@ impl InfoStatement { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("fd".to_owned(), tmp.into()); - // Process the indexes - let mut tmp = Object::default(); - for v in run.all_ix(opt.ns(), opt.db(), tb).await?.iter() { - tmp.insert(v.name.to_string(), v.to_string().into()); - } - res.insert("ix".to_owned(), tmp.into()); // Process the tables let mut tmp = Object::default(); for v in run.all_ft(opt.ns(), opt.db(), tb).await?.iter() { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("ft".to_owned(), tmp.into()); + // Process the indexes + let mut tmp = Object::default(); + for v in run.all_ix(opt.ns(), opt.db(), tb).await?.iter() { + tmp.insert(v.name.to_string(), v.to_string().into()); + } + res.insert("ix".to_owned(), tmp.into()); // Ok all good Value::from(res).ok() } diff --git a/lib/src/sql/statements/mod.rs b/lib/src/sql/statements/mod.rs index daedd1a4..9bfaf0c7 100644 --- a/lib/src/sql/statements/mod.rs +++ b/lib/src/sql/statements/mod.rs @@ -44,6 +44,7 @@ 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; @@ -57,6 +58,7 @@ pub use self::remove::RemoveFieldStatement; pub use self::remove::RemoveIndexStatement; pub use self::remove::RemoveLoginStatement; pub use self::remove::RemoveNamespaceStatement; +pub use self::remove::RemoveParamStatement; pub use self::remove::RemoveScopeStatement; pub use self::remove::RemoveStatement; pub use self::remove::RemoveTableStatement; diff --git a/lib/src/sql/statements/remove.rs b/lib/src/sql/statements/remove.rs index 7d4d71d9..50f399b6 100644 --- a/lib/src/sql/statements/remove.rs +++ b/lib/src/sql/statements/remove.rs @@ -13,6 +13,7 @@ use crate::sql::value::Value; use derive::Store; use nom::branch::alt; use nom::bytes::complete::tag_no_case; +use nom::character::complete::char; use nom::combinator::{map, opt}; use nom::sequence::tuple; use serde::{Deserialize, Serialize}; @@ -25,6 +26,7 @@ pub enum RemoveStatement { Login(RemoveLoginStatement), Token(RemoveTokenStatement), Scope(RemoveScopeStatement), + Param(RemoveParamStatement), Table(RemoveTableStatement), Event(RemoveEventStatement), Field(RemoveFieldStatement), @@ -45,6 +47,7 @@ impl RemoveStatement { 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, + Self::Param(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Table(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Event(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Field(ref v) => v.compute(ctx, opt, txn, doc).await, @@ -61,6 +64,7 @@ impl Display for RemoveStatement { Self::Login(v) => Display::fmt(v, f), Self::Token(v) => Display::fmt(v, f), Self::Scope(v) => Display::fmt(v, f), + Self::Param(v) => Display::fmt(v, f), Self::Table(v) => Display::fmt(v, f), Self::Event(v) => Display::fmt(v, f), Self::Field(v) => Display::fmt(v, f), @@ -76,6 +80,7 @@ pub fn remove(i: &str) -> IResult<&str, RemoveStatement> { map(login, RemoveStatement::Login), map(token, RemoveStatement::Token), map(scope, RemoveStatement::Scope), + map(param, RemoveStatement::Param), map(table, RemoveStatement::Table), map(event, RemoveStatement::Event), map(field, RemoveStatement::Field), @@ -428,6 +433,60 @@ fn scope(i: &str) -> IResult<&str, RemoveScopeStatement> { // -------------------------------------------------- // -------------------------------------------------- +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +pub struct RemoveParamStatement { + pub name: Ident, +} + +impl RemoveParamStatement { + 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::pa::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl fmt::Display for RemoveParamStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "REMOVE PARAM {}", self.name) + } +} + +fn param(i: &str) -> IResult<&str, RemoveParamStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("PARAM")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = char('$')(i)?; + let (i, name) = ident(i)?; + Ok(( + i, + RemoveParamStatement { + name, + }, + )) +} + +// -------------------------------------------------- +// -------------------------------------------------- +// -------------------------------------------------- + #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] pub struct RemoveTableStatement { pub name: Ident, diff --git a/lib/tests/define.rs b/lib/tests/define.rs index 7b7f2565..e689718f 100644 --- a/lib/tests/define.rs +++ b/lib/tests/define.rs @@ -76,6 +76,9 @@ async fn define_statement_table_drop() -> Result<(), Error> { "{ dl: {}, dt: {}, + pa: {}, + sc: {}, + pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test DROP SCHEMALESS' }, }", @@ -104,6 +107,7 @@ async fn define_statement_table_schemaless() -> Result<(), Error> { "{ dl: {}, dt: {}, + pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMALESS' }, }", @@ -136,6 +140,7 @@ async fn define_statement_table_schemafull() -> Result<(), Error> { "{ dl: {}, dt: {}, + pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMAFULL' }, }", @@ -164,6 +169,7 @@ async fn define_statement_table_schemaful() -> Result<(), Error> { "{ dl: {}, dt: {}, + pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMAFULL' }, }", diff --git a/lib/tests/param.rs b/lib/tests/param.rs index 83d8903e..02c1fac9 100644 --- a/lib/tests/param.rs +++ b/lib/tests/param.rs @@ -5,6 +5,49 @@ use surrealdb::err::Error; use surrealdb::kvs::Datastore; use surrealdb::sql::Value; +#[tokio::test] +async fn define_global_param() -> Result<(), Error> { + let sql = " + DEFINE PARAM $test VALUE 12345; + INFO FOR DB; + SELECT * FROM $test; + LET $test = 56789; + SELECT * FROM $test; + "; + 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(), 5); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + dl: {}, + dt: {}, + pa: { test: 'DEFINE PARAM $test VALUE 12345' }, + sc: {}, + tb: {}, + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[12345]"); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[56789]"); + assert_eq!(tmp, val); + // + Ok(()) +} + #[tokio::test] async fn define_protected_param() -> Result<(), Error> { let sql = " diff --git a/lib/tests/strict.rs b/lib/tests/strict.rs index c678fb56..29288d16 100644 --- a/lib/tests/strict.rs +++ b/lib/tests/strict.rs @@ -192,6 +192,7 @@ async fn loose_mode_all_ok() -> Result<(), Error> { "{ dl: {}, dt: {}, + pa: {}, sc: {}, tb: { test: 'DEFINE TABLE test SCHEMALESS PERMISSIONS NONE' }, }",