From d34b23d26970dfba9ada8117b19087f3e1ec810c Mon Sep 17 00:00:00 2001 From: Avi Raboah Date: Sun, 23 Apr 2023 18:22:37 +0300 Subject: [PATCH] Support multiple-table DELETE syntax --- src/ast/mod.rs | 19 ++++-- src/parser.rs | 16 +++-- tests/sqlparser_common.rs | 122 +++++++++++++++++++++++++++++++++----- 3 files changed, 131 insertions(+), 26 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 94b237b4f..44dc9e019 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1207,10 +1207,12 @@ pub enum Statement { }, /// DELETE Delete { + /// Multi tables delete are supported in mysql + tables: Vec, /// FROM - table_name: TableFactor, - /// USING (Snowflake, Postgres) - using: Option, + from: Vec, + /// USING (Snowflake, Postgres, MySQL) + using: Option>, /// WHERE selection: Option, /// RETURNING @@ -1960,14 +1962,19 @@ impl fmt::Display for Statement { Ok(()) } Statement::Delete { - table_name, + tables, + from, using, selection, returning, } => { - write!(f, "DELETE FROM {table_name}")?; + write!(f, "DELETE ")?; + if !tables.is_empty() { + write!(f, "{} ", display_comma_separated(tables))?; + } + write!(f, "FROM {}", display_comma_separated(from))?; if let Some(using) = using { - write!(f, " USING {using}")?; + write!(f, " USING {}", display_comma_separated(using))?; } if let Some(selection) = selection { write!(f, " WHERE {selection}")?; diff --git a/src/parser.rs b/src/parser.rs index b06e6bd25..003369de4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4813,10 +4813,17 @@ impl<'a> Parser<'a> { } pub fn parse_delete(&mut self) -> Result { - self.expect_keyword(Keyword::FROM)?; - let table_name = self.parse_table_factor()?; + let tables = if !self.parse_keyword(Keyword::FROM) { + let tables = self.parse_comma_separated(Parser::parse_object_name)?; + self.expect_keyword(Keyword::FROM)?; + tables + } else { + vec![] + }; + + let from = self.parse_comma_separated(Parser::parse_table_and_joins)?; let using = if self.parse_keyword(Keyword::USING) { - Some(self.parse_table_factor()?) + Some(self.parse_comma_separated(Parser::parse_table_and_joins)?) } else { None }; @@ -4833,7 +4840,8 @@ impl<'a> Parser<'a> { }; Ok(Statement::Delete { - table_name, + tables, + from, using, selection, returning, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 55350e5a6..aee9efd70 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -385,7 +385,7 @@ fn parse_no_table_name() { fn parse_delete_statement() { let sql = "DELETE FROM \"table\""; match verified_stmt(sql) { - Statement::Delete { table_name, .. } => { + Statement::Delete { from, .. } => { assert_eq!( TableFactor::Table { name: ObjectName(vec![Ident::with_quote('"', "table")]), @@ -393,7 +393,93 @@ fn parse_delete_statement() { args: None, with_hints: vec![], }, - table_name + from[0].relation + ); + } + _ => unreachable!(), + } +} + +#[test] +fn parse_delete_statement_for_multi_tables() { + let sql = "DELETE schema1.table1, schema2.table2 FROM schema1.table1 JOIN schema2.table2 ON schema2.table2.col1 = schema1.table1.col1 WHERE schema2.table2.col2 = 1"; + match verified_stmt(sql) { + Statement::Delete { tables, from, .. } => { + assert_eq!( + ObjectName(vec![Ident::new("schema1"), Ident::new("table1")]), + tables[0] + ); + assert_eq!( + ObjectName(vec![Ident::new("schema2"), Ident::new("table2")]), + tables[1] + ); + assert_eq!( + TableFactor::Table { + name: ObjectName(vec![Ident::new("schema1"), Ident::new("table1")]), + alias: None, + args: None, + with_hints: vec![], + }, + from[0].relation + ); + assert_eq!( + TableFactor::Table { + name: ObjectName(vec![Ident::new("schema2"), Ident::new("table2")]), + alias: None, + args: None, + with_hints: vec![], + }, + from[0].joins[0].relation + ); + } + _ => unreachable!(), + } +} + +#[test] +fn parse_delete_statement_for_multi_tables_with_using() { + let sql = "DELETE FROM schema1.table1, schema2.table2 USING schema1.table1 JOIN schema2.table2 ON schema2.table2.pk = schema1.table1.col1 WHERE schema2.table2.col2 = 1"; + match verified_stmt(sql) { + Statement::Delete { + from, + using: Some(using), + .. + } => { + assert_eq!( + TableFactor::Table { + name: ObjectName(vec![Ident::new("schema1"), Ident::new("table1")]), + alias: None, + args: None, + with_hints: vec![], + }, + from[0].relation + ); + assert_eq!( + TableFactor::Table { + name: ObjectName(vec![Ident::new("schema2"), Ident::new("table2")]), + alias: None, + args: None, + with_hints: vec![], + }, + from[1].relation + ); + assert_eq!( + TableFactor::Table { + name: ObjectName(vec![Ident::new("schema1"), Ident::new("table1")]), + alias: None, + args: None, + with_hints: vec![], + }, + using[0].relation + ); + assert_eq!( + TableFactor::Table { + name: ObjectName(vec![Ident::new("schema2"), Ident::new("table2")]), + alias: None, + args: None, + with_hints: vec![], + }, + using[0].joins[0].relation ); } _ => unreachable!(), @@ -407,7 +493,8 @@ fn parse_where_delete_statement() { let sql = "DELETE FROM foo WHERE name = 5"; match verified_stmt(sql) { Statement::Delete { - table_name, + tables: _, + from, using, selection, returning, @@ -419,7 +506,7 @@ fn parse_where_delete_statement() { args: None, with_hints: vec![], }, - table_name, + from[0].relation, ); assert_eq!(None, using); @@ -444,7 +531,8 @@ fn parse_where_delete_with_alias_statement() { let sql = "DELETE FROM basket AS a USING basket AS b WHERE a.id < b.id"; match verified_stmt(sql) { Statement::Delete { - table_name, + tables: _, + from, using, selection, returning, @@ -459,19 +547,21 @@ fn parse_where_delete_with_alias_statement() { args: None, with_hints: vec![], }, - table_name, + from[0].relation, ); - assert_eq!( - Some(TableFactor::Table { - name: ObjectName(vec![Ident::new("basket")]), - alias: Some(TableAlias { - name: Ident::new("b"), - columns: vec![], - }), - args: None, - with_hints: vec![], - }), + Some(vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new("basket")]), + alias: Some(TableAlias { + name: Ident::new("b"), + columns: vec![], + }), + args: None, + with_hints: vec![], + }, + joins: vec![], + }]), using ); assert_eq!(