Skip to content

Commit ad72cda

Browse files
nickolayeyalsatori
andcommitted
[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 apache#154 by (ab)using `TableFactor::NestedJoin` to represent anything nested in extra set of parens. Afterwards we learned in apache#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 apache#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 <[email protected]>
1 parent d9e044a commit ad72cda

File tree

5 files changed

+131
-46
lines changed

5 files changed

+131
-46
lines changed

src/ast/query.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,10 @@ pub enum TableFactor {
242242
},
243243
/// Represents a parenthesized table factor. The SQL spec only allows a
244244
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
245-
/// possibly several times, but the parser also accepts the non-standard
246-
/// nesting of bare tables (`table_with_joins.joins.is_empty()`), so the
247-
/// name `NestedJoin` is a bit of misnomer.
245+
/// possibly several times.
246+
///
247+
/// The parser may also accept non-standard nesting of bare tables for some
248+
/// dialects, but the information about such nesting is stripped from AST.
248249
NestedJoin(Box<TableWithJoins>),
249250
}
250251

src/parser.rs

+52-8
Original file line numberDiff line numberDiff line change
@@ -2156,14 +2156,58 @@ impl<'a> Parser<'a> {
21562156
// recently consumed does not start a derived table (cases 1, 2, or 4).
21572157
// `maybe_parse` will ignore such an error and rewind to be after the opening '('.
21582158

2159-
// Inside the parentheses we expect to find a table factor
2160-
// followed by some joins or another level of nesting.
2161-
let table_and_joins = self.parse_table_and_joins()?;
2162-
self.expect_token(&Token::RParen)?;
2163-
// The SQL spec prohibits derived and bare tables from appearing
2164-
// alone in parentheses. We don't enforce this as some databases
2165-
// (e.g. Snowflake) allow such syntax.
2166-
Ok(TableFactor::NestedJoin(Box::new(table_and_joins)))
2159+
// Inside the parentheses we expect to find an (A) table factor
2160+
// followed by some joins or (B) another level of nesting.
2161+
let mut table_and_joins = self.parse_table_and_joins()?;
2162+
2163+
if !table_and_joins.joins.is_empty() {
2164+
self.expect_token(&Token::RParen)?;
2165+
Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) // (A)
2166+
} else if let TableFactor::NestedJoin(_) = &table_and_joins.relation {
2167+
// (B): `table_and_joins` (what we found inside the parentheses)
2168+
// is a nested join `(foo JOIN bar)`, not followed by other joins.
2169+
self.expect_token(&Token::RParen)?;
2170+
Ok(TableFactor::NestedJoin(Box::new(table_and_joins)))
2171+
} else if dialect_of!(self is SnowflakeDialect | GenericDialect) {
2172+
// Dialect-specific behavior: Snowflake diverges from the
2173+
// standard and from most of the other implementations by
2174+
// allowing extra parentheses not only around a join (B), but
2175+
// around lone table names (e.g. `FROM (mytable [AS alias])`)
2176+
// and around derived tables (e.g. `FROM ((SELECT ...)
2177+
// [AS alias])`) as well.
2178+
self.expect_token(&Token::RParen)?;
2179+
2180+
if let Some(outer_alias) =
2181+
self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?
2182+
{
2183+
// Snowflake also allows specifying an alias *after* parens
2184+
// e.g. `FROM (mytable) AS alias`
2185+
match &mut table_and_joins.relation {
2186+
TableFactor::Derived { alias, .. }
2187+
| TableFactor::Table { alias, .. }
2188+
| TableFactor::TableFunction { alias, .. } => {
2189+
// but not `FROM (mytable AS alias1) AS alias2`.
2190+
if let Some(inner_alias) = alias {
2191+
return Err(ParserError::ParserError(format!(
2192+
"duplicate alias {}",
2193+
inner_alias
2194+
)));
2195+
}
2196+
// Act as if the alias was specified normally next
2197+
// to the table name: `(mytable) AS alias` ->
2198+
// `(mytable AS alias)`
2199+
alias.replace(outer_alias);
2200+
}
2201+
TableFactor::NestedJoin(_) => unreachable!(),
2202+
};
2203+
}
2204+
// Do not store the extra set of parens in the AST
2205+
Ok(table_and_joins.relation)
2206+
} else {
2207+
// The SQL spec prohibits derived tables and bare tables from
2208+
// appearing alone in parentheses (e.g. `FROM (mytable)`)
2209+
self.expected("joined table", self.peek_token())
2210+
}
21672211
} else {
21682212
let name = self.parse_object_name()?;
21692213
// Postgres, MSSQL: table-valued functions:

src/test_utils.rs

+7
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ pub fn number(n: &'static str) -> Value {
156156
Value::Number(n.parse().unwrap())
157157
}
158158

159+
pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
160+
Some(TableAlias {
161+
name: Ident::new(name),
162+
columns: vec![],
163+
})
164+
}
165+
159166
pub fn table(name: impl Into<String>) -> TableFactor {
160167
TableFactor::Table {
161168
name: ObjectName(vec![Ident::new(name.into())]),

tests/sqlparser_common.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
2121
#[macro_use]
2222
mod test_utils;
23-
use test_utils::{all_dialects, expr_from_projection, join, number, only, table};
23+
use test_utils::{all_dialects, expr_from_projection, join, number, only, table, table_alias};
2424

2525
use matches::assert_matches;
2626
use sqlparser::ast::*;
@@ -2130,13 +2130,6 @@ fn parse_cross_join() {
21302130
);
21312131
}
21322132

2133-
fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
2134-
Some(TableAlias {
2135-
name: Ident::new(name),
2136-
columns: vec![],
2137-
})
2138-
}
2139-
21402133
#[test]
21412134
fn parse_joins_on() {
21422135
fn join_with_constraint(

tests/sqlparser_snowflake.rs

+67-27
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use test_utils::*;
2020

2121
use sqlparser::ast::*;
2222
use sqlparser::dialect::{GenericDialect, SnowflakeDialect};
23+
use sqlparser::parser::ParserError;
2324
use sqlparser::tokenizer::*;
2425

2526
#[test]
@@ -72,42 +73,81 @@ fn test_snowflake_single_line_tokenize() {
7273
}
7374

7475
#[test]
75-
fn test_sf_derives_single_table_in_parenthesis() {
76-
// Nesting a subquery in parentheses is non-standard, but supported in Snowflake SQL
77-
let sql = "SELECT * FROM ((SELECT 1) AS t)";
78-
let select = snowflake_and_generic().verified_only_select(sql);
79-
let from = only(select.from);
80-
assert_eq!(
81-
from.relation,
82-
TableFactor::NestedJoin(Box::new(TableWithJoins {
83-
relation: TableFactor::Derived {
84-
lateral: false,
85-
subquery: Box::new(snowflake_and_generic().verified_query("SELECT 1")),
86-
alias: Some(TableAlias {
87-
name: "t".into(),
88-
columns: vec![],
89-
})
90-
},
91-
joins: Vec::new(),
92-
}))
76+
fn test_sf_derived_table_in_parenthesis() {
77+
// Nesting a subquery in an extra set of parentheses is non-standard,
78+
// but supported in Snowflake SQL
79+
snowflake_and_generic().one_statement_parses_to(
80+
"SELECT * FROM ((SELECT 1) AS t)",
81+
"SELECT * FROM (SELECT 1) AS t",
82+
);
83+
snowflake_and_generic().one_statement_parses_to(
84+
"SELECT * FROM (((SELECT 1) AS t))",
85+
"SELECT * FROM (SELECT 1) AS t",
9386
);
9487
}
9588

9689
#[test]
9790
fn test_single_table_in_parenthesis() {
9891
// Parenthesized table names are non-standard, but supported in Snowflake SQL
99-
let sql = "SELECT * FROM (a NATURAL JOIN (b))";
100-
let select = snowflake_and_generic().verified_only_select(sql);
101-
let from = only(select.from);
92+
snowflake_and_generic().one_statement_parses_to(
93+
"SELECT * FROM (a NATURAL JOIN (b))",
94+
"SELECT * FROM (a NATURAL JOIN b)",
95+
);
96+
snowflake_and_generic().one_statement_parses_to(
97+
"SELECT * FROM (a NATURAL JOIN ((b)))",
98+
"SELECT * FROM (a NATURAL JOIN b)",
99+
);
100+
}
102101

103-
assert_eq!(from.relation, nest!(table("a"), nest!(table("b"))));
102+
#[test]
103+
fn test_single_table_in_parenthesis_with_alias() {
104+
snowflake_and_generic().one_statement_parses_to(
105+
"SELECT * FROM (a NATURAL JOIN (b) c )",
106+
"SELECT * FROM (a NATURAL JOIN b AS c)",
107+
);
104108

105-
// Double parentheses around table names are non-standard, but supported in Snowflake SQL
106-
let sql = "SELECT * FROM (a NATURAL JOIN ((b)))";
107-
let select = snowflake_and_generic().verified_only_select(sql);
108-
let from = only(select.from);
109+
snowflake_and_generic().one_statement_parses_to(
110+
"SELECT * FROM (a NATURAL JOIN ((b)) c )",
111+
"SELECT * FROM (a NATURAL JOIN b AS c)",
112+
);
113+
114+
snowflake_and_generic().one_statement_parses_to(
115+
"SELECT * FROM (a NATURAL JOIN ( (b) c ) )",
116+
"SELECT * FROM (a NATURAL JOIN b AS c)",
117+
);
109118

110-
assert_eq!(from.relation, nest!(table("a"), nest!(nest!(table("b")))));
119+
snowflake_and_generic().one_statement_parses_to(
120+
"SELECT * FROM (a NATURAL JOIN ( (b) as c ) )",
121+
"SELECT * FROM (a NATURAL JOIN b AS c)",
122+
);
123+
124+
snowflake_and_generic().one_statement_parses_to(
125+
"SELECT * FROM (a alias1 NATURAL JOIN ( (b) c ) )",
126+
"SELECT * FROM (a AS alias1 NATURAL JOIN b AS c)",
127+
);
128+
129+
snowflake_and_generic().one_statement_parses_to(
130+
"SELECT * FROM (a as alias1 NATURAL JOIN ( (b) as c ) )",
131+
"SELECT * FROM (a AS alias1 NATURAL JOIN b AS c)",
132+
);
133+
134+
let res = snowflake_and_generic().parse_sql_statements("SELECT * FROM (a NATURAL JOIN b) c");
135+
assert_eq!(
136+
ParserError::ParserError("Expected end of statement, found: c".to_string()),
137+
res.unwrap_err()
138+
);
139+
140+
let res = snowflake().parse_sql_statements("SELECT * FROM (a b) c");
141+
assert_eq!(
142+
ParserError::ParserError("duplicate alias b".to_string()),
143+
res.unwrap_err()
144+
);
145+
}
146+
147+
fn snowflake() -> TestedDialects {
148+
TestedDialects {
149+
dialects: vec![Box::new(SnowflakeDialect {})],
150+
}
111151
}
112152

113153
fn snowflake_and_generic() -> TestedDialects {

0 commit comments

Comments
 (0)