Feature: Add IF EXISTS
to REMOVE TABLE
statement (#3243)
Co-authored-by: Mees Delzenne <DelSkayn@users.noreply.github.com> Co-authored-by: Micha de Vries <micha@devrie.sh>
This commit is contained in:
parent
595c994062
commit
334c117a48
8 changed files with 110 additions and 17 deletions
|
@ -10,10 +10,12 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 2)]
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub struct RemoveTableStatement {
|
pub struct RemoveTableStatement {
|
||||||
pub name: Ident,
|
pub name: Ident,
|
||||||
|
#[revision(start = 2)]
|
||||||
|
pub if_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoveTableStatement {
|
impl RemoveTableStatement {
|
||||||
|
@ -33,29 +35,43 @@ impl RemoveTableStatement {
|
||||||
// Clear the cache
|
// Clear the cache
|
||||||
run.clear_cache();
|
run.clear_cache();
|
||||||
// Get the defined table
|
// Get the defined table
|
||||||
let tb = run.get_tb(opt.ns(), opt.db(), &self.name).await?;
|
match run.get_tb(opt.ns(), opt.db(), &self.name).await {
|
||||||
// Delete the definition
|
Ok(tb) => {
|
||||||
let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name);
|
// Delete the definition
|
||||||
run.del(key).await?;
|
let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name);
|
||||||
// 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?;
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Ok all good
|
|
||||||
Ok(Value::None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RemoveTableStatement {
|
impl Display for RemoveTableStatement {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "REMOVE TABLE {}", self.name)
|
write!(f, "REMOVE TABLE {}", self.name)?;
|
||||||
|
if self.if_exists {
|
||||||
|
write!(f, " IF EXISTS")?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ impl ser::Serializer for Serializer {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SerializeRemoveTableStatement {
|
pub struct SerializeRemoveTableStatement {
|
||||||
name: Ident,
|
name: Ident,
|
||||||
|
if_exists: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
|
impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
|
||||||
|
@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
|
||||||
"name" => {
|
"name" => {
|
||||||
self.name = Ident(value.serialize(ser::string::Serializer.wrap())?);
|
self.name = Ident(value.serialize(ser::string::Serializer.wrap())?);
|
||||||
}
|
}
|
||||||
|
"if_exists" => {
|
||||||
|
self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?
|
||||||
|
}
|
||||||
key => {
|
key => {
|
||||||
return Err(Error::custom(format!(
|
return Err(Error::custom(format!(
|
||||||
"unexpected field `RemoveTableStatement::{key}`"
|
"unexpected field `RemoveTableStatement::{key}`"
|
||||||
|
@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
|
||||||
fn end(self) -> Result<Self::Ok, Error> {
|
fn end(self) -> Result<Self::Ok, Error> {
|
||||||
Ok(RemoveTableStatement {
|
Ok(RemoveTableStatement {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
|
if_exists: self.if_exists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,10 +178,16 @@ pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> {
|
||||||
let (i, _) = tag_no_case("TABLE")(i)?;
|
let (i, _) = tag_no_case("TABLE")(i)?;
|
||||||
let (i, _) = shouldbespace(i)?;
|
let (i, _) = shouldbespace(i)?;
|
||||||
let (i, name) = cut(ident)(i)?;
|
let (i, name) = cut(ident)(i)?;
|
||||||
|
let (i, if_exists) = opt(tuple((
|
||||||
|
shouldbespace,
|
||||||
|
tag_no_case("IF"),
|
||||||
|
cut(tuple((shouldbespace, tag_no_case("EXISTS")))),
|
||||||
|
)))(i)?;
|
||||||
Ok((
|
Ok((
|
||||||
i,
|
i,
|
||||||
RemoveTableStatement {
|
RemoveTableStatement {
|
||||||
name,
|
name,
|
||||||
|
if_exists: if_exists.is_some(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -242,4 +248,27 @@ mod tests {
|
||||||
let out = res.unwrap().1;
|
let out = res.unwrap().1;
|
||||||
assert_eq!("REMOVE FUNCTION fn::foo::bar::baz::bac", format!("{}", out))
|
assert_eq!("REMOVE FUNCTION fn::foo::bar::baz::bac", format!("{}", out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_table() {
|
||||||
|
let sql = "REMOVE TABLE test";
|
||||||
|
let res = remove(sql);
|
||||||
|
let out = res.unwrap().1;
|
||||||
|
assert_eq!("REMOVE TABLE test", format!("{}", out))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_table_if_exists() {
|
||||||
|
let sql = "REMOVE TABLE test IF EXISTS";
|
||||||
|
let res = remove(sql);
|
||||||
|
let out = res.unwrap().1;
|
||||||
|
assert_eq!("REMOVE TABLE test IF EXISTS", format!("{}", out))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_table_if() {
|
||||||
|
let sql = "REMOVE TABLE test IF";
|
||||||
|
let res = remove(sql);
|
||||||
|
assert!(res.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ pub(crate) static KEYWORDS: phf::Map<UniCase<&'static str>, TokenKind> = phf_map
|
||||||
UniCase::ascii("EVENT") => TokenKind::Keyword(Keyword::Event),
|
UniCase::ascii("EVENT") => TokenKind::Keyword(Keyword::Event),
|
||||||
UniCase::ascii("ELSE") => TokenKind::Keyword(Keyword::Else),
|
UniCase::ascii("ELSE") => TokenKind::Keyword(Keyword::Else),
|
||||||
UniCase::ascii("END") => TokenKind::Keyword(Keyword::End),
|
UniCase::ascii("END") => TokenKind::Keyword(Keyword::End),
|
||||||
|
UniCase::ascii("EXISTS") => TokenKind::Keyword(Keyword::Exists),
|
||||||
UniCase::ascii("EXPLAIN") => TokenKind::Keyword(Keyword::Explain),
|
UniCase::ascii("EXPLAIN") => TokenKind::Keyword(Keyword::Explain),
|
||||||
UniCase::ascii("false") => TokenKind::Keyword(Keyword::False),
|
UniCase::ascii("false") => TokenKind::Keyword(Keyword::False),
|
||||||
UniCase::ascii("FETCH") => TokenKind::Keyword(Keyword::Fetch),
|
UniCase::ascii("FETCH") => TokenKind::Keyword(Keyword::Fetch),
|
||||||
|
|
|
@ -65,8 +65,16 @@ impl Parser<'_> {
|
||||||
}
|
}
|
||||||
t!("TABLE") => {
|
t!("TABLE") => {
|
||||||
let name = self.next_token_value()?;
|
let name = self.next_token_value()?;
|
||||||
|
let if_exists = if self.eat(t!("IF")) {
|
||||||
|
expected!(self, t!("EXISTS"));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
RemoveStatement::Table(crate::sql::statements::RemoveTableStatement {
|
RemoveStatement::Table(crate::sql::statements::RemoveTableStatement {
|
||||||
name,
|
name,
|
||||||
|
if_exists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
t!("EVENT") => {
|
t!("EVENT") => {
|
||||||
|
|
|
@ -1106,6 +1106,7 @@ fn parse_remove() {
|
||||||
res,
|
res,
|
||||||
Statement::Remove(RemoveStatement::Table(RemoveTableStatement {
|
Statement::Remove(RemoveStatement::Table(RemoveTableStatement {
|
||||||
name: Ident("foo".to_owned()),
|
name: Ident("foo".to_owned()),
|
||||||
|
if_exists: false,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ keyword! {
|
||||||
Event => "EVENT",
|
Event => "EVENT",
|
||||||
Else => "ELSE",
|
Else => "ELSE",
|
||||||
End => "END",
|
End => "END",
|
||||||
|
Exists => "EXISTS",
|
||||||
Explain => "EXPLAIN",
|
Explain => "EXPLAIN",
|
||||||
False => "false",
|
False => "false",
|
||||||
Fetch => "FETCH",
|
Fetch => "FETCH",
|
||||||
|
|
|
@ -129,6 +129,38 @@ async fn remove_statement_index() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn should_error_when_remove_and_table_does_not_exist() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
REMOVE TABLE 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::TbNotFound { .. }),);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn should_not_error_when_remove_if_exists() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
REMOVE TABLE foo IF EXISTS;
|
||||||
|
";
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Permissions
|
// Permissions
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue