From 50b4b07b383f9c7fc4bab02eb3a65728276ade5c Mon Sep 17 00:00:00 2001 From: Micha de Vries Date: Tue, 5 Mar 2024 20:28:38 +0100 Subject: [PATCH] Implement `IF EXISTS` for additional `REMOVE`-statements (#3377) Co-authored-by: Tobie Morgan Hitchcock Co-authored-by: Rushmore Mushambi --- core/src/err/mod.rs | 14 + core/src/kvs/tx.rs | 84 ++++ core/src/sql/v1/statements/remove/param.rs | 2 +- core/src/sql/v2/statements/remove/analyzer.rs | 46 +- core/src/sql/v2/statements/remove/database.rs | 50 +- core/src/sql/v2/statements/remove/event.rs | 50 +- core/src/sql/v2/statements/remove/field.rs | 53 ++- core/src/sql/v2/statements/remove/function.rs | 44 +- core/src/sql/v2/statements/remove/index.rs | 58 ++- core/src/sql/v2/statements/remove/model.rs | 46 +- .../src/sql/v2/statements/remove/namespace.rs | 52 ++- core/src/sql/v2/statements/remove/param.rs | 44 +- core/src/sql/v2/statements/remove/scope.rs | 50 +- core/src/sql/v2/statements/remove/table.rs | 70 +-- core/src/sql/v2/statements/remove/token.rs | 107 +++-- core/src/sql/v2/statements/remove/user.rs | 107 +++-- .../serde/ser/statement/remove/analyzer.rs | 5 + .../serde/ser/statement/remove/database.rs | 5 + .../value/serde/ser/statement/remove/event.rs | 5 + .../value/serde/ser/statement/remove/field.rs | 5 + .../serde/ser/statement/remove/function.rs | 5 + .../value/serde/ser/statement/remove/index.rs | 5 + .../serde/ser/statement/remove/namespace.rs | 5 + .../value/serde/ser/statement/remove/param.rs | 5 + .../value/serde/ser/statement/remove/scope.rs | 5 + .../value/serde/ser/statement/remove/token.rs | 5 + .../value/serde/ser/statement/remove/user.rs | 5 + core/src/syn/v1/stmt/remove.rs | 426 +++++++++++++++++- core/src/syn/v2/parser/stmt/remove.rs | 198 ++++++-- core/src/syn/v2/parser/test/stmt.rs | 34 +- core/src/syn/v2/parser/test/streaming.rs | 2 + lib/tests/remove.rs | 378 +++++++++++++++- 32 files changed, 1632 insertions(+), 338 deletions(-) diff --git a/core/src/err/mod.rs b/core/src/err/mod.rs index e001aeea..3393583f 100644 --- a/core/src/err/mod.rs +++ b/core/src/err/mod.rs @@ -319,12 +319,26 @@ pub enum Error { value: String, }, + /// The requested event does not exist + #[error("The event '{value}' does not exist")] + #[cfg(feature = "sql2")] + EvNotFound { + value: String, + }, + /// The requested function does not exist #[error("The function 'fn::{value}' does not exist")] FcNotFound { value: String, }, + /// The requested field does not exist + #[error("The field '{value}' does not exist")] + #[cfg(feature = "sql2")] + FdNotFound { + value: String, + }, + /// The requested model does not exist #[error("The model 'ml::{value}' does not exist")] MlNotFound { diff --git a/core/src/kvs/tx.rs b/core/src/kvs/tx.rs index b54ce0f1..2a6041a1 100644 --- a/core/src/kvs/tx.rs +++ b/core/src/kvs/tx.rs @@ -1908,6 +1908,36 @@ impl Transaction { Ok(val.into()) } + /// Retrieve a specific function definition from a database. + #[cfg(feature = "sql2")] + pub async fn get_db_function( + &mut self, + ns: &str, + db: &str, + fc: &str, + ) -> Result { + let key = crate::key::database::fc::new(ns, db, fc); + let val = self.get(key).await?.ok_or(Error::FcNotFound { + value: fc.to_owned(), + })?; + Ok(val.into()) + } + + /// Retrieve a specific function definition from a database. + #[cfg(feature = "sql2")] + pub async fn get_db_param( + &mut self, + ns: &str, + db: &str, + pa: &str, + ) -> Result { + let key = crate::key::database::pa::new(ns, db, pa); + let val = self.get(key).await?.ok_or(Error::PaNotFound { + value: pa.to_owned(), + })?; + Ok(val.into()) + } + /// Retrieve a specific scope definition. pub async fn get_sc( &mut self, @@ -1983,6 +2013,60 @@ impl Transaction { Ok(val.into()) } + /// Retrieve an event for a table. + #[cfg(feature = "sql2")] + pub async fn get_tb_event( + &mut self, + ns: &str, + db: &str, + tb: &str, + ev: &str, + ) -> Result { + let key = crate::key::table::ev::new(ns, db, tb, ev); + let key_enc = crate::key::table::ev::Ev::encode(&key)?; + trace!("Getting ev ({:?}) {:?}", ev, crate::key::debug::sprint_key(&key_enc)); + let val = self.get(key_enc).await?.ok_or(Error::EvNotFound { + value: ev.to_string(), + })?; + Ok(val.into()) + } + + /// Retrieve an event for a table. + #[cfg(feature = "sql2")] + pub async fn get_tb_field( + &mut self, + ns: &str, + db: &str, + tb: &str, + fd: &str, + ) -> Result { + let key = crate::key::table::fd::new(ns, db, tb, fd); + let key_enc = crate::key::table::fd::Fd::encode(&key)?; + trace!("Getting fd ({:?}) {:?}", fd, crate::key::debug::sprint_key(&key_enc)); + let val = self.get(key_enc).await?.ok_or(Error::FdNotFound { + value: fd.to_string(), + })?; + Ok(val.into()) + } + + /// Retrieve an event for a table. + #[cfg(feature = "sql2")] + pub async fn get_tb_index( + &mut self, + ns: &str, + db: &str, + tb: &str, + ix: &str, + ) -> Result { + let key = crate::key::table::ix::new(ns, db, tb, ix); + let key_enc = crate::key::table::ix::Ix::encode(&key)?; + trace!("Getting ix ({:?}) {:?}", ix, crate::key::debug::sprint_key(&key_enc)); + let val = self.get(key_enc).await?.ok_or(Error::IxNotFound { + value: ix.to_string(), + })?; + Ok(val.into()) + } + /// Add a namespace with a default configuration, only if we are in dynamic mode. pub async fn add_ns( &mut self, diff --git a/core/src/sql/v1/statements/remove/param.rs b/core/src/sql/v1/statements/remove/param.rs index 89c82e0b..53da35fa 100644 --- a/core/src/sql/v1/statements/remove/param.rs +++ b/core/src/sql/v1/statements/remove/param.rs @@ -39,6 +39,6 @@ impl RemoveParamStatement { impl Display for RemoveParamStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE PARAM {}", self.name) + write!(f, "REMOVE PARAM ${}", self.name) } } diff --git a/core/src/sql/v2/statements/remove/analyzer.rs b/core/src/sql/v2/statements/remove/analyzer.rs index b32c7e15..2880b6bf 100644 --- a/core/src/sql/v2/statements/remove/analyzer.rs +++ b/core/src/sql/v2/statements/remove/analyzer.rs @@ -10,9 +10,11 @@ use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveAnalyzerStatement { pub name: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveAnalyzerStatement { @@ -22,23 +24,39 @@ impl RemoveAnalyzerStatement { 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) + match async { + // 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(); + // Get the definition + let az = run.get_db_analyzer(opt.ns(), opt.db(), &self.name).await?; + // Delete the definition + let key = crate::key::database::az::new(opt.ns(), opt.db(), &az.name); + run.del(key).await?; + // TODO Check that the analyzer is not used in any schema + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::AzNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveAnalyzerStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE ANALYZER {}", self.name) + write!(f, "REMOVE ANALYZER")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {}", self.name)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/database.rs b/core/src/sql/v2/statements/remove/database.rs index 5dbd3e1d..c40761ec 100644 --- a/core/src/sql/v2/statements/remove/database.rs +++ b/core/src/sql/v2/statements/remove/database.rs @@ -10,9 +10,11 @@ use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveDatabaseStatement { pub name: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveDatabaseStatement { @@ -23,25 +25,41 @@ impl RemoveDatabaseStatement { 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) + match async { + // 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(); + // Get the definition + let db = run.get_db(opt.ns(), &self.name).await?; + // Delete the definition + let key = crate::key::namespace::db::new(opt.ns(), &db.name); + run.del(key).await?; + // Delete the resource data + let key = crate::key::database::all::new(opt.ns(), &db.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::DbNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveDatabaseStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE DATABASE {}", self.name) + write!(f, "REMOVE DATABASE")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {}", self.name)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/event.rs b/core/src/sql/v2/statements/remove/event.rs index 30d7d121..9f35e6c1 100644 --- a/core/src/sql/v2/statements/remove/event.rs +++ b/core/src/sql/v2/statements/remove/event.rs @@ -10,10 +10,12 @@ use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveEventStatement { pub name: Ident, pub what: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveEventStatement { @@ -24,25 +26,41 @@ impl RemoveEventStatement { 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) + match async { + // 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(); + // Get the definition + let ev = run.get_tb_event(opt.ns(), opt.db(), &self.what, &self.name).await?; + // Delete the definition + let key = crate::key::table::ev::new(opt.ns(), opt.db(), &ev.what, &ev.name); + run.del(key).await?; + // Clear the cache + let key = crate::key::table::ev::prefix(opt.ns(), opt.db(), &ev.what); + run.clr(key).await?; + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::EvNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveEventStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE EVENT {} ON {}", self.name, self.what) + write!(f, "REMOVE EVENT")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {} ON {}", self.name, self.what)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/field.rs b/core/src/sql/v2/statements/remove/field.rs index 51f3f2dc..0d9fa939 100644 --- a/core/src/sql/v2/statements/remove/field.rs +++ b/core/src/sql/v2/statements/remove/field.rs @@ -9,11 +9,13 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RemoveFieldStatement { pub name: Idiom, pub what: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveFieldStatement { @@ -24,26 +26,43 @@ impl RemoveFieldStatement { 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) + match async { + // 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(); + // Get the definition + let fd_name = self.name.to_string(); + let fd = run.get_tb_field(opt.ns(), opt.db(), &self.what, &fd_name).await?; + // Delete the definition + let fd_name = fd.name.to_string(); + let key = crate::key::table::fd::new(opt.ns(), opt.db(), &self.what, &fd_name); + 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) + } + .await + { + Err(Error::FdNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveFieldStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE FIELD {} ON {}", self.name, self.what) + write!(f, "REMOVE FIELD")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {} ON {}", self.name, self.what)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/function.rs b/core/src/sql/v2/statements/remove/function.rs index d35037d9..431a08e3 100644 --- a/core/src/sql/v2/statements/remove/function.rs +++ b/core/src/sql/v2/statements/remove/function.rs @@ -10,9 +10,11 @@ use std::fmt::{self, Display}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveFunctionStatement { pub name: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveFunctionStatement { @@ -23,23 +25,39 @@ impl RemoveFunctionStatement { 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) + match async { + // 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(); + // Get the definition + let fc = run.get_db_function(opt.ns(), opt.db(), &self.name).await?; + // Delete the definition + let key = crate::key::database::fc::new(opt.ns(), opt.db(), &fc.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::FcNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveFunctionStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Bypass ident display since we don't want backticks arround the ident. - write!(f, "REMOVE FUNCTION fn::{}", self.name.0) + write!(f, "REMOVE FUNCTION")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " fn::{}", self.name.0)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/index.rs b/core/src/sql/v2/statements/remove/index.rs index 06da30c3..6cbfdf2a 100644 --- a/core/src/sql/v2/statements/remove/index.rs +++ b/core/src/sql/v2/statements/remove/index.rs @@ -9,11 +9,13 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RemoveIndexStatement { pub name: Ident, pub what: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveIndexStatement { @@ -24,30 +26,44 @@ impl RemoveIndexStatement { 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 index store cache - ctx.get_index_stores().index_removed(opt, &mut run, &self.what, &self.name).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) + match async { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Index, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the index store cache + ctx.get_index_stores().index_removed(opt, &mut run, &self.what, &self.name).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) + } + .await + { + Err(Error::IxNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveIndexStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE INDEX {} ON {}", self.name, self.what) + write!(f, "REMOVE INDEX")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {} ON {}", self.name, self.what)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/model.rs b/core/src/sql/v2/statements/remove/model.rs index dfec396e..6d7460e5 100644 --- a/core/src/sql/v2/statements/remove/model.rs +++ b/core/src/sql/v2/statements/remove/model.rs @@ -9,11 +9,13 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Display}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RemoveModelStatement { pub name: Ident, pub version: String, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveModelStatement { @@ -24,25 +26,39 @@ impl RemoveModelStatement { opt: &Options, txn: &Transaction, ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Model, &Base::Db)?; - // Claim transaction - let mut run = txn.lock().await; - // Clear the cache - run.clear_cache(); - // Delete the definition - let key = crate::key::database::ml::new(opt.ns(), opt.db(), &self.name, &self.version); - run.del(key).await?; - // Remove the model file - // TODO - // Ok all good - Ok(Value::None) + match async { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Model, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Delete the definition + let key = crate::key::database::ml::new(opt.ns(), opt.db(), &self.name, &self.version); + run.del(key).await?; + // Remove the model file + // TODO + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::MlNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveModelStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Bypass ident display since we don't want backticks arround the ident. - write!(f, "REMOVE MODEL ml::{}<{}>", self.name.0, self.version) + write!(f, "REMOVE MODEL")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " ml::{}<{}>", self.name.0, self.version)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/namespace.rs b/core/src/sql/v2/statements/remove/namespace.rs index 51559eb1..68d72d90 100644 --- a/core/src/sql/v2/statements/remove/namespace.rs +++ b/core/src/sql/v2/statements/remove/namespace.rs @@ -10,9 +10,11 @@ use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveNamespaceStatement { pub name: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveNamespaceStatement { @@ -23,26 +25,42 @@ impl RemoveNamespaceStatement { 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; - ctx.get_index_stores().namespace_removed(opt, &mut run).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) + match async { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Namespace, &Base::Root)?; + // Claim transaction + let mut run = txn.lock().await; + ctx.get_index_stores().namespace_removed(opt, &mut run).await?; + // Clear the cache + run.clear_cache(); + // Get the definition + let ns = run.get_ns(&self.name).await?; + // Delete the definition + let key = crate::key::root::ns::new(&ns.name); + run.del(key).await?; + // Delete the resource data + let key = crate::key::namespace::all::new(&ns.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::NsNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveNamespaceStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE NAMESPACE {}", self.name) + write!(f, "REMOVE NAMESPACE")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {}", self.name)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/param.rs b/core/src/sql/v2/statements/remove/param.rs index 89c82e0b..13aedc96 100644 --- a/core/src/sql/v2/statements/remove/param.rs +++ b/core/src/sql/v2/statements/remove/param.rs @@ -9,10 +9,12 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RemoveParamStatement { pub name: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveParamStatement { @@ -23,22 +25,38 @@ impl RemoveParamStatement { 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) + match async { + // 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(); + // Get the definition + let pa = run.get_db_param(opt.ns(), opt.db(), &self.name).await?; + // Delete the definition + let key = crate::key::database::pa::new(opt.ns(), opt.db(), &pa.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::PaNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveParamStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE PARAM {}", self.name) + write!(f, "REMOVE PARAM")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " ${}", self.name)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/scope.rs b/core/src/sql/v2/statements/remove/scope.rs index 290ad9ea..18af9785 100644 --- a/core/src/sql/v2/statements/remove/scope.rs +++ b/core/src/sql/v2/statements/remove/scope.rs @@ -10,9 +10,11 @@ use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveScopeStatement { pub name: Ident, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveScopeStatement { @@ -23,25 +25,41 @@ impl RemoveScopeStatement { 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) + match async { + // 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(); + // Get the definition + let sc = run.get_sc(opt.ns(), opt.db(), &self.name).await?; + // Delete the definition + let key = crate::key::database::sc::new(opt.ns(), opt.db(), &sc.name); + run.del(key).await?; + // Remove the resource data + let key = crate::key::scope::all::new(opt.ns(), opt.db(), &sc.name); + run.delp(key, u32::MAX).await?; + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::ScNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, + } } } impl Display for RemoveScopeStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE SCOPE {}", self.name) + write!(f, "REMOVE SCOPE")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {}", self.name)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/table.rs b/core/src/sql/v2/statements/remove/table.rs index 968d571e..92e5eea3 100644 --- a/core/src/sql/v2/statements/remove/table.rs +++ b/core/src/sql/v2/statements/remove/table.rs @@ -26,52 +26,52 @@ impl RemoveTableStatement { 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; - // Remove the index stores - ctx.get_index_stores().table_removed(opt, &mut run, &self.name).await?; - // Clear the cache - run.clear_cache(); - // Get the defined table - match run.get_tb(opt.ns(), opt.db(), &self.name).await { - Ok(tb) => { - // 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?; - // Check if this is a foreign table - if let Some(view) = &tb.view { - // 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.del(key).await?; - } - } - // Ok all good - Ok(Value::None) - } - Err(err) => { - if matches!(err, Error::TbNotFound { .. }) && self.if_exists { - Ok(Value::None) - } else { - Err(err) + match async { + // Allowed to run? + opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?; + // Claim transaction + let mut run = txn.lock().await; + // Remove the index stores + ctx.get_index_stores().table_removed(opt, &mut run, &self.name).await?; + // Clear the cache + run.clear_cache(); + // Get the defined table + let tb = run.get_tb(opt.ns(), opt.db(), &self.name).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?; + // Check if this is a foreign table + if let Some(view) = &tb.view { + // 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.del(key).await?; } } + // Ok all good + Ok(Value::None) + } + .await + { + Err(Error::TbNotFound { + .. + }) if self.if_exists => Ok(Value::None), + v => v, } } } impl Display for RemoveTableStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE TABLE {}", self.name)?; + write!(f, "REMOVE TABLE")?; if self.if_exists { write!(f, " IF EXISTS")? } + write!(f, " {}", self.name)?; Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/token.rs b/core/src/sql/v2/statements/remove/token.rs index 9b689f95..5adcdfd2 100644 --- a/core/src/sql/v2/statements/remove/token.rs +++ b/core/src/sql/v2/statements/remove/token.rs @@ -10,10 +10,12 @@ use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] pub struct RemoveTokenStatement { pub name: Ident, pub base: Base, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveTokenStatement { @@ -24,50 +26,79 @@ impl RemoveTokenStatement { opt: &Options, txn: &Transaction, ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; + match async { + // 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) + match &self.base { + Base::Ns => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Get the definition + let tk = run.get_ns_token(opt.ns(), &self.name).await?; + // Delete the definition + let key = crate::key::namespace::tk::new(opt.ns(), &tk.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(); + // Get the definition + let tk = run.get_db_token(opt.ns(), opt.db(), &self.name).await?; + // Delete the definition + let key = crate::key::database::tk::new(opt.ns(), opt.db(), &tk.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(); + // Get the definition + let tk = run.get_sc_token(opt.ns(), opt.db(), sc, &self.name).await?; + // Delete the definition + let key = crate::key::scope::tk::new(opt.ns(), opt.db(), sc, &tk.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + _ => Err(Error::InvalidLevel(self.base.to_string())), } - 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())), + } + .await + { + Err(e) if self.if_exists => match e { + Error::NtNotFound { + .. + } => Ok(Value::None), + Error::DtNotFound { + .. + } => Ok(Value::None), + Error::StNotFound { + .. + } => Ok(Value::None), + e => Err(e), + }, + v => v, } } } impl Display for RemoveTokenStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE TOKEN {} ON {}", self.name, self.base) + write!(f, "REMOVE TOKEN")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {} ON {}", self.name, self.base)?; + Ok(()) } } diff --git a/core/src/sql/v2/statements/remove/user.rs b/core/src/sql/v2/statements/remove/user.rs index 5248d59f..bfc93b1c 100644 --- a/core/src/sql/v2/statements/remove/user.rs +++ b/core/src/sql/v2/statements/remove/user.rs @@ -9,11 +9,13 @@ use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RemoveUserStatement { pub name: Ident, pub base: Base, + #[revision(start = 2)] + pub if_exists: bool, } impl RemoveUserStatement { @@ -24,50 +26,79 @@ impl RemoveUserStatement { opt: &Options, txn: &Transaction, ) -> Result { - // Allowed to run? - opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?; + match async { + // 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) + match self.base { + Base::Root => { + // Claim transaction + let mut run = txn.lock().await; + // Clear the cache + run.clear_cache(); + // Get the definition + let us = run.get_root_user(&self.name).await?; + // Process the statement + let key = crate::key::root::us::new(&us.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(); + // Get the definition + let us = run.get_ns_user(opt.ns(), &self.name).await?; + // Delete the definition + let key = crate::key::namespace::us::new(opt.ns(), &us.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(); + // Get the definition + let us = run.get_db_user(opt.ns(), opt.db(), &self.name).await?; + // Delete the definition + let key = crate::key::database::us::new(opt.ns(), opt.db(), &us.name); + run.del(key).await?; + // Ok all good + Ok(Value::None) + } + _ => Err(Error::InvalidLevel(self.base.to_string())), } - 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())), + } + .await + { + Err(e) if self.if_exists => match e { + Error::UserRootNotFound { + .. + } => Ok(Value::None), + Error::UserNsNotFound { + .. + } => Ok(Value::None), + Error::UserDbNotFound { + .. + } => Ok(Value::None), + e => Err(e), + }, + v => v, } } } impl Display for RemoveUserStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "REMOVE USER {} ON {}", self.name, self.base) + write!(f, "REMOVE USER")?; + if self.if_exists { + write!(f, " IF EXISTS")? + } + write!(f, " {} ON {}", self.name, self.base)?; + Ok(()) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/analyzer.rs b/core/src/sql/v2/value/serde/ser/statement/remove/analyzer.rs index c78ce590..ed9028bd 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/analyzer.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/analyzer.rs @@ -36,6 +36,7 @@ impl ser::Serializer for Serializer { #[derive(Default)] pub struct SerializeRemoveAnalyzerStatement { name: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveAnalyzerStatement { @@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveAnalyzerStatement { "name" => { self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `RemoveAnalyzerStatement::{key}`" @@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveAnalyzerStatement { fn end(self) -> Result { Ok(RemoveAnalyzerStatement { name: self.name, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/database.rs b/core/src/sql/v2/value/serde/ser/statement/remove/database.rs index 98b7b153..55b1eb70 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/database.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/database.rs @@ -36,6 +36,7 @@ impl ser::Serializer for Serializer { #[derive(Default)] pub struct SerializeRemoveDatabaseStatement { name: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveDatabaseStatement { @@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveDatabaseStatement { "name" => { self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `RemoveDatabaseStatement::{key}`" @@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveDatabaseStatement { fn end(self) -> Result { Ok(RemoveDatabaseStatement { name: self.name, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/event.rs b/core/src/sql/v2/value/serde/ser/statement/remove/event.rs index 33bf77f5..b06094d6 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/event.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/event.rs @@ -37,6 +37,7 @@ impl ser::Serializer for Serializer { pub struct SerializeRemoveEventStatement { name: Ident, what: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveEventStatement { @@ -54,6 +55,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveEventStatement { "what" => { self.what = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `RemoveEventStatement::{key}`" @@ -67,6 +71,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveEventStatement { Ok(RemoveEventStatement { name: self.name, what: self.what, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/field.rs b/core/src/sql/v2/value/serde/ser/statement/remove/field.rs index 78ca4e57..c5272249 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/field.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/field.rs @@ -38,6 +38,7 @@ impl ser::Serializer for Serializer { pub struct SerializeRemoveFieldStatement { name: Idiom, what: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveFieldStatement { @@ -55,6 +56,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveFieldStatement { "what" => { self.what = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `RemoveFieldStatement::{key}`" @@ -68,6 +72,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveFieldStatement { Ok(RemoveFieldStatement { name: self.name, what: self.what, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/function.rs b/core/src/sql/v2/value/serde/ser/statement/remove/function.rs index a608fe6d..a0734162 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/function.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/function.rs @@ -36,6 +36,7 @@ impl ser::Serializer for Serializer { #[derive(Default)] pub struct SerializeRemoveFunctionStatement { name: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveFunctionStatement { @@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveFunctionStatement { "name" => { self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveFunctionStatement::{key}`" @@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveFunctionStatement { fn end(self) -> Result { Ok(RemoveFunctionStatement { name: self.name, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/index.rs b/core/src/sql/v2/value/serde/ser/statement/remove/index.rs index 950045f0..5f0787a4 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/index.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/index.rs @@ -37,6 +37,7 @@ impl ser::Serializer for Serializer { pub struct SerializeRemoveIndexStatement { name: Ident, what: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveIndexStatement { @@ -54,6 +55,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveIndexStatement { "what" => { self.what = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveIndexStatement::{key}`" @@ -67,6 +71,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveIndexStatement { Ok(RemoveIndexStatement { name: self.name, what: self.what, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/namespace.rs b/core/src/sql/v2/value/serde/ser/statement/remove/namespace.rs index 77659b3f..b4c5b8df 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/namespace.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/namespace.rs @@ -36,6 +36,7 @@ impl ser::Serializer for Serializer { #[derive(Default)] pub struct SerializeRemoveNamespaceStatement { name: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveNamespaceStatement { @@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveNamespaceStatement { "name" => { self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveNamespaceStatement::{key}`" @@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveNamespaceStatement { fn end(self) -> Result { Ok(RemoveNamespaceStatement { name: self.name, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/param.rs b/core/src/sql/v2/value/serde/ser/statement/remove/param.rs index a0b99800..0656abc7 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/param.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/param.rs @@ -36,6 +36,7 @@ impl ser::Serializer for Serializer { #[derive(Default)] pub struct SerializeRemoveParamStatement { name: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveParamStatement { @@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveParamStatement { "name" => { self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveParamStatement::{key}`" @@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveParamStatement { fn end(self) -> Result { Ok(RemoveParamStatement { name: self.name, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/scope.rs b/core/src/sql/v2/value/serde/ser/statement/remove/scope.rs index 54eb1b93..ba84fd19 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/scope.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/scope.rs @@ -36,6 +36,7 @@ impl ser::Serializer for Serializer { #[derive(Default)] pub struct SerializeRemoveScopeStatement { name: Ident, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveScopeStatement { @@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveScopeStatement { "name" => { self.name = Ident(value.serialize(ser::string::Serializer.wrap())?); } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveScopeStatement::{key}`" @@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveScopeStatement { fn end(self) -> Result { Ok(RemoveScopeStatement { name: self.name, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/token.rs b/core/src/sql/v2/value/serde/ser/statement/remove/token.rs index cd558205..a81f1948 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/token.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/token.rs @@ -38,6 +38,7 @@ impl ser::Serializer for Serializer { pub struct SerializeRemoveTokenStatement { name: Ident, base: Base, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement { @@ -55,6 +56,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement { "base" => { self.base = value.serialize(ser::base::Serializer.wrap())?; } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveTokenStatement::{key}`" @@ -68,6 +72,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveTokenStatement { Ok(RemoveTokenStatement { name: self.name, base: self.base, + if_exists: self.if_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/remove/user.rs b/core/src/sql/v2/value/serde/ser/statement/remove/user.rs index b60af58b..55fe3b5a 100644 --- a/core/src/sql/v2/value/serde/ser/statement/remove/user.rs +++ b/core/src/sql/v2/value/serde/ser/statement/remove/user.rs @@ -38,6 +38,7 @@ impl ser::Serializer for Serializer { pub struct SerializeRemoveUserStatement { name: Ident, base: Base, + if_exists: bool, } impl serde::ser::SerializeStruct for SerializeRemoveUserStatement { @@ -55,6 +56,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveUserStatement { "base" => { self.base = value.serialize(ser::base::Serializer.wrap())?; } + "if_exists" => { + self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?; + } key => { return Err(Error::custom(format!( "unexpected field `RemoveUserStatement::{key}`" @@ -68,6 +72,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveUserStatement { Ok(RemoveUserStatement { name: self.name, base: self.base, + if_exists: self.if_exists, }) } } diff --git a/core/src/syn/v1/stmt/remove.rs b/core/src/syn/v1/stmt/remove.rs index c4fff7ed..028b9278 100644 --- a/core/src/syn/v1/stmt/remove.rs +++ b/core/src/syn/v1/stmt/remove.rs @@ -41,30 +41,52 @@ pub fn remove(i: &str) -> IResult<&str, RemoveStatement> { pub fn analyzer(i: &str) -> IResult<&str, RemoveAnalyzerStatement> { let (i, _) = tag_no_case("ANALYZER")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; Ok(( i, RemoveAnalyzerStatement { name, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn database(i: &str) -> IResult<&str, RemoveDatabaseStatement> { let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; Ok(( i, RemoveDatabaseStatement { name, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn event(i: &str) -> IResult<&str, RemoveEventStatement> { let (i, _) = tag_no_case("EVENT")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; @@ -77,12 +99,20 @@ pub fn event(i: &str) -> IResult<&str, RemoveEventStatement> { RemoveEventStatement { name, what, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn field(i: &str) -> IResult<&str, RemoveFieldStatement> { let (i, _) = tag_no_case("FIELD")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(idiom::local)(i)?; let (i, _) = shouldbespace(i)?; @@ -95,12 +125,20 @@ pub fn field(i: &str) -> IResult<&str, RemoveFieldStatement> { RemoveFieldStatement { name, what, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn function(i: &str) -> IResult<&str, RemoveFunctionStatement> { let (i, _) = tag_no_case("FUNCTION")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = tag("fn::")(i)?; let (i, name) = ident_path(i)?; @@ -115,12 +153,20 @@ pub fn function(i: &str) -> IResult<&str, RemoveFunctionStatement> { i, RemoveFunctionStatement { name, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn index(i: &str) -> IResult<&str, RemoveIndexStatement> { let (i, _) = tag_no_case("INDEX")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; @@ -133,24 +179,40 @@ pub fn index(i: &str) -> IResult<&str, RemoveIndexStatement> { RemoveIndexStatement { name, what, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn namespace(i: &str) -> IResult<&str, RemoveNamespaceStatement> { let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; Ok(( i, RemoveNamespaceStatement { name, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn param(i: &str) -> IResult<&str, RemoveParamStatement> { let (i, _) = tag_no_case("PARAM")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = cut(char('$'))(i)?; let (i, name) = cut(ident)(i)?; @@ -158,32 +220,42 @@ pub fn param(i: &str) -> IResult<&str, RemoveParamStatement> { i, RemoveParamStatement { name, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn scope(i: &str) -> IResult<&str, RemoveScopeStatement> { let (i, _) = tag_no_case("SCOPE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = cut(ident)(i)?; - Ok(( - i, - RemoveScopeStatement { - name, - }, - )) -} - -pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> { - let (i, _) = tag_no_case("TABLE")(i)?; - let (i, _) = shouldbespace(i)?; - let (i, name) = cut(ident)(i)?; #[cfg(feature = "sql2")] let (i, if_exists) = opt(tuple(( shouldbespace, tag_no_case("IF"), cut(tuple((shouldbespace, tag_no_case("EXISTS")))), )))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = cut(ident)(i)?; + Ok(( + i, + RemoveScopeStatement { + name, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), + }, + )) +} + +pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> { + let (i, _) = tag_no_case("TABLE")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; + let (i, _) = shouldbespace(i)?; + let (i, name) = cut(ident)(i)?; Ok(( i, RemoveTableStatement { @@ -196,6 +268,12 @@ pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> { pub fn token(i: &str) -> IResult<&str, RemoveTokenStatement> { let (i, _) = tag_no_case("TOKEN")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; @@ -207,12 +285,20 @@ pub fn token(i: &str) -> IResult<&str, RemoveTokenStatement> { RemoveTokenStatement { name, base, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } pub fn user(i: &str) -> IResult<&str, RemoveUserStatement> { let (i, _) = tag_no_case("USER")(i)?; + #[cfg(feature = "sql2")] + let (i, if_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, _) = shouldbespace(i)?; @@ -224,6 +310,8 @@ pub fn user(i: &str) -> IResult<&str, RemoveUserStatement> { RemoveUserStatement { name, base, + #[cfg(feature = "sql2")] + if_exists: if_exists.is_some(), }, )) } @@ -238,9 +326,149 @@ mod tests { fn check_remove_serialize() { let stm = RemoveStatement::Namespace(RemoveNamespaceStatement { name: Ident::from("test"), + #[cfg(feature = "sql2")] + if_exists: false, }); let enc: Vec = stm.try_into().unwrap(); + #[cfg(not(feature = "sql2"))] assert_eq!(9, enc.len()); + #[cfg(feature = "sql2")] + assert_eq!(10, enc.len()); + } + + /// REMOVE ANALYZER tests + + #[test] + fn remove_analyzer() { + let sql = "REMOVE ANALYZER test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE ANALYZER test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_analyzer_if_exists() { + let sql = "REMOVE ANALYZER IF EXISTS test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE ANALYZER IF EXISTS test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_analyzer_if() { + let sql = "REMOVE ANALYZER IF test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE DATABASE tests + + #[test] + fn remove_database() { + let sql = "REMOVE DATABASE test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE DATABASE test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_database_if_exists() { + let sql = "REMOVE DATABASE IF EXISTS test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE DATABASE IF EXISTS test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_database_if() { + let sql = "REMOVE DATABASE IF test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE EVENT tests + + #[test] + fn remove_event() { + let sql = "REMOVE EVENT test ON test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE EVENT test ON test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_event_if_exists() { + let sql = "REMOVE EVENT IF EXISTS test ON test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE EVENT IF EXISTS test ON test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_event_if() { + let sql = "REMOVE EVENT IF test ON test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE FIELD tests + + #[test] + fn remove_field() { + let sql = "REMOVE FIELD test ON test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE FIELD test ON test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_field_if_exists() { + let sql = "REMOVE FIELD IF EXISTS test ON test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE FIELD IF EXISTS test ON test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_field_if() { + let sql = "REMOVE FIELD IF test ON test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE FUNCTION tests + + #[test] + fn remove_function() { + let sql = "REMOVE FUNCTION fn::test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE FUNCTION fn::test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_function_if_exists() { + let sql = "REMOVE FUNCTION IF EXISTS fn::test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE FUNCTION IF EXISTS fn::test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_function_if() { + let sql = "REMOVE FUNCTION IF fn::test"; + let res = remove(sql); + assert!(res.is_err()); } #[test] @@ -251,6 +479,116 @@ mod tests { assert_eq!("REMOVE FUNCTION fn::foo::bar::baz::bac", format!("{}", out)) } + /// REMOVE INDEX tests + + #[test] + fn remove_index() { + let sql = "REMOVE INDEX test ON test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE INDEX test ON test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_index_if_exists() { + let sql = "REMOVE INDEX IF EXISTS test ON test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE INDEX IF EXISTS test ON test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_index_if() { + let sql = "REMOVE INDEX IF test ON test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE NAMESPACE tests + + #[test] + fn remove_namespace() { + let sql = "REMOVE NAMESPACE test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE NAMESPACE test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_namespace_if_exists() { + let sql = "REMOVE NAMESPACE IF EXISTS test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE NAMESPACE IF EXISTS test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_namespace_if() { + let sql = "REMOVE NAMESPACE IF test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE PARAM tests + + #[test] + fn remove_param() { + let sql = "REMOVE PARAM $test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE PARAM $test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_param_if_exists() { + let sql = "REMOVE PARAM IF EXISTS $test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE PARAM IF EXISTS $test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_param_if() { + let sql = "REMOVE PARAM IF $test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE SCOPE tests + + #[test] + fn remove_scope() { + let sql = "REMOVE SCOPE test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE SCOPE test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_scope_if_exists() { + let sql = "REMOVE SCOPE IF EXISTS test"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE SCOPE IF EXISTS test", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_scope_if() { + let sql = "REMOVE SCOPE IF test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE TABLE tests + #[test] fn remove_table() { let sql = "REMOVE TABLE test"; @@ -262,16 +600,70 @@ mod tests { #[test] #[cfg(feature = "sql2")] fn remove_table_if_exists() { - let sql = "REMOVE TABLE test IF EXISTS"; + let sql = "REMOVE TABLE IF EXISTS test"; let res = remove(sql); let out = res.unwrap().1; - assert_eq!("REMOVE TABLE test IF EXISTS", format!("{}", out)) + assert_eq!("REMOVE TABLE IF EXISTS test", format!("{}", out)) } #[test] #[cfg(feature = "sql2")] fn remove_table_if() { - let sql = "REMOVE TABLE test IF"; + let sql = "REMOVE TABLE IF test"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE TOKEN tests + + #[test] + fn remove_token() { + let sql = "REMOVE TOKEN test ON NAMESPACE"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE TOKEN test ON NAMESPACE", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_token_if_exists() { + let sql = "REMOVE TOKEN IF EXISTS test ON NAMESPACE"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE TOKEN IF EXISTS test ON NAMESPACE", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_token_if() { + let sql = "REMOVE TOKEN IF test ON NAMESPACE"; + let res = remove(sql); + assert!(res.is_err()); + } + + /// REMOVE USER tests + + #[test] + fn remove_user() { + let sql = "REMOVE USER test ON ROOT"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE USER test ON ROOT", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_user_if_exists() { + let sql = "REMOVE USER IF EXISTS test ON ROOT"; + let res = remove(sql); + let out = res.unwrap().1; + assert_eq!("REMOVE USER IF EXISTS test ON ROOT", format!("{}", out)) + } + + #[test] + #[cfg(feature = "sql2")] + fn remove_user_if() { + let sql = "REMOVE USER IF test ON ROOT"; let res = remove(sql); assert!(res.is_err()); } diff --git a/core/src/syn/v2/parser/stmt/remove.rs b/core/src/syn/v2/parser/stmt/remove.rs index 36a3736a..c6e13765 100644 --- a/core/src/syn/v2/parser/stmt/remove.rs +++ b/core/src/syn/v2/parser/stmt/remove.rs @@ -21,50 +21,6 @@ impl Parser<'_> { pub fn parse_remove_stmt(&mut self) -> ParseResult { let res = match self.next().kind { t!("NAMESPACE") => { - let name = self.next_token_value()?; - RemoveStatement::Namespace(RemoveNamespaceStatement { - name, - }) - } - t!("DATABASE") => { - let name = self.next_token_value()?; - RemoveStatement::Database(RemoveDatabaseStatement { - name, - }) - } - t!("FUNCTION") => { - let name = self.parse_custom_function_name()?; - let next = self.peek(); - if self.eat(t!("(")) { - self.expect_closing_delimiter(t!(")"), next.span)?; - } - RemoveStatement::Function(RemoveFunctionStatement { - name, - }) - } - t!("TOKEN") => { - let name = self.next_token_value()?; - expected!(self, t!("ON")); - let base = self.parse_base(true)?; - RemoveStatement::Token(crate::sql::statements::RemoveTokenStatement { - name, - base, - }) - } - t!("SCOPE") => { - let name = self.next_token_value()?; - RemoveStatement::Scope(RemoveScopeStatement { - name, - }) - } - t!("PARAM") => { - let name = self.next_token_value::()?; - RemoveStatement::Param(RemoveParamStatement { - name: name.0, - }) - } - t!("TABLE") => { - let name = self.next_token_value()?; #[cfg(feature = "sql2")] let if_exists = if self.eat(t!("IF")) { expected!(self, t!("EXISTS")); @@ -72,6 +28,110 @@ impl Parser<'_> { } else { false }; + let name = self.next_token_value()?; + + RemoveStatement::Namespace(RemoveNamespaceStatement { + name, + #[cfg(feature = "sql2")] + if_exists, + }) + } + t!("DATABASE") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.next_token_value()?; + + RemoveStatement::Database(RemoveDatabaseStatement { + name, + #[cfg(feature = "sql2")] + if_exists, + }) + } + t!("FUNCTION") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.parse_custom_function_name()?; + let next = self.peek(); + if self.eat(t!("(")) { + self.expect_closing_delimiter(t!(")"), next.span)?; + } + + RemoveStatement::Function(RemoveFunctionStatement { + name, + #[cfg(feature = "sql2")] + if_exists, + }) + } + t!("TOKEN") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.next_token_value()?; + expected!(self, t!("ON")); + let base = self.parse_base(true)?; + + RemoveStatement::Token(crate::sql::statements::RemoveTokenStatement { + name, + base, + #[cfg(feature = "sql2")] + if_exists, + }) + } + t!("SCOPE") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.next_token_value()?; + + RemoveStatement::Scope(RemoveScopeStatement { + name, + #[cfg(feature = "sql2")] + if_exists, + }) + } + t!("PARAM") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.next_token_value::()?; + + RemoveStatement::Param(RemoveParamStatement { + name: name.0, + #[cfg(feature = "sql2")] + if_exists, + }) + } + t!("TABLE") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; + let name = self.next_token_value()?; RemoveStatement::Table(crate::sql::statements::RemoveTableStatement { name, @@ -80,48 +140,98 @@ impl Parser<'_> { }) } t!("EVENT") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); self.eat(t!("TABLE")); let table = self.next_token_value()?; + RemoveStatement::Event(RemoveEventStatement { name, what: table, + #[cfg(feature = "sql2")] + if_exists, }) } t!("FIELD") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; let idiom = self.parse_local_idiom()?; expected!(self, t!("ON")); self.eat(t!("TABLE")); let table = self.next_token_value()?; + RemoveStatement::Field(RemoveFieldStatement { name: idiom, what: table, + #[cfg(feature = "sql2")] + if_exists, }) } t!("INDEX") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); self.eat(t!("TABLE")); let what = self.next_token_value()?; + RemoveStatement::Index(RemoveIndexStatement { name, what, + #[cfg(feature = "sql2")] + if_exists, }) } t!("ANALYZER") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; + RemoveStatement::Analyzer(RemoveAnalyzerStatement { name, + #[cfg(feature = "sql2")] + if_exists, }) } t!("USER") => { + #[cfg(feature = "sql2")] + let if_exists = if self.eat(t!("IF")) { + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); let base = self.parse_base(false)?; + RemoveStatement::User(RemoveUserStatement { name, base, + #[cfg(feature = "sql2")] + if_exists, }) } x => unexpected!(self, x, "a remove statement keyword"), diff --git a/core/src/syn/v2/parser/test/stmt.rs b/core/src/syn/v2/parser/test/stmt.rs index 59c176a2..9fb63162 100644 --- a/core/src/syn/v2/parser/test/stmt.rs +++ b/core/src/syn/v2/parser/test/stmt.rs @@ -1057,7 +1057,9 @@ fn parse_remove() { assert_eq!( res, Statement::Remove(RemoveStatement::Namespace(RemoveNamespaceStatement { - name: Ident("ns".to_owned()) + name: Ident("ns".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1065,7 +1067,9 @@ fn parse_remove() { assert_eq!( res, Statement::Remove(RemoveStatement::Database(RemoveDatabaseStatement { - name: Ident("database".to_owned()) + name: Ident("database".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1073,14 +1077,18 @@ fn parse_remove() { assert_eq!( res, Statement::Remove(RemoveStatement::Function(RemoveFunctionStatement { - name: Ident("foo::bar".to_owned()) + name: Ident("foo::bar".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); let res = test_parse!(parse_stmt, r#"REMOVE FUNCTION fn::foo::bar();"#).unwrap(); assert_eq!( res, Statement::Remove(RemoveStatement::Function(RemoveFunctionStatement { - name: Ident("foo::bar".to_owned()) + name: Ident("foo::bar".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1089,7 +1097,9 @@ fn parse_remove() { res, Statement::Remove(RemoveStatement::Token(RemoveTokenStatement { name: Ident("foo".to_owned()), - base: Base::Sc(Ident("bar".to_owned())) + base: Base::Sc(Ident("bar".to_owned())), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1098,6 +1108,8 @@ fn parse_remove() { res, Statement::Remove(RemoveStatement::Scope(RemoveScopeStatement { name: Ident("foo".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1106,6 +1118,8 @@ fn parse_remove() { res, Statement::Remove(RemoveStatement::Param(RemoveParamStatement { name: Ident("foo".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1125,6 +1139,8 @@ fn parse_remove() { Statement::Remove(RemoveStatement::Event(RemoveEventStatement { name: Ident("foo".to_owned()), what: Ident("bar".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1138,6 +1154,8 @@ fn parse_remove() { Part::Index(Number::Int(10)) ]), what: Ident("bar".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1147,6 +1165,8 @@ fn parse_remove() { Statement::Remove(RemoveStatement::Index(RemoveIndexStatement { name: Ident("foo".to_owned()), what: Ident("bar".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1155,6 +1175,8 @@ fn parse_remove() { res, Statement::Remove(RemoveStatement::Analyzer(RemoveAnalyzerStatement { name: Ident("foo".to_owned()), + #[cfg(feature = "sql2")] + if_exists: false, })) ); @@ -1164,6 +1186,8 @@ fn parse_remove() { Statement::Remove(RemoveStatement::User(RemoveUserStatement { name: Ident("foo".to_owned()), base: Base::Db, + #[cfg(feature = "sql2")] + if_exists: false, })) ); } diff --git a/core/src/syn/v2/parser/test/streaming.rs b/core/src/syn/v2/parser/test/streaming.rs index bb7dc230..56685191 100644 --- a/core/src/syn/v2/parser/test/streaming.rs +++ b/core/src/syn/v2/parser/test/streaming.rs @@ -589,6 +589,7 @@ fn statements() -> Vec { }), Statement::Remove(RemoveStatement::Function(RemoveFunctionStatement { name: Ident("foo::bar".to_owned()), + if_exists: false, })), Statement::Remove(RemoveStatement::Field(RemoveFieldStatement { name: Idiom(vec![ @@ -597,6 +598,7 @@ fn statements() -> Vec { Part::Index(Number::Int(10)), ]), what: Ident("bar".to_owned()), + if_exists: false, })), Statement::Update(UpdateStatement { only: true, diff --git a/lib/tests/remove.rs b/lib/tests/remove.rs index 0cabcd50..7939e8aa 100644 --- a/lib/tests/remove.rs +++ b/lib/tests/remove.rs @@ -149,9 +149,383 @@ async fn should_error_when_remove_and_table_does_not_exist() -> Result<(), Error #[tokio::test] #[cfg(feature = "sql2")] -async fn should_not_error_when_remove_if_exists() -> Result<(), Error> { +async fn should_not_error_when_remove_table_if_exists() -> Result<(), Error> { let sql = " - REMOVE TABLE foo IF EXISTS; + REMOVE TABLE IF EXISTS foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_analyzer_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE ANALYZER foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::AzNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_analyzer_if_exists() -> Result<(), Error> { + let sql = " + REMOVE ANALYZER IF EXISTS foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_database_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE DATABASE foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::DbNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_database_if_exists() -> Result<(), Error> { + let sql = " + REMOVE DATABASE IF EXISTS foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_event_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE EVENT foo ON bar; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::EvNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_event_if_exists() -> Result<(), Error> { + let sql = " + REMOVE EVENT IF EXISTS foo ON bar; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_field_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE FIELD foo ON bar; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::FdNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_field_if_exists() -> Result<(), Error> { + let sql = " + REMOVE FIELD IF EXISTS foo ON bar; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_function_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE FUNCTION fn::foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::FcNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_function_if_exists() -> Result<(), Error> { + let sql = " + REMOVE FUNCTION IF EXISTS fn::foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_index_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE INDEX foo ON bar; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::IxNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_index_if_exists() -> Result<(), Error> { + let sql = " + REMOVE INDEX IF EXISTS foo ON bar; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_namespace_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE NAMESPACE foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::NsNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_namespace_if_exists() -> Result<(), Error> { + let sql = " + REMOVE NAMESPACE IF EXISTS foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_param_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE PARAM $foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::PaNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_param_if_exists() -> Result<(), Error> { + let sql = " + REMOVE PARAM IF EXISTS $foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_scope_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE SCOPE foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::ScNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_scope_if_exists() -> Result<(), Error> { + let sql = " + REMOVE SCOPE IF EXISTS foo; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_token_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE TOKEN foo ON NAMESPACE; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::NtNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_token_if_exists() -> Result<(), Error> { + let sql = " + REMOVE TOKEN IF EXISTS foo ON NAMESPACE; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_error_when_remove_and_user_does_not_exist() -> Result<(), Error> { + let sql = " + REMOVE USER foo ON ROOT; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::UserRootNotFound { .. }),); + + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn should_not_error_when_remove_user_if_exists() -> Result<(), Error> { + let sql = " + REMOVE USER IF EXISTS foo ON ROOT; "; let dbs = new_ds().await?; let ses = Session::owner().with_ns("test").with_db("test");