diff --git a/.rustfmt.toml b/.rustfmt.toml index 966fbe4a..82712654 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -11,6 +11,7 @@ use_small_heuristics = "Off" # ----------------------------------- #blank_lines_lower_bound = 1 +#group_imports = "One" #indent_style = "Block" #match_arm_blocks = true #reorder_impl_items = true diff --git a/lib/src/api/method/mod.rs b/lib/src/api/method/mod.rs index 6ba080f4..4c47a205 100644 --- a/lib/src/api/method/mod.rs +++ b/lib/src/api/method/mod.rs @@ -916,8 +916,8 @@ where /// ```no_run /// # #[tokio::main] /// # async fn main() -> surrealdb::Result<()> { - /// # let db = surrealdb::engine::any::connect("mem://").unwrap(); - /// db.tick_at(123).await.unwrap(); + /// # let db = surrealdb::engine::any::connect("mem://").await?; + /// db.tick(123).await?; /// # Ok(()) /// # } /// ``` diff --git a/lib/src/iam/verify.rs b/lib/src/iam/verify.rs index 368626da..207b48c4 100644 --- a/lib/src/iam/verify.rs +++ b/lib/src/iam/verify.rs @@ -1,6 +1,5 @@ use crate::dbs::Session; use crate::err::Error; -use crate::error; use crate::iam::token::Claims; use crate::iam::Auth; use crate::iam::{Actor, Level, Role}; @@ -454,21 +453,6 @@ async fn verify_ns_creds( let user_res = match tx.get_ns_user(ns, user).await { Ok(u) => Ok(u), - // TODO(sgirones): Remove this fallback once we remove LOGIN from the system completely. We are backwards compatible with LOGIN for now. - // If the USER resource is not found in the namespace, try to find the LOGIN resource - Err(error::Db::UserNsNotFound { - ns: _, - value: _, - }) => match tx.get_nl(ns, user).await { - Ok(u) => Ok(DefineUserStatement { - base: u.base, - name: u.name, - hash: u.hash, - code: u.code, - roles: vec![Role::Editor.into()], - }), - Err(e) => Err(e), - }, Err(e) => Err(e), }?; @@ -488,22 +472,6 @@ async fn verify_db_creds( let user_res = match tx.get_db_user(ns, db, user).await { Ok(u) => Ok(u), - // TODO(sgirones): Remove this fallback once we remove LOGIN from the system completely. We are backwards compatible with LOGIN for now. - // If the USER resource is not found in the database, try to find the LOGIN resource - Err(error::Db::UserDbNotFound { - ns: _, - db: _, - value: _, - }) => match tx.get_dl(ns, db, user).await { - Ok(u) => Ok(DefineUserStatement { - base: u.base, - name: u.name, - hash: u.hash, - code: u.code, - roles: vec![Role::Editor.into()], - }), - Err(e) => Err(e), - }, Err(e) => Err(e), }?; diff --git a/lib/src/kvs/cache.rs b/lib/src/kvs/cache.rs index 9db2fd43..b9f4f699 100644 --- a/lib/src/kvs/cache.rs +++ b/lib/src/kvs/cache.rs @@ -6,12 +6,12 @@ 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; use crate::sql::statements::DefineParamStatement; use crate::sql::statements::DefineScopeStatement; use crate::sql::statements::DefineTableStatement; use crate::sql::statements::DefineTokenStatement; +use crate::sql::statements::DefineUserStatement; use crate::sql::statements::LiveStatement; use std::collections::HashMap; use std::sync::Arc; @@ -25,17 +25,17 @@ pub enum Entry { // Multi definitions Azs(Arc<[DefineAnalyzerStatement]>), Dbs(Arc<[DefineDatabaseStatement]>), - Dls(Arc<[DefineLoginStatement]>), Dts(Arc<[DefineTokenStatement]>), + Dus(Arc<[DefineUserStatement]>), 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]>), + Nus(Arc<[DefineUserStatement]>), Pas(Arc<[DefineParamStatement]>), Scs(Arc<[DefineScopeStatement]>), Seq(U32), @@ -59,4 +59,8 @@ impl Cache { pub fn del(&mut self, key: &Key) -> Option { self.0.remove(key) } + /// Clears a cache completely + pub fn clear(&mut self) { + self.0.clear() + } } diff --git a/lib/src/kvs/tests/tb.rs b/lib/src/kvs/tests/tb.rs index b106b266..9779d773 100644 --- a/lib/src/kvs/tests/tb.rs +++ b/lib/src/kvs/tests/tb.rs @@ -22,6 +22,7 @@ async fn table_definitions_can_be_scanned() { view: None, permissions: Default::default(), changefeed: None, + comment: None, }; tx.set(&key, &value).await.unwrap(); @@ -57,6 +58,7 @@ async fn table_definitions_can_be_deleted() { view: None, permissions: Default::default(), changefeed: None, + comment: None, }; tx.set(&key, &value).await.unwrap(); diff --git a/lib/src/kvs/tx.rs b/lib/src/kvs/tx.rs index 3143da42..77dffd9b 100644 --- a/lib/src/kvs/tx.rs +++ b/lib/src/kvs/tx.rs @@ -15,7 +15,6 @@ use crate::sql; use crate::sql::paths::EDGE; use crate::sql::paths::IN; use crate::sql::paths::OUT; -use crate::sql::statements::DefineUserStatement; use crate::sql::thing::Thing; use crate::sql::Strand; use crate::sql::Value; @@ -30,12 +29,12 @@ use sql::statements::DefineEventStatement; use sql::statements::DefineFieldStatement; use sql::statements::DefineFunctionStatement; 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; +use sql::statements::DefineUserStatement; use sql::statements::LiveStatement; use std::borrow::Cow; use std::collections::HashMap; @@ -1205,34 +1204,25 @@ 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::namespace::lg::prefix(ns); + /// Retrieve all namespace user definitions for a specific namespace. + pub async fn all_ns_users(&mut self, ns: &str) -> Result, Error> { + let key = crate::key::namespace::us::prefix(ns); Ok(if let Some(e) = self.cache.get(&key) { - if let Entry::Nls(v) = e { + if let Entry::Nus(v) = e { v } else { unreachable!(); } } else { - let beg = crate::key::namespace::lg::prefix(ns); - let end = crate::key::namespace::lg::suffix(ns); + let beg = crate::key::namespace::us::prefix(ns); + let end = crate::key::namespace::us::suffix(ns); let val = self.getr(beg..end, u32::MAX).await?; let val = val.convert().into(); - self.cache.set(key, Entry::Nls(Arc::clone(&val))); + self.cache.set(key, Entry::Nus(Arc::clone(&val))); val }) } - /// Retrieve all namespace user definitions for a specific namespace. - pub async fn all_ns_users(&mut self, ns: &str) -> Result, Error> { - let beg = crate::key::namespace::us::prefix(ns); - let end = crate::key::namespace::us::suffix(ns); - let val = self.getr(beg..end, u32::MAX).await?; - let val = val.convert().into(); - Ok(val) - } - /// Retrieve all namespace token definitions for a specific namespace. pub async fn all_nt(&mut self, ns: &str) -> Result, Error> { let key = crate::key::namespace::tk::prefix(ns); @@ -1271,40 +1261,27 @@ impl Transaction { }) } - /// Retrieve all database login definitions for a specific database. - pub async fn all_dl( - &mut self, - ns: &str, - db: &str, - ) -> Result, Error> { - let key = crate::key::database::lg::prefix(ns, db); - Ok(if let Some(e) = self.cache.get(&key) { - if let Entry::Dls(v) = e { - v - } else { - unreachable!(); - } - } else { - let beg = crate::key::database::lg::prefix(ns, db); - let end = crate::key::database::lg::suffix(ns, db); - let val = self.getr(beg..end, u32::MAX).await?; - let val = val.convert().into(); - self.cache.set(key, Entry::Dls(Arc::clone(&val))); - val - }) - } - /// Retrieve all database user definitions for a specific database. pub async fn all_db_users( &mut self, ns: &str, db: &str, ) -> Result, Error> { - let beg = crate::key::database::us::prefix(ns, db); - let end = crate::key::database::us::suffix(ns, db); - let val = self.getr(beg..end, u32::MAX).await?; - let val = val.convert().into(); - Ok(val) + let key = crate::key::database::us::prefix(ns, db); + Ok(if let Some(e) = self.cache.get(&key) { + if let Entry::Dus(v) = e { + v + } else { + unreachable!(); + } + } else { + let beg = crate::key::database::us::prefix(ns, db); + let end = crate::key::database::us::suffix(ns, db); + let val = self.getr(beg..end, u32::MAX).await?; + let val = val.convert().into(); + self.cache.set(key, Entry::Dus(Arc::clone(&val))); + val + }) } /// Retrieve all database token definitions for a specific database. @@ -1631,15 +1608,6 @@ impl Transaction { 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::namespace::lg::new(ns, nl); - 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::namespace::tk::new(ns, nt); @@ -1658,20 +1626,6 @@ impl Transaction { Ok(val.into()) } - /// Retrieve a specific database login definition. - pub async fn get_dl( - &mut self, - ns: &str, - db: &str, - dl: &str, - ) -> Result { - let key = crate::key::database::lg::new(ns, db, dl); - 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, @@ -1868,7 +1822,7 @@ impl Transaction { let key = crate::key::root::ns::new(ns); let val = DefineNamespaceStatement { name: ns.to_owned().into(), - id: None, + ..Default::default() }; self.put(key, &val).await?; Ok(val) @@ -1897,8 +1851,7 @@ impl Transaction { let key = crate::key::namespace::db::new(ns, db); let val = DefineDatabaseStatement { name: db.to_owned().into(), - changefeed: None, - id: None, + ..Default::default() }; self.put(key, &val).await?; Ok(val) @@ -1928,7 +1881,7 @@ impl Transaction { let key = crate::key::database::sc::new(ns, db, sc); let val = DefineScopeStatement { name: sc.to_owned().into(), - ..DefineScopeStatement::default() + ..Default::default() }; self.put(key, &val).await?; Ok(val) @@ -1959,8 +1912,7 @@ impl Transaction { let val = DefineTableStatement { name: tb.to_owned().into(), permissions: Permissions::none(), - changefeed: None, - ..DefineTableStatement::default() + ..Default::default() }; self.put(key, &val).await?; Ok(val) @@ -2057,7 +2009,7 @@ impl Transaction { let key = crate::key::root::ns::new(ns); let val = DefineNamespaceStatement { name: ns.to_owned().into(), - id: None, + ..Default::default() }; self.put(key, &val).await?; Ok(Arc::new(val)) @@ -2086,8 +2038,7 @@ impl Transaction { let key = crate::key::namespace::db::new(ns, db); let val = DefineDatabaseStatement { name: db.to_owned().into(), - changefeed: None, - id: None, + ..Default::default() }; self.put(key, &val).await?; Ok(Arc::new(val)) @@ -2118,8 +2069,7 @@ impl Transaction { let val = DefineTableStatement { name: tb.to_owned().into(), permissions: Permissions::none(), - changefeed: None, - ..DefineTableStatement::default() + ..Default::default() }; self.put(key, &val).await?; Ok(Arc::new(val)) @@ -2183,16 +2133,16 @@ impl Transaction { chn.send(bytes!("")).await?; } } - // Output LOGINS + // Output USERS { - let dls = self.all_dl(ns, db).await?; - if !dls.is_empty() { + let dus = self.all_db_users(ns, db).await?; + if !dus.is_empty() { chn.send(bytes!("-- ------------------------------")).await?; - chn.send(bytes!("-- LOGINS")).await?; + chn.send(bytes!("-- USERS")).await?; chn.send(bytes!("-- ------------------------------")).await?; chn.send(bytes!("")).await?; - for dl in dls.iter() { - chn.send(bytes!(format!("{dl};"))).await?; + for us in dus.iter() { + chn.send(bytes!(format!("{us};"))).await?; } chn.send(bytes!("")).await?; } @@ -2385,6 +2335,13 @@ impl Transaction { Ok(()) } + // change will record the change in the changefeed if enabled. + // To actually persist the record changes into the underlying kvs, + // you must call the `complete_changes` function and then commit the transaction. + pub(crate) fn clear_cache(&mut self) { + self.cache.clear() + } + // change will record the change in the changefeed if enabled. // To actually persist the record changes into the underlying kvs, // you must call the `complete_changes` function and then commit the transaction. @@ -2800,6 +2757,9 @@ mod tests { let key2 = crate::key::namespace::us::new("ns", "user2"); let _ = txn.set(key1, data.to_owned()).await.unwrap(); let _ = txn.set(key2, data.to_owned()).await.unwrap(); + + txn.cache.clear(); + let res = txn.all_ns_users("ns").await.unwrap(); assert_eq!(res.len(), 2); @@ -2808,7 +2768,7 @@ mod tests { } #[tokio::test] - async fn test_db_users() { + async fn test_all_db_users() { let ds = Datastore::new("memory").await.unwrap(); let mut txn = ds.transaction(true, false).await.unwrap(); @@ -2827,6 +2787,9 @@ mod tests { let key2 = crate::key::database::us::new("ns", "db", "user2"); let _ = txn.set(key1, data.to_owned()).await.unwrap(); let _ = txn.set(key2, data.to_owned()).await.unwrap(); + + txn.cache.clear(); + let res = txn.all_db_users("ns", "db").await.unwrap(); assert_eq!(res.len(), 2); diff --git a/lib/src/sql/filter.rs b/lib/src/sql/filter.rs index 3c3cad2c..321fb521 100644 --- a/lib/src/sql/filter.rs +++ b/lib/src/sql/filter.rs @@ -1,4 +1,3 @@ -use crate::sql::comment::shouldbespace; use crate::sql::common::{closeparentheses, commas, openparentheses}; use crate::sql::error::IResult; use crate::sql::language::{language, Language}; @@ -83,7 +82,5 @@ fn filter(i: &str) -> IResult<&str, Filter> { } pub(super) fn filters(i: &str) -> IResult<&str, Vec> { - let (i, _) = tag_no_case("FILTERS")(i)?; - let (i, _) = shouldbespace(i)?; separated_list1(commas, filter)(i) } diff --git a/lib/src/sql/index.rs b/lib/src/sql/index.rs index 78007b0d..4ea625ac 100644 --- a/lib/src/sql/index.rs +++ b/lib/src/sql/index.rs @@ -11,10 +11,11 @@ use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] #[revisioned(revision = 1)] pub enum Index { /// (Basic) non unique + #[default] Idx, /// Unique index Uniq, @@ -27,12 +28,6 @@ pub enum Index { }, } -impl Default for Index { - fn default() -> Self { - Self::Idx - } -} - impl fmt::Display for Index { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -55,12 +50,7 @@ impl fmt::Display for Index { } pub fn index(i: &str) -> IResult<&str, Index> { - alt((unique, search, non_unique))(i) -} - -pub fn non_unique(i: &str) -> IResult<&str, Index> { - let (i, _) = tag("")(i)?; - Ok((i, Index::Idx)) + alt((unique, search))(i) } pub fn unique(i: &str) -> IResult<&str, Index> { diff --git a/lib/src/sql/mod.rs b/lib/src/sql/mod.rs index 754b08c1..4f416b0f 100644 --- a/lib/src/sql/mod.rs +++ b/lib/src/sql/mod.rs @@ -107,6 +107,7 @@ pub use self::id::Id; pub use self::ident::Ident; pub use self::idiom::Idiom; pub use self::idiom::Idioms; +pub use self::index::Index; pub use self::kind::Kind; pub use self::limit::Limit; pub use self::model::Model; @@ -124,6 +125,7 @@ pub use self::permission::Permissions; pub use self::query::Query; pub use self::range::Range; pub use self::regex::Regex; +pub use self::scoring::Scoring; pub use self::script::Script; pub use self::split::Split; pub use self::split::Splits; diff --git a/lib/src/sql/statements/define.rs b/lib/src/sql/statements/define.rs deleted file mode 100644 index 6dcb945e..00000000 --- a/lib/src/sql/statements/define.rs +++ /dev/null @@ -1,1674 +0,0 @@ -use crate::ctx::Context; -use crate::dbs::Options; -use crate::dbs::Transaction; -use crate::doc::CursorDoc; -use crate::err::Error; -use crate::iam::Action; -use crate::iam::ResourceKind; -use crate::iam::Role; -use crate::sql::algorithm::{algorithm, Algorithm}; -use crate::sql::base::{base, base_or_scope, Base}; -use crate::sql::block::{block, Block}; -use crate::sql::changefeed::{changefeed, ChangeFeed}; -use crate::sql::comment::{mightbespace, shouldbespace}; -use crate::sql::common::commas; -use crate::sql::duration::{duration, Duration}; -use crate::sql::error::Error as SqlError; -use crate::sql::error::IResult; -use crate::sql::escape::quote_str; -use crate::sql::filter::{filters, Filter}; -use crate::sql::fmt::is_pretty; -use crate::sql::fmt::pretty_indent; -use crate::sql::fmt::Fmt; -use crate::sql::ident::{ident, Ident}; -use crate::sql::idiom; -use crate::sql::idiom::{Idiom, Idioms}; -use crate::sql::index::Index; -use crate::sql::kind::{kind, Kind}; -use crate::sql::permission::{permissions, Permissions}; -use crate::sql::statements::UpdateStatement; -use crate::sql::strand::strand_raw; -use crate::sql::tokenizer::{tokenizers, Tokenizer}; -use crate::sql::value::{value, values, Value, Values}; -use crate::sql::view::{view, View}; -use crate::sql::{ident, index}; -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 nom::Err::Failure; -use rand::distributions::Alphanumeric; -use rand::rngs::OsRng; -use rand::Rng; -use revision::revisioned; -use serde::{Deserialize, Serialize}; -use std::fmt::{self, Display, Write}; - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub enum DefineStatement { - Namespace(DefineNamespaceStatement), - Database(DefineDatabaseStatement), - Function(DefineFunctionStatement), - Analyzer(DefineAnalyzerStatement), - Login(DefineLoginStatement), - Token(DefineTokenStatement), - Scope(DefineScopeStatement), - Param(DefineParamStatement), - Table(DefineTableStatement), - Event(DefineEventStatement), - Field(DefineFieldStatement), - Index(DefineIndexStatement), - User(DefineUserStatement), -} - -impl DefineStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - doc: Option<&CursorDoc<'_>>, - ) -> Result { - 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(_) => Err(Error::Deprecated( - "DEFINE LOGIN is no longer supported. Use DEFINE USER instead".to_string(), - )), - 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, - Self::Index(ref v) => v.compute(ctx, opt, txn, doc).await, - Self::Analyzer(ref v) => v.compute(ctx, opt, txn, doc).await, - Self::User(ref v) => v.compute(ctx, opt, txn, doc).await, - } - } -} - -impl Display for DefineStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - 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::User(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), - Self::Index(v) => Display::fmt(v, f), - Self::Analyzer(v) => Display::fmt(v, f), - } - } -} - -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(user, DefineStatement::User), - map(token, DefineStatement::Token), - map(scope, DefineStatement::Scope), - map(param, DefineStatement::Param), - map(table, DefineStatement::Table), - map(event, DefineStatement::Event), - map(field, DefineStatement::Field), - map(index, DefineStatement::Index), - map(analyzer, DefineStatement::Analyzer), - ))(i) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineNamespaceStatement { - pub name: Ident, - pub id: Option, -} - -impl DefineNamespaceStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Namespace, &Base::Root)?; - // Process the statement - let key = crate::key::root::ns::new(&self.name); - // Claim transaction - let mut run = txn.lock().await; - // Set the id - if self.id.is_none() { - let mut ns = self.clone(); - ns.id = Some(run.get_next_ns_id().await?); - run.set(key, ns).await?; - } else { - run.set(key, self).await?; - } - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineNamespaceStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE NAMESPACE {}", self.name) - } -} - -fn namespace(i: &str) -> IResult<&str, DefineNamespaceStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - Ok(( - i, - DefineNamespaceStatement { - name, - id: None, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineDatabaseStatement { - pub name: Ident, - pub changefeed: Option, - pub id: Option, -} - -impl DefineDatabaseStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Database, &Base::Ns)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::namespace::db::new(opt.ns(), &self.name); - let ns = run.add_ns(opt.ns(), opt.strict).await?; - // Set the id - if self.id.is_none() && ns.id.is_some() { - let mut db = self.clone(); - db.id = Some(run.get_next_db_id(ns.id.unwrap()).await?); - // Store the db - run.set(key, db).await?; - } else { - // Store the db - run.set(key, self).await?; - } - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineDatabaseStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE DATABASE {}", self.name)?; - if let Some(ref cf) = self.changefeed { - write!(f, " CHANGEFEED {}", crate::sql::duration::Duration(cf.expiry))?; - } - Ok(()) - } -} - -fn database(i: &str) -> IResult<&str, DefineDatabaseStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, opts) = many0(database_opts)(i)?; - Ok(( - i, - DefineDatabaseStatement { - name, - changefeed: opts - .iter() - .map(|x| match x { - DefineDatabaseOption::ChangeFeed(ref v) => v.to_owned(), - }) - .next(), - id: None, - }, - )) -} - -fn database_changefeed(i: &str) -> IResult<&str, DefineDatabaseOption> { - let (i, _) = shouldbespace(i)?; - let (i, v) = changefeed(i)?; - Ok((i, DefineDatabaseOption::ChangeFeed(v))) -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] -pub enum DefineDatabaseOption { - ChangeFeed(ChangeFeed), -} - -fn database_opts(i: &str) -> IResult<&str, DefineDatabaseOption> { - database_changefeed(i) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineFunctionStatement { - pub name: Ident, - pub args: Vec<(Ident, Kind)>, - pub block: Block, -} - -impl DefineFunctionStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Function, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::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::multi(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)] -#[revisioned(revision = 1)] -pub struct DefineAnalyzerStatement { - pub name: Ident, - pub tokenizers: Option>, - pub filters: Option>, -} - -impl DefineAnalyzerStatement { - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Analyzer, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::az::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?; - // Release the transaction - drop(run); // Do we really need this? - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineAnalyzerStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE ANALYZER {}", self.name)?; - if let Some(tokenizers) = &self.tokenizers { - let tokens: Vec = tokenizers.iter().map(|f| f.to_string()).collect(); - write!(f, " TOKENIZERS {}", tokens.join(","))?; - } - if let Some(filters) = &self.filters { - let tokens: Vec = filters.iter().map(|f| f.to_string()).collect(); - write!(f, " FILTERS {}", tokens.join(","))?; - } - Ok(()) - } -} - -pub(crate) fn analyzer(i: &str) -> IResult<&str, DefineAnalyzerStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ANALYZER")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, tokenizers) = opt(tokenizers)(i)?; - let (i, _) = mightbespace(i)?; - let (i, filters) = opt(filters)(i)?; - Ok(( - i, - DefineAnalyzerStatement { - name, - tokenizers, - filters, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineLoginStatement { - pub name: Ident, - pub base: Base, - pub hash: String, - pub code: String, -} - -impl Display for DefineLoginStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE LOGIN {} ON {} PASSHASH {}", self.name, self.base, quote_str(&self.hash)) - } -} - -fn login(i: &str) -> IResult<&str, DefineLoginStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("LOGIN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; - let (i, opts) = login_opts(i)?; - Ok(( - i, - DefineLoginStatement { - name, - base, - code: rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(128) - .map(char::from) - .collect::(), - hash: match opts { - DefineLoginOption::Passhash(v) => v, - DefineLoginOption::Password(v) => Argon2::default() - .hash_password(v.as_ref(), &SaltString::generate(&mut OsRng)) - .unwrap() - .to_string(), - }, - }, - )) -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum DefineLoginOption { - Password(String), - Passhash(String), -} - -fn login_opts(i: &str) -> IResult<&str, DefineLoginOption> { - alt((login_pass, login_hash))(i) -} - -fn login_pass(i: &str) -> IResult<&str, DefineLoginOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("PASSWORD")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; - Ok((i, DefineLoginOption::Password(v))) -} - -fn login_hash(i: &str) -> IResult<&str, DefineLoginOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("PASSHASH")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; - Ok((i, DefineLoginOption::Passhash(v))) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineUserStatement { - pub name: Ident, - pub base: Base, - pub hash: String, - pub code: String, - pub roles: Vec, -} - -impl From<(Base, &str, &str)> for DefineUserStatement { - fn from((base, user, pass): (Base, &str, &str)) -> Self { - DefineUserStatement { - base, - name: user.into(), - hash: Argon2::default() - .hash_password(pass.as_ref(), &SaltString::generate(&mut OsRng)) - .unwrap() - .to_string(), - code: rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(128) - .map(char::from) - .collect::(), - roles: vec!["owner".into()], - } - } -} - -impl DefineUserStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; - - match self.base { - Base::Root => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::root::us::new(&self.name); - run.set(key, self).await?; - // Ok all good - Ok(Value::None) - } - Base::Ns => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::namespace::us::new(opt.ns(), &self.name); - run.add_ns(opt.ns(), opt.strict).await?; - run.set(key, self).await?; - // Ok all good - Ok(Value::None) - } - Base::Db => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::us::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) - } - // Other levels are not supported - _ => Err(Error::InvalidLevel(self.base.to_string())), - } - } -} - -impl Display for DefineUserStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "DEFINE USER {} ON {} PASSHASH {} ROLES {}", - self.name, - self.base, - quote_str(&self.hash), - Fmt::comma_separated( - &self.roles.iter().map(|r| r.to_string().to_uppercase()).collect::>() - ) - ) - } -} - -fn user(i: &str) -> IResult<&str, DefineUserStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("USER")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; - let (i, opts) = user_opts(i)?; - - let mut res = DefineUserStatement { - name, - base, - roles: vec!["Viewer".into()], // New users get the viewer role by default - code: rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(128) - .map(char::from) - .collect::(), - ..Default::default() - }; - - for opt in opts { - match opt { - DefineUserOption::Password(v) => { - res.hash = Argon2::default() - .hash_password(v.as_ref(), &SaltString::generate(&mut OsRng)) - .unwrap() - .to_string() - } - DefineUserOption::Passhash(v) => { - res.hash = v; - } - DefineUserOption::Roles(v) => { - res.roles = v; - } - } - } - - Ok((i, res)) -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum DefineUserOption { - Password(String), - Passhash(String), - Roles(Vec), -} - -fn user_opts(i: &str) -> IResult<&str, Vec> { - many0(alt((alt((user_pass, user_hash)), user_roles)))(i) -} - -fn user_pass(i: &str) -> IResult<&str, DefineUserOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("PASSWORD")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; - Ok((i, DefineUserOption::Password(v))) -} - -fn user_hash(i: &str) -> IResult<&str, DefineUserOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("PASSHASH")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = strand_raw(i)?; - Ok((i, DefineUserOption::Passhash(v))) -} - -fn user_roles(i: &str) -> IResult<&str, DefineUserOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ROLES")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, roles) = separated_list0(commas, |i| { - let (i, v) = ident(i)?; - // Verify the role is valid - Role::try_from(v.as_str()).map_err(|_| Failure(SqlError::Role(i, v.to_string())))?; - - Ok((i, v)) - })(i)?; - - Ok((i, DefineUserOption::Roles(roles))) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineTokenStatement { - pub name: Ident, - pub base: Base, - pub kind: Algorithm, - pub code: String, -} - -impl DefineTokenStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; - - match &self.base { - Base::Ns => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::namespace::tk::new(opt.ns(), &self.name); - run.add_ns(opt.ns(), opt.strict).await?; - run.set(key, self).await?; - // Ok all good - Ok(Value::None) - } - Base::Db => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::tk::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) - } - Base::Sc(sc) => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &self.name); - run.add_ns(opt.ns(), opt.strict).await?; - run.add_db(opt.ns(), opt.db(), opt.strict).await?; - run.add_sc(opt.ns(), opt.db(), sc, opt.strict).await?; - run.set(key, self).await?; - // Ok all good - Ok(Value::None) - } - // Other levels are not supported - _ => Err(Error::InvalidLevel(self.base.to_string())), - } - } -} - -impl Display for DefineTokenStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "DEFINE TOKEN {} ON {} TYPE {} VALUE {}", - self.name, - self.base, - self.kind, - quote_str(&self.code) - ) - } -} - -fn token(i: &str) -> IResult<&str, DefineTokenStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("TOKEN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base_or_scope(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("TYPE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, kind) = algorithm(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("VALUE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, code) = strand_raw(i)?; - Ok(( - i, - DefineTokenStatement { - name, - base, - kind, - code, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineScopeStatement { - pub name: Ident, - pub code: String, - pub session: Option, - pub signup: Option, - pub signin: Option, -} - -impl DefineScopeStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Scope, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::sc::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 Display for DefineScopeStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE SCOPE {}", self.name)?; - if let Some(ref v) = self.session { - write!(f, " SESSION {v}")? - } - if let Some(ref v) = self.signup { - write!(f, " SIGNUP {v}")? - } - if let Some(ref v) = self.signin { - write!(f, " SIGNIN {v}")? - } - Ok(()) - } -} - -fn scope(i: &str) -> IResult<&str, DefineScopeStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("SCOPE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, opts) = many0(scope_opts)(i)?; - Ok(( - i, - DefineScopeStatement { - name, - code: rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(128) - .map(char::from) - .collect::(), - session: opts.iter().find_map(|x| match x { - DefineScopeOption::Session(ref v) => Some(v.to_owned()), - _ => None, - }), - signup: opts.iter().find_map(|x| match x { - DefineScopeOption::Signup(ref v) => Some(v.to_owned()), - _ => None, - }), - signin: opts.iter().find_map(|x| match x { - DefineScopeOption::Signin(ref v) => Some(v.to_owned()), - _ => None, - }), - }, - )) -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] -pub enum DefineScopeOption { - Session(Duration), - Signup(Value), - Signin(Value), -} - -fn scope_opts(i: &str) -> IResult<&str, DefineScopeOption> { - alt((scope_session, scope_signup, scope_signin))(i) -} - -fn scope_session(i: &str) -> IResult<&str, DefineScopeOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("SESSION")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = duration(i)?; - Ok((i, DefineScopeOption::Session(v))) -} - -fn scope_signup(i: &str) -> IResult<&str, DefineScopeOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("SIGNUP")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; - Ok((i, DefineScopeOption::Signup(v))) -} - -fn scope_signin(i: &str) -> IResult<&str, DefineScopeOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("SIGNIN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; - Ok((i, DefineScopeOption::Signin(v))) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineParamStatement { - pub name: Ident, - pub value: Value, -} - -impl DefineParamStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Parameter, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::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 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)] -#[revisioned(revision = 1)] -pub struct DefineTableStatement { - pub name: Ident, - pub drop: bool, - pub full: bool, - pub id: Option, - pub view: Option, - pub permissions: Permissions, - pub changefeed: Option, -} - -impl DefineTableStatement { - pub(crate) async fn compute( - &self, - ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name); - let ns = run.add_ns(opt.ns(), opt.strict).await?; - let db = run.add_db(opt.ns(), opt.db(), opt.strict).await?; - if self.id.is_none() && ns.id.is_some() && db.id.is_some() { - let mut dt = self.clone(); - dt.id = Some(run.get_next_tb_id(ns.id.unwrap(), db.id.unwrap()).await?); - run.set(key, dt).await?; - } else { - run.set(key, self).await?; - } - // Check if table is a view - if let Some(view) = &self.view { - // Remove the table data - let key = crate::key::table::all::new(opt.ns(), opt.db(), &self.name); - run.delp(key, u32::MAX).await?; - // Process each foreign table - for v in view.what.0.iter() { - // Save the view config - let key = crate::key::table::ft::new(opt.ns(), opt.db(), v, &self.name); - run.set(key, self).await?; - // Clear the cache - let key = crate::key::table::ft::prefix(opt.ns(), opt.db(), v); - run.clr(key).await?; - } - // Release the transaction - drop(run); - // Force queries to run - let opt = &opt.new_with_force(true); - // Don't process field queries - let opt = &opt.new_with_fields(false); - // Don't process event queries - let opt = &opt.new_with_events(false); - // Don't process index queries - let opt = &opt.new_with_indexes(false); - // Process each foreign table - for v in view.what.0.iter() { - // Process the view data - let stm = UpdateStatement { - what: Values(vec![Value::Table(v.clone())]), - ..UpdateStatement::default() - }; - stm.compute(ctx, opt, txn, doc).await?; - } - } - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineTableStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE TABLE {}", self.name)?; - if self.drop { - f.write_str(" DROP")?; - } - f.write_str(if self.full { - " SCHEMAFULL" - } else { - " SCHEMALESS" - })?; - if let Some(ref v) = self.view { - write!(f, " {v}")? - } - if !self.permissions.is_full() { - let _indent = if is_pretty() { - Some(pretty_indent()) - } else { - f.write_char(' ')?; - None - }; - write!(f, "{}", self.permissions)?; - } - if let Some(ref cf) = self.changefeed { - write!(f, " CHANGEFEED {}", crate::sql::duration::Duration(cf.expiry))?; - } - Ok(()) - } -} - -fn table(i: &str) -> IResult<&str, DefineTableStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("TABLE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, opts) = many0(table_opts)(i)?; - Ok(( - i, - DefineTableStatement { - name, - drop: opts - .iter() - .find_map(|x| match x { - DefineTableOption::Drop => Some(true), - _ => None, - }) - .unwrap_or_default(), - full: opts - .iter() - .find_map(|x| match x { - DefineTableOption::Schemafull => Some(true), - DefineTableOption::Schemaless => Some(false), - _ => None, - }) - .unwrap_or_default(), - id: None, - view: opts.iter().find_map(|x| match x { - DefineTableOption::View(ref v) => Some(v.to_owned()), - _ => None, - }), - permissions: opts - .iter() - .find_map(|x| match x { - DefineTableOption::Permissions(ref v) => Some(v.to_owned()), - _ => None, - }) - .unwrap_or_default(), - changefeed: opts.iter().find_map(|x| match x { - DefineTableOption::ChangeFeed(ref v) => Some(v.to_owned()), - _ => None, - }), - }, - )) -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] -pub enum DefineTableOption { - Drop, - View(View), - Schemaless, - Schemafull, - Permissions(Permissions), - ChangeFeed(ChangeFeed), -} - -fn table_opts(i: &str) -> IResult<&str, DefineTableOption> { - alt(( - table_drop, - table_view, - table_schemaless, - table_schemafull, - table_permissions, - table_changefeed, - ))(i) -} - -fn table_drop(i: &str) -> IResult<&str, DefineTableOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("DROP")(i)?; - Ok((i, DefineTableOption::Drop)) -} - -fn table_changefeed(i: &str) -> IResult<&str, DefineTableOption> { - let (i, _) = shouldbespace(i)?; - let (i, v) = changefeed(i)?; - Ok((i, DefineTableOption::ChangeFeed(v))) -} - -fn table_view(i: &str) -> IResult<&str, DefineTableOption> { - let (i, _) = shouldbespace(i)?; - let (i, v) = view(i)?; - Ok((i, DefineTableOption::View(v))) -} - -fn table_schemaless(i: &str) -> IResult<&str, DefineTableOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("SCHEMALESS")(i)?; - Ok((i, DefineTableOption::Schemaless)) -} - -fn table_schemafull(i: &str) -> IResult<&str, DefineTableOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("SCHEMAFULL"), tag_no_case("SCHEMAFUL")))(i)?; - Ok((i, DefineTableOption::Schemafull)) -} - -fn table_permissions(i: &str) -> IResult<&str, DefineTableOption> { - let (i, _) = shouldbespace(i)?; - let (i, v) = permissions(i)?; - Ok((i, DefineTableOption::Permissions(v))) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineEventStatement { - pub name: Ident, - pub what: Ident, - pub when: Value, - pub then: Values, -} - -impl DefineEventStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Event, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::table::ev::new(opt.ns(), opt.db(), &self.what, &self.name); - run.add_ns(opt.ns(), opt.strict).await?; - run.add_db(opt.ns(), opt.db(), opt.strict).await?; - run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?; - run.set(key, self).await?; - // Clear the cache - let key = crate::key::table::ev::prefix(opt.ns(), opt.db(), &self.what); - run.clr(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineEventStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "DEFINE EVENT {} ON {} WHEN {} THEN {}", - self.name, self.what, self.when, self.then - ) - } -} - -fn event(i: &str) -> IResult<&str, DefineEventStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("EVENT")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("WHEN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, when) = value(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("THEN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, then) = values(i)?; - Ok(( - i, - DefineEventStatement { - name, - what, - when, - then, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineFieldStatement { - pub name: Idiom, - pub what: Ident, - pub flex: bool, - pub kind: Option, - pub value: Option, - pub assert: Option, - pub default: Option, - pub permissions: Permissions, -} - -impl DefineFieldStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Field, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let fd = self.name.to_string(); - let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd); - run.add_ns(opt.ns(), opt.strict).await?; - run.add_db(opt.ns(), opt.db(), opt.strict).await?; - run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?; - run.set(key, self).await?; - // Clear the cache - let key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.what); - run.clr(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineFieldStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE FIELD {} ON {}", self.name, self.what)?; - if self.flex { - write!(f, " FLEXIBLE")? - } - if let Some(ref v) = self.kind { - write!(f, " TYPE {v}")? - } - if let Some(ref v) = self.value { - write!(f, " VALUE {v}")? - } - if let Some(ref v) = self.assert { - write!(f, " ASSERT {v}")? - } - if !self.permissions.is_full() { - write!(f, " {}", self.permissions)?; - } - Ok(()) - } -} - -fn field(i: &str) -> IResult<&str, DefineFieldStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("FIELD")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = idiom::local(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - let (i, opts) = many0(field_opts)(i)?; - Ok(( - i, - DefineFieldStatement { - name, - what, - flex: opts - .iter() - .find_map(|x| match x { - DefineFieldOption::Flex => Some(true), - _ => None, - }) - .unwrap_or_default(), - kind: opts.iter().find_map(|x| match x { - DefineFieldOption::Kind(ref v) => Some(v.to_owned()), - _ => None, - }), - value: opts.iter().find_map(|x| match x { - DefineFieldOption::Value(ref v) => Some(v.to_owned()), - _ => None, - }), - assert: opts.iter().find_map(|x| match x { - DefineFieldOption::Assert(ref v) => Some(v.to_owned()), - _ => None, - }), - default: opts.iter().find_map(|x| match x { - DefineFieldOption::Default(ref v) => Some(v.to_owned()), - _ => None, - }), - permissions: opts - .iter() - .find_map(|x| match x { - DefineFieldOption::Permissions(ref v) => Some(v.to_owned()), - _ => None, - }) - .unwrap_or_default(), - }, - )) -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] -pub enum DefineFieldOption { - Flex, - Kind(Kind), - Value(Value), - Assert(Value), - Default(Value), - Permissions(Permissions), -} - -fn field_opts(i: &str) -> IResult<&str, DefineFieldOption> { - alt((field_flex, field_kind, field_value, field_assert, field_default, field_permissions))(i) -} - -fn field_flex(i: &str) -> IResult<&str, DefineFieldOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("FLEXIBLE"), tag_no_case("FLEXI"), tag_no_case("FLEX")))(i)?; - Ok((i, DefineFieldOption::Flex)) -} - -fn field_kind(i: &str) -> IResult<&str, DefineFieldOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("TYPE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = kind(i)?; - Ok((i, DefineFieldOption::Kind(v))) -} - -fn field_value(i: &str) -> IResult<&str, DefineFieldOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("VALUE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; - Ok((i, DefineFieldOption::Value(v))) -} - -fn field_assert(i: &str) -> IResult<&str, DefineFieldOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ASSERT")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; - Ok((i, DefineFieldOption::Assert(v))) -} - -fn field_default(i: &str) -> IResult<&str, DefineFieldOption> { - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("DEFAULT")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, v) = value(i)?; - Ok((i, DefineFieldOption::Default(v))) -} - -fn field_permissions(i: &str) -> IResult<&str, DefineFieldOption> { - let (i, _) = shouldbespace(i)?; - let (i, v) = permissions(i)?; - Ok((i, DefineFieldOption::Permissions(v))) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct DefineIndexStatement { - pub name: Ident, - pub what: Ident, - pub cols: Idioms, - pub index: Index, -} - -impl DefineIndexStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - doc: Option<&CursorDoc<'_>>, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Index, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::table::ix::new(opt.ns(), opt.db(), &self.what, &self.name); - run.add_ns(opt.ns(), opt.strict).await?; - run.add_db(opt.ns(), opt.db(), opt.strict).await?; - run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?; - run.set(key, self).await?; - // Remove the index data - let key = crate::key::index::all::new(opt.ns(), opt.db(), &self.what, &self.name); - run.delp(key, u32::MAX).await?; - // Clear the cache - let key = crate::key::table::ix::prefix(opt.ns(), opt.db(), &self.what); - run.clr(key).await?; - // Release the transaction - drop(run); - // Force queries to run - let opt = &opt.new_with_force(true); - // Don't process field queries - let opt = &opt.new_with_fields(false); - // Don't process event queries - let opt = &opt.new_with_events(false); - // Don't process table queries - let opt = &opt.new_with_tables(false); - // Update the index data - let stm = UpdateStatement { - what: Values(vec![Value::Table(self.what.clone().into())]), - ..UpdateStatement::default() - }; - stm.compute(ctx, opt, txn, doc).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for DefineIndexStatement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE INDEX {} ON {} FIELDS {}", self.name, self.what, self.cols)?; - if Index::Idx != self.index { - write!(f, " {}", self.index)?; - } - Ok(()) - } -} - -fn index(i: &str) -> IResult<&str, DefineIndexStatement> { - let (i, _) = tag_no_case("DEFINE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("INDEX")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("COLUMNS"), tag_no_case("FIELDS")))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, cols) = idiom::locals(i)?; - let (i, _) = mightbespace(i)?; - let (i, index) = index::index(i)?; - Ok(( - i, - DefineIndexStatement { - name, - what, - cols, - index, - }, - )) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::sql::scoring::Scoring; - use crate::sql::Part; - - #[test] - fn check_define_serialize() { - let stm = DefineStatement::Namespace(DefineNamespaceStatement { - name: Ident::from("test"), - id: None, - }); - let enc: Vec = stm.try_into().unwrap(); - assert_eq!(10, enc.len()); - } - - #[test] - fn check_create_non_unique_index() { - let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col"; - let (_, idx) = index(sql).unwrap(); - assert_eq!( - idx, - DefineIndexStatement { - name: Ident("my_index".to_string()), - what: Ident("my_table".to_string()), - cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), - index: Index::Idx, - } - ); - assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col"); - } - - #[test] - fn check_create_unique_index() { - let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col UNIQUE"; - let (_, idx) = index(sql).unwrap(); - assert_eq!( - idx, - DefineIndexStatement { - name: Ident("my_index".to_string()), - what: Ident("my_table".to_string()), - cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), - index: Index::Uniq, - } - ); - assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col UNIQUE"); - } - - #[test] - fn check_create_search_index_with_highlights() { - let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) ORDER 1000 HIGHLIGHTS"; - let (_, idx) = index(sql).unwrap(); - assert_eq!( - idx, - DefineIndexStatement { - name: Ident("my_index".to_string()), - what: Ident("my_table".to_string()), - cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), - index: Index::Search { - az: Ident("my_analyzer".to_string()), - hl: true, - sc: Scoring::Bm { - k1: 1.2, - b: 0.75, - }, - order: 1000 - }, - } - ); - assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) ORDER 1000 HIGHLIGHTS"); - } - - #[test] - fn check_create_search_index() { - let sql = - "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer VS"; - let (_, idx) = index(sql).unwrap(); - assert_eq!( - idx, - DefineIndexStatement { - name: Ident("my_index".to_string()), - what: Ident("my_table".to_string()), - cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), - index: Index::Search { - az: Ident("my_analyzer".to_string()), - hl: false, - sc: Scoring::Vs, - order: 100 - }, - } - ); - assert_eq!( - idx.to_string(), - "DEFINE INDEX my_index ON my_table FIELDS my_col SEARCH ANALYZER my_analyzer VS ORDER 100" - ); - } - - #[test] - fn define_database_with_changefeed() { - let sql = "DEFINE DATABASE mydatabase CHANGEFEED 1h"; - let res = database(sql); - assert!(res.is_ok()); - let out = res.unwrap().1; - assert_eq!(sql, format!("{}", out)); - - let serialized: Vec = (&out).try_into().unwrap(); - let deserialized = DefineDatabaseStatement::try_from(&serialized).unwrap(); - assert_eq!(out, deserialized); - } - - #[test] - fn define_table_with_changefeed() { - let sql = "DEFINE TABLE mytable SCHEMALESS CHANGEFEED 1h"; - let res = table(sql); - assert!(res.is_ok()); - let out = res.unwrap().1; - assert_eq!(sql, format!("{}", out)); - - let serialized: Vec = (&out).try_into().unwrap(); - let deserialized = DefineTableStatement::try_from(&serialized).unwrap(); - assert_eq!(out, deserialized); - } -} diff --git a/lib/src/sql/statements/define/analyzer.rs b/lib/src/sql/statements/define/analyzer.rs new file mode 100644 index 00000000..1fd16b87 --- /dev/null +++ b/lib/src/sql/statements/define/analyzer.rs @@ -0,0 +1,139 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::filter::{filters, Filter}; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::tokenizer::{tokenizers, Tokenizer}; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineAnalyzerStatement { + pub name: Ident, + pub tokenizers: Option>, + pub filters: Option>, + pub comment: Option, +} + +impl DefineAnalyzerStatement { + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Analyzer, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::az::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?; + // Release the transaction + drop(run); // Do we really need this? + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineAnalyzerStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE ANALYZER {}", self.name)?; + if let Some(v) = &self.tokenizers { + let tokens: Vec = v.iter().map(|f| f.to_string()).collect(); + write!(f, " TOKENIZERS {}", tokens.join(","))?; + } + if let Some(v) = &self.filters { + let tokens: Vec = v.iter().map(|f| f.to_string()).collect(); + write!(f, " FILTERS {}", tokens.join(","))?; + } + if let Some(ref v) = self.comment { + write!(f, " COMMENT {v}")? + } + Ok(()) + } +} + +pub fn analyzer(i: &str) -> IResult<&str, DefineAnalyzerStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ANALYZER")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, opts) = many0(analyzer_opts)(i)?; + // Create the base statement + let mut res = DefineAnalyzerStatement { + name, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineAnalyzerOption::Comment(v) => { + res.comment = Some(v); + } + DefineAnalyzerOption::Filters(v) => { + res.filters = Some(v); + } + DefineAnalyzerOption::Tokenizers(v) => { + res.tokenizers = Some(v); + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineAnalyzerOption { + Comment(Strand), + Filters(Vec), + Tokenizers(Vec), +} + +fn analyzer_opts(i: &str) -> IResult<&str, DefineAnalyzerOption> { + alt((analyzer_comment, analyzer_filters, analyzer_tokenizers))(i) +} + +fn analyzer_comment(i: &str) -> IResult<&str, DefineAnalyzerOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineAnalyzerOption::Comment(v))) +} + +fn analyzer_filters(i: &str) -> IResult<&str, DefineAnalyzerOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("FILTERS")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = filters(i)?; + Ok((i, DefineAnalyzerOption::Filters(v))) +} + +fn analyzer_tokenizers(i: &str) -> IResult<&str, DefineAnalyzerOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TOKENIZERS")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = tokenizers(i)?; + Ok((i, DefineAnalyzerOption::Tokenizers(v))) +} diff --git a/lib/src/sql/statements/define/database.rs b/lib/src/sql/statements/define/database.rs new file mode 100644 index 00000000..672384a2 --- /dev/null +++ b/lib/src/sql/statements/define/database.rs @@ -0,0 +1,145 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::changefeed::{changefeed, ChangeFeed}; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineDatabaseStatement { + pub id: Option, + pub name: Ident, + pub comment: Option, + pub changefeed: Option, +} + +impl DefineDatabaseStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Database, &Base::Ns)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::namespace::db::new(opt.ns(), &self.name); + let ns = run.add_ns(opt.ns(), opt.strict).await?; + // Set the id + if self.id.is_none() && ns.id.is_some() { + let mut db = self.clone(); + db.id = Some(run.get_next_db_id(ns.id.unwrap()).await?); + // Store the db + run.set(key, db).await?; + } else { + // Store the db + run.set(key, self).await?; + } + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineDatabaseStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE DATABASE {}", self.name)?; + if let Some(ref v) = self.comment { + write!(f, " COMMENT {v}")? + } + if let Some(ref v) = self.changefeed { + write!(f, " {v}")?; + } + Ok(()) + } +} + +pub fn database(i: &str) -> IResult<&str, DefineDatabaseStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, opts) = many0(database_opts)(i)?; + // Create the base statement + let mut res = DefineDatabaseStatement { + name, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineDatabaseOption::Comment(v) => { + res.comment = Some(v); + } + DefineDatabaseOption::ChangeFeed(v) => { + res.changefeed = Some(v); + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineDatabaseOption { + Comment(Strand), + ChangeFeed(ChangeFeed), +} + +fn database_opts(i: &str) -> IResult<&str, DefineDatabaseOption> { + alt((database_comment, database_changefeed))(i) +} + +fn database_comment(i: &str) -> IResult<&str, DefineDatabaseOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineDatabaseOption::Comment(v))) +} + +fn database_changefeed(i: &str) -> IResult<&str, DefineDatabaseOption> { + let (i, _) = shouldbespace(i)?; + let (i, v) = changefeed(i)?; + Ok((i, DefineDatabaseOption::ChangeFeed(v))) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn define_database_with_changefeed() { + let sql = "DEFINE DATABASE mydatabase CHANGEFEED 1h"; + let res = database(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!(sql, format!("{}", out)); + + let serialized: Vec = (&out).try_into().unwrap(); + let deserialized = DefineDatabaseStatement::try_from(&serialized).unwrap(); + assert_eq!(out, deserialized); + } +} diff --git a/lib/src/sql/statements/define/event.rs b/lib/src/sql/statements/define/event.rs new file mode 100644 index 00000000..5c12c06d --- /dev/null +++ b/lib/src/sql/statements/define/event.rs @@ -0,0 +1,145 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::{value, values, Value, Values}; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::combinator::opt; +use nom::multi::many0; +use nom::sequence::tuple; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineEventStatement { + pub name: Ident, + pub what: Ident, + pub when: Value, + pub then: Values, + pub comment: Option, +} + +impl DefineEventStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Event, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::table::ev::new(opt.ns(), opt.db(), &self.what, &self.name); + run.add_ns(opt.ns(), opt.strict).await?; + run.add_db(opt.ns(), opt.db(), opt.strict).await?; + run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?; + run.set(key, self).await?; + // Clear the cache + let key = crate::key::table::ev::prefix(opt.ns(), opt.db(), &self.what); + run.clr(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineEventStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "DEFINE EVENT {} ON {} WHEN {} THEN {}", + self.name, self.what, self.when, self.then + ) + } +} + +pub fn event(i: &str) -> IResult<&str, DefineEventStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("EVENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + let (i, opts) = many0(event_opts)(i)?; + // Create the base statement + let mut res = DefineEventStatement { + name, + what, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineEventOption::When(v) => { + res.when = v; + } + DefineEventOption::Then(v) => { + res.then = v; + } + DefineEventOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Check necessary options + if res.then.is_empty() { + // TODO throw error + } + // Return the statement + Ok((i, res)) +} + +enum DefineEventOption { + When(Value), + Then(Values), + Comment(Strand), +} + +fn event_opts(i: &str) -> IResult<&str, DefineEventOption> { + alt((event_when, event_then, event_comment))(i) +} + +fn event_when(i: &str) -> IResult<&str, DefineEventOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("WHEN")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineEventOption::When(v))) +} + +fn event_then(i: &str) -> IResult<&str, DefineEventOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("THEN")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = values(i)?; + Ok((i, DefineEventOption::Then(v))) +} + +fn event_comment(i: &str) -> IResult<&str, DefineEventOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineEventOption::Comment(v))) +} diff --git a/lib/src/sql/statements/define/field.rs b/lib/src/sql/statements/define/field.rs new file mode 100644 index 00000000..9501e948 --- /dev/null +++ b/lib/src/sql/statements/define/field.rs @@ -0,0 +1,214 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::idiom; +use crate::sql::idiom::Idiom; +use crate::sql::kind::{kind, Kind}; +use crate::sql::permission::{permissions, Permissions}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::{value, Value}; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::combinator::opt; +use nom::multi::many0; +use nom::sequence::tuple; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineFieldStatement { + pub name: Idiom, + pub what: Ident, + pub flex: bool, + pub kind: Option, + pub value: Option, + pub assert: Option, + pub default: Option, + pub permissions: Permissions, + pub comment: Option, +} + +impl DefineFieldStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Field, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let fd = self.name.to_string(); + let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd); + run.add_ns(opt.ns(), opt.strict).await?; + run.add_db(opt.ns(), opt.db(), opt.strict).await?; + run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?; + run.set(key, self).await?; + // Clear the cache + let key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.what); + run.clr(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineFieldStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE FIELD {} ON {}", self.name, self.what)?; + if self.flex { + write!(f, " FLEXIBLE")? + } + if let Some(ref v) = self.kind { + write!(f, " TYPE {v}")? + } + if let Some(ref v) = self.value { + write!(f, " VALUE {v}")? + } + if let Some(ref v) = self.assert { + write!(f, " ASSERT {v}")? + } + if !self.permissions.is_full() { + write!(f, " {}", self.permissions)?; + } + Ok(()) + } +} + +pub fn field(i: &str) -> IResult<&str, DefineFieldStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("FIELD")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = idiom::local(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + let (i, opts) = many0(field_opts)(i)?; + // Create the base statement + let mut res = DefineFieldStatement { + name, + what, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineFieldOption::Flex => { + res.flex = true; + } + DefineFieldOption::Kind(v) => { + res.kind = Some(v); + } + DefineFieldOption::Value(v) => { + res.value = Some(v); + } + DefineFieldOption::Assert(v) => { + res.assert = Some(v); + } + DefineFieldOption::Default(v) => { + res.default = Some(v); + } + DefineFieldOption::Comment(v) => { + res.comment = Some(v); + } + DefineFieldOption::Permissions(v) => { + res.permissions = v; + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineFieldOption { + Flex, + Kind(Kind), + Value(Value), + Assert(Value), + Default(Value), + Comment(Strand), + Permissions(Permissions), +} + +fn field_opts(i: &str) -> IResult<&str, DefineFieldOption> { + alt(( + field_flex, + field_kind, + field_value, + field_assert, + field_default, + field_comment, + field_permissions, + ))(i) +} + +fn field_flex(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("FLEXIBLE"), tag_no_case("FLEXI"), tag_no_case("FLEX")))(i)?; + Ok((i, DefineFieldOption::Flex)) +} + +fn field_kind(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TYPE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = kind(i)?; + Ok((i, DefineFieldOption::Kind(v))) +} + +fn field_value(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("VALUE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineFieldOption::Value(v))) +} + +fn field_assert(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ASSERT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineFieldOption::Assert(v))) +} + +fn field_default(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("DEFAULT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineFieldOption::Default(v))) +} + +fn field_comment(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineFieldOption::Comment(v))) +} + +fn field_permissions(i: &str) -> IResult<&str, DefineFieldOption> { + let (i, _) = shouldbespace(i)?; + let (i, v) = permissions(i)?; + Ok((i, DefineFieldOption::Permissions(v))) +} diff --git a/lib/src/sql/statements/define/function.rs b/lib/src/sql/statements/define/function.rs new file mode 100644 index 00000000..da05088b --- /dev/null +++ b/lib/src/sql/statements/define/function.rs @@ -0,0 +1,137 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::block::{block, Block}; +use crate::sql::comment::{mightbespace, shouldbespace}; +use crate::sql::common::commas; +use crate::sql::error::IResult; +use crate::sql::ident; +use crate::sql::ident::{ident, Ident}; +use crate::sql::kind::{kind, Kind}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::char; +use nom::multi::many0; +use nom::multi::separated_list0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineFunctionStatement { + pub name: Ident, + pub args: Vec<(Ident, Kind)>, + pub block: Block, + pub comment: Option, +} + +impl DefineFunctionStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Function, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::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)?; + if let Some(ref v) = self.comment { + write!(f, " COMMENT {v}")? + } + Ok(()) + } +} + +pub 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::multi(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)?; + let (i, opts) = many0(function_opts)(i)?; + // Create the base statement + let mut res = DefineFunctionStatement { + name, + args, + block, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineFunctionOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineFunctionOption { + Comment(Strand), +} + +fn function_opts(i: &str) -> IResult<&str, DefineFunctionOption> { + function_comment(i) +} + +fn function_comment(i: &str) -> IResult<&str, DefineFunctionOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineFunctionOption::Comment(v))) +} diff --git a/lib/src/sql/statements/define/index.rs b/lib/src/sql/statements/define/index.rs new file mode 100644 index 00000000..2da86a14 --- /dev/null +++ b/lib/src/sql/statements/define/index.rs @@ -0,0 +1,264 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::idiom; +use crate::sql::idiom::Idioms; +use crate::sql::index; +use crate::sql::index::Index; +use crate::sql::statements::UpdateStatement; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::{Value, Values}; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::combinator::opt; +use nom::multi::many0; +use nom::sequence::tuple; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineIndexStatement { + pub name: Ident, + pub what: Ident, + pub cols: Idioms, + pub index: Index, + pub comment: Option, +} + +impl DefineIndexStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Index, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::table::ix::new(opt.ns(), opt.db(), &self.what, &self.name); + run.add_ns(opt.ns(), opt.strict).await?; + run.add_db(opt.ns(), opt.db(), opt.strict).await?; + run.add_tb(opt.ns(), opt.db(), &self.what, opt.strict).await?; + run.set(key, self).await?; + // Remove the index data + let key = crate::key::index::all::new(opt.ns(), opt.db(), &self.what, &self.name); + run.delp(key, u32::MAX).await?; + // Clear the cache + let key = crate::key::table::ix::prefix(opt.ns(), opt.db(), &self.what); + run.clr(key).await?; + // Release the transaction + drop(run); + // Force queries to run + let opt = &opt.new_with_force(true); + // Don't process field queries + let opt = &opt.new_with_fields(false); + // Don't process event queries + let opt = &opt.new_with_events(false); + // Don't process table queries + let opt = &opt.new_with_tables(false); + // Update the index data + let stm = UpdateStatement { + what: Values(vec![Value::Table(self.what.clone().into())]), + ..UpdateStatement::default() + }; + stm.compute(ctx, opt, txn, doc).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineIndexStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE INDEX {} ON {} FIELDS {}", self.name, self.what, self.cols)?; + if Index::Idx != self.index { + write!(f, " {}", self.index)?; + } + Ok(()) + } +} + +pub fn index(i: &str) -> IResult<&str, DefineIndexStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("INDEX")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + let (i, opts) = many0(index_opts)(i)?; + // Create the base statement + let mut res = DefineIndexStatement { + name, + what, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineIndexOption::Index(v) => { + res.index = v; + } + DefineIndexOption::Columns(v) => { + res.cols = v; + } + DefineIndexOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Check necessary options + if res.cols.is_empty() { + // TODO throw error + } + // Return the statement + Ok((i, res)) +} + +enum DefineIndexOption { + Index(Index), + Columns(Idioms), + Comment(Strand), +} + +fn index_opts(i: &str) -> IResult<&str, DefineIndexOption> { + alt((index_kind, index_columns, index_comment))(i) +} + +fn index_kind(i: &str) -> IResult<&str, DefineIndexOption> { + let (i, _) = shouldbespace(i)?; + let (i, v) = index::index(i)?; + Ok((i, DefineIndexOption::Index(v))) +} + +fn index_columns(i: &str) -> IResult<&str, DefineIndexOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("COLUMNS"), tag_no_case("FIELDS")))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = idiom::locals(i)?; + Ok((i, DefineIndexOption::Columns(v))) +} + +fn index_comment(i: &str) -> IResult<&str, DefineIndexOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineIndexOption::Comment(v))) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::Ident; + use crate::sql::Idiom; + use crate::sql::Idioms; + use crate::sql::Index; + use crate::sql::Part; + use crate::sql::Scoring; + + #[test] + fn check_create_non_unique_index() { + let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col"; + let (_, idx) = index(sql).unwrap(); + assert_eq!( + idx, + DefineIndexStatement { + name: Ident("my_index".to_string()), + what: Ident("my_table".to_string()), + cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), + index: Index::Idx, + comment: None, + } + ); + assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col"); + } + + #[test] + fn check_create_unique_index() { + let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col UNIQUE"; + let (_, idx) = index(sql).unwrap(); + assert_eq!( + idx, + DefineIndexStatement { + name: Ident("my_index".to_string()), + what: Ident("my_table".to_string()), + cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), + index: Index::Uniq, + comment: None, + } + ); + assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col UNIQUE"); + } + + #[test] + fn check_create_search_index_with_highlights() { + let sql = "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) ORDER 1000 HIGHLIGHTS"; + let (_, idx) = index(sql).unwrap(); + assert_eq!( + idx, + DefineIndexStatement { + name: Ident("my_index".to_string()), + what: Ident("my_table".to_string()), + cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), + index: Index::Search { + az: Ident("my_analyzer".to_string()), + hl: true, + sc: Scoring::Bm { + k1: 1.2, + b: 0.75, + }, + order: 1000 + }, + comment: None, + } + ); + assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) ORDER 1000 HIGHLIGHTS"); + } + + #[test] + fn check_create_search_index() { + let sql = + "DEFINE INDEX my_index ON TABLE my_table COLUMNS my_col SEARCH ANALYZER my_analyzer VS"; + let (_, idx) = index(sql).unwrap(); + assert_eq!( + idx, + DefineIndexStatement { + name: Ident("my_index".to_string()), + what: Ident("my_table".to_string()), + cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), + index: Index::Search { + az: Ident("my_analyzer".to_string()), + hl: false, + sc: Scoring::Vs, + order: 100 + }, + comment: None, + } + ); + assert_eq!( + idx.to_string(), + "DEFINE INDEX my_index ON my_table FIELDS my_col SEARCH ANALYZER my_analyzer VS ORDER 100" + ); + } +} diff --git a/lib/src/sql/statements/define/mod.rs b/lib/src/sql/statements/define/mod.rs new file mode 100644 index 00000000..a46804fe --- /dev/null +++ b/lib/src/sql/statements/define/mod.rs @@ -0,0 +1,135 @@ +mod analyzer; +mod database; +mod event; +mod field; +mod function; +mod index; +mod namespace; +mod param; +mod scope; +mod table; +mod token; +mod user; + +pub use analyzer::{analyzer, DefineAnalyzerStatement}; +pub use database::{database, DefineDatabaseStatement}; +pub use event::{event, DefineEventStatement}; +pub use field::{field, DefineFieldStatement}; +pub use function::{function, DefineFunctionStatement}; +pub use index::{index, DefineIndexStatement}; +pub use namespace::{namespace, DefineNamespaceStatement}; +pub use param::{param, DefineParamStatement}; +pub use scope::{scope, DefineScopeStatement}; +pub use table::{table, DefineTableStatement}; +pub use token::{token, DefineTokenStatement}; +pub use user::{user, DefineUserStatement}; + +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::sql::error::IResult; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::combinator::map; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub enum DefineStatement { + Namespace(DefineNamespaceStatement), + Database(DefineDatabaseStatement), + Function(DefineFunctionStatement), + Analyzer(DefineAnalyzerStatement), + Token(DefineTokenStatement), + Scope(DefineScopeStatement), + Param(DefineParamStatement), + Table(DefineTableStatement), + Event(DefineEventStatement), + Field(DefineFieldStatement), + Index(DefineIndexStatement), + User(DefineUserStatement), +} + +impl DefineStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + doc: Option<&CursorDoc<'_>>, + ) -> Result { + 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::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, + Self::Index(ref v) => v.compute(ctx, opt, txn, doc).await, + Self::Analyzer(ref v) => v.compute(ctx, opt, txn, doc).await, + Self::User(ref v) => v.compute(ctx, opt, txn, doc).await, + } + } +} + +impl Display for DefineStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Namespace(v) => Display::fmt(v, f), + Self::Database(v) => Display::fmt(v, f), + Self::Function(v) => Display::fmt(v, f), + Self::User(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), + Self::Index(v) => Display::fmt(v, f), + Self::Analyzer(v) => Display::fmt(v, f), + } + } +} + +pub fn define(i: &str) -> IResult<&str, DefineStatement> { + alt(( + map(namespace, DefineStatement::Namespace), + map(database, DefineStatement::Database), + map(function, DefineStatement::Function), + map(user, DefineStatement::User), + map(token, DefineStatement::Token), + map(scope, DefineStatement::Scope), + map(param, DefineStatement::Param), + map(table, DefineStatement::Table), + map(event, DefineStatement::Event), + map(field, DefineStatement::Field), + map(index, DefineStatement::Index), + map(analyzer, DefineStatement::Analyzer), + ))(i) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::Ident; + + #[test] + fn check_define_serialize() { + let stm = DefineStatement::Namespace(DefineNamespaceStatement { + name: Ident::from("test"), + ..Default::default() + }); + let enc: Vec = stm.try_into().unwrap(); + assert_eq!(11, enc.len()); + } +} diff --git a/lib/src/sql/statements/define/namespace.rs b/lib/src/sql/statements/define/namespace.rs new file mode 100644 index 00000000..0b893b26 --- /dev/null +++ b/lib/src/sql/statements/define/namespace.rs @@ -0,0 +1,108 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineNamespaceStatement { + pub id: Option, + pub name: Ident, + pub comment: Option, +} + +impl DefineNamespaceStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Namespace, &Base::Root)?; + // Process the statement + let key = crate::key::root::ns::new(&self.name); + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Set the id + if self.id.is_none() { + let mut ns = self.clone(); + ns.id = Some(run.get_next_ns_id().await?); + run.set(key, ns).await?; + } else { + run.set(key, self).await?; + } + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineNamespaceStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE NAMESPACE {}", self.name)?; + if let Some(ref v) = self.comment { + write!(f, " COMMENT {v}")? + } + Ok(()) + } +} + +pub fn namespace(i: &str) -> IResult<&str, DefineNamespaceStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, opts) = many0(namespace_opts)(i)?; + // Create the base statement + let mut res = DefineNamespaceStatement { + name, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineNamespaceOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineNamespaceOption { + Comment(Strand), +} + +fn namespace_opts(i: &str) -> IResult<&str, DefineNamespaceOption> { + namespace_comment(i) +} + +fn namespace_comment(i: &str) -> IResult<&str, DefineNamespaceOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineNamespaceOption::Comment(v))) +} diff --git a/lib/src/sql/statements/define/param.rs b/lib/src/sql/statements/define/param.rs new file mode 100644 index 00000000..138b13d2 --- /dev/null +++ b/lib/src/sql/statements/define/param.rs @@ -0,0 +1,117 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::{value, Value}; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::char; +use nom::multi::many0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineParamStatement { + pub name: Ident, + pub value: Value, + pub comment: Option, +} + +impl DefineParamStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Parameter, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::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 Display for DefineParamStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE PARAM ${} VALUE {}", self.name, self.value) + } +} + +pub 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, opts) = many0(param_opts)(i)?; + // Create the base statement + let mut res = DefineParamStatement { + name, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineParamOption::Value(v) => { + res.value = v; + } + DefineParamOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Check necessary options + if res.value.is_none() { + // TODO throw error + } + // Return the statement + Ok((i, res)) +} + +enum DefineParamOption { + Value(Value), + Comment(Strand), +} + +fn param_opts(i: &str) -> IResult<&str, DefineParamOption> { + alt((param_value, param_comment))(i) +} + +fn param_value(i: &str) -> IResult<&str, DefineParamOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("VALUE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineParamOption::Value(v))) +} + +fn param_comment(i: &str) -> IResult<&str, DefineParamOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineParamOption::Comment(v))) +} diff --git a/lib/src/sql/statements/define/scope.rs b/lib/src/sql/statements/define/scope.rs new file mode 100644 index 00000000..8a47c872 --- /dev/null +++ b/lib/src/sql/statements/define/scope.rs @@ -0,0 +1,156 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::duration::{duration, Duration}; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::{value, Value}; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use rand::distributions::Alphanumeric; +use rand::Rng; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineScopeStatement { + pub name: Ident, + pub code: String, + pub session: Option, + pub signup: Option, + pub signin: Option, + pub comment: Option, +} + +impl DefineScopeStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Scope, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::sc::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 Display for DefineScopeStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE SCOPE {}", self.name)?; + if let Some(ref v) = self.session { + write!(f, " SESSION {v}")? + } + if let Some(ref v) = self.signup { + write!(f, " SIGNUP {v}")? + } + if let Some(ref v) = self.signin { + write!(f, " SIGNIN {v}")? + } + Ok(()) + } +} + +pub fn scope(i: &str) -> IResult<&str, DefineScopeStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("SCOPE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, opts) = many0(scope_opts)(i)?; + // Create the base statement + let mut res = DefineScopeStatement { + name, + code: rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(128) + .map(char::from) + .collect::(), + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineScopeOption::Session(v) => { + res.session = Some(v); + } + DefineScopeOption::Signup(v) => { + res.signup = Some(v); + } + DefineScopeOption::Signin(v) => { + res.signin = Some(v); + } + DefineScopeOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineScopeOption { + Session(Duration), + Signup(Value), + Signin(Value), + Comment(Strand), +} + +fn scope_opts(i: &str) -> IResult<&str, DefineScopeOption> { + alt((scope_session, scope_signup, scope_signin, scope_comment))(i) +} + +fn scope_session(i: &str) -> IResult<&str, DefineScopeOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("SESSION")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = duration(i)?; + Ok((i, DefineScopeOption::Session(v))) +} + +fn scope_signup(i: &str) -> IResult<&str, DefineScopeOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("SIGNUP")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineScopeOption::Signup(v))) +} + +fn scope_signin(i: &str) -> IResult<&str, DefineScopeOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("SIGNIN")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = value(i)?; + Ok((i, DefineScopeOption::Signin(v))) +} + +fn scope_comment(i: &str) -> IResult<&str, DefineScopeOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineScopeOption::Comment(v))) +} diff --git a/lib/src/sql/statements/define/table.rs b/lib/src/sql/statements/define/table.rs new file mode 100644 index 00000000..c6aeb1fb --- /dev/null +++ b/lib/src/sql/statements/define/table.rs @@ -0,0 +1,260 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::base::Base; +use crate::sql::changefeed::{changefeed, ChangeFeed}; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::fmt::is_pretty; +use crate::sql::fmt::pretty_indent; +use crate::sql::ident::{ident, Ident}; +use crate::sql::permission::{permissions, Permissions}; +use crate::sql::statements::UpdateStatement; +use crate::sql::strand::{strand, Strand}; +use crate::sql::value::{Value, Values}; +use crate::sql::view::{view, View}; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Write}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineTableStatement { + pub id: Option, + pub name: Ident, + pub drop: bool, + pub full: bool, + pub view: Option, + pub permissions: Permissions, + pub changefeed: Option, + pub comment: Option, +} + +impl DefineTableStatement { + pub(crate) async fn compute( + &self, + ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name); + let ns = run.add_ns(opt.ns(), opt.strict).await?; + let db = run.add_db(opt.ns(), opt.db(), opt.strict).await?; + if self.id.is_none() && ns.id.is_some() && db.id.is_some() { + let mut tb = self.clone(); + tb.id = Some(run.get_next_tb_id(ns.id.unwrap(), db.id.unwrap()).await?); + run.set(key, tb).await?; + } else { + run.set(key, self).await?; + } + // Check if table is a view + if let Some(view) = &self.view { + // Remove the table data + let key = crate::key::table::all::new(opt.ns(), opt.db(), &self.name); + run.delp(key, u32::MAX).await?; + // Process each foreign table + for v in view.what.0.iter() { + // Save the view config + let key = crate::key::table::ft::new(opt.ns(), opt.db(), v, &self.name); + run.set(key, self).await?; + // Clear the cache + let key = crate::key::table::ft::prefix(opt.ns(), opt.db(), v); + run.clr(key).await?; + } + // Release the transaction + drop(run); + // Force queries to run + let opt = &opt.new_with_force(true); + // Don't process field queries + let opt = &opt.new_with_fields(false); + // Don't process event queries + let opt = &opt.new_with_events(false); + // Don't process index queries + let opt = &opt.new_with_indexes(false); + // Process each foreign table + for v in view.what.0.iter() { + // Process the view data + let stm = UpdateStatement { + what: Values(vec![Value::Table(v.clone())]), + ..UpdateStatement::default() + }; + stm.compute(ctx, opt, txn, doc).await?; + } + } + // Ok all good + Ok(Value::None) + } +} + +impl Display for DefineTableStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE TABLE {}", self.name)?; + if self.drop { + f.write_str(" DROP")?; + } + f.write_str(if self.full { + " SCHEMAFULL" + } else { + " SCHEMALESS" + })?; + if let Some(ref v) = self.view { + write!(f, " {v}")? + } + if let Some(ref v) = self.changefeed { + write!(f, " {v}")?; + } + if !self.permissions.is_full() { + let _indent = if is_pretty() { + Some(pretty_indent()) + } else { + f.write_char(' ')?; + None + }; + write!(f, "{}", self.permissions)?; + } + Ok(()) + } +} + +pub fn table(i: &str) -> IResult<&str, DefineTableStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TABLE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, opts) = many0(table_opts)(i)?; + // Create the base statement + let mut res = DefineTableStatement { + name, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineTableOption::Drop => { + res.drop = true; + } + DefineTableOption::Schemafull => { + res.full = true; + } + DefineTableOption::Schemaless => { + res.full = false; + } + DefineTableOption::View(v) => { + res.view = Some(v); + } + DefineTableOption::Comment(v) => { + res.comment = Some(v); + } + DefineTableOption::ChangeFeed(v) => { + res.changefeed = Some(v); + } + DefineTableOption::Permissions(v) => { + res.permissions = v; + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineTableOption { + Drop, + View(View), + Schemaless, + Schemafull, + Comment(Strand), + Permissions(Permissions), + ChangeFeed(ChangeFeed), +} + +fn table_opts(i: &str) -> IResult<&str, DefineTableOption> { + alt(( + table_drop, + table_view, + table_comment, + table_schemaless, + table_schemafull, + table_permissions, + table_changefeed, + ))(i) +} + +fn table_drop(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("DROP")(i)?; + Ok((i, DefineTableOption::Drop)) +} + +fn table_changefeed(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, v) = changefeed(i)?; + Ok((i, DefineTableOption::ChangeFeed(v))) +} + +fn table_view(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, v) = view(i)?; + Ok((i, DefineTableOption::View(v))) +} + +fn table_schemaless(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("SCHEMALESS")(i)?; + Ok((i, DefineTableOption::Schemaless)) +} + +fn table_schemafull(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("SCHEMAFULL"), tag_no_case("SCHEMAFUL")))(i)?; + Ok((i, DefineTableOption::Schemafull)) +} + +fn table_comment(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineTableOption::Comment(v))) +} + +fn table_permissions(i: &str) -> IResult<&str, DefineTableOption> { + let (i, _) = shouldbespace(i)?; + let (i, v) = permissions(i)?; + Ok((i, DefineTableOption::Permissions(v))) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn define_table_with_changefeed() { + let sql = "DEFINE TABLE mytable SCHEMALESS CHANGEFEED 1h"; + let res = table(sql); + assert!(res.is_ok()); + let out = res.unwrap().1; + assert_eq!(sql, format!("{}", out)); + + let serialized: Vec = (&out).try_into().unwrap(); + let deserialized = DefineTableStatement::try_from(&serialized).unwrap(); + assert_eq!(out, deserialized); + } +} diff --git a/lib/src/sql/statements/define/token.rs b/lib/src/sql/statements/define/token.rs new file mode 100644 index 00000000..78fd184c --- /dev/null +++ b/lib/src/sql/statements/define/token.rs @@ -0,0 +1,179 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::sql::algorithm::{algorithm, Algorithm}; +use crate::sql::base::{base_or_scope, Base}; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::escape::quote_str; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, strand_raw, Strand}; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineTokenStatement { + pub name: Ident, + pub base: Base, + pub kind: Algorithm, + pub code: String, + pub comment: Option, +} + +impl DefineTokenStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; + + match &self.base { + Base::Ns => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::namespace::tk::new(opt.ns(), &self.name); + run.add_ns(opt.ns(), opt.strict).await?; + run.set(key, self).await?; + // Ok all good + Ok(Value::None) + } + Base::Db => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::tk::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) + } + Base::Sc(sc) => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &self.name); + run.add_ns(opt.ns(), opt.strict).await?; + run.add_db(opt.ns(), opt.db(), opt.strict).await?; + run.add_sc(opt.ns(), opt.db(), sc, opt.strict).await?; + run.set(key, self).await?; + // Ok all good + Ok(Value::None) + } + // Other levels are not supported + _ => Err(Error::InvalidLevel(self.base.to_string())), + } + } +} + +impl Display for DefineTokenStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "DEFINE TOKEN {} ON {} TYPE {} VALUE {}", + self.name, + self.base, + self.kind, + quote_str(&self.code) + )?; + if let Some(ref v) = self.comment { + write!(f, " COMMENT {v}")? + } + Ok(()) + } +} + +pub fn token(i: &str) -> IResult<&str, DefineTokenStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TOKEN")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, base) = base_or_scope(i)?; + let (i, opts) = many0(token_opts)(i)?; + // Create the base statement + let mut res = DefineTokenStatement { + name, + base, + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineTokenOption::Type(v) => { + res.kind = v; + } + DefineTokenOption::Value(v) => { + res.code = v; + } + DefineTokenOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Check necessary options + if res.code.is_empty() { + // TODO throw error + } + // Return the statement + Ok((i, res)) +} + +enum DefineTokenOption { + Type(Algorithm), + Value(String), + Comment(Strand), +} + +fn token_opts(i: &str) -> IResult<&str, DefineTokenOption> { + alt((token_type, token_value, token_comment))(i) +} + +fn token_type(i: &str) -> IResult<&str, DefineTokenOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TYPE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = algorithm(i)?; + Ok((i, DefineTokenOption::Type(v))) +} + +fn token_value(i: &str) -> IResult<&str, DefineTokenOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("VALUE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand_raw(i)?; + Ok((i, DefineTokenOption::Value(v))) +} + +fn token_comment(i: &str) -> IResult<&str, DefineTokenOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineTokenOption::Comment(v))) +} diff --git a/lib/src/sql/statements/define/user.rs b/lib/src/sql/statements/define/user.rs new file mode 100644 index 00000000..7b8ebb87 --- /dev/null +++ b/lib/src/sql/statements/define/user.rs @@ -0,0 +1,230 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::iam::Action; +use crate::iam::ResourceKind; +use crate::iam::Role; +use crate::sql::base::{base, Base}; +use crate::sql::comment::shouldbespace; +use crate::sql::common::commas; +use crate::sql::error::Error as SqlError; +use crate::sql::error::IResult; +use crate::sql::escape::quote_str; +use crate::sql::fmt::Fmt; +use crate::sql::ident::{ident, Ident}; +use crate::sql::strand::{strand, strand_raw, Strand}; +use crate::sql::value::Value; +use argon2::password_hash::{PasswordHasher, SaltString}; +use argon2::Argon2; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use nom::multi::many0; +use nom::multi::separated_list0; +use nom::Err::Failure; +use rand::distributions::Alphanumeric; +use rand::rngs::OsRng; +use rand::Rng; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct DefineUserStatement { + pub name: Ident, + pub base: Base, + pub hash: String, + pub code: String, + pub roles: Vec, + pub comment: Option, +} + +impl From<(Base, &str, &str)> for DefineUserStatement { + fn from((base, user, pass): (Base, &str, &str)) -> Self { + DefineUserStatement { + base, + name: user.into(), + hash: Argon2::default() + .hash_password(pass.as_ref(), &SaltString::generate(&mut OsRng)) + .unwrap() + .to_string(), + code: rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(128) + .map(char::from) + .collect::(), + roles: vec!["owner".into()], + comment: None, + } + } +} + +impl DefineUserStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; + + match self.base { + Base::Root => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::root::us::new(&self.name); + run.set(key, self).await?; + // Ok all good + Ok(Value::None) + } + Base::Ns => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::namespace::us::new(opt.ns(), &self.name); + run.add_ns(opt.ns(), opt.strict).await?; + run.set(key, self).await?; + // Ok all good + Ok(Value::None) + } + Base::Db => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::database::us::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) + } + // Other levels are not supported + _ => Err(Error::InvalidLevel(self.base.to_string())), + } + } +} + +impl Display for DefineUserStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "DEFINE USER {} ON {} PASSHASH {} ROLES {}", + self.name, + self.base, + quote_str(&self.hash), + Fmt::comma_separated( + &self.roles.iter().map(|r| r.to_string().to_uppercase()).collect::>() + ) + ) + } +} + +pub fn user(i: &str) -> IResult<&str, DefineUserStatement> { + let (i, _) = tag_no_case("DEFINE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("USER")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, base) = base(i)?; + let (i, opts) = user_opts(i)?; + // Create the base statement + let mut res = DefineUserStatement { + name, + base, + roles: vec!["Viewer".into()], // New users get the viewer role by default + code: rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(128) + .map(char::from) + .collect::(), + ..Default::default() + }; + // Assign any defined options + for opt in opts { + match opt { + DefineUserOption::Password(v) => { + res.hash = Argon2::default() + .hash_password(v.as_ref(), &SaltString::generate(&mut OsRng)) + .unwrap() + .to_string() + } + DefineUserOption::Passhash(v) => { + res.hash = v; + } + DefineUserOption::Roles(v) => { + res.roles = v; + } + DefineUserOption::Comment(v) => { + res.comment = Some(v); + } + } + } + // Return the statement + Ok((i, res)) +} + +enum DefineUserOption { + Password(String), + Passhash(String), + Roles(Vec), + Comment(Strand), +} + +fn user_opts(i: &str) -> IResult<&str, Vec> { + many0(alt((alt((user_pass, user_hash)), user_roles, user_comment)))(i) +} + +fn user_pass(i: &str) -> IResult<&str, DefineUserOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("PASSWORD")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand_raw(i)?; + Ok((i, DefineUserOption::Password(v))) +} + +fn user_hash(i: &str) -> IResult<&str, DefineUserOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("PASSHASH")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand_raw(i)?; + Ok((i, DefineUserOption::Passhash(v))) +} + +fn user_comment(i: &str) -> IResult<&str, DefineUserOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("COMMENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, v) = strand(i)?; + Ok((i, DefineUserOption::Comment(v))) +} + +fn user_roles(i: &str) -> IResult<&str, DefineUserOption> { + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ROLES")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, roles) = separated_list0(commas, |i| { + let (i, v) = ident(i)?; + // Verify the role is valid + Role::try_from(v.as_str()).map_err(|_| Failure(SqlError::Role(i, v.to_string())))?; + + Ok((i, v)) + })(i)?; + + Ok((i, DefineUserOption::Roles(roles))) +} diff --git a/lib/src/sql/statements/info.rs b/lib/src/sql/statements/info.rs index 1469e0ca..4fef4d6d 100644 --- a/lib/src/sql/statements/info.rs +++ b/lib/src/sql/statements/info.rs @@ -77,12 +77,6 @@ impl InfoStatement { tmp.insert(v.name.to_string(), v.to_string().into()); } res.insert("databases".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("logins".to_owned(), tmp.into()); // Process the users let mut tmp = Object::default(); for v in run.all_ns_users(opt.ns()).await?.iter() { @@ -105,12 +99,6 @@ impl InfoStatement { let mut run = txn.lock().await; // Create the result set let mut res = Object::default(); - // 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("logins".to_owned(), tmp.into()); // Process the users let mut tmp = Object::default(); for v in run.all_db_users(opt.ns(), opt.db()).await?.iter() { diff --git a/lib/src/sql/statements/mod.rs b/lib/src/sql/statements/mod.rs index f435b0ca..31ae1ab6 100644 --- a/lib/src/sql/statements/mod.rs +++ b/lib/src/sql/statements/mod.rs @@ -53,7 +53,6 @@ pub use self::define::DefineEventStatement; pub use self::define::DefineFieldStatement; pub use self::define::DefineFunctionStatement; pub use self::define::DefineIndexStatement; -pub use self::define::DefineLoginStatement; pub use self::define::DefineNamespaceStatement; pub use self::define::DefineParamStatement; pub use self::define::DefineScopeStatement; @@ -67,7 +66,6 @@ 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; pub use self::remove::RemoveParamStatement; pub use self::remove::RemoveScopeStatement; diff --git a/lib/src/sql/statements/remove.rs b/lib/src/sql/statements/remove.rs deleted file mode 100644 index d7c8c60b..00000000 --- a/lib/src/sql/statements/remove.rs +++ /dev/null @@ -1,908 +0,0 @@ -use crate::ctx::Context; -use crate::dbs::Options; -use crate::dbs::Transaction; -use crate::doc::CursorDoc; -use crate::err::Error; -use crate::iam::{Action, ResourceKind}; -use crate::sql::base::{base, base_or_scope, Base}; -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}; -use nom::sequence::tuple; -use revision::revisioned; -use serde::{Deserialize, Serialize}; -use std::fmt::{self, Display, Formatter}; - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub enum RemoveStatement { - Namespace(RemoveNamespaceStatement), - Database(RemoveDatabaseStatement), - Function(RemoveFunctionStatement), - Analyzer(RemoveAnalyzerStatement), - Login(RemoveLoginStatement), - Token(RemoveTokenStatement), - Scope(RemoveScopeStatement), - Param(RemoveParamStatement), - Table(RemoveTableStatement), - Event(RemoveEventStatement), - Field(RemoveFieldStatement), - Index(RemoveIndexStatement), - User(RemoveUserStatement), -} - -impl RemoveStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - _doc: Option<&CursorDoc<'_>>, - ) -> Result { - match self { - Self::Namespace(ref v) => v.compute(ctx, opt, txn).await, - Self::Database(ref v) => v.compute(ctx, opt, txn).await, - Self::Function(ref v) => v.compute(ctx, opt, txn).await, - Self::Login(ref v) => v.compute(ctx, opt, txn).await, - Self::Token(ref v) => v.compute(ctx, opt, txn).await, - Self::Scope(ref v) => v.compute(ctx, opt, txn).await, - Self::Param(ref v) => v.compute(ctx, opt, txn).await, - Self::Table(ref v) => v.compute(ctx, opt, txn).await, - Self::Event(ref v) => v.compute(ctx, opt, txn).await, - Self::Field(ref v) => v.compute(ctx, opt, txn).await, - Self::Index(ref v) => v.compute(ctx, opt, txn).await, - Self::Analyzer(ref v) => v.compute(ctx, opt, txn).await, - Self::User(ref v) => v.compute(ctx, opt, txn).await, - } - } -} - -impl Display for RemoveStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - 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), - 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), - Self::Index(v) => Display::fmt(v, f), - Self::Analyzer(v) => Display::fmt(v, f), - Self::User(v) => Display::fmt(v, f), - } - } -} - -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), - map(param, RemoveStatement::Param), - map(table, RemoveStatement::Table), - map(event, RemoveStatement::Event), - map(field, RemoveStatement::Field), - map(index, RemoveStatement::Index), - map(analyzer, RemoveStatement::Analyzer), - map(user, RemoveStatement::User), - ))(i) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveNamespaceStatement { - pub name: Ident, -} - -impl RemoveNamespaceStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Namespace, &Base::Root)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::root::ns::new(&self.name); - run.del(key).await?; - // Delete the resource data - let key = crate::key::namespace::all::new(&self.name); - run.delp(key, u32::MAX).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveNamespaceStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE NAMESPACE {}", self.name) - } -} - -fn namespace(i: &str) -> IResult<&str, RemoveNamespaceStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - Ok(( - i, - RemoveNamespaceStatement { - name, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveDatabaseStatement { - pub name: Ident, -} - -impl RemoveDatabaseStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Database, &Base::Ns)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::namespace::db::new(opt.ns(), &self.name); - run.del(key).await?; - // Delete the resource data - let key = crate::key::database::all::new(opt.ns(), &self.name); - run.delp(key, u32::MAX).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveDatabaseStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE DATABASE {}", self.name) - } -} - -fn database(i: &str) -> IResult<&str, RemoveDatabaseStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - Ok(( - i, - RemoveDatabaseStatement { - name, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveFunctionStatement { - pub name: Ident, -} - -impl RemoveFunctionStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Function, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::fc::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl 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)] -#[revisioned(revision = 1)] -pub struct RemoveAnalyzerStatement { - pub name: Ident, -} - -impl RemoveAnalyzerStatement { - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Analyzer, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::az::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // TODO Check that the analyzer is not used in any schema - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveAnalyzerStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE ANALYZER {}", self.name) - } -} - -fn analyzer(i: &str) -> IResult<&str, RemoveAnalyzerStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ANALYZER")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - Ok(( - i, - RemoveAnalyzerStatement { - name, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveLoginStatement { - pub name: Ident, - pub base: Base, -} - -impl RemoveLoginStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; - - match self.base { - Base::Ns => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::namespace::lg::new(opt.ns(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - Base::Db => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::lg::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - _ => Err(Error::InvalidLevel(self.base.to_string())), - } - } -} - -impl Display for RemoveLoginStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE LOGIN {} ON {}", self.name, self.base) - } -} - -fn login(i: &str) -> IResult<&str, RemoveLoginStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("LOGIN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; - Ok(( - i, - RemoveLoginStatement { - name, - base, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveTokenStatement { - pub name: Ident, - pub base: Base, -} - -impl RemoveTokenStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; - - match &self.base { - Base::Ns => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::namespace::tk::new(opt.ns(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - Base::Db => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::tk::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - Base::Sc(sc) => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - _ => Err(Error::InvalidLevel(self.base.to_string())), - } - } -} - -impl Display for RemoveTokenStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE TOKEN {} ON {}", self.name, self.base) - } -} - -fn token(i: &str) -> IResult<&str, RemoveTokenStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("TOKEN")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base_or_scope(i)?; - Ok(( - i, - RemoveTokenStatement { - name, - base, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveScopeStatement { - pub name: Ident, -} - -impl RemoveScopeStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Scope, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::sc::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Remove the resource data - let key = crate::key::scope::all::new(opt.ns(), opt.db(), &self.name); - run.delp(key, u32::MAX).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveScopeStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE SCOPE {}", self.name) - } -} - -fn scope(i: &str) -> IResult<&str, RemoveScopeStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("SCOPE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - Ok(( - i, - RemoveScopeStatement { - name, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveParamStatement { - pub name: Ident, -} - -impl RemoveParamStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Parameter, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::pa::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveParamStatement { - fn fmt(&self, f: &mut 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)] -#[revisioned(revision = 1)] -pub struct RemoveTableStatement { - pub name: Ident, -} - -impl RemoveTableStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Remove the resource data - let key = crate::key::table::all::new(opt.ns(), opt.db(), &self.name); - run.delp(key, u32::MAX).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveTableStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE TABLE {}", self.name) - } -} - -fn table(i: &str) -> IResult<&str, RemoveTableStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("TABLE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - Ok(( - i, - RemoveTableStatement { - name, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveEventStatement { - pub name: Ident, - pub what: Ident, -} - -impl RemoveEventStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Event, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::table::ev::new(opt.ns(), opt.db(), &self.what, &self.name); - run.del(key).await?; - // Clear the cache - let key = crate::key::table::ev::prefix(opt.ns(), opt.db(), &self.what); - run.clr(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveEventStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE EVENT {} ON {}", self.name, self.what) - } -} - -fn event(i: &str) -> IResult<&str, RemoveEventStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("EVENT")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - Ok(( - i, - RemoveEventStatement { - name, - what, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveFieldStatement { - pub name: Idiom, - pub what: Ident, -} - -impl RemoveFieldStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Field, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let fd = self.name.to_string(); - let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd); - run.del(key).await?; - // Clear the cache - let key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.what); - run.clr(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveFieldStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE FIELD {} ON {}", self.name, self.what) - } -} - -fn field(i: &str) -> IResult<&str, RemoveFieldStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("FIELD")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = idiom::local(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - Ok(( - i, - RemoveFieldStatement { - name, - what, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveIndexStatement { - pub name: Ident, - pub what: Ident, -} - -impl RemoveIndexStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Index, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::table::ix::new(opt.ns(), opt.db(), &self.what, &self.name); - run.del(key).await?; - // Remove the index data - let key = crate::key::index::all::new(opt.ns(), opt.db(), &self.what, &self.name); - run.delp(key, u32::MAX).await?; - // Clear the cache - let key = crate::key::table::ix::prefix(opt.ns(), opt.db(), &self.what); - run.clr(key).await?; - // Ok all good - Ok(Value::None) - } -} - -impl Display for RemoveIndexStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE INDEX {} ON {}", self.name, self.what) - } -} - -fn index(i: &str) -> IResult<&str, RemoveIndexStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("INDEX")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; - let (i, _) = shouldbespace(i)?; - let (i, what) = ident(i)?; - Ok(( - i, - RemoveIndexStatement { - name, - what, - }, - )) -} - -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- - -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] -pub struct RemoveUserStatement { - pub name: Ident, - pub base: Base, -} - -impl RemoveUserStatement { - /// Process this type returning a computed simple Value - pub(crate) async fn compute( - &self, - _ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; - - match self.base { - Base::Root => { - // Claim transaction - let mut run = txn.lock().await; - // Process the statement - let key = crate::key::root::us::new(&self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - Base::Ns => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::namespace::us::new(opt.ns(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - Base::Db => { - // Claim transaction - let mut run = txn.lock().await; - // Delete the definition - let key = crate::key::database::us::new(opt.ns(), opt.db(), &self.name); - run.del(key).await?; - // Ok all good - Ok(Value::None) - } - _ => Err(Error::InvalidLevel(self.base.to_string())), - } - } -} - -impl Display for RemoveUserStatement { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE USER {} ON {}", self.name, self.base) - } -} - -fn user(i: &str) -> IResult<&str, RemoveUserStatement> { - let (i, _) = tag_no_case("REMOVE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("USER")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = ident(i)?; - let (i, _) = shouldbespace(i)?; - let (i, _) = tag_no_case("ON")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, base) = base(i)?; - Ok(( - i, - RemoveUserStatement { - name, - base, - }, - )) -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn check_remove_serialize() { - let stm = RemoveStatement::Namespace(RemoveNamespaceStatement { - name: Ident::from("test"), - }); - let enc: Vec = stm.try_into().unwrap(); - assert_eq!(9, enc.len()); - } -} diff --git a/lib/src/sql/statements/remove/analyzer.rs b/lib/src/sql/statements/remove/analyzer.rs new file mode 100644 index 00000000..94d32a1f --- /dev/null +++ b/lib/src/sql/statements/remove/analyzer.rs @@ -0,0 +1,63 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveAnalyzerStatement { + pub name: Ident, +} + +impl RemoveAnalyzerStatement { + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Analyzer, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::az::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // TODO Check that the analyzer is not used in any schema + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveAnalyzerStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE ANALYZER {}", self.name) + } +} + +pub fn analyzer(i: &str) -> IResult<&str, RemoveAnalyzerStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ANALYZER")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + Ok(( + i, + RemoveAnalyzerStatement { + name, + }, + )) +} diff --git a/lib/src/sql/statements/remove/database.rs b/lib/src/sql/statements/remove/database.rs new file mode 100644 index 00000000..2daca642 --- /dev/null +++ b/lib/src/sql/statements/remove/database.rs @@ -0,0 +1,67 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveDatabaseStatement { + pub name: Ident, +} + +impl RemoveDatabaseStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Database, &Base::Ns)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::namespace::db::new(opt.ns(), &self.name); + run.del(key).await?; + // Delete the resource data + let key = crate::key::database::all::new(opt.ns(), &self.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveDatabaseStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE DATABASE {}", self.name) + } +} + +pub fn database(i: &str) -> IResult<&str, RemoveDatabaseStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + Ok(( + i, + RemoveDatabaseStatement { + name, + }, + )) +} diff --git a/lib/src/sql/statements/remove/event.rs b/lib/src/sql/statements/remove/event.rs new file mode 100644 index 00000000..a8eb895d --- /dev/null +++ b/lib/src/sql/statements/remove/event.rs @@ -0,0 +1,75 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use nom::combinator::opt; +use nom::sequence::tuple; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveEventStatement { + pub name: Ident, + pub what: Ident, +} + +impl RemoveEventStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Event, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::table::ev::new(opt.ns(), opt.db(), &self.what, &self.name); + run.del(key).await?; + // Clear the cache + let key = crate::key::table::ev::prefix(opt.ns(), opt.db(), &self.what); + run.clr(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveEventStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE EVENT {} ON {}", self.name, self.what) + } +} + +pub fn event(i: &str) -> IResult<&str, RemoveEventStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("EVENT")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + Ok(( + i, + RemoveEventStatement { + name, + what, + }, + )) +} diff --git a/lib/src/sql/statements/remove/field.rs b/lib/src/sql/statements/remove/field.rs new file mode 100644 index 00000000..220d44a3 --- /dev/null +++ b/lib/src/sql/statements/remove/field.rs @@ -0,0 +1,78 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +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::bytes::complete::tag_no_case; +use nom::combinator::opt; +use nom::sequence::tuple; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveFieldStatement { + pub name: Idiom, + pub what: Ident, +} + +impl RemoveFieldStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Field, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let fd = self.name.to_string(); + let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd); + run.del(key).await?; + // Clear the cache + let key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.what); + run.clr(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveFieldStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE FIELD {} ON {}", self.name, self.what) + } +} + +pub fn field(i: &str) -> IResult<&str, RemoveFieldStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("FIELD")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = idiom::local(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + Ok(( + i, + RemoveFieldStatement { + name, + what, + }, + )) +} diff --git a/lib/src/sql/statements/remove/function.rs b/lib/src/sql/statements/remove/function.rs new file mode 100644 index 00000000..8d5e9a29 --- /dev/null +++ b/lib/src/sql/statements/remove/function.rs @@ -0,0 +1,75 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::{mightbespace, shouldbespace}; +use crate::sql::error::IResult; +use crate::sql::ident; +use crate::sql::ident::Ident; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::char; +use nom::combinator::opt; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveFunctionStatement { + pub name: Ident, +} + +impl RemoveFunctionStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Function, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::fc::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveFunctionStatement { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "REMOVE FUNCTION fn::{}", self.name) + } +} + +pub 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, + }, + )) +} diff --git a/lib/src/sql/statements/remove/index.rs b/lib/src/sql/statements/remove/index.rs new file mode 100644 index 00000000..d8041e7e --- /dev/null +++ b/lib/src/sql/statements/remove/index.rs @@ -0,0 +1,78 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use nom::combinator::opt; +use nom::sequence::tuple; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveIndexStatement { + pub name: Ident, + pub what: Ident, +} + +impl RemoveIndexStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Index, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::table::ix::new(opt.ns(), opt.db(), &self.what, &self.name); + run.del(key).await?; + // Remove the index data + let key = crate::key::index::all::new(opt.ns(), opt.db(), &self.what, &self.name); + run.delp(key, u32::MAX).await?; + // Clear the cache + let key = crate::key::table::ix::prefix(opt.ns(), opt.db(), &self.what); + run.clr(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveIndexStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE INDEX {} ON {}", self.name, self.what) + } +} + +pub fn index(i: &str) -> IResult<&str, RemoveIndexStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("INDEX")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = opt(tuple((shouldbespace, tag_no_case("TABLE"))))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, what) = ident(i)?; + Ok(( + i, + RemoveIndexStatement { + name, + what, + }, + )) +} diff --git a/lib/src/sql/statements/remove/mod.rs b/lib/src/sql/statements/remove/mod.rs new file mode 100644 index 00000000..1009f1d3 --- /dev/null +++ b/lib/src/sql/statements/remove/mod.rs @@ -0,0 +1,135 @@ +mod analyzer; +mod database; +mod event; +mod field; +mod function; +mod index; +mod namespace; +mod param; +mod scope; +mod table; +mod token; +mod user; + +pub use analyzer::{analyzer, RemoveAnalyzerStatement}; +pub use database::{database, RemoveDatabaseStatement}; +pub use event::{event, RemoveEventStatement}; +pub use field::{field, RemoveFieldStatement}; +pub use function::{function, RemoveFunctionStatement}; +pub use index::{index, RemoveIndexStatement}; +pub use namespace::{namespace, RemoveNamespaceStatement}; +pub use param::{param, RemoveParamStatement}; +pub use scope::{scope, RemoveScopeStatement}; +pub use table::{table, RemoveTableStatement}; +pub use token::{token, RemoveTokenStatement}; +pub use user::{user, RemoveUserStatement}; + +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::doc::CursorDoc; +use crate::err::Error; +use crate::sql::error::IResult; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::combinator::map; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub enum RemoveStatement { + Namespace(RemoveNamespaceStatement), + Database(RemoveDatabaseStatement), + Function(RemoveFunctionStatement), + Analyzer(RemoveAnalyzerStatement), + Token(RemoveTokenStatement), + Scope(RemoveScopeStatement), + Param(RemoveParamStatement), + Table(RemoveTableStatement), + Event(RemoveEventStatement), + Field(RemoveFieldStatement), + Index(RemoveIndexStatement), + User(RemoveUserStatement), +} + +impl RemoveStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + _doc: Option<&CursorDoc<'_>>, + ) -> Result { + match self { + Self::Namespace(ref v) => v.compute(ctx, opt, txn).await, + Self::Database(ref v) => v.compute(ctx, opt, txn).await, + Self::Function(ref v) => v.compute(ctx, opt, txn).await, + Self::Token(ref v) => v.compute(ctx, opt, txn).await, + Self::Scope(ref v) => v.compute(ctx, opt, txn).await, + Self::Param(ref v) => v.compute(ctx, opt, txn).await, + Self::Table(ref v) => v.compute(ctx, opt, txn).await, + Self::Event(ref v) => v.compute(ctx, opt, txn).await, + Self::Field(ref v) => v.compute(ctx, opt, txn).await, + Self::Index(ref v) => v.compute(ctx, opt, txn).await, + Self::Analyzer(ref v) => v.compute(ctx, opt, txn).await, + Self::User(ref v) => v.compute(ctx, opt, txn).await, + } + } +} + +impl Display for RemoveStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Namespace(v) => Display::fmt(v, f), + Self::Database(v) => Display::fmt(v, f), + Self::Function(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), + Self::Index(v) => Display::fmt(v, f), + Self::Analyzer(v) => Display::fmt(v, f), + Self::User(v) => Display::fmt(v, f), + } + } +} + +pub fn remove(i: &str) -> IResult<&str, RemoveStatement> { + alt(( + map(namespace, RemoveStatement::Namespace), + map(database, RemoveStatement::Database), + map(function, RemoveStatement::Function), + map(token, RemoveStatement::Token), + map(scope, RemoveStatement::Scope), + map(param, RemoveStatement::Param), + map(table, RemoveStatement::Table), + map(event, RemoveStatement::Event), + map(field, RemoveStatement::Field), + map(index, RemoveStatement::Index), + map(analyzer, RemoveStatement::Analyzer), + map(user, RemoveStatement::User), + ))(i) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::sql::Ident; + + #[test] + fn check_remove_serialize() { + let stm = RemoveStatement::Namespace(RemoveNamespaceStatement { + name: Ident::from("test"), + ..Default::default() + }); + let enc: Vec = stm.try_into().unwrap(); + assert_eq!(9, enc.len()); + } +} diff --git a/lib/src/sql/statements/remove/namespace.rs b/lib/src/sql/statements/remove/namespace.rs new file mode 100644 index 00000000..eb4d1e99 --- /dev/null +++ b/lib/src/sql/statements/remove/namespace.rs @@ -0,0 +1,67 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::branch::alt; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveNamespaceStatement { + pub name: Ident, +} + +impl RemoveNamespaceStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Namespace, &Base::Root)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::root::ns::new(&self.name); + run.del(key).await?; + // Delete the resource data + let key = crate::key::namespace::all::new(&self.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveNamespaceStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE NAMESPACE {}", self.name) + } +} + +pub fn namespace(i: &str) -> IResult<&str, RemoveNamespaceStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + Ok(( + i, + RemoveNamespaceStatement { + name, + }, + )) +} diff --git a/lib/src/sql/statements/remove/param.rs b/lib/src/sql/statements/remove/param.rs new file mode 100644 index 00000000..d67ba080 --- /dev/null +++ b/lib/src/sql/statements/remove/param.rs @@ -0,0 +1,65 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use nom::character::complete::char; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveParamStatement { + pub name: Ident, +} + +impl RemoveParamStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Parameter, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::pa::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveParamStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE PARAM {}", self.name) + } +} + +pub 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, + }, + )) +} diff --git a/lib/src/sql/statements/remove/scope.rs b/lib/src/sql/statements/remove/scope.rs new file mode 100644 index 00000000..50761b58 --- /dev/null +++ b/lib/src/sql/statements/remove/scope.rs @@ -0,0 +1,66 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveScopeStatement { + pub name: Ident, +} + +impl RemoveScopeStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Scope, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::sc::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Remove the resource data + let key = crate::key::scope::all::new(opt.ns(), opt.db(), &self.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveScopeStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE SCOPE {}", self.name) + } +} + +pub fn scope(i: &str) -> IResult<&str, RemoveScopeStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("SCOPE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + Ok(( + i, + RemoveScopeStatement { + name, + }, + )) +} diff --git a/lib/src/sql/statements/remove/table.rs b/lib/src/sql/statements/remove/table.rs new file mode 100644 index 00000000..dc38757f --- /dev/null +++ b/lib/src/sql/statements/remove/table.rs @@ -0,0 +1,66 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::Base; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveTableStatement { + pub name: Ident, +} + +impl RemoveTableStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Remove the resource data + let key = crate::key::table::all::new(opt.ns(), opt.db(), &self.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } +} + +impl Display for RemoveTableStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE TABLE {}", self.name) + } +} + +pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TABLE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + Ok(( + i, + RemoveTableStatement { + name, + }, + )) +} diff --git a/lib/src/sql/statements/remove/token.rs b/lib/src/sql/statements/remove/token.rs new file mode 100644 index 00000000..e68fea44 --- /dev/null +++ b/lib/src/sql/statements/remove/token.rs @@ -0,0 +1,97 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::{base_or_scope, Base}; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveTokenStatement { + pub name: Ident, + pub base: Base, +} + +impl RemoveTokenStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; + + match &self.base { + Base::Ns => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::namespace::tk::new(opt.ns(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + Base::Db => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::tk::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + Base::Sc(sc) => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + _ => Err(Error::InvalidLevel(self.base.to_string())), + } + } +} + +impl Display for RemoveTokenStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE TOKEN {} ON {}", self.name, self.base) + } +} + +pub fn token(i: &str) -> IResult<&str, RemoveTokenStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("TOKEN")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, base) = base_or_scope(i)?; + Ok(( + i, + RemoveTokenStatement { + name, + base, + }, + )) +} diff --git a/lib/src/sql/statements/remove/user.rs b/lib/src/sql/statements/remove/user.rs new file mode 100644 index 00000000..4c0dd5ee --- /dev/null +++ b/lib/src/sql/statements/remove/user.rs @@ -0,0 +1,97 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Transaction; +use crate::err::Error; +use crate::iam::{Action, ResourceKind}; +use crate::sql::base::{base, Base}; +use crate::sql::comment::shouldbespace; +use crate::sql::error::IResult; +use crate::sql::ident::{ident, Ident}; +use crate::sql::value::Value; +use derive::Store; +use nom::bytes::complete::tag_no_case; +use revision::revisioned; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, Store, Hash)] +#[revisioned(revision = 1)] +pub struct RemoveUserStatement { + pub name: Ident, + pub base: Base, +} + +impl RemoveUserStatement { + /// Process this type returning a computed simple Value + pub(crate) async fn compute( + &self, + _ctx: &Context<'_>, + opt: &Options, + txn: &Transaction, + ) -> Result { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; + + match self.base { + Base::Root => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Process the statement + let key = crate::key::root::us::new(&self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + Base::Ns => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::namespace::us::new(opt.ns(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + Base::Db => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::us::new(opt.ns(), opt.db(), &self.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + _ => Err(Error::InvalidLevel(self.base.to_string())), + } + } +} + +impl Display for RemoveUserStatement { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "REMOVE USER {} ON {}", self.name, self.base) + } +} + +pub fn user(i: &str) -> IResult<&str, RemoveUserStatement> { + let (i, _) = tag_no_case("REMOVE")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("USER")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = ident(i)?; + let (i, _) = shouldbespace(i)?; + let (i, _) = tag_no_case("ON")(i)?; + let (i, _) = shouldbespace(i)?; + let (i, base) = base(i)?; + Ok(( + i, + RemoveUserStatement { + name, + base, + }, + )) +} diff --git a/lib/src/sql/tokenizer.rs b/lib/src/sql/tokenizer.rs index d30ddddb..45769e6b 100644 --- a/lib/src/sql/tokenizer.rs +++ b/lib/src/sql/tokenizer.rs @@ -1,4 +1,3 @@ -use crate::sql::comment::shouldbespace; use crate::sql::common::commas; use crate::sql::error::IResult; use nom::branch::alt; @@ -41,8 +40,5 @@ fn tokenizer(i: &str) -> IResult<&str, Tokenizer> { } pub(super) fn tokenizers(i: &str) -> IResult<&str, Vec> { - let (i, _) = tag_no_case("TOKENIZERS")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, t) = separated_list1(commas, tokenizer)(i)?; - Ok((i, t)) + separated_list1(commas, tokenizer)(i) } diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index 5bbf5a26..20e8b74f 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -148,6 +148,7 @@ pub enum Value { Edges(Box), Future(Box), Constant(Constant), + // Closure(Box), Function(Box), Subquery(Box), Expression(Box), diff --git a/lib/tests/define.rs b/lib/tests/define.rs index 2af80042..50ffd3fd 100644 --- a/lib/tests/define.rs +++ b/lib/tests/define.rs @@ -57,7 +57,6 @@ async fn define_statement_database() -> Result<(), Error> { let val = Value::parse( "{ databases: { test: 'DEFINE DATABASE test' }, - logins: {}, tokens: {}, users: {}, }", @@ -87,7 +86,6 @@ async fn define_statement_function() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: { test: 'DEFINE FUNCTION fn::test($first: string, $last: string) { RETURN $first + $last; }' }, params: {}, @@ -121,7 +119,6 @@ async fn define_statement_table_drop() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -153,7 +150,6 @@ async fn define_statement_table_schemaless() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -189,7 +185,6 @@ async fn define_statement_table_schemafull() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -221,7 +216,6 @@ async fn define_statement_table_schemaful() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -1075,7 +1069,6 @@ async fn define_statement_analyzer() -> Result<(), Error> { autocomplete: 'DEFINE ANALYZER autocomplete FILTERS LOWERCASE,EDGENGRAM(2,10)', english: 'DEFINE ANALYZER english TOKENIZERS BLANK,CLASS FILTERS LOWERCASE,SNOWBALL(ENGLISH)', }, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -1352,10 +1345,8 @@ async fn permissions_checks_define_db() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec![ - "{ databases: { DB: 'DEFINE DATABASE DB' }, logins: { }, tokens: { }, users: { } }", - ], - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"], + vec!["{ databases: { DB: 'DEFINE DATABASE DB' }, tokens: { }, users: { } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -1396,8 +1387,8 @@ async fn permissions_checks_define_function() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; }\" }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; }\" }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1438,8 +1429,8 @@ async fn permissions_checks_define_analyzer() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1480,8 +1471,8 @@ async fn permissions_checks_define_token_ns() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ databases: { }, logins: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"], - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"] + vec!["{ databases: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1522,8 +1513,8 @@ async fn permissions_checks_define_token_db() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1606,8 +1597,8 @@ async fn permissions_checks_define_user_ns() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ databases: { }, logins: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"], - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"] + vec!["{ databases: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1648,8 +1639,8 @@ async fn permissions_checks_define_user_db() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1690,8 +1681,8 @@ async fn permissions_checks_define_scope() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1732,8 +1723,8 @@ async fn permissions_checks_define_param() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo'\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { }, functions: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo'\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ @@ -1771,8 +1762,8 @@ async fn permissions_checks_define_table() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB SCHEMALESS' }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB SCHEMALESS' }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"] ]; let test_cases = [ diff --git a/lib/tests/info.rs b/lib/tests/info.rs index 6d43a135..245ffae3 100644 --- a/lib/tests/info.rs +++ b/lib/tests/info.rs @@ -55,7 +55,7 @@ async fn info_for_ns() { assert!(out.is_ok(), "Unexpected error: {:?}", out); let output_regex = Regex::new( - r"\{ databases: \{ DB: .* \}, logins: \{ \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}", + r"\{ databases: \{ DB: .* \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}", ) .unwrap(); let out_str = out.unwrap().to_string(); @@ -88,7 +88,7 @@ async fn info_for_db() { let out = res.pop().unwrap().output(); assert!(out.is_ok(), "Unexpected error: {:?}", out); - let output_regex = Regex::new(r"\{ analyzers: \{ analyzer: .* \}, functions: \{ greet: .* \}, logins: \{ \}, params: \{ param: .* \}, scopes: \{ account: .* \}, tables: \{ TB: .* \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}").unwrap(); + let output_regex = Regex::new(r"\{ analyzers: \{ analyzer: .* \}, functions: \{ greet: .* \}, params: \{ param: .* \}, scopes: \{ account: .* \}, tables: \{ TB: .* \}, tokens: \{ token: .* \}, users: \{ user: .* \} \}").unwrap(); let out_str = out.unwrap().to_string(); assert!( output_regex.is_match(&out_str), @@ -276,8 +276,8 @@ async fn permissions_checks_info_ns() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"], - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -315,8 +315,8 @@ async fn permissions_checks_info_db() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], ]; let test_cases = [ diff --git a/lib/tests/param.rs b/lib/tests/param.rs index 8a6ef37e..fa6fa6ef 100644 --- a/lib/tests/param.rs +++ b/lib/tests/param.rs @@ -26,7 +26,6 @@ async fn define_global_param() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: { test: 'DEFINE PARAM $test VALUE 12345' }, diff --git a/lib/tests/remove.rs b/lib/tests/remove.rs index c99dc97d..8aa53c82 100644 --- a/lib/tests/remove.rs +++ b/lib/tests/remove.rs @@ -37,7 +37,6 @@ async fn remove_statement_table() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -72,7 +71,6 @@ async fn remove_statement_analyzer() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {}, @@ -181,10 +179,8 @@ async fn permissions_checks_remove_db() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"], - vec![ - "{ databases: { DB: 'DEFINE DATABASE DB' }, logins: { }, tokens: { }, users: { } }", - ], + vec!["{ databases: { }, tokens: { }, users: { } }"], + vec!["{ databases: { DB: 'DEFINE DATABASE DB' }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -225,8 +221,8 @@ async fn permissions_checks_remove_function() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; }\" }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { greet: \"DEFINE FUNCTION fn::greet() { RETURN 'Hello'; }\" }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -267,8 +263,8 @@ async fn permissions_checks_remove_analyzer() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { analyzer: 'DEFINE ANALYZER analyzer TOKENIZERS BLANK' }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -309,8 +305,8 @@ async fn permissions_checks_remove_ns_token() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"], - vec!["{ databases: { }, logins: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"], + vec!["{ databases: { }, tokens: { token: \"DEFINE TOKEN token ON NAMESPACE TYPE HS512 VALUE 'secret'\" }, users: { } }"], ]; let test_cases = [ @@ -351,8 +347,8 @@ async fn permissions_checks_remove_db_token() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { token: \"DEFINE TOKEN token ON DATABASE TYPE HS512 VALUE 'secret'\" }, users: { } }"], ]; let test_cases = [ @@ -435,8 +431,8 @@ async fn permissions_checks_remove_ns_user() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ databases: { }, logins: { }, tokens: { }, users: { } }"], - vec!["{ databases: { }, logins: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"], + vec!["{ databases: { }, tokens: { }, users: { } }"], + vec!["{ databases: { }, tokens: { }, users: { user: \"DEFINE USER user ON NAMESPACE PASSHASH 'secret' ROLES VIEWER\" } }"], ]; let test_cases = [ @@ -477,8 +473,8 @@ async fn permissions_checks_remove_db_user() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { user: \"DEFINE USER user ON DATABASE PASSHASH 'secret' ROLES VIEWER\" } }"], ]; let test_cases = [ @@ -519,8 +515,8 @@ async fn permissions_checks_remove_scope() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { account: 'DEFINE SCOPE account SESSION 1h' }, tables: { }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -561,8 +557,8 @@ async fn permissions_checks_remove_param() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo'\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { param: \"DEFINE PARAM $param VALUE 'foo'\" }, scopes: { }, tables: { }, tokens: { }, users: { } }"], ]; let test_cases = [ @@ -603,8 +599,8 @@ async fn permissions_checks_remove_table() { // Define the expected results for the check statement when the test statement succeeded and when it failed let check_results = [ - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], - vec!["{ analyzers: { }, functions: { }, logins: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB SCHEMALESS' }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { }, tokens: { }, users: { } }"], + vec!["{ analyzers: { }, functions: { }, params: { }, scopes: { }, tables: { TB: 'DEFINE TABLE TB SCHEMALESS' }, tokens: { }, users: { } }"], ]; let test_cases = [ diff --git a/lib/tests/strict.rs b/lib/tests/strict.rs index 138e4739..10f595d9 100644 --- a/lib/tests/strict.rs +++ b/lib/tests/strict.rs @@ -242,7 +242,6 @@ async fn loose_mode_all_ok() -> Result<(), Error> { let val = Value::parse( "{ databases: { test: 'DEFINE DATABASE test' }, - logins: {}, tokens: {}, users: {}, }", @@ -253,7 +252,6 @@ async fn loose_mode_all_ok() -> Result<(), Error> { let val = Value::parse( "{ analyzers: {}, - logins: {}, tokens: {}, functions: {}, params: {},