Skip to content

Commit 929fc67

Browse files
authored
Merge pull request #260 from eyalleshem/single_tables_in_parens
[snowflake] Support `FROM (table_name) alias`
2 parents 9f772f0 + ad72cda commit 929fc67

12 files changed

+239
-88
lines changed

src/ast/query.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,10 @@ pub enum TableFactor {
260260
},
261261
/// Represents a parenthesized table factor. The SQL spec only allows a
262262
/// join expression (`(foo <JOIN> bar [ <JOIN> baz ... ])`) to be nested,
263-
/// possibly several times, but the parser also accepts the non-standard
264-
/// nesting of bare tables (`table_with_joins.joins.is_empty()`), so the
265-
/// name `NestedJoin` is a bit of misnomer.
263+
/// possibly several times.
264+
///
265+
/// The parser may also accept non-standard nesting of bare tables for some
266+
/// dialects, but the information about such nesting is stripped from AST.
266267
NestedJoin(Box<TableWithJoins>),
267268
}
268269

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ pub mod tokenizer;
4343
#[doc(hidden)]
4444
// This is required to make utilities accessible by both the crate-internal
4545
// unit-tests and by the integration tests <https://stackoverflow.com/a/44541071/1026>
46+
// External users are not supposed to rely on this module.
4647
pub mod test_utils;

src/parser.rs

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

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

src/test_utils.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
// See the License for the specific language governing permissions and
1111
// limitations under the License.
1212

13+
/// This module contains internal utilities used for testing the library.
14+
/// While technically public, the library's users are not supposed to rely
15+
/// on this module, as it will change without notice.
16+
//
17+
// Integration tests (i.e. everything under `tests/`) import this
18+
// via `tests/test_utils/mod.rs`.
1319
use std::fmt::Debug;
1420

1521
use super::ast::*;
@@ -63,13 +69,19 @@ impl TestedDialects {
6369
// Parser::parse_sql(&**self.dialects.first().unwrap(), sql)
6470
}
6571

66-
/// Ensures that `sql` parses as a single statement, optionally checking
67-
/// that converting AST back to string equals to `canonical` (unless an
68-
/// empty canonical string is provided).
72+
/// Ensures that `sql` parses as a single statement and returns it.
73+
/// If non-empty `canonical` SQL representation is provided,
74+
/// additionally asserts that parsing `sql` results in the same parse
75+
/// tree as parsing `canonical`, and that serializing it back to string
76+
/// results in the `canonical` representation.
6977
pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement {
7078
let mut statements = self.parse_sql_statements(&sql).unwrap();
7179
assert_eq!(statements.len(), 1);
7280

81+
if !canonical.is_empty() && sql != canonical {
82+
assert_eq!(self.parse_sql_statements(&canonical).unwrap(), statements);
83+
}
84+
7385
let only_statement = statements.pop().unwrap();
7486
if !canonical.is_empty() {
7587
assert_eq!(canonical, only_statement.to_string())
@@ -143,3 +155,26 @@ pub fn expr_from_projection(item: &SelectItem) -> &Expr {
143155
pub fn number(n: &'static str) -> Value {
144156
Value::Number(n.parse().unwrap())
145157
}
158+
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+
166+
pub fn table(name: impl Into<String>) -> TableFactor {
167+
TableFactor::Table {
168+
name: ObjectName(vec![Ident::new(name.into())]),
169+
alias: None,
170+
args: vec![],
171+
with_hints: vec![],
172+
}
173+
}
174+
175+
pub fn join(relation: TableFactor) -> Join {
176+
Join {
177+
relation,
178+
join_operator: JoinOperator::Inner(JoinConstraint::Natural),
179+
}
180+
}

tests/sqlparser_common.rs

+4-68
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
//! sqlparser regardless of the chosen dialect (i.e. it doesn't conflict with
1919
//! dialect-specific parsing rules).
2020
21-
use matches::assert_matches;
21+
#[macro_use]
22+
mod test_utils;
23+
use test_utils::{all_dialects, expr_from_projection, join, number, only, table, table_alias};
2224

25+
use matches::assert_matches;
2326
use sqlparser::ast::*;
2427
use sqlparser::dialect::keywords::ALL_KEYWORDS;
2528
use sqlparser::parser::ParserError;
26-
use sqlparser::test_utils::{all_dialects, expr_from_projection, number, only};
2729

2830
#[test]
2931
fn parse_insert_values() {
@@ -2128,13 +2130,6 @@ fn parse_cross_join() {
21282130
);
21292131
}
21302132

2131-
fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
2132-
Some(TableAlias {
2133-
name: Ident::new(name),
2134-
columns: vec![],
2135-
})
2136-
}
2137-
21382133
#[test]
21392134
fn parse_joins_on() {
21402135
fn join_with_constraint(
@@ -2282,31 +2277,6 @@ fn parse_complex_join() {
22822277

22832278
#[test]
22842279
fn parse_join_nesting() {
2285-
fn table(name: impl Into<String>) -> TableFactor {
2286-
TableFactor::Table {
2287-
name: ObjectName(vec![Ident::new(name.into())]),
2288-
alias: None,
2289-
args: vec![],
2290-
with_hints: vec![],
2291-
}
2292-
}
2293-
2294-
fn join(relation: TableFactor) -> Join {
2295-
Join {
2296-
relation,
2297-
join_operator: JoinOperator::Inner(JoinConstraint::Natural),
2298-
}
2299-
}
2300-
2301-
macro_rules! nest {
2302-
($base:expr $(, $join:expr)*) => {
2303-
TableFactor::NestedJoin(Box::new(TableWithJoins {
2304-
relation: $base,
2305-
joins: vec![$(join($join)),*]
2306-
}))
2307-
};
2308-
}
2309-
23102280
let sql = "SELECT * FROM a NATURAL JOIN (b NATURAL JOIN (c NATURAL JOIN d NATURAL JOIN e)) \
23112281
NATURAL JOIN (f NATURAL JOIN (g NATURAL JOIN h))";
23122282
assert_eq!(
@@ -2337,20 +2307,6 @@ fn parse_join_nesting() {
23372307
from.joins,
23382308
vec![join(nest!(nest!(nest!(table("b"), table("c")))))]
23392309
);
2340-
2341-
// Parenthesized table names are non-standard, but supported in Snowflake SQL
2342-
let sql = "SELECT * FROM (a NATURAL JOIN (b))";
2343-
let select = verified_only_select(sql);
2344-
let from = only(select.from);
2345-
2346-
assert_eq!(from.relation, nest!(table("a"), nest!(table("b"))));
2347-
2348-
// Double parentheses around table names are non-standard, but supported in Snowflake SQL
2349-
let sql = "SELECT * FROM (a NATURAL JOIN ((b)))";
2350-
let select = verified_only_select(sql);
2351-
let from = only(select.from);
2352-
2353-
assert_eq!(from.relation, nest!(table("a"), nest!(nest!(table("b")))));
23542310
}
23552311

23562312
#[test]
@@ -2527,26 +2483,6 @@ fn parse_derived_tables() {
25272483
}],
25282484
}))
25292485
);
2530-
2531-
// Nesting a subquery in parentheses is non-standard, but supported in Snowflake SQL
2532-
let sql = "SELECT * FROM ((SELECT 1) AS t)";
2533-
let select = verified_only_select(sql);
2534-
let from = only(select.from);
2535-
2536-
assert_eq!(
2537-
from.relation,
2538-
TableFactor::NestedJoin(Box::new(TableWithJoins {
2539-
relation: TableFactor::Derived {
2540-
lateral: false,
2541-
subquery: Box::new(verified_query("SELECT 1")),
2542-
alias: Some(TableAlias {
2543-
name: "t".into(),
2544-
columns: vec![],
2545-
})
2546-
},
2547-
joins: Vec::new(),
2548-
}))
2549-
);
25502486
}
25512487

25522488
#[test]

tests/sqlparser_mssql.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
//! Test SQL syntax specific to Microsoft's T-SQL. The parser based on the
1515
//! generic dialect is also tested (on the inputs it can handle).
1616
17+
#[macro_use]
18+
mod test_utils;
19+
use test_utils::*;
20+
1721
use sqlparser::ast::*;
1822
use sqlparser::dialect::{GenericDialect, MsSqlDialect};
19-
use sqlparser::test_utils::*;
2023

2124
#[test]
2225
fn parse_mssql_identifiers() {

tests/sqlparser_mysql.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
// limitations under the License.
1212

1313
#![warn(clippy::all)]
14-
1514
//! Test SQL syntax specific to MySQL. The parser based on the generic dialect
1615
//! is also tested (on the inputs it can handle).
1716
17+
#[macro_use]
18+
mod test_utils;
19+
use test_utils::*;
20+
1821
use sqlparser::ast::*;
1922
use sqlparser::dialect::{GenericDialect, MySqlDialect};
20-
use sqlparser::test_utils::*;
2123
use sqlparser::tokenizer::Token;
2224

2325
#[test]

tests/sqlparser_postgres.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
//! Test SQL syntax specific to PostgreSQL. The parser based on the
1515
//! generic dialect is also tested (on the inputs it can handle).
1616
17+
#[macro_use]
18+
mod test_utils;
19+
use test_utils::*;
20+
1721
use sqlparser::ast::*;
1822
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect};
1923
use sqlparser::parser::ParserError;
20-
use sqlparser::test_utils::*;
2124

2225
#[test]
2326
fn parse_create_table_with_defaults() {

tests/sqlparser_regression.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// See the License for the specific language governing permissions and
1111
// limitations under the License.
1212

13+
#![warn(clippy::all)]
14+
1315
use sqlparser::dialect::GenericDialect;
1416
use sqlparser::parser::Parser;
1517

0 commit comments

Comments
 (0)