diff --git a/core/src/err/mod.rs b/core/src/err/mod.rs index 3393583f..a21c10d9 100644 --- a/core/src/err/mod.rs +++ b/core/src/err/mod.rs @@ -794,6 +794,128 @@ pub enum Error { #[error("The db is running without an available storage engine")] MissingStorageEngine, + /// The requested analyzer already exists + #[error("The analyzer '{value}' already exists")] + #[cfg(feature = "sql2")] + AzAlreadyExists { + value: String, + }, + + /// The requested database already exists + #[error("The database '{value}' already exists")] + #[cfg(feature = "sql2")] + DbAlreadyExists { + value: String, + }, + + /// The requested event already exists + #[error("The event '{value}' already exists")] + #[cfg(feature = "sql2")] + EvAlreadyExists { + value: String, + }, + + /// The requested field already exists + #[error("The field '{value}' already exists")] + #[cfg(feature = "sql2")] + FdAlreadyExists { + value: String, + }, + + /// The requested function already exists + #[error("The function 'fn::{value}' already exists")] + #[cfg(feature = "sql2")] + FcAlreadyExists { + value: String, + }, + + /// The requested index already exists + #[error("The index '{value}' already exists")] + #[cfg(feature = "sql2")] + IxAlreadyExists { + value: String, + }, + + /// The requested model already exists + #[error("The model '{value}' already exists")] + #[cfg(feature = "sql2")] + MlAlreadyExists { + value: String, + }, + + /// The requested namespace already exists + #[error("The namespace '{value}' already exists")] + #[cfg(feature = "sql2")] + NsAlreadyExists { + value: String, + }, + + /// The requested param already exists + #[error("The param '${value}' already exists")] + #[cfg(feature = "sql2")] + PaAlreadyExists { + value: String, + }, + + /// The requested scope already exists + #[error("The scope '{value}' already exists")] + #[cfg(feature = "sql2")] + ScAlreadyExists { + value: String, + }, + + /// The requested table already exists + #[error("The table '{value}' already exists")] + #[cfg(feature = "sql2")] + TbAlreadyExists { + value: String, + }, + + /// The requested namespace token already exists + #[error("The namespace token '{value}' already exists")] + #[cfg(feature = "sql2")] + NtAlreadyExists { + value: String, + }, + + /// The requested database token already exists + #[error("The database token '{value}' already exists")] + #[cfg(feature = "sql2")] + DtAlreadyExists { + value: String, + }, + + /// The requested scope token already exists + #[error("The scope token '{value}' already exists")] + #[cfg(feature = "sql2")] + StAlreadyExists { + value: String, + }, + + /// The requested user already exists + #[error("The user '{value}' already exists")] + #[cfg(feature = "sql2")] + UserRootAlreadyExists { + value: String, + }, + + /// The requested namespace user already exists + #[error("The user '{value}' already exists in the namespace '{ns}'")] + #[cfg(feature = "sql2")] + UserNsAlreadyExists { + value: String, + ns: String, + }, + + /// The requested database user already exists + #[error("The user '{value}' already exists in the database '{db}'")] + #[cfg(feature = "sql2")] + UserDbAlreadyExists { + value: String, + ns: String, + db: String, + }, + /// The session has expired either because the token used /// to establish it has expired or because an expiration /// was explicitly defined when establishing it diff --git a/core/src/kvs/tests/tb.rs b/core/src/kvs/tests/tb.rs index bc96592e..5402b164 100644 --- a/core/src/kvs/tests/tb.rs +++ b/core/src/kvs/tests/tb.rs @@ -25,7 +25,7 @@ async fn table_definitions_can_be_scanned() { view: None, permissions: Default::default(), changefeed: None, - comment: None, + ..Default::default() }; tx.set(&key, &value).await.unwrap(); @@ -70,6 +70,8 @@ async fn table_definitions_can_be_deleted() { permissions: Default::default(), changefeed: None, comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, }; tx.set(&key, &value).await.unwrap(); diff --git a/core/src/kvs/tx.rs b/core/src/kvs/tx.rs index 2a6041a1..71c2aafb 100644 --- a/core/src/kvs/tx.rs +++ b/core/src/kvs/tx.rs @@ -2057,7 +2057,7 @@ impl Transaction { db: &str, tb: &str, ix: &str, - ) -> Result { + ) -> 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)); diff --git a/core/src/sql/v1/statements/define/database.rs b/core/src/sql/v1/statements/define/database.rs index 6e13dd80..cc86469f 100644 --- a/core/src/sql/v1/statements/define/database.rs +++ b/core/src/sql/v1/statements/define/database.rs @@ -37,7 +37,7 @@ impl DefineDatabaseStatement { // 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 + // Store the db 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?); diff --git a/core/src/sql/v2/statements/define/analyzer.rs b/core/src/sql/v2/statements/define/analyzer.rs index c2911acf..71fb6df7 100644 --- a/core/src/sql/v2/statements/define/analyzer.rs +++ b/core/src/sql/v2/statements/define/analyzer.rs @@ -11,7 +11,7 @@ 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 = 2)] +#[revisioned(revision = 3)] pub struct DefineAnalyzerStatement { pub name: Ident, #[revision(start = 2)] @@ -19,6 +19,8 @@ pub struct DefineAnalyzerStatement { pub tokenizers: Option>, pub filters: Option>, pub comment: Option, + #[revision(start = 3)] + pub if_not_exists: bool, } impl DefineAnalyzerStatement { @@ -35,11 +37,26 @@ impl DefineAnalyzerStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if analyzer already exists + if self.if_not_exists && run.get_db_analyzer(opt.ns(), opt.db(), &self.name).await.is_ok() { + return Err(Error::AzAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + // Persist the definition + run.set( + key, + DefineAnalyzerStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Release the transaction drop(run); // Do we really need this? // Ok all good @@ -49,7 +66,11 @@ impl DefineAnalyzerStatement { impl Display for DefineAnalyzerStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE ANALYZER {}", self.name)?; + write!(f, "DEFINE ANALYZER")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {}", self.name)?; if let Some(ref i) = self.function { write!(f, " FUNCTION fn::{i}")? } diff --git a/core/src/sql/v2/statements/define/database.rs b/core/src/sql/v2/statements/define/database.rs index 6e13dd80..dbbdf857 100644 --- a/core/src/sql/v2/statements/define/database.rs +++ b/core/src/sql/v2/statements/define/database.rs @@ -11,12 +11,14 @@ 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 DefineDatabaseStatement { pub id: Option, pub name: Ident, pub comment: Option, pub changefeed: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineDatabaseStatement { @@ -34,18 +36,34 @@ impl DefineDatabaseStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if database already exists + if self.if_not_exists && run.get_db(opt.ns(), &self.name).await.is_ok() { + return Err(Error::DbAlreadyExists { + value: self.name.to_string(), + }); + } // 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 + // Set the id + let db = DefineDatabaseStatement { + id: Some(run.get_next_db_id(ns.id.unwrap()).await?), + if_not_exists: false, + ..self.clone() + }; + run.set(key, db).await?; } else { - // Store the db - run.set(key, self).await?; + run.set( + key, + DefineDatabaseStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; } // Ok all good Ok(Value::None) @@ -54,7 +72,11 @@ impl DefineDatabaseStatement { impl Display for DefineDatabaseStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE DATABASE {}", self.name)?; + write!(f, "DEFINE DATABASE")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {}", self.name)?; if let Some(ref v) = self.comment { write!(f, " COMMENT {v}")? } diff --git a/core/src/sql/v2/statements/define/event.rs b/core/src/sql/v2/statements/define/event.rs index abb3213a..e3cf7270 100644 --- a/core/src/sql/v2/statements/define/event.rs +++ b/core/src/sql/v2/statements/define/event.rs @@ -11,13 +11,15 @@ 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 DefineEventStatement { pub name: Ident, pub what: Ident, pub when: Value, pub then: Values, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineEventStatement { @@ -35,12 +37,27 @@ impl DefineEventStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if event already exists + if self.if_not_exists + && run.get_tb_event(opt.ns(), opt.db(), &self.what, &self.name).await.is_ok() + { + return Err(Error::EvAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineEventStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Clear the cache let key = crate::key::table::ev::prefix(opt.ns(), opt.db(), &self.what); run.clr(key).await?; @@ -51,11 +68,11 @@ impl DefineEventStatement { 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 - )?; + write!(f, "DEFINE EVENT",)?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {} ON {} WHEN {} THEN {}", self.name, self.what, self.when, self.then)?; if let Some(ref v) = self.comment { write!(f, " COMMENT {v}")? } diff --git a/core/src/sql/v2/statements/define/field.rs b/core/src/sql/v2/statements/define/field.rs index a41b0116..636afa3d 100644 --- a/core/src/sql/v2/statements/define/field.rs +++ b/core/src/sql/v2/statements/define/field.rs @@ -14,7 +14,7 @@ use std::fmt::{self, Display, Write}; #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -#[revisioned(revision = 2)] +#[revisioned(revision = 3)] pub struct DefineFieldStatement { pub name: Idiom, pub what: Ident, @@ -27,6 +27,8 @@ pub struct DefineFieldStatement { pub default: Option, pub permissions: Permissions, pub comment: Option, + #[revision(start = 3)] + pub if_not_exists: bool, } impl DefineFieldStatement { @@ -44,8 +46,15 @@ impl DefineFieldStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); - // Process the statement + // Check if field already exists let fd = self.name.to_string(); + if self.if_not_exists && run.get_tb_field(opt.ns(), opt.db(), &self.what, &fd).await.is_ok() + { + return Err(Error::FdAlreadyExists { + value: self.name.to_string(), + }); + } + // Process the statement 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?; @@ -71,6 +80,7 @@ impl DefineFieldStatement { { DefineFieldStatement { kind: Some(cur_kind), + if_not_exists: false, ..existing.clone() } } else { @@ -93,7 +103,14 @@ impl DefineFieldStatement { } } - run.set(key, self).await?; + run.set( + key, + DefineFieldStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Clear the cache let key = crate::key::table::fd::prefix(opt.ns(), opt.db(), &self.what); run.clr(key).await?; @@ -104,7 +121,11 @@ impl DefineFieldStatement { impl Display for DefineFieldStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE FIELD {} ON {}", self.name, self.what)?; + write!(f, "DEFINE FIELD")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {} ON {}", self.name, self.what)?; if self.flex { write!(f, " FLEXIBLE")? } diff --git a/core/src/sql/v2/statements/define/function.rs b/core/src/sql/v2/statements/define/function.rs index dd06df9b..83a9ff42 100644 --- a/core/src/sql/v2/statements/define/function.rs +++ b/core/src/sql/v2/statements/define/function.rs @@ -14,13 +14,15 @@ use std::fmt::{self, Display, Write}; #[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 DefineFunctionStatement { pub name: Ident, pub args: Vec<(Ident, Kind)>, pub block: Block, pub comment: Option, pub permissions: Permission, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineFunctionStatement { @@ -38,11 +40,25 @@ impl DefineFunctionStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if function already exists + if self.if_not_exists && run.get_db_function(opt.ns(), opt.db(), &self.name).await.is_ok() { + return Err(Error::FcAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineFunctionStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -50,7 +66,11 @@ impl DefineFunctionStatement { impl fmt::Display for DefineFunctionStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE FUNCTION fn::{}(", self.name.0)?; + write!(f, "DEFINE FUNCTION")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " fn::{}(", self.name.0)?; for (i, (name, kind)) in self.args.iter().enumerate() { if i > 0 { f.write_str(", ")?; diff --git a/core/src/sql/v2/statements/define/index.rs b/core/src/sql/v2/statements/define/index.rs index 4985bd0f..190c2331 100644 --- a/core/src/sql/v2/statements/define/index.rs +++ b/core/src/sql/v2/statements/define/index.rs @@ -11,13 +11,15 @@ 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 DefineIndexStatement { pub name: Ident, pub what: Ident, pub cols: Idioms, pub index: Index, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineIndexStatement { @@ -35,12 +37,28 @@ impl DefineIndexStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if index already exists + if self.if_not_exists + && run.get_tb_index(opt.ns(), opt.db(), &self.what, &self.name).await.is_ok() + { + return Err(Error::IxAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineIndexStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .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?; @@ -70,7 +88,11 @@ impl DefineIndexStatement { 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)?; + write!(f, "DEFINE INDEX")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {} ON {} FIELDS {}", self.name, self.what, self.cols)?; if Index::Idx != self.index { write!(f, " {}", self.index)?; } diff --git a/core/src/sql/v2/statements/define/mod.rs b/core/src/sql/v2/statements/define/mod.rs index db2f43e7..c7603117 100644 --- a/core/src/sql/v2/statements/define/mod.rs +++ b/core/src/sql/v2/statements/define/mod.rs @@ -120,6 +120,6 @@ mod tests { ..Default::default() }); let enc: Vec = stm.try_into().unwrap(); - assert_eq!(11, enc.len()); + assert_eq!(12, enc.len()); } } diff --git a/core/src/sql/v2/statements/define/model.rs b/core/src/sql/v2/statements/define/model.rs index 8122d282..0a70126d 100644 --- a/core/src/sql/v2/statements/define/model.rs +++ b/core/src/sql/v2/statements/define/model.rs @@ -14,18 +14,24 @@ use std::fmt::{self, Write}; #[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 DefineModelStatement { pub hash: String, pub name: Ident, pub version: String, pub comment: Option, pub permissions: Permission, + #[revision(start = 2)] + pub if_not_exists: bool, } impl fmt::Display for DefineModelStatement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "DEFINE MODEL ml::{}<{}>", self.name, self.version)?; + write!(f, "DEFINE MODEL")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " ml::{}<{}>", self.name, self.version)?; if let Some(comment) = self.comment.as_ref() { write!(f, " COMMENT {}", comment)?; } @@ -55,11 +61,27 @@ impl DefineModelStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if model already exists + if self.if_not_exists + && run.get_db_model(opt.ns(), opt.db(), &self.name, &self.version).await.is_ok() + { + return Err(Error::MlAlreadyExists { + value: self.name.to_string(), + }); + } // Process the statement let key = crate::key::database::ml::new(opt.ns(), opt.db(), &self.name, &self.version); run.add_ns(opt.ns(), opt.strict).await?; run.add_db(opt.ns(), opt.db(), opt.strict).await?; - run.set(key, self).await?; + run.set( + key, + DefineModelStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Store the model file // TODO // Ok all good diff --git a/core/src/sql/v2/statements/define/namespace.rs b/core/src/sql/v2/statements/define/namespace.rs index e2db1a25..ff6f6bd7 100644 --- a/core/src/sql/v2/statements/define/namespace.rs +++ b/core/src/sql/v2/statements/define/namespace.rs @@ -11,11 +11,13 @@ 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 DefineNamespaceStatement { pub id: Option, pub name: Ident, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineNamespaceStatement { @@ -35,13 +37,29 @@ impl DefineNamespaceStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); - // Set the id + // Check if namespace already exists + if self.if_not_exists && run.get_ns(&self.name).await.is_ok() { + return Err(Error::NsAlreadyExists { + value: self.name.to_string(), + }); + } if self.id.is_none() { - let mut ns = self.clone(); - ns.id = Some(run.get_next_ns_id().await?); + // Set the id + let ns = DefineNamespaceStatement { + id: Some(run.get_next_ns_id().await?), + if_not_exists: false, + ..self.clone() + }; run.set(key, ns).await?; } else { - run.set(key, self).await?; + run.set( + key, + DefineNamespaceStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; } // Ok all good Ok(Value::None) @@ -50,7 +68,11 @@ impl DefineNamespaceStatement { impl Display for DefineNamespaceStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE NAMESPACE {}", self.name)?; + write!(f, "DEFINE NAMESPACE")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {}", self.name)?; if let Some(ref v) = self.comment { write!(f, " COMMENT {v}")? } diff --git a/core/src/sql/v2/statements/define/param.rs b/core/src/sql/v2/statements/define/param.rs index f2be9707..e07dd919 100644 --- a/core/src/sql/v2/statements/define/param.rs +++ b/core/src/sql/v2/statements/define/param.rs @@ -12,12 +12,14 @@ use std::fmt::{self, Display, Write}; #[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 DefineParamStatement { pub name: Ident, pub value: Value, pub comment: Option, pub permissions: Permission, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineParamStatement { @@ -35,16 +37,27 @@ impl DefineParamStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); - // Compute the param - let val = DefineParamStatement { - value: self.value.compute(ctx, opt, txn, doc).await?, - ..self.clone() - }; + // Check if param already exists + if self.if_not_exists && run.get_db_param(opt.ns(), opt.db(), &self.name).await.is_ok() { + return Err(Error::PaAlreadyExists { + value: self.name.to_string(), + }); + } // 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, val).await?; + run.set( + key, + DefineParamStatement { + // Compute the param + value: self.value.compute(ctx, opt, txn, doc).await?, + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -52,7 +65,11 @@ impl DefineParamStatement { impl Display for DefineParamStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE PARAM ${} VALUE {}", self.name, self.value)?; + write!(f, "DEFINE PARAM")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " ${} VALUE {}", self.name, self.value)?; if let Some(ref v) = self.comment { write!(f, " COMMENT {v}")? } diff --git a/core/src/sql/v2/statements/define/scope.rs b/core/src/sql/v2/statements/define/scope.rs index b4cf4888..83a83307 100644 --- a/core/src/sql/v2/statements/define/scope.rs +++ b/core/src/sql/v2/statements/define/scope.rs @@ -13,7 +13,7 @@ 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 DefineScopeStatement { pub name: Ident, pub code: String, @@ -21,6 +21,8 @@ pub struct DefineScopeStatement { pub signup: Option, pub signin: Option, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineScopeStatement { @@ -44,11 +46,25 @@ impl DefineScopeStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if scope already exists + if self.if_not_exists && run.get_sc(opt.ns(), opt.db(), &self.name).await.is_ok() { + return Err(Error::ScAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineScopeStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -56,7 +72,11 @@ impl DefineScopeStatement { impl Display for DefineScopeStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE SCOPE {}", self.name)?; + write!(f, "DEFINE SCOPE")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {}", self.name)?; if let Some(ref v) = self.session { write!(f, " SESSION {v}")? } diff --git a/core/src/sql/v2/statements/define/table.rs b/core/src/sql/v2/statements/define/table.rs index ef2fdb62..d6daa8f9 100644 --- a/core/src/sql/v2/statements/define/table.rs +++ b/core/src/sql/v2/statements/define/table.rs @@ -18,7 +18,7 @@ use crate::sql::{ #[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 DefineTableStatement { pub id: Option, pub name: Ident, @@ -28,6 +28,8 @@ pub struct DefineTableStatement { pub permissions: Permissions, pub changefeed: Option, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineTableStatement { @@ -44,19 +46,29 @@ impl DefineTableStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if table already exists + if self.if_not_exists && run.get_tb(opt.ns(), opt.db(), &self.name).await.is_ok() { + return Err(Error::TbAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; let dt = 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?; - tb + DefineTableStatement { + id: Some(run.get_next_tb_id(ns.id.unwrap(), db.id.unwrap()).await?), + if_not_exists: false, + ..self.clone() + } } else { - run.set(key, self).await?; - self.to_owned() + DefineTableStatement { + if_not_exists: false, + ..self.clone() + } }; + run.set(key, &dt).await?; // Check if table is a view if let Some(view) = &self.view { // Remove the table data @@ -100,7 +112,11 @@ impl DefineTableStatement { impl Display for DefineTableStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "DEFINE TABLE {}", self.name)?; + write!(f, "DEFINE TABLE")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } + write!(f, " {}", self.name)?; if self.drop { f.write_str(" DROP")?; } diff --git a/core/src/sql/v2/statements/define/token.rs b/core/src/sql/v2/statements/define/token.rs index cd870b52..5651ae79 100644 --- a/core/src/sql/v2/statements/define/token.rs +++ b/core/src/sql/v2/statements/define/token.rs @@ -11,13 +11,15 @@ 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 DefineTokenStatement { pub name: Ident, pub base: Base, pub kind: Algorithm, pub code: String, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl DefineTokenStatement { @@ -37,10 +39,23 @@ impl DefineTokenStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if token already exists + if self.if_not_exists && run.get_ns_token(opt.ns(), &self.name).await.is_ok() { + return Err(Error::NtAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineTokenStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -49,11 +64,26 @@ impl DefineTokenStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if token already exists + if self.if_not_exists + && run.get_db_token(opt.ns(), opt.db(), &self.name).await.is_ok() + { + return Err(Error::DtAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineTokenStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -62,12 +92,27 @@ impl DefineTokenStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if token already exists + if self.if_not_exists + && run.get_sc_token(opt.ns(), opt.db(), sc, &self.name).await.is_ok() + { + return Err(Error::StAlreadyExists { + value: self.name.to_string(), + }); + } // 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?; + run.set( + key, + DefineTokenStatement { + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -79,9 +124,13 @@ impl DefineTokenStatement { impl Display for DefineTokenStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE TOKEN",)?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } write!( f, - "DEFINE TOKEN {} ON {} TYPE {} VALUE {}", + " {} ON {} TYPE {} VALUE {}", self.name, self.base, self.kind, diff --git a/core/src/sql/v2/statements/define/user.rs b/core/src/sql/v2/statements/define/user.rs index a466e5df..0f70616f 100644 --- a/core/src/sql/v2/statements/define/user.rs +++ b/core/src/sql/v2/statements/define/user.rs @@ -16,7 +16,7 @@ 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 DefineUserStatement { pub name: Ident, pub base: Base, @@ -24,6 +24,8 @@ pub struct DefineUserStatement { pub code: String, pub roles: Vec, pub comment: Option, + #[revision(start = 2)] + pub if_not_exists: bool, } impl From<(Base, &str, &str)> for DefineUserStatement { @@ -42,6 +44,7 @@ impl From<(Base, &str, &str)> for DefineUserStatement { .collect::(), roles: vec!["owner".into()], comment: None, + if_not_exists: false, } } } @@ -89,9 +92,23 @@ impl DefineUserStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if user already exists + if self.if_not_exists && run.get_root_user(&self.name).await.is_ok() { + return Err(Error::UserRootAlreadyExists { + value: self.name.to_string(), + }); + } // Process the statement let key = crate::key::root::us::new(&self.name); - run.set(key, self).await?; + run.set( + key, + DefineUserStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -100,10 +117,25 @@ impl DefineUserStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if user already exists + if self.if_not_exists && run.get_ns_user(opt.ns(), &self.name).await.is_ok() { + return Err(Error::UserNsAlreadyExists { + value: self.name.to_string(), + ns: opt.ns().into(), + }); + } // 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?; + run.set( + key, + DefineUserStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -112,11 +144,29 @@ impl DefineUserStatement { let mut run = txn.lock().await; // Clear the cache run.clear_cache(); + // Check if user already exists + if self.if_not_exists + && run.get_db_user(opt.ns(), opt.db(), &self.name).await.is_ok() + { + return Err(Error::UserDbAlreadyExists { + value: self.name.to_string(), + ns: opt.ns().into(), + db: opt.db().into(), + }); + } // 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?; + run.set( + key, + DefineUserStatement { + // Don't persist the "IF NOT EXISTS" clause to schema + if_not_exists: false, + ..self.clone() + }, + ) + .await?; // Ok all good Ok(Value::None) } @@ -128,9 +178,13 @@ impl DefineUserStatement { impl Display for DefineUserStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DEFINE USER")?; + if self.if_not_exists { + write!(f, " IF NOT EXISTS")? + } write!( f, - "DEFINE USER {} ON {} PASSHASH {} ROLES {}", + " {} ON {} PASSHASH {} ROLES {}", self.name, self.base, quote_str(&self.hash), diff --git a/core/src/sql/v2/value/serde/ser/statement/define/analyzer.rs b/core/src/sql/v2/value/serde/ser/statement/define/analyzer.rs index 01612fc6..3debd976 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/analyzer.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/analyzer.rs @@ -43,6 +43,7 @@ pub struct SerializeDefineAnalyzerStatement { tokenizers: Option>, filters: Option>, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineAnalyzerStatement { @@ -69,6 +70,9 @@ impl serde::ser::SerializeStruct for SerializeDefineAnalyzerStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineAnalyzerStatement::{key}`" @@ -85,6 +89,7 @@ impl serde::ser::SerializeStruct for SerializeDefineAnalyzerStatement { tokenizers: self.tokenizers, filters: self.filters, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/database.rs b/core/src/sql/v2/value/serde/ser/statement/define/database.rs index 2daf9355..12868f2a 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/database.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/database.rs @@ -41,6 +41,7 @@ pub struct SerializeDefineDatabaseStatement { changefeed: Option, id: Option, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineDatabaseStatement { @@ -64,6 +65,9 @@ impl serde::ser::SerializeStruct for SerializeDefineDatabaseStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineDatabaseStatement::{key}`" @@ -79,6 +83,7 @@ impl serde::ser::SerializeStruct for SerializeDefineDatabaseStatement { changefeed: self.changefeed, id: self.id, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/event.rs b/core/src/sql/v2/value/serde/ser/statement/define/event.rs index 19e5c949..612e40ec 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/event.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/event.rs @@ -43,6 +43,7 @@ pub struct SerializeDefineEventStatement { when: Value, then: Values, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineEventStatement { @@ -69,6 +70,9 @@ impl serde::ser::SerializeStruct for SerializeDefineEventStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineEventStatement::{key}`" @@ -85,6 +89,7 @@ impl serde::ser::SerializeStruct for SerializeDefineEventStatement { when: self.when, then: self.then, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/field.rs b/core/src/sql/v2/value/serde/ser/statement/define/field.rs index 486a5103..c7d208c1 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/field.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/field.rs @@ -50,6 +50,7 @@ pub struct SerializeDefineFieldStatement { default: Option, permissions: Permissions, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineFieldStatement { @@ -91,6 +92,9 @@ impl serde::ser::SerializeStruct for SerializeDefineFieldStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineFieldStatement::{key}`" @@ -112,6 +116,7 @@ impl serde::ser::SerializeStruct for SerializeDefineFieldStatement { default: self.default, permissions: self.permissions, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/function.rs b/core/src/sql/v2/value/serde/ser/statement/define/function.rs index b9a3686a..d23c311c 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/function.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/function.rs @@ -44,6 +44,7 @@ pub struct SerializeDefineFunctionStatement { block: Block, comment: Option, permissions: Permission, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineFunctionStatement { @@ -70,6 +71,9 @@ impl serde::ser::SerializeStruct for SerializeDefineFunctionStatement { "permissions" => { self.permissions = value.serialize(ser::permission::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineFunctionStatement::{key}`" @@ -86,6 +90,7 @@ impl serde::ser::SerializeStruct for SerializeDefineFunctionStatement { block: self.block, comment: self.comment, permissions: self.permissions, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/index.rs b/core/src/sql/v2/value/serde/ser/statement/define/index.rs index 0934a794..80004fa0 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/index.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/index.rs @@ -43,6 +43,7 @@ pub struct SerializeDefineIndexStatement { cols: Idioms, index: Index, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineIndexStatement { @@ -69,6 +70,9 @@ impl serde::ser::SerializeStruct for SerializeDefineIndexStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineIndexStatement::{key}`" @@ -85,6 +89,7 @@ impl serde::ser::SerializeStruct for SerializeDefineIndexStatement { cols: self.cols, index: self.index, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/namespace.rs b/core/src/sql/v2/value/serde/ser/statement/define/namespace.rs index a3d5e032..d2aad500 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/namespace.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/namespace.rs @@ -39,6 +39,7 @@ pub struct SerializeDefineNamespaceStatement { name: Ident, id: Option, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineNamespaceStatement { @@ -59,6 +60,9 @@ impl serde::ser::SerializeStruct for SerializeDefineNamespaceStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineNamespaceStatement::{key}`" @@ -73,6 +77,7 @@ impl serde::ser::SerializeStruct for SerializeDefineNamespaceStatement { name: self.name, id: self.id, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/param.rs b/core/src/sql/v2/value/serde/ser/statement/define/param.rs index 2e101084..64da50dd 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/param.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/param.rs @@ -42,6 +42,7 @@ pub struct SerializeDefineParamStatement { value: Value, comment: Option, permissions: Permission, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineParamStatement { @@ -65,6 +66,9 @@ impl serde::ser::SerializeStruct for SerializeDefineParamStatement { "permissions" => { self.permissions = value.serialize(ser::permission::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineParamStatement::{key}`" @@ -80,6 +84,7 @@ impl serde::ser::SerializeStruct for SerializeDefineParamStatement { value: self.value, comment: self.comment, permissions: self.permissions, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/scope.rs b/core/src/sql/v2/value/serde/ser/statement/define/scope.rs index 6aa2a2d0..5a13b45f 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/scope.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/scope.rs @@ -44,6 +44,7 @@ pub struct SerializeDefineScopeStatement { signup: Option, signin: Option, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineScopeStatement { @@ -74,6 +75,9 @@ impl serde::ser::SerializeStruct for SerializeDefineScopeStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineScopeStatement::{key}`" @@ -91,6 +95,7 @@ impl serde::ser::SerializeStruct for SerializeDefineScopeStatement { signup: self.signup, signin: self.signin, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/table.rs b/core/src/sql/v2/value/serde/ser/statement/define/table.rs index 379caf2d..1dae4ef7 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/table.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/table.rs @@ -47,6 +47,7 @@ pub struct SerializeDefineTableStatement { permissions: Permissions, changefeed: Option, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineTableStatement { @@ -82,6 +83,9 @@ impl serde::ser::SerializeStruct for SerializeDefineTableStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineTableStatement::{key}`" @@ -101,6 +105,7 @@ impl serde::ser::SerializeStruct for SerializeDefineTableStatement { permissions: self.permissions, changefeed: self.changefeed, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/token.rs b/core/src/sql/v2/value/serde/ser/statement/define/token.rs index fe5989b6..97152675 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/token.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/token.rs @@ -43,6 +43,7 @@ pub struct SerializeDefineTokenStatement { kind: Algorithm, code: String, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineTokenStatement { @@ -69,6 +70,9 @@ impl serde::ser::SerializeStruct for SerializeDefineTokenStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineTokenStatement::{key}`" @@ -85,6 +89,7 @@ impl serde::ser::SerializeStruct for SerializeDefineTokenStatement { kind: self.kind, code: self.code, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/sql/v2/value/serde/ser/statement/define/user.rs b/core/src/sql/v2/value/serde/ser/statement/define/user.rs index 9d85feaa..418f77b8 100644 --- a/core/src/sql/v2/value/serde/ser/statement/define/user.rs +++ b/core/src/sql/v2/value/serde/ser/statement/define/user.rs @@ -43,6 +43,7 @@ pub struct SerializeDefineUserStatement { code: String, roles: Vec, comment: Option, + if_not_exists: bool, } impl serde::ser::SerializeStruct for SerializeDefineUserStatement { @@ -72,6 +73,9 @@ impl serde::ser::SerializeStruct for SerializeDefineUserStatement { "comment" => { self.comment = value.serialize(ser::strand::opt::Serializer.wrap())?; } + "if_not_exists" => { + self.if_not_exists = value.serialize(ser::primitive::bool::Serializer.wrap())? + } key => { return Err(Error::custom(format!( "unexpected field `DefineUserStatement::{key}`" @@ -89,6 +93,7 @@ impl serde::ser::SerializeStruct for SerializeDefineUserStatement { code: self.code, roles: self.roles, comment: self.comment, + if_not_exists: self.if_not_exists, }) } } diff --git a/core/src/syn/v1/stmt/define/analyzer.rs b/core/src/syn/v1/stmt/define/analyzer.rs index eac41039..c74f8435 100644 --- a/core/src/syn/v1/stmt/define/analyzer.rs +++ b/core/src/syn/v1/stmt/define/analyzer.rs @@ -10,10 +10,19 @@ use crate::sql::Ident; use crate::sql::{filter::Filter, statements::DefineAnalyzerStatement, Strand, Tokenizer}; #[cfg(feature = "sql2")] use nom::bytes::complete::tag; -use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0}; +use nom::{ + branch::alt, bytes::complete::tag_no_case, combinator::cut, combinator::opt, multi::many0, + sequence::tuple, +}; pub fn analyzer(i: &str) -> IResult<&str, DefineAnalyzerStatement> { let (i, _) = tag_no_case("ANALYZER")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, opts) = many0(analyzer_opts)(i)?; @@ -21,6 +30,8 @@ pub fn analyzer(i: &str) -> IResult<&str, DefineAnalyzerStatement> { // Create the base statement let mut res = DefineAnalyzerStatement { name, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/database.rs b/core/src/syn/v1/stmt/define/database.rs index 2c37dd8c..cd5f852a 100644 --- a/core/src/syn/v1/stmt/define/database.rs +++ b/core/src/syn/v1/stmt/define/database.rs @@ -7,10 +7,19 @@ use super::super::super::{ IResult, }; use crate::sql::{statements::DefineDatabaseStatement, ChangeFeed, Strand}; -use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0}; +use nom::{ + branch::alt, bytes::complete::tag_no_case, combinator::cut, combinator::opt, multi::many0, + sequence::tuple, +}; pub fn database(i: &str) -> IResult<&str, DefineDatabaseStatement> { let (i, _) = alt((tag_no_case("DB"), tag_no_case("DATABASE")))(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, opts) = many0(database_opts)(i)?; @@ -19,6 +28,8 @@ pub fn database(i: &str) -> IResult<&str, DefineDatabaseStatement> { // Create the base statement let mut res = DefineDatabaseStatement { name, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options @@ -58,6 +69,7 @@ fn database_changefeed(i: &str) -> IResult<&str, DefineDatabaseOption> { let (i, v) = changefeed(i)?; Ok((i, DefineDatabaseOption::ChangeFeed(v))) } + #[cfg(test)] mod tests { diff --git a/core/src/syn/v1/stmt/define/event.rs b/core/src/syn/v1/stmt/define/event.rs index 408677f0..edf67051 100644 --- a/core/src/syn/v1/stmt/define/event.rs +++ b/core/src/syn/v1/stmt/define/event.rs @@ -18,6 +18,12 @@ use nom::{ pub fn event(i: &str) -> IResult<&str, DefineEventStatement> { let (i, _) = tag_no_case("EVENT")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, (name, what, opts)) = cut(|i| { let (i, name) = ident(i)?; @@ -35,6 +41,8 @@ pub fn event(i: &str) -> IResult<&str, DefineEventStatement> { name, what, when: Value::Bool(true), + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/field.rs b/core/src/syn/v1/stmt/define/field.rs index 431e6fbc..88f62b38 100644 --- a/core/src/syn/v1/stmt/define/field.rs +++ b/core/src/syn/v1/stmt/define/field.rs @@ -20,6 +20,12 @@ use nom::{ pub fn field(i: &str) -> IResult<&str, DefineFieldStatement> { let (i, _) = tag_no_case("FIELD")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, (name, what, opts)) = cut(|i| { let (i, name) = idiom::local(i)?; @@ -40,6 +46,8 @@ pub fn field(i: &str) -> IResult<&str, DefineFieldStatement> { let mut res = DefineFieldStatement { name, what, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/function.rs b/core/src/syn/v1/stmt/define/function.rs index da3c840e..e41bd4cb 100644 --- a/core/src/syn/v1/stmt/define/function.rs +++ b/core/src/syn/v1/stmt/define/function.rs @@ -15,11 +15,19 @@ use nom::{ bytes::complete::{tag, tag_no_case}, character::complete::char, combinator::cut, + combinator::opt, multi::many0, + sequence::tuple, }; pub fn function(i: &str) -> IResult<&str, DefineFunctionStatement> { let (i, _) = tag_no_case("FUNCTION")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = tag("fn::")(i)?; let (i, name) = ident_path(i)?; @@ -47,6 +55,8 @@ pub fn function(i: &str) -> IResult<&str, DefineFunctionStatement> { name, args, block, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/index.rs b/core/src/syn/v1/stmt/define/index.rs index 32102378..e23abc40 100644 --- a/core/src/syn/v1/stmt/define/index.rs +++ b/core/src/syn/v1/stmt/define/index.rs @@ -22,6 +22,12 @@ use nom::{ pub fn index(i: &str) -> IResult<&str, DefineIndexStatement> { let (i, _) = tag_no_case("INDEX")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, (name, what, opts)) = cut(|i| { let (i, name) = ident(i)?; @@ -38,6 +44,8 @@ pub fn index(i: &str) -> IResult<&str, DefineIndexStatement> { let mut res = DefineIndexStatement { name, what, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options @@ -122,6 +130,8 @@ mod tests { cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), index: Index::Idx, comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, } ); assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col"); @@ -139,6 +149,8 @@ mod tests { cols: Idioms(vec![Idiom(vec![Part::Field(Ident("my_col".to_string()))])]), index: Index::Uniq, comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, } ); assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col UNIQUE"); @@ -173,6 +185,8 @@ mod tests { terms_cache: 400, }), comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, } ); assert_eq!(idx.to_string(), "DEFINE INDEX my_index ON my_table FIELDS my_col SEARCH ANALYZER my_analyzer BM25(1.2,0.75) \ @@ -204,6 +218,8 @@ mod tests { terms_cache: 100, }), comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, } ); assert_eq!( @@ -233,6 +249,8 @@ mod tests { mtree_cache: 100, }), comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, } ); assert_eq!( diff --git a/core/src/syn/v1/stmt/define/namespace.rs b/core/src/syn/v1/stmt/define/namespace.rs index 5651dda0..22fb8ce3 100644 --- a/core/src/syn/v1/stmt/define/namespace.rs +++ b/core/src/syn/v1/stmt/define/namespace.rs @@ -6,10 +6,19 @@ use super::super::super::{ IResult, }; use crate::sql::{statements::DefineNamespaceStatement, Strand}; -use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0}; +use nom::{ + branch::alt, bytes::complete::tag_no_case, combinator::cut, combinator::opt, multi::many0, + sequence::tuple, +}; pub fn namespace(i: &str) -> IResult<&str, DefineNamespaceStatement> { let (i, _) = alt((tag_no_case("NS"), tag_no_case("NAMESPACE")))(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, opts) = many0(namespace_opts)(i)?; @@ -17,6 +26,8 @@ pub fn namespace(i: &str) -> IResult<&str, DefineNamespaceStatement> { // Create the base statement let mut res = DefineNamespaceStatement { name, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options @@ -36,7 +47,7 @@ enum DefineNamespaceOption { } fn namespace_opts(i: &str) -> IResult<&str, DefineNamespaceOption> { - namespace_comment(i) + alt((namespace_comment,))(i) } fn namespace_comment(i: &str) -> IResult<&str, DefineNamespaceOption> { diff --git a/core/src/syn/v1/stmt/define/param.rs b/core/src/syn/v1/stmt/define/param.rs index 03af2719..b4455856 100644 --- a/core/src/syn/v1/stmt/define/param.rs +++ b/core/src/syn/v1/stmt/define/param.rs @@ -13,11 +13,17 @@ use crate::{ }; use nom::{ branch::alt, bytes::complete::tag_no_case, character::complete::char, combinator::cut, - multi::many0, Err, + combinator::opt, multi::many0, sequence::tuple, Err, }; pub fn param(i: &str) -> IResult<&str, DefineParamStatement> { let (i, _) = tag_no_case("PARAM")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, _) = cut(char('$'))(i)?; let (i, name) = cut(ident)(i)?; @@ -26,6 +32,8 @@ pub fn param(i: &str) -> IResult<&str, DefineParamStatement> { // Create the base statement let mut res = DefineParamStatement { name, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/scope.rs b/core/src/syn/v1/stmt/define/scope.rs index 03ce0f8e..3f62754a 100644 --- a/core/src/syn/v1/stmt/define/scope.rs +++ b/core/src/syn/v1/stmt/define/scope.rs @@ -7,10 +7,19 @@ use super::super::super::{ IResult, }; use crate::sql::{statements::DefineScopeStatement, Duration, Strand, Value}; -use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0}; +use nom::{ + branch::alt, bytes::complete::tag_no_case, combinator::cut, combinator::opt, multi::many0, + sequence::tuple, +}; pub fn scope(i: &str) -> IResult<&str, DefineScopeStatement> { let (i, _) = tag_no_case("SCOPE")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, opts) = many0(scope_opts)(i)?; @@ -19,6 +28,8 @@ pub fn scope(i: &str) -> IResult<&str, DefineScopeStatement> { let mut res = DefineScopeStatement { name, code: DefineScopeStatement::random_code(), + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/table.rs b/core/src/syn/v1/stmt/define/table.rs index 8f1ef680..b82dc19c 100644 --- a/core/src/syn/v1/stmt/define/table.rs +++ b/core/src/syn/v1/stmt/define/table.rs @@ -9,10 +9,19 @@ use super::super::super::{ use crate::sql::{ statements::DefineTableStatement, ChangeFeed, Permission, Permissions, Strand, View, }; -use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0}; +use nom::{ + branch::alt, bytes::complete::tag_no_case, combinator::cut, combinator::opt, multi::many0, + sequence::tuple, +}; pub fn table(i: &str) -> IResult<&str, DefineTableStatement> { let (i, _) = tag_no_case("TABLE")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, name) = cut(ident)(i)?; let (i, opts) = many0(table_opts)(i)?; @@ -24,6 +33,8 @@ pub fn table(i: &str) -> IResult<&str, DefineTableStatement> { let mut res = DefineTableStatement { name, permissions: Permissions::none(), + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/token.rs b/core/src/syn/v1/stmt/define/token.rs index eb2e8b2a..1e44c282 100644 --- a/core/src/syn/v1/stmt/define/token.rs +++ b/core/src/syn/v1/stmt/define/token.rs @@ -13,10 +13,19 @@ use crate::{ syn::v1::ParseError, }; use nom::Err; -use nom::{branch::alt, bytes::complete::tag_no_case, combinator::cut, multi::many0}; +use nom::{ + branch::alt, bytes::complete::tag_no_case, combinator::cut, combinator::opt, multi::many0, + sequence::tuple, +}; pub fn token(i: &str) -> IResult<&str, DefineTokenStatement> { let (i, _) = tag_no_case("TOKEN")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, (name, base, opts)) = cut(|i| { let (i, name) = ident(i)?; @@ -32,6 +41,8 @@ pub fn token(i: &str) -> IResult<&str, DefineTokenStatement> { let mut res = DefineTokenStatement { name, base, + #[cfg(feature = "sql2")] + if_not_exists: if_not_exists.is_some(), ..Default::default() }; // Assign any defined options diff --git a/core/src/syn/v1/stmt/define/user.rs b/core/src/syn/v1/stmt/define/user.rs index a536562f..0b26dd02 100644 --- a/core/src/syn/v1/stmt/define/user.rs +++ b/core/src/syn/v1/stmt/define/user.rs @@ -15,12 +15,20 @@ use nom::{ branch::alt, bytes::complete::tag_no_case, combinator::cut, + combinator::opt, multi::{many0, separated_list1}, + sequence::tuple, Err, }; pub fn user(i: &str) -> IResult<&str, DefineUserStatement> { let (i, _) = tag_no_case("USER")(i)?; + #[cfg(feature = "sql2")] + let (i, if_not_exists) = opt(tuple(( + shouldbespace, + tag_no_case("IF"), + cut(tuple((shouldbespace, tag_no_case("NOT"), shouldbespace, tag_no_case("EXISTS")))), + )))(i)?; let (i, _) = shouldbespace(i)?; let (i, (name, base, opts)) = cut(|i| { let (i, name) = ident(i)?; @@ -38,6 +46,12 @@ pub fn user(i: &str) -> IResult<&str, DefineUserStatement> { base, vec!["Viewer".into()], // New users get the viewer role by default ); + + #[cfg(feature = "sql2")] + if if_not_exists.is_some() { + res.if_not_exists = true; + }; + // Assign any defined options for opt in opts { match opt { diff --git a/core/src/syn/v2/parser/stmt/define.rs b/core/src/syn/v2/parser/stmt/define.rs index ba19ba63..1a48b313 100644 --- a/core/src/syn/v2/parser/stmt/define.rs +++ b/core/src/syn/v2/parser/stmt/define.rs @@ -40,22 +40,51 @@ impl Parser<'_> { } pub fn parse_define_namespace(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; - let comment = self.eat(t!("COMMENT")).then(|| self.next_token_value()).transpose()?; - Ok(DefineNamespaceStatement { + let mut res = DefineNamespaceStatement { id: None, name, - comment, - }) + #[cfg(feature = "sql2")] + if_not_exists, + ..Default::default() + }; + + loop { + match self.peek_kind() { + t!("COMMENT") => { + self.pop_peek(); + res.comment = Some(self.next_token_value()?); + } + _ => break, + } + } + + Ok(res) } pub fn parse_define_database(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; let mut res = DefineDatabaseStatement { - id: None, name, - comment: None, - changefeed: None, + #[cfg(feature = "sql2")] + if_not_exists, + ..Default::default() }; loop { match self.peek_kind() { @@ -75,6 +104,14 @@ impl Parser<'_> { } pub fn parse_define_function(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.parse_custom_function_name()?; let token = expected!(self, t!("(")).span; let mut args = Vec::new(); @@ -102,6 +139,8 @@ impl Parser<'_> { name, args, block, + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -123,6 +162,14 @@ impl Parser<'_> { } pub fn parse_define_user(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); let base = self.parse_base(false)?; @@ -133,6 +180,11 @@ impl Parser<'_> { vec!["Viewer".into()], // New users get the viewer role by default ); + #[cfg(feature = "sql2")] + if if_not_exists { + res.if_not_exists = true; + } + loop { match self.peek_kind() { t!("COMMENT") => { @@ -162,6 +214,14 @@ impl Parser<'_> { } pub fn parse_define_token(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); let base = self.parse_base(true)?; @@ -169,6 +229,8 @@ impl Parser<'_> { let mut res = DefineTokenStatement { name, base, + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -199,10 +261,20 @@ impl Parser<'_> { } pub fn parse_define_scope(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; let mut res = DefineScopeStatement { name, code: DefineScopeStatement::random_code(), + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -232,10 +304,20 @@ impl Parser<'_> { } pub fn parse_define_param(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value::()?.0; let mut res = DefineParamStatement { name, + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -260,10 +342,20 @@ impl Parser<'_> { } pub fn parse_define_table(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; let mut res = DefineTableStatement { name, permissions: Permissions::none(), + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -315,6 +407,14 @@ impl Parser<'_> { } pub fn parse_define_event(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); self.eat(t!("TABLE")); @@ -323,6 +423,8 @@ impl Parser<'_> { let mut res = DefineEventStatement { name, what, + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -350,6 +452,14 @@ impl Parser<'_> { } pub fn parse_define_field(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.parse_local_idiom()?; expected!(self, t!("ON")); self.eat(t!("TABLE")); @@ -358,6 +468,8 @@ impl Parser<'_> { let mut res = DefineFieldStatement { name, what, + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -405,6 +517,14 @@ impl Parser<'_> { } pub fn parse_define_index(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; expected!(self, t!("ON")); self.eat(t!("TABLE")); @@ -413,6 +533,8 @@ impl Parser<'_> { let mut res = DefineIndexStatement { name, what, + #[cfg(feature = "sql2")] + if_not_exists, ..Default::default() }; @@ -564,6 +686,14 @@ impl Parser<'_> { } pub fn parse_define_analyzer(&mut self) -> ParseResult { + #[cfg(feature = "sql2")] + let if_not_exists = if self.eat(t!("IF")) { + expected!(self, t!("NOT")); + expected!(self, t!("EXISTS")); + true + } else { + false + }; let name = self.next_token_value()?; let mut res = DefineAnalyzerStatement { name, @@ -572,6 +702,8 @@ impl Parser<'_> { tokenizers: None, filters: None, comment: None, + #[cfg(feature = "sql2")] + if_not_exists, }; loop { match self.peek_kind() { diff --git a/core/src/syn/v2/parser/test/stmt.rs b/core/src/syn/v2/parser/test/stmt.rs index 9fb63162..94c69c5f 100644 --- a/core/src/syn/v2/parser/test/stmt.rs +++ b/core/src/syn/v2/parser/test/stmt.rs @@ -121,7 +121,9 @@ fn parse_define_namespace() { Statement::Define(DefineStatement::Namespace(DefineNamespaceStatement { id: None, name: Ident("a".to_string()), - comment: Some(Strand("test".to_string())) + comment: Some(Strand("test".to_string())), + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); @@ -131,7 +133,9 @@ fn parse_define_namespace() { Statement::Define(DefineStatement::Namespace(DefineNamespaceStatement { id: None, name: Ident("a".to_string()), - comment: None + comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ) } @@ -150,7 +154,9 @@ fn parse_define_database() { changefeed: Some(ChangeFeed { expiry: std::time::Duration::from_secs(60) * 10, store_original: true, - }) + }), + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); @@ -161,7 +167,9 @@ fn parse_define_database() { id: None, name: Ident("a".to_string()), comment: None, - changefeed: None + changefeed: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ) } @@ -191,6 +199,8 @@ fn parse_define_function() { })]), comment: Some(Strand("test".to_string())), permissions: Permission::Full, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ) } @@ -228,7 +238,9 @@ fn parse_define_token() { base: Base::Sc(Ident("b".to_string())), kind: Algorithm::EdDSA, code: "foo".to_string(), - comment: Some(Strand("bar".to_string())) + comment: Some(Strand("bar".to_string())), + #[cfg(feature = "sql2")] + if_not_exists: false, })) ) } @@ -272,7 +284,9 @@ fn parse_define_param() { .collect() )), comment: None, - permissions: Permission::Specific(Value::Null) + permissions: Permission::Specific(Value::Null), + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); } @@ -319,6 +333,8 @@ fn parse_define_table() { store_original: true, }), comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); } @@ -337,6 +353,8 @@ fn parse_define_event() { when: Value::Null, then: Values(vec![Value::Null, Value::None]), comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ) } @@ -374,7 +392,9 @@ fn parse_define_field() { create: Permission::Specific(Value::Bool(true)), select: Permission::Full, }, - comment: None + comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ) } @@ -421,7 +441,9 @@ fn parse_define_index() { postings_cache: 7, terms_cache: 8, }), - comment: None + comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); @@ -435,7 +457,9 @@ fn parse_define_index() { what: Ident("table".to_owned()), cols: Idioms(vec![Idiom(vec![Part::Field(Ident("a".to_owned()))]),]), index: Index::Uniq, - comment: None + comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); @@ -458,7 +482,9 @@ fn parse_define_index() { mtree_cache: 9, vector_type: VectorType::F64, }), - comment: None + comment: None, + #[cfg(feature = "sql2")] + if_not_exists: false, })) ); } @@ -491,6 +517,8 @@ fn parse_define_analyzer() { comment: None, #[cfg(feature = "sql2")] function: Some(Ident("foo::bar".to_string())), + #[cfg(feature = "sql2")] + if_not_exists: false, })), ) } diff --git a/core/src/syn/v2/parser/test/streaming.rs b/core/src/syn/v2/parser/test/streaming.rs index 56685191..ca5e366e 100644 --- a/core/src/syn/v2/parser/test/streaming.rs +++ b/core/src/syn/v2/parser/test/streaming.rs @@ -151,11 +151,13 @@ fn statements() -> Vec { id: None, name: Ident("a".to_string()), comment: Some(Strand("test".to_string())), + if_not_exists: false, })), Statement::Define(DefineStatement::Namespace(DefineNamespaceStatement { id: None, name: Ident("a".to_string()), comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Database(DefineDatabaseStatement { id: None, @@ -165,12 +167,14 @@ fn statements() -> Vec { expiry: std::time::Duration::from_secs(60) * 10, store_original: false, }), + if_not_exists: false, })), Statement::Define(DefineStatement::Database(DefineDatabaseStatement { id: None, name: Ident("a".to_string()), comment: None, changefeed: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Function(DefineFunctionStatement { name: Ident("foo::bar".to_string()), @@ -184,6 +188,7 @@ fn statements() -> Vec { })]), comment: Some(Strand("test".to_string())), permissions: Permission::Full, + if_not_exists: false, })), Statement::Define(DefineStatement::Token(DefineTokenStatement { name: Ident("a".to_string()), @@ -191,6 +196,7 @@ fn statements() -> Vec { kind: Algorithm::EdDSA, code: "foo".to_string(), comment: Some(Strand("bar".to_string())), + if_not_exists: false, })), Statement::Define(DefineStatement::Param(DefineParamStatement { name: Ident("a".to_string()), @@ -204,6 +210,7 @@ fn statements() -> Vec { )), comment: None, permissions: Permission::Specific(Value::Null), + if_not_exists: false, })), Statement::Define(DefineStatement::Table(DefineTableStatement { id: None, @@ -239,6 +246,7 @@ fn statements() -> Vec { store_original: false, }), comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Event(DefineEventStatement { name: Ident("event".to_owned()), @@ -246,6 +254,7 @@ fn statements() -> Vec { when: Value::Null, then: Values(vec![Value::Null, Value::None]), comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Field(DefineFieldStatement { name: Idiom(vec![ @@ -271,6 +280,7 @@ fn statements() -> Vec { select: Permission::Full, }, comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Index(DefineIndexStatement { name: Ident("index".to_owned()), @@ -296,6 +306,7 @@ fn statements() -> Vec { terms_cache: 8, }), comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Index(DefineIndexStatement { name: Ident("index".to_owned()), @@ -303,6 +314,7 @@ fn statements() -> Vec { cols: Idioms(vec![Idiom(vec![Part::Field(Ident("a".to_owned()))])]), index: Index::Uniq, comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Index(DefineIndexStatement { name: Ident("index".to_owned()), @@ -319,6 +331,7 @@ fn statements() -> Vec { vector_type: VectorType::F64, }), comment: None, + if_not_exists: false, })), Statement::Define(DefineStatement::Analyzer(DefineAnalyzerStatement { name: Ident("ana".to_owned()), @@ -338,6 +351,7 @@ fn statements() -> Vec { ]), function: Some(Ident("foo::bar".to_string())), comment: None, + if_not_exists: false, })), Statement::Delete(DeleteStatement { only: true, diff --git a/lib/tests/define.rs b/lib/tests/define.rs index 4a5c0766..dbb74419 100644 --- a/lib/tests/define.rs +++ b/lib/tests/define.rs @@ -2118,3 +2118,495 @@ async fn define_statement_table_permissions() -> Result<(), Error> { // Ok(()) } + +#[tokio::test] +async fn redefining_existing_analyzer_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE ANALYZER example_blank TOKENIZERS blank; + DEFINE ANALYZER example_blank TOKENIZERS blank; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_analyzer_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE ANALYZER IF NOT EXISTS example TOKENIZERS blank; + DEFINE ANALYZER IF NOT EXISTS example TOKENIZERS blank; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::AzAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_database_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE DATABASE example; + DEFINE DATABASE example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_database_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE DATABASE IF NOT EXISTS example; + DEFINE DATABASE IF NOT EXISTS example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::DbAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_event_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE EVENT example ON example THEN {}; + DEFINE EVENT example ON example THEN {}; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_event_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE EVENT IF NOT EXISTS example ON example THEN {}; + DEFINE EVENT IF NOT EXISTS example ON example THEN {}; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::EvAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_field_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE FIELD example ON example; + DEFINE FIELD example ON example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_field_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE FIELD IF NOT EXISTS example ON example; + DEFINE FIELD IF NOT EXISTS example ON example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::FdAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_function_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE FUNCTION fn::example() {}; + DEFINE FUNCTION fn::example() {}; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_function_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE FUNCTION IF NOT EXISTS fn::example() {}; + DEFINE FUNCTION IF NOT EXISTS fn::example() {}; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::FcAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_index_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE INDEX example ON example FIELDS example; + DEFINE INDEX example ON example FIELDS example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_index_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE INDEX IF NOT EXISTS example ON example FIELDS example; + DEFINE INDEX IF NOT EXISTS example ON example FIELDS example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::IxAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_namespace_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE NAMESPACE example; + DEFINE NAMESPACE example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_namespace_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE NAMESPACE IF NOT EXISTS example; + DEFINE NAMESPACE IF NOT EXISTS example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::NsAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_param_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE PARAM $example VALUE 123; + DEFINE PARAM $example VALUE 123; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_param_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE PARAM IF NOT EXISTS $example VALUE 123; + DEFINE PARAM IF NOT EXISTS $example VALUE 123; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::PaAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_scope_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE SCOPE example; + DEFINE SCOPE example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_scope_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE SCOPE IF NOT EXISTS example; + DEFINE SCOPE IF NOT EXISTS example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::ScAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_table_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE TABLE example; + DEFINE TABLE example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_table_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE TABLE IF NOT EXISTS example; + DEFINE TABLE IF NOT EXISTS example; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::TbAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_token_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE TOKEN example ON SCOPE example TYPE HS512 VALUE \"example\"; + DEFINE TOKEN example ON SCOPE example TYPE HS512 VALUE \"example\"; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_token_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE TOKEN IF NOT EXISTS example ON SCOPE example TYPE HS512 VALUE \"example\"; + DEFINE TOKEN IF NOT EXISTS example ON SCOPE example TYPE HS512 VALUE \"example\"; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::StAlreadyExists { .. }),); + // + Ok(()) +} + +#[tokio::test] +async fn redefining_existing_user_should_not_error() -> Result<(), Error> { + let sql = " + DEFINE USER example ON ROOT PASSWORD \"example\" ROLES OWNER; + DEFINE USER example ON ROOT PASSWORD \"example\" ROLES OWNER; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + Ok(()) +} + +#[tokio::test] +#[cfg(feature = "sql2")] +async fn redefining_existing_user_with_if_not_exists_should_error() -> Result<(), Error> { + let sql = " + DEFINE USER IF NOT EXISTS example ON ROOT PASSWORD \"example\" ROLES OWNER; + DEFINE USER IF NOT EXISTS example ON ROOT PASSWORD \"example\" ROLES OWNER; + "; + 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(), 2); + // + let tmp = res.remove(0).result?; + assert_eq!(tmp, Value::None); + // + let tmp = res.remove(0).result.unwrap_err(); + assert!(matches!(tmp, Error::UserRootAlreadyExists { .. }),); + // + Ok(()) +}