Skip to content

Commit df738f9

Browse files
authored
BigQuery: Parse optional DELETE FROM statement (#1120)
1 parent 61089f9 commit df738f9

File tree

5 files changed

+120
-27
lines changed

5 files changed

+120
-27
lines changed

src/ast/mod.rs

+26-2
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,23 @@ impl fmt::Display for CreateTableOptions {
14361436
}
14371437
}
14381438

1439+
/// A `FROM` clause within a `DELETE` statement.
1440+
///
1441+
/// Syntax
1442+
/// ```sql
1443+
/// [FROM] table
1444+
/// ```
1445+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1446+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1447+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1448+
pub enum FromTable {
1449+
/// An explicit `FROM` keyword was specified.
1450+
WithFromKeyword(Vec<TableWithJoins>),
1451+
/// BigQuery: `FROM` keyword was omitted.
1452+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement>
1453+
WithoutKeyword(Vec<TableWithJoins>),
1454+
}
1455+
14391456
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
14401457
#[allow(clippy::large_enum_variant)]
14411458
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -1599,7 +1616,7 @@ pub enum Statement {
15991616
/// Multi tables delete are supported in mysql
16001617
tables: Vec<ObjectName>,
16011618
/// FROM
1602-
from: Vec<TableWithJoins>,
1619+
from: FromTable,
16031620
/// USING (Snowflake, Postgres, MySQL)
16041621
using: Option<Vec<TableWithJoins>>,
16051622
/// WHERE
@@ -2702,7 +2719,14 @@ impl fmt::Display for Statement {
27022719
if !tables.is_empty() {
27032720
write!(f, "{} ", display_comma_separated(tables))?;
27042721
}
2705-
write!(f, "FROM {}", display_comma_separated(from))?;
2722+
match from {
2723+
FromTable::WithFromKeyword(from) => {
2724+
write!(f, "FROM {}", display_comma_separated(from))?;
2725+
}
2726+
FromTable::WithoutKeyword(from) => {
2727+
write!(f, "{}", display_comma_separated(from))?;
2728+
}
2729+
}
27062730
if let Some(using) = using {
27072731
write!(f, " USING {}", display_comma_separated(using))?;
27082732
}

src/parser/mod.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -6324,12 +6324,18 @@ impl<'a> Parser<'a> {
63246324
}
63256325

63266326
pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
6327-
let tables = if !self.parse_keyword(Keyword::FROM) {
6328-
let tables = self.parse_comma_separated(|p| p.parse_object_name(false))?;
6329-
self.expect_keyword(Keyword::FROM)?;
6330-
tables
6327+
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
6328+
// `FROM` keyword is optional in BigQuery SQL.
6329+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement
6330+
if dialect_of!(self is BigQueryDialect | GenericDialect) {
6331+
(vec![], false)
6332+
} else {
6333+
let tables = self.parse_comma_separated(|p| p.parse_object_name(false))?;
6334+
self.expect_keyword(Keyword::FROM)?;
6335+
(tables, true)
6336+
}
63316337
} else {
6332-
vec![]
6338+
(vec![], true)
63336339
};
63346340

63356341
let from = self.parse_comma_separated(Parser::parse_table_and_joins)?;
@@ -6361,7 +6367,11 @@ impl<'a> Parser<'a> {
63616367

63626368
Ok(Statement::Delete {
63636369
tables,
6364-
from,
6370+
from: if with_from_keyword {
6371+
FromTable::WithFromKeyword(from)
6372+
} else {
6373+
FromTable::WithoutKeyword(from)
6374+
},
63656375
using,
63666376
selection,
63676377
returning,

src/test_utils.rs

+27-13
Original file line numberDiff line numberDiff line change
@@ -193,21 +193,35 @@ impl TestedDialects {
193193
}
194194
}
195195

196+
/// Returns all available dialects.
196197
pub fn all_dialects() -> TestedDialects {
198+
all_dialects_except(|_| false)
199+
}
200+
201+
/// Returns available dialects. The `except` predicate is used
202+
/// to filter out specific dialects.
203+
pub fn all_dialects_except<F>(except: F) -> TestedDialects
204+
where
205+
F: Fn(&dyn Dialect) -> bool,
206+
{
207+
let all_dialects = vec![
208+
Box::new(GenericDialect {}) as Box<dyn Dialect>,
209+
Box::new(PostgreSqlDialect {}) as Box<dyn Dialect>,
210+
Box::new(MsSqlDialect {}) as Box<dyn Dialect>,
211+
Box::new(AnsiDialect {}) as Box<dyn Dialect>,
212+
Box::new(SnowflakeDialect {}) as Box<dyn Dialect>,
213+
Box::new(HiveDialect {}) as Box<dyn Dialect>,
214+
Box::new(RedshiftSqlDialect {}) as Box<dyn Dialect>,
215+
Box::new(MySqlDialect {}) as Box<dyn Dialect>,
216+
Box::new(BigQueryDialect {}) as Box<dyn Dialect>,
217+
Box::new(SQLiteDialect {}) as Box<dyn Dialect>,
218+
Box::new(DuckDbDialect {}) as Box<dyn Dialect>,
219+
];
197220
TestedDialects {
198-
dialects: vec![
199-
Box::new(GenericDialect {}),
200-
Box::new(PostgreSqlDialect {}),
201-
Box::new(MsSqlDialect {}),
202-
Box::new(AnsiDialect {}),
203-
Box::new(SnowflakeDialect {}),
204-
Box::new(HiveDialect {}),
205-
Box::new(RedshiftSqlDialect {}),
206-
Box::new(MySqlDialect {}),
207-
Box::new(BigQueryDialect {}),
208-
Box::new(SQLiteDialect {}),
209-
Box::new(DuckDbDialect {}),
210-
],
221+
dialects: all_dialects
222+
.into_iter()
223+
.filter(|d| !except(d.as_ref()))
224+
.collect(),
211225
options: None,
212226
}
213227
}

tests/sqlparser_bigquery.rs

+24
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,30 @@ fn parse_raw_literal() {
8686
panic!("invalid query")
8787
}
8888

89+
#[test]
90+
fn parse_delete_statement() {
91+
let sql = "DELETE \"table\" WHERE 1";
92+
match bigquery_and_generic().verified_stmt(sql) {
93+
Statement::Delete {
94+
from: FromTable::WithoutKeyword(from),
95+
..
96+
} => {
97+
assert_eq!(
98+
TableFactor::Table {
99+
name: ObjectName(vec![Ident::with_quote('"', "table")]),
100+
alias: None,
101+
args: None,
102+
with_hints: vec![],
103+
version: None,
104+
partitions: vec![],
105+
},
106+
from[0].relation
107+
);
108+
}
109+
_ => unreachable!(),
110+
}
111+
}
112+
89113
#[test]
90114
fn parse_create_view_with_options() {
91115
let sql = concat!(

tests/sqlparser_common.rs

+27-6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod test_utils;
4242

4343
#[cfg(test)]
4444
use pretty_assertions::assert_eq;
45+
use sqlparser::test_utils::all_dialects_except;
4546

4647
#[test]
4748
fn parse_insert_values() {
@@ -523,7 +524,10 @@ fn parse_no_table_name() {
523524
fn parse_delete_statement() {
524525
let sql = "DELETE FROM \"table\"";
525526
match verified_stmt(sql) {
526-
Statement::Delete { from, .. } => {
527+
Statement::Delete {
528+
from: FromTable::WithFromKeyword(from),
529+
..
530+
} => {
527531
assert_eq!(
528532
TableFactor::Table {
529533
name: ObjectName(vec![Ident::with_quote('"', "table")]),
@@ -540,11 +544,28 @@ fn parse_delete_statement() {
540544
}
541545
}
542546

547+
#[test]
548+
fn parse_delete_without_from_error() {
549+
let sql = "DELETE \"table\" WHERE 1";
550+
551+
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
552+
let res = dialects.parse_sql_statements(sql);
553+
assert_eq!(
554+
ParserError::ParserError("Expected FROM, found: WHERE".to_string()),
555+
res.unwrap_err()
556+
);
557+
}
558+
543559
#[test]
544560
fn parse_delete_statement_for_multi_tables() {
545561
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";
546-
match verified_stmt(sql) {
547-
Statement::Delete { tables, from, .. } => {
562+
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
563+
match dialects.verified_stmt(sql) {
564+
Statement::Delete {
565+
tables,
566+
from: FromTable::WithFromKeyword(from),
567+
..
568+
} => {
548569
assert_eq!(
549570
ObjectName(vec![Ident::new("schema1"), Ident::new("table1")]),
550571
tables[0]
@@ -585,7 +606,7 @@ fn parse_delete_statement_for_multi_tables_with_using() {
585606
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";
586607
match verified_stmt(sql) {
587608
Statement::Delete {
588-
from,
609+
from: FromTable::WithFromKeyword(from),
589610
using: Some(using),
590611
..
591612
} => {
@@ -646,7 +667,7 @@ fn parse_where_delete_statement() {
646667
match verified_stmt(sql) {
647668
Statement::Delete {
648669
tables: _,
649-
from,
670+
from: FromTable::WithFromKeyword(from),
650671
using,
651672
selection,
652673
returning,
@@ -687,7 +708,7 @@ fn parse_where_delete_with_alias_statement() {
687708
match verified_stmt(sql) {
688709
Statement::Delete {
689710
tables: _,
690-
from,
711+
from: FromTable::WithFromKeyword(from),
691712
using,
692713
selection,
693714
returning,

0 commit comments

Comments
 (0)