From 187376e657929093a914d34dff9ff73cf681aa28 Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Thu, 4 Apr 2019 10:51:34 -0400 Subject: [PATCH] Support DROP [TABLE|VIEW] Co-authored-by: Jamie Brandon --- src/dialect/keywords.rs | 3 ++ src/sqlast/mod.rs | 34 +++++++++++++++++++ src/sqlparser.rs | 38 +++++++++++++++++++++ tests/sqlparser_common.rs | 70 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) diff --git a/src/dialect/keywords.rs b/src/dialect/keywords.rs index 1d4d99939..6469210c7 100644 --- a/src/dialect/keywords.rs +++ b/src/dialect/keywords.rs @@ -71,6 +71,7 @@ define_keywords!( CALL, CALLED, CARDINALITY, + CASCADE, CASCADED, CASE, CAST, @@ -178,6 +179,7 @@ define_keywords!( HOLD, HOUR, IDENTITY, + IF, IN, INDICATOR, INNER, @@ -290,6 +292,7 @@ define_keywords!( REGR_SXY, REGR_SYY, RELEASE, + RESTRICT, RESULT, RETURN, RETURNS, diff --git a/src/sqlast/mod.rs b/src/sqlast/mod.rs index 678588ba0..25a4f3572 100644 --- a/src/sqlast/mod.rs +++ b/src/sqlast/mod.rs @@ -394,6 +394,13 @@ pub enum SQLStatement { name: SQLObjectName, operation: AlterOperation, }, + /// DROP TABLE + SQLDrop { + object_type: SQLObjectType, + if_exists: bool, + names: Vec, + cascade: bool, + }, } impl ToString for SQLStatement { @@ -502,6 +509,18 @@ impl ToString for SQLStatement { SQLStatement::SQLAlterTable { name, operation } => { format!("ALTER TABLE {} {}", name.to_string(), operation.to_string()) } + SQLStatement::SQLDrop { + object_type, + if_exists, + names, + cascade, + } => format!( + "DROP {}{} {}{}", + object_type.to_string(), + if *if_exists { " IF EXISTS" } else { "" }, + comma_separated_string(&names), + if *cascade { " CASCADE" } else { "" }, + ), } } } @@ -608,3 +627,18 @@ impl FromStr for FileFormat { } } } + +#[derive(Debug, Clone, PartialEq)] +pub enum SQLObjectType { + Table, + View, +} + +impl SQLObjectType { + fn to_string(&self) -> String { + match self { + SQLObjectType::Table => "TABLE".into(), + SQLObjectType::View => "VIEW".into(), + } + } +} diff --git a/src/sqlparser.rs b/src/sqlparser.rs index edfa0c573..c04f70d9d 100644 --- a/src/sqlparser.rs +++ b/src/sqlparser.rs @@ -97,6 +97,7 @@ impl Parser { Ok(SQLStatement::SQLQuery(Box::new(self.parse_query()?))) } "CREATE" => Ok(self.parse_create()?), + "DROP" => Ok(self.parse_drop()?), "DELETE" => Ok(self.parse_delete()?), "INSERT" => Ok(self.parse_insert()?), "ALTER" => Ok(self.parse_alter()?), @@ -738,6 +739,43 @@ impl Parser { }) } + pub fn parse_drop(&mut self) -> Result { + let object_type = if self.parse_keyword("TABLE") { + SQLObjectType::Table + } else if self.parse_keyword("VIEW") { + SQLObjectType::View + } else { + return parser_err!(format!( + "Unexpected token after DROP: {:?}", + self.peek_token() + )); + }; + let if_exists = self.parse_keywords(vec!["IF", "EXISTS"]); + let mut names = vec![self.parse_object_name()?]; + loop { + let token = &self.next_token(); + if let Some(Token::Comma) = token { + names.push(self.parse_object_name()?) + } else { + if token.is_some() { + self.prev_token(); + } + break; + } + } + let cascade = self.parse_keyword("CASCADE"); + let restrict = self.parse_keyword("RESTRICT"); + if cascade && restrict { + return parser_err!("Cannot specify both CASCADE and RESTRICT in DROP"); + } + Ok(SQLStatement::SQLDrop { + object_type, + if_exists, + names, + cascade, + }) + } + pub fn parse_create_table(&mut self) -> Result { let table_name = self.parse_object_name()?; // parse optional column list (schema) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e141c840c..8324a30af 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1200,6 +1200,76 @@ fn parse_create_materialized_view() { } } +#[test] +fn parse_drop_table() { + let sql = "DROP TABLE foo"; + match verified_stmt(sql) { + SQLStatement::SQLDrop { + object_type, + if_exists, + names, + cascade, + } => { + assert_eq!(false, if_exists); + assert_eq!(SQLObjectType::Table, object_type); + assert_eq!( + vec!["foo"], + names.iter().map(|n| n.to_string()).collect::>() + ); + assert_eq!(false, cascade); + } + _ => assert!(false), + } + + let sql = "DROP TABLE IF EXISTS foo, bar CASCADE"; + match verified_stmt(sql) { + SQLStatement::SQLDrop { + object_type, + if_exists, + names, + cascade, + } => { + assert_eq!(true, if_exists); + assert_eq!(SQLObjectType::Table, object_type); + assert_eq!( + vec!["foo", "bar"], + names.iter().map(|n| n.to_string()).collect::>() + ); + assert_eq!(true, cascade); + } + _ => assert!(false), + } + + let sql = "DROP TABLE"; + assert_eq!( + ParserError::ParserError("Expected identifier, found: EOF".to_string()), + parse_sql_statements(sql).unwrap_err(), + ); + + let sql = "DROP TABLE IF EXISTS foo, bar CASCADE RESTRICT"; + assert_eq!( + ParserError::ParserError("Cannot specify both CASCADE and RESTRICT in DROP".to_string()), + parse_sql_statements(sql).unwrap_err(), + ); +} + +#[test] +fn parse_drop_view() { + let sql = "DROP VIEW myschema.myview"; + match verified_stmt(sql) { + SQLStatement::SQLDrop { + names, object_type, .. + } => { + assert_eq!( + vec!["myschema.myview"], + names.iter().map(|n| n.to_string()).collect::>() + ); + assert_eq!(SQLObjectType::View, object_type); + } + _ => assert!(false), + } +} + #[test] fn parse_invalid_subquery_without_parens() { let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz");