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:
Gustavo 2024-01-19 14:02:07 +00:00 committed by GitHub
parent 595c994062
commit 334c117a48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 110 additions and 17 deletions

View file

@ -10,10 +10,12 @@ use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[revisioned(revision = 1)]
#[revisioned(revision = 2)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct RemoveTableStatement {
pub name: Ident,
#[revision(start = 2)]
pub if_exists: bool,
}
impl RemoveTableStatement {
@ -33,7 +35,8 @@ impl RemoveTableStatement {
// Clear the cache
run.clear_cache();
// 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 {
Ok(tb) => {
// Delete the definition
let key = crate::key::database::tb::new(opt.ns(), opt.db(), &self.name);
run.del(key).await?;
@ -52,10 +55,23 @@ impl RemoveTableStatement {
// Ok all good
Ok(Value::None)
}
Err(err) => {
if matches!(err, Error::TbNotFound { .. }) && self.if_exists {
Ok(Value::None)
} else {
Err(err)
}
}
}
}
}
impl Display for RemoveTableStatement {
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(())
}
}

View file

@ -36,6 +36,7 @@ impl ser::Serializer for Serializer {
#[derive(Default)]
pub struct SerializeRemoveTableStatement {
name: Ident,
if_exists: bool,
}
impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
@ -50,6 +51,9 @@ impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
"name" => {
self.name = Ident(value.serialize(ser::string::Serializer.wrap())?);
}
"if_exists" => {
self.if_exists = value.serialize(ser::primitive::bool::Serializer.wrap())?
}
key => {
return Err(Error::custom(format!(
"unexpected field `RemoveTableStatement::{key}`"
@ -62,6 +66,7 @@ impl serde::ser::SerializeStruct for SerializeRemoveTableStatement {
fn end(self) -> Result<Self::Ok, Error> {
Ok(RemoveTableStatement {
name: self.name,
if_exists: self.if_exists,
})
}
}

View file

@ -178,10 +178,16 @@ pub fn table(i: &str) -> IResult<&str, RemoveTableStatement> {
let (i, _) = tag_no_case("TABLE")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, name) = cut(ident)(i)?;
let (i, if_exists) = opt(tuple((
shouldbespace,
tag_no_case("IF"),
cut(tuple((shouldbespace, tag_no_case("EXISTS")))),
)))(i)?;
Ok((
i,
RemoveTableStatement {
name,
if_exists: if_exists.is_some(),
},
))
}
@ -242,4 +248,27 @@ mod tests {
let out = res.unwrap().1;
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());
}
}

View file

@ -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("ELSE") => TokenKind::Keyword(Keyword::Else),
UniCase::ascii("END") => TokenKind::Keyword(Keyword::End),
UniCase::ascii("EXISTS") => TokenKind::Keyword(Keyword::Exists),
UniCase::ascii("EXPLAIN") => TokenKind::Keyword(Keyword::Explain),
UniCase::ascii("false") => TokenKind::Keyword(Keyword::False),
UniCase::ascii("FETCH") => TokenKind::Keyword(Keyword::Fetch),

View file

@ -65,8 +65,16 @@ impl Parser<'_> {
}
t!("TABLE") => {
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 {
name,
if_exists,
})
}
t!("EVENT") => {

View file

@ -1106,6 +1106,7 @@ fn parse_remove() {
res,
Statement::Remove(RemoveStatement::Table(RemoveTableStatement {
name: Ident("foo".to_owned()),
if_exists: false,
}))
);

View file

@ -68,6 +68,7 @@ keyword! {
Event => "EVENT",
Else => "ELSE",
End => "END",
Exists => "EXISTS",
Explain => "EXPLAIN",
False => "false",
Fetch => "FETCH",

View file

@ -129,6 +129,38 @@ async fn remove_statement_index() -> Result<(), Error> {
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
//