mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-09 21:42:05 +00:00
BigQuery: Parse optional DELETE FROM
statement (#1120)
This commit is contained in:
parent
61089f977c
commit
df738f9b10
5 changed files with 120 additions and 27 deletions
|
@ -1436,6 +1436,23 @@ impl fmt::Display for CreateTableOptions {
|
|||
}
|
||||
}
|
||||
|
||||
/// A `FROM` clause within a `DELETE` statement.
|
||||
///
|
||||
/// Syntax
|
||||
/// ```sql
|
||||
/// [FROM] table
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum FromTable {
|
||||
/// An explicit `FROM` keyword was specified.
|
||||
WithFromKeyword(Vec<TableWithJoins>),
|
||||
/// BigQuery: `FROM` keyword was omitted.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement>
|
||||
WithoutKeyword(Vec<TableWithJoins>),
|
||||
}
|
||||
|
||||
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
|
@ -1599,7 +1616,7 @@ pub enum Statement {
|
|||
/// Multi tables delete are supported in mysql
|
||||
tables: Vec<ObjectName>,
|
||||
/// FROM
|
||||
from: Vec<TableWithJoins>,
|
||||
from: FromTable,
|
||||
/// USING (Snowflake, Postgres, MySQL)
|
||||
using: Option<Vec<TableWithJoins>>,
|
||||
/// WHERE
|
||||
|
@ -2702,7 +2719,14 @@ impl fmt::Display for Statement {
|
|||
if !tables.is_empty() {
|
||||
write!(f, "{} ", display_comma_separated(tables))?;
|
||||
}
|
||||
write!(f, "FROM {}", display_comma_separated(from))?;
|
||||
match from {
|
||||
FromTable::WithFromKeyword(from) => {
|
||||
write!(f, "FROM {}", display_comma_separated(from))?;
|
||||
}
|
||||
FromTable::WithoutKeyword(from) => {
|
||||
write!(f, "{}", display_comma_separated(from))?;
|
||||
}
|
||||
}
|
||||
if let Some(using) = using {
|
||||
write!(f, " USING {}", display_comma_separated(using))?;
|
||||
}
|
||||
|
|
|
@ -6324,12 +6324,18 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
|
||||
let tables = if !self.parse_keyword(Keyword::FROM) {
|
||||
let tables = self.parse_comma_separated(|p| p.parse_object_name(false))?;
|
||||
self.expect_keyword(Keyword::FROM)?;
|
||||
tables
|
||||
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
|
||||
// `FROM` keyword is optional in BigQuery SQL.
|
||||
// https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement
|
||||
if dialect_of!(self is BigQueryDialect | GenericDialect) {
|
||||
(vec![], false)
|
||||
} else {
|
||||
let tables = self.parse_comma_separated(|p| p.parse_object_name(false))?;
|
||||
self.expect_keyword(Keyword::FROM)?;
|
||||
(tables, true)
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
(vec![], true)
|
||||
};
|
||||
|
||||
let from = self.parse_comma_separated(Parser::parse_table_and_joins)?;
|
||||
|
@ -6361,7 +6367,11 @@ impl<'a> Parser<'a> {
|
|||
|
||||
Ok(Statement::Delete {
|
||||
tables,
|
||||
from,
|
||||
from: if with_from_keyword {
|
||||
FromTable::WithFromKeyword(from)
|
||||
} else {
|
||||
FromTable::WithoutKeyword(from)
|
||||
},
|
||||
using,
|
||||
selection,
|
||||
returning,
|
||||
|
|
|
@ -193,21 +193,35 @@ impl TestedDialects {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns all available dialects.
|
||||
pub fn all_dialects() -> TestedDialects {
|
||||
all_dialects_except(|_| false)
|
||||
}
|
||||
|
||||
/// Returns available dialects. The `except` predicate is used
|
||||
/// to filter out specific dialects.
|
||||
pub fn all_dialects_except<F>(except: F) -> TestedDialects
|
||||
where
|
||||
F: Fn(&dyn Dialect) -> bool,
|
||||
{
|
||||
let all_dialects = vec![
|
||||
Box::new(GenericDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(PostgreSqlDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(MsSqlDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(AnsiDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(SnowflakeDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(HiveDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(RedshiftSqlDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(MySqlDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(BigQueryDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(SQLiteDialect {}) as Box<dyn Dialect>,
|
||||
Box::new(DuckDbDialect {}) as Box<dyn Dialect>,
|
||||
];
|
||||
TestedDialects {
|
||||
dialects: vec![
|
||||
Box::new(GenericDialect {}),
|
||||
Box::new(PostgreSqlDialect {}),
|
||||
Box::new(MsSqlDialect {}),
|
||||
Box::new(AnsiDialect {}),
|
||||
Box::new(SnowflakeDialect {}),
|
||||
Box::new(HiveDialect {}),
|
||||
Box::new(RedshiftSqlDialect {}),
|
||||
Box::new(MySqlDialect {}),
|
||||
Box::new(BigQueryDialect {}),
|
||||
Box::new(SQLiteDialect {}),
|
||||
Box::new(DuckDbDialect {}),
|
||||
],
|
||||
dialects: all_dialects
|
||||
.into_iter()
|
||||
.filter(|d| !except(d.as_ref()))
|
||||
.collect(),
|
||||
options: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,30 @@ fn parse_raw_literal() {
|
|||
panic!("invalid query")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_delete_statement() {
|
||||
let sql = "DELETE \"table\" WHERE 1";
|
||||
match bigquery_and_generic().verified_stmt(sql) {
|
||||
Statement::Delete {
|
||||
from: FromTable::WithoutKeyword(from),
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
TableFactor::Table {
|
||||
name: ObjectName(vec![Ident::with_quote('"', "table")]),
|
||||
alias: None,
|
||||
args: None,
|
||||
with_hints: vec![],
|
||||
version: None,
|
||||
partitions: vec![],
|
||||
},
|
||||
from[0].relation
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_view_with_options() {
|
||||
let sql = concat!(
|
||||
|
|
|
@ -42,6 +42,7 @@ mod test_utils;
|
|||
|
||||
#[cfg(test)]
|
||||
use pretty_assertions::assert_eq;
|
||||
use sqlparser::test_utils::all_dialects_except;
|
||||
|
||||
#[test]
|
||||
fn parse_insert_values() {
|
||||
|
@ -523,7 +524,10 @@ fn parse_no_table_name() {
|
|||
fn parse_delete_statement() {
|
||||
let sql = "DELETE FROM \"table\"";
|
||||
match verified_stmt(sql) {
|
||||
Statement::Delete { from, .. } => {
|
||||
Statement::Delete {
|
||||
from: FromTable::WithFromKeyword(from),
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
TableFactor::Table {
|
||||
name: ObjectName(vec![Ident::with_quote('"', "table")]),
|
||||
|
@ -540,11 +544,28 @@ fn parse_delete_statement() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_delete_without_from_error() {
|
||||
let sql = "DELETE \"table\" WHERE 1";
|
||||
|
||||
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
|
||||
let res = dialects.parse_sql_statements(sql);
|
||||
assert_eq!(
|
||||
ParserError::ParserError("Expected FROM, found: WHERE".to_string()),
|
||||
res.unwrap_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[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, .. } => {
|
||||
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
|
||||
match dialects.verified_stmt(sql) {
|
||||
Statement::Delete {
|
||||
tables,
|
||||
from: FromTable::WithFromKeyword(from),
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
ObjectName(vec![Ident::new("schema1"), Ident::new("table1")]),
|
||||
tables[0]
|
||||
|
@ -585,7 +606,7 @@ 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,
|
||||
from: FromTable::WithFromKeyword(from),
|
||||
using: Some(using),
|
||||
..
|
||||
} => {
|
||||
|
@ -646,7 +667,7 @@ fn parse_where_delete_statement() {
|
|||
match verified_stmt(sql) {
|
||||
Statement::Delete {
|
||||
tables: _,
|
||||
from,
|
||||
from: FromTable::WithFromKeyword(from),
|
||||
using,
|
||||
selection,
|
||||
returning,
|
||||
|
@ -687,7 +708,7 @@ fn parse_where_delete_with_alias_statement() {
|
|||
match verified_stmt(sql) {
|
||||
Statement::Delete {
|
||||
tables: _,
|
||||
from,
|
||||
from: FromTable::WithFromKeyword(from),
|
||||
using,
|
||||
selection,
|
||||
returning,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue