mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
[snowflake] Support specifying an alias after FROM (table_factor)
Snowflake diverges from the standard and from most of the other implementations by allowing extra parentheses not only around a join, but around lone table names (e.g. `FROM (mytable [AS alias])`) and around derived tables (e.g. `FROM ((SELECT ...) [AS alias])`) as well. Initially this was implemented in https://github.com/ballista-compute/sqlparser-rs/issues/154 by (ab)using `TableFactor::NestedJoin` to represent anything nested in extra set of parens. Afterwards we learned in https://github.com/ballista-compute/sqlparser-rs/issues/223 that in cases of such extraneous nesting Snowflake allows specifying the alias both inside and outside parens, but not both - consider: FROM (table_factor AS inner_alias) AS outer_alias We've considered implementing this by changing `TableFactor::NestedJoin` to a `TableFactor::Nested { inner: TableWithJoins, alias: Option<TableAlias> }`, but that seemed too generic, as no known dialect supports duplicate aliases, as shown above, nor naming nested joins `(foo NATURAL JOIN bar) alias`. So we decided on making a smaller change (with no modifications to the AST), that is also more appropriate to the contributors to the Snowflake dialect: 1) Revert #154 by rejecting `FROM (table or derived table)` in most dialects. 2) For `dialect_of!(self is SnowflakeDialect | GenericDialect)` parse and strip the extraneous parentheses, e.g. `(mytable) AS alias` -> `(mytable AS alias)` Co-authored-by: Eyal Leshem <eyal@satoricyber.com>
This commit is contained in:
parent
d9e044aabb
commit
ad72cda6b0
5 changed files with 131 additions and 46 deletions
|
@ -242,9 +242,10 @@ pub enum TableFactor {
|
||||||
},
|
},
|
||||||
/// Represents a parenthesized table factor. The SQL spec only allows a
|
/// Represents a parenthesized table factor. The SQL spec only allows a
|
||||||
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
|
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
|
||||||
/// possibly several times, but the parser also accepts the non-standard
|
/// possibly several times.
|
||||||
/// nesting of bare tables (`table_with_joins.joins.is_empty()`), so the
|
///
|
||||||
/// name `NestedJoin` is a bit of misnomer.
|
/// The parser may also accept non-standard nesting of bare tables for some
|
||||||
|
/// dialects, but the information about such nesting is stripped from AST.
|
||||||
NestedJoin(Box<TableWithJoins>),
|
NestedJoin(Box<TableWithJoins>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2156,14 +2156,58 @@ impl<'a> Parser<'a> {
|
||||||
// recently consumed does not start a derived table (cases 1, 2, or 4).
|
// recently consumed does not start a derived table (cases 1, 2, or 4).
|
||||||
// `maybe_parse` will ignore such an error and rewind to be after the opening '('.
|
// `maybe_parse` will ignore such an error and rewind to be after the opening '('.
|
||||||
|
|
||||||
// Inside the parentheses we expect to find a table factor
|
// Inside the parentheses we expect to find an (A) table factor
|
||||||
// followed by some joins or another level of nesting.
|
// followed by some joins or (B) another level of nesting.
|
||||||
let table_and_joins = self.parse_table_and_joins()?;
|
let mut table_and_joins = self.parse_table_and_joins()?;
|
||||||
self.expect_token(&Token::RParen)?;
|
|
||||||
// The SQL spec prohibits derived and bare tables from appearing
|
if !table_and_joins.joins.is_empty() {
|
||||||
// alone in parentheses. We don't enforce this as some databases
|
self.expect_token(&Token::RParen)?;
|
||||||
// (e.g. Snowflake) allow such syntax.
|
Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) // (A)
|
||||||
Ok(TableFactor::NestedJoin(Box::new(table_and_joins)))
|
} else if let TableFactor::NestedJoin(_) = &table_and_joins.relation {
|
||||||
|
// (B): `table_and_joins` (what we found inside the parentheses)
|
||||||
|
// is a nested join `(foo JOIN bar)`, not followed by other joins.
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(TableFactor::NestedJoin(Box::new(table_and_joins)))
|
||||||
|
} else if dialect_of!(self is SnowflakeDialect | GenericDialect) {
|
||||||
|
// Dialect-specific behavior: Snowflake diverges from the
|
||||||
|
// standard and from most of the other implementations by
|
||||||
|
// allowing extra parentheses not only around a join (B), but
|
||||||
|
// around lone table names (e.g. `FROM (mytable [AS alias])`)
|
||||||
|
// and around derived tables (e.g. `FROM ((SELECT ...)
|
||||||
|
// [AS alias])`) as well.
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
|
||||||
|
if let Some(outer_alias) =
|
||||||
|
self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?
|
||||||
|
{
|
||||||
|
// Snowflake also allows specifying an alias *after* parens
|
||||||
|
// e.g. `FROM (mytable) AS alias`
|
||||||
|
match &mut table_and_joins.relation {
|
||||||
|
TableFactor::Derived { alias, .. }
|
||||||
|
| TableFactor::Table { alias, .. }
|
||||||
|
| TableFactor::TableFunction { alias, .. } => {
|
||||||
|
// but not `FROM (mytable AS alias1) AS alias2`.
|
||||||
|
if let Some(inner_alias) = alias {
|
||||||
|
return Err(ParserError::ParserError(format!(
|
||||||
|
"duplicate alias {}",
|
||||||
|
inner_alias
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
// Act as if the alias was specified normally next
|
||||||
|
// to the table name: `(mytable) AS alias` ->
|
||||||
|
// `(mytable AS alias)`
|
||||||
|
alias.replace(outer_alias);
|
||||||
|
}
|
||||||
|
TableFactor::NestedJoin(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Do not store the extra set of parens in the AST
|
||||||
|
Ok(table_and_joins.relation)
|
||||||
|
} else {
|
||||||
|
// The SQL spec prohibits derived tables and bare tables from
|
||||||
|
// appearing alone in parentheses (e.g. `FROM (mytable)`)
|
||||||
|
self.expected("joined table", self.peek_token())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let name = self.parse_object_name()?;
|
let name = self.parse_object_name()?;
|
||||||
// Postgres, MSSQL: table-valued functions:
|
// Postgres, MSSQL: table-valued functions:
|
||||||
|
|
|
@ -156,6 +156,13 @@ pub fn number(n: &'static str) -> Value {
|
||||||
Value::Number(n.parse().unwrap())
|
Value::Number(n.parse().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
|
||||||
|
Some(TableAlias {
|
||||||
|
name: Ident::new(name),
|
||||||
|
columns: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn table(name: impl Into<String>) -> TableFactor {
|
pub fn table(name: impl Into<String>) -> TableFactor {
|
||||||
TableFactor::Table {
|
TableFactor::Table {
|
||||||
name: ObjectName(vec![Ident::new(name.into())]),
|
name: ObjectName(vec![Ident::new(name.into())]),
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
use test_utils::{all_dialects, expr_from_projection, join, number, only, table};
|
use test_utils::{all_dialects, expr_from_projection, join, number, only, table, table_alias};
|
||||||
|
|
||||||
use matches::assert_matches;
|
use matches::assert_matches;
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
|
@ -2130,13 +2130,6 @@ fn parse_cross_join() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
|
|
||||||
Some(TableAlias {
|
|
||||||
name: Ident::new(name),
|
|
||||||
columns: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_joins_on() {
|
fn parse_joins_on() {
|
||||||
fn join_with_constraint(
|
fn join_with_constraint(
|
||||||
|
|
|
@ -20,6 +20,7 @@ use test_utils::*;
|
||||||
|
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
|
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
|
||||||
|
use sqlparser::parser::ParserError;
|
||||||
use sqlparser::tokenizer::*;
|
use sqlparser::tokenizer::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -72,42 +73,81 @@ fn test_snowflake_single_line_tokenize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sf_derives_single_table_in_parenthesis() {
|
fn test_sf_derived_table_in_parenthesis() {
|
||||||
// Nesting a subquery in parentheses is non-standard, but supported in Snowflake SQL
|
// Nesting a subquery in an extra set of parentheses is non-standard,
|
||||||
let sql = "SELECT * FROM ((SELECT 1) AS t)";
|
// but supported in Snowflake SQL
|
||||||
let select = snowflake_and_generic().verified_only_select(sql);
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
let from = only(select.from);
|
"SELECT * FROM ((SELECT 1) AS t)",
|
||||||
assert_eq!(
|
"SELECT * FROM (SELECT 1) AS t",
|
||||||
from.relation,
|
);
|
||||||
TableFactor::NestedJoin(Box::new(TableWithJoins {
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
relation: TableFactor::Derived {
|
"SELECT * FROM (((SELECT 1) AS t))",
|
||||||
lateral: false,
|
"SELECT * FROM (SELECT 1) AS t",
|
||||||
subquery: Box::new(snowflake_and_generic().verified_query("SELECT 1")),
|
|
||||||
alias: Some(TableAlias {
|
|
||||||
name: "t".into(),
|
|
||||||
columns: vec![],
|
|
||||||
})
|
|
||||||
},
|
|
||||||
joins: Vec::new(),
|
|
||||||
}))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_table_in_parenthesis() {
|
fn test_single_table_in_parenthesis() {
|
||||||
// Parenthesized table names are non-standard, but supported in Snowflake SQL
|
// Parenthesized table names are non-standard, but supported in Snowflake SQL
|
||||||
let sql = "SELECT * FROM (a NATURAL JOIN (b))";
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
let select = snowflake_and_generic().verified_only_select(sql);
|
"SELECT * FROM (a NATURAL JOIN (b))",
|
||||||
let from = only(select.from);
|
"SELECT * FROM (a NATURAL JOIN b)",
|
||||||
|
);
|
||||||
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
|
"SELECT * FROM (a NATURAL JOIN ((b)))",
|
||||||
|
"SELECT * FROM (a NATURAL JOIN b)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(from.relation, nest!(table("a"), nest!(table("b"))));
|
#[test]
|
||||||
|
fn test_single_table_in_parenthesis_with_alias() {
|
||||||
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
|
"SELECT * FROM (a NATURAL JOIN (b) c )",
|
||||||
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
||||||
|
);
|
||||||
|
|
||||||
// Double parentheses around table names are non-standard, but supported in Snowflake SQL
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
let sql = "SELECT * FROM (a NATURAL JOIN ((b)))";
|
"SELECT * FROM (a NATURAL JOIN ((b)) c )",
|
||||||
let select = snowflake_and_generic().verified_only_select(sql);
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
||||||
let from = only(select.from);
|
);
|
||||||
|
|
||||||
assert_eq!(from.relation, nest!(table("a"), nest!(nest!(table("b")))));
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
|
"SELECT * FROM (a NATURAL JOIN ( (b) c ) )",
|
||||||
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
||||||
|
);
|
||||||
|
|
||||||
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
|
"SELECT * FROM (a NATURAL JOIN ( (b) as c ) )",
|
||||||
|
"SELECT * FROM (a NATURAL JOIN b AS c)",
|
||||||
|
);
|
||||||
|
|
||||||
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
|
"SELECT * FROM (a alias1 NATURAL JOIN ( (b) c ) )",
|
||||||
|
"SELECT * FROM (a AS alias1 NATURAL JOIN b AS c)",
|
||||||
|
);
|
||||||
|
|
||||||
|
snowflake_and_generic().one_statement_parses_to(
|
||||||
|
"SELECT * FROM (a as alias1 NATURAL JOIN ( (b) as c ) )",
|
||||||
|
"SELECT * FROM (a AS alias1 NATURAL JOIN b AS c)",
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = snowflake_and_generic().parse_sql_statements("SELECT * FROM (a NATURAL JOIN b) c");
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected end of statement, found: c".to_string()),
|
||||||
|
res.unwrap_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = snowflake().parse_sql_statements("SELECT * FROM (a b) c");
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("duplicate alias b".to_string()),
|
||||||
|
res.unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snowflake() -> TestedDialects {
|
||||||
|
TestedDialects {
|
||||||
|
dialects: vec![Box::new(SnowflakeDialect {})],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snowflake_and_generic() -> TestedDialects {
|
fn snowflake_and_generic() -> TestedDialects {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue