From 99fb633221c48c481edb669cd3b376688c88a5dc Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Mon, 5 Oct 2020 05:08:55 +0300 Subject: [PATCH 1/4] Move existing SF tests to sqlparser_snowflake.rs Co-authored-by: Eyal Leshem --- src/test_utils.rs | 16 +++++++++ tests/macros/mod.rs | 20 +++++++++++ tests/sqlparser_common.rs | 66 +++--------------------------------- tests/sqlparser_snowflake.rs | 46 ++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 62 deletions(-) create mode 100644 tests/macros/mod.rs diff --git a/src/test_utils.rs b/src/test_utils.rs index 7d9636068..0507fa866 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -143,3 +143,19 @@ pub fn expr_from_projection(item: &SelectItem) -> &Expr { pub fn number(n: &'static str) -> Value { Value::Number(n.parse().unwrap()) } + +pub fn table(name: impl Into) -> TableFactor { + TableFactor::Table { + name: ObjectName(vec![Ident::new(name.into())]), + alias: None, + args: vec![], + with_hints: vec![], + } +} + +pub fn join(relation: TableFactor) -> Join { + Join { + relation, + join_operator: JoinOperator::Inner(JoinConstraint::Natural), + } +} diff --git a/tests/macros/mod.rs b/tests/macros/mod.rs new file mode 100644 index 000000000..365024a90 --- /dev/null +++ b/tests/macros/mod.rs @@ -0,0 +1,20 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +macro_rules! nest { + ($base:expr $(, $join:expr)*) => { + TableFactor::NestedJoin(Box::new(TableWithJoins { + relation: $base, + joins: vec![$(join($join)),*] + })) + }; +} diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 6b032d926..a535fa212 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -18,12 +18,15 @@ //! sqlparser regardless of the chosen dialect (i.e. it doesn't conflict with //! dialect-specific parsing rules). -use matches::assert_matches; +#[macro_use] +#[path = "macros/mod.rs"] +mod macros; +use matches::assert_matches; use sqlparser::ast::*; use sqlparser::dialect::keywords::ALL_KEYWORDS; use sqlparser::parser::ParserError; -use sqlparser::test_utils::{all_dialects, expr_from_projection, number, only}; +use sqlparser::test_utils::{all_dialects, expr_from_projection, join, number, only, table}; #[test] fn parse_insert_values() { @@ -2282,31 +2285,6 @@ fn parse_complex_join() { #[test] fn parse_join_nesting() { - fn table(name: impl Into) -> TableFactor { - TableFactor::Table { - name: ObjectName(vec![Ident::new(name.into())]), - alias: None, - args: vec![], - with_hints: vec![], - } - } - - fn join(relation: TableFactor) -> Join { - Join { - relation, - join_operator: JoinOperator::Inner(JoinConstraint::Natural), - } - } - - macro_rules! nest { - ($base:expr $(, $join:expr)*) => { - TableFactor::NestedJoin(Box::new(TableWithJoins { - relation: $base, - joins: vec![$(join($join)),*] - })) - }; - } - let sql = "SELECT * FROM a NATURAL JOIN (b NATURAL JOIN (c NATURAL JOIN d NATURAL JOIN e)) \ NATURAL JOIN (f NATURAL JOIN (g NATURAL JOIN h))"; assert_eq!( @@ -2337,20 +2315,6 @@ fn parse_join_nesting() { from.joins, vec![join(nest!(nest!(nest!(table("b"), table("c")))))] ); - - // Parenthesized table names are non-standard, but supported in Snowflake SQL - let sql = "SELECT * FROM (a NATURAL JOIN (b))"; - let select = verified_only_select(sql); - let from = only(select.from); - - assert_eq!(from.relation, nest!(table("a"), nest!(table("b")))); - - // Double parentheses around table names are non-standard, but supported in Snowflake SQL - let sql = "SELECT * FROM (a NATURAL JOIN ((b)))"; - let select = verified_only_select(sql); - let from = only(select.from); - - assert_eq!(from.relation, nest!(table("a"), nest!(nest!(table("b"))))); } #[test] @@ -2490,26 +2454,6 @@ fn parse_derived_tables() { }], })) ); - - // Nesting a subquery in parentheses is non-standard, but supported in Snowflake SQL - let sql = "SELECT * FROM ((SELECT 1) AS t)"; - let select = verified_only_select(sql); - let from = only(select.from); - - assert_eq!( - from.relation, - TableFactor::NestedJoin(Box::new(TableWithJoins { - relation: TableFactor::Derived { - lateral: false, - subquery: Box::new(verified_query("SELECT 1")), - alias: Some(TableAlias { - name: "t".into(), - columns: vec![], - }) - }, - joins: Vec::new(), - })) - ); } #[test] diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index c1aa41db3..5915d1231 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -9,9 +9,14 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +#[macro_use] +#[path = "macros/mod.rs"] +mod macros; + +use sqlparser::test_utils::*; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; -use sqlparser::test_utils::*; use sqlparser::tokenizer::*; #[test] @@ -63,6 +68,45 @@ fn test_snowflake_single_line_tokenize() { assert_eq!(expected, tokens); } +#[test] +fn test_sf_derives_single_table_in_parenthesis() { + // Nesting a subquery in parentheses is non-standard, but supported in Snowflake SQL + let sql = "SELECT * FROM ((SELECT 1) AS t)"; + let select = snowflake_and_generic().verified_only_select(sql); + let from = only(select.from); + assert_eq!( + from.relation, + TableFactor::NestedJoin(Box::new(TableWithJoins { + relation: TableFactor::Derived { + lateral: false, + subquery: Box::new(snowflake_and_generic().verified_query("SELECT 1")), + alias: Some(TableAlias { + name: "t".into(), + columns: vec![], + }) + }, + joins: Vec::new(), + })) + ); +} + +#[test] +fn test_single_table_in_parenthesis() { + // Parenthesized table names are non-standard, but supported in Snowflake SQL + let sql = "SELECT * FROM (a NATURAL JOIN (b))"; + let select = snowflake_and_generic().verified_only_select(sql); + let from = only(select.from); + + assert_eq!(from.relation, nest!(table("a"), nest!(table("b")))); + + // Double parentheses around table names are non-standard, but supported in Snowflake SQL + let sql = "SELECT * FROM (a NATURAL JOIN ((b)))"; + let select = snowflake_and_generic().verified_only_select(sql); + let from = only(select.from); + + assert_eq!(from.relation, nest!(table("a"), nest!(nest!(table("b"))))); +} + fn snowflake_and_generic() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})], From 4128dfe1db9d5ee80e7282087234cc1dbedbef99 Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Mon, 5 Oct 2020 07:43:51 +0300 Subject: [PATCH 2/4] Introduce tests/test_utils/mod.rs and use it consistently To share helper macros between various tests/* we added a new module (tests/macros/mod.rs). This made the prologue to be used in tests quite long and a little weird: ``` #[macro_use] #[path = "macros/mod.rs"] mod macros; use sqlparser::test_utils::*; ``` This simplifies it to: ``` #[macro_use] mod test_utils; use test_utils::*; ``` - and switches all existing tests to the new prologue simultaneously... ...while fixing a few other inconsistencies and adding a few comments about the way `test_utils` work. --- src/lib.rs | 1 + src/test_utils.rs | 6 ++++++ tests/sqlparser_common.rs | 5 ++--- tests/sqlparser_mssql.rs | 5 ++++- tests/sqlparser_mysql.rs | 6 ++++-- tests/sqlparser_postgres.rs | 5 ++++- tests/sqlparser_regression.rs | 2 ++ tests/sqlparser_snowflake.rs | 9 ++++++--- tests/sqlparser_sqlite.rs | 5 ++++- tests/{macros => test_utils}/mod.rs | 14 ++++++++++++++ 10 files changed, 47 insertions(+), 11 deletions(-) rename tests/{macros => test_utils}/mod.rs (53%) diff --git a/src/lib.rs b/src/lib.rs index 8c9b01702..5b2324579 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,4 +43,5 @@ pub mod tokenizer; #[doc(hidden)] // This is required to make utilities accessible by both the crate-internal // unit-tests and by the integration tests +// External users are not supposed to rely on this module. pub mod test_utils; diff --git a/src/test_utils.rs b/src/test_utils.rs index 0507fa866..1c3985750 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -10,6 +10,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// This module contains internal utilities used for testing the library. +/// While technically public, the library's users are not supposed to rely +/// on this module, as it will change without notice. +// +// Integration tests (i.e. everything under `tests/`) import this +// via `tests/test_utils/mod.rs`. use std::fmt::Debug; use super::ast::*; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index a535fa212..6bf3ad060 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -19,14 +19,13 @@ //! dialect-specific parsing rules). #[macro_use] -#[path = "macros/mod.rs"] -mod macros; +mod test_utils; +use test_utils::{all_dialects, expr_from_projection, join, number, only, table}; use matches::assert_matches; use sqlparser::ast::*; use sqlparser::dialect::keywords::ALL_KEYWORDS; use sqlparser::parser::ParserError; -use sqlparser::test_utils::{all_dialects, expr_from_projection, join, number, only, table}; #[test] fn parse_insert_values() { diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 2774d43ef..f4e19dfa5 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -14,9 +14,12 @@ //! Test SQL syntax specific to Microsoft's T-SQL. The parser based on the //! generic dialect is also tested (on the inputs it can handle). +#[macro_use] +mod test_utils; +use test_utils::*; + use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MsSqlDialect}; -use sqlparser::test_utils::*; #[test] fn parse_mssql_identifiers() { diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index c0fc8c8ba..a8f85584c 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -11,13 +11,15 @@ // limitations under the License. #![warn(clippy::all)] - //! Test SQL syntax specific to MySQL. The parser based on the generic dialect //! is also tested (on the inputs it can handle). +#[macro_use] +mod test_utils; +use test_utils::*; + use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MySqlDialect}; -use sqlparser::test_utils::*; use sqlparser::tokenizer::Token; #[test] diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ecc0e77e3..20f186100 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -14,10 +14,13 @@ //! Test SQL syntax specific to PostgreSQL. The parser based on the //! generic dialect is also tested (on the inputs it can handle). +#[macro_use] +mod test_utils; +use test_utils::*; + use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, PostgreSqlDialect}; use sqlparser::parser::ParserError; -use sqlparser::test_utils::*; #[test] fn parse_create_table_with_defaults() { diff --git a/tests/sqlparser_regression.rs b/tests/sqlparser_regression.rs index 5262ff7aa..bbf1b2977 100644 --- a/tests/sqlparser_regression.rs +++ b/tests/sqlparser_regression.rs @@ -10,6 +10,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![warn(clippy::all)] + use sqlparser::dialect::GenericDialect; use sqlparser::parser::Parser; diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 5915d1231..694603a7e 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -10,11 +10,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![warn(clippy::all)] +//! Test SQL syntax specific to Snowflake. The parser based on the +//! generic dialect is also tested (on the inputs it can handle). + #[macro_use] -#[path = "macros/mod.rs"] -mod macros; +mod test_utils; +use test_utils::*; -use sqlparser::test_utils::*; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; use sqlparser::tokenizer::*; diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 2a421e94b..10816e1bb 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -14,9 +14,12 @@ //! Test SQL syntax specific to SQLite. The parser based on the //! generic dialect is also tested (on the inputs it can handle). +#[macro_use] +mod test_utils; +use test_utils::*; + use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SQLiteDialect}; -use sqlparser::test_utils::*; use sqlparser::tokenizer::Token; #[test] diff --git a/tests/macros/mod.rs b/tests/test_utils/mod.rs similarity index 53% rename from tests/macros/mod.rs rename to tests/test_utils/mod.rs index 365024a90..f224314b9 100644 --- a/tests/macros/mod.rs +++ b/tests/test_utils/mod.rs @@ -10,6 +10,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Re-export everything from `src/test_utils.rs`. +pub use sqlparser::test_utils::*; + +// For the test-only macros we take a different approach of keeping them here +// rather than in the library crate. +// +// This is because we don't need any of them to be shared between the +// integration tests (i.e. `tests/*`) and the unit tests (i.e. `src/*`), +// but also because Rust doesn't scope macros to a particular module +// (and while we export internal helpers as sqlparser::test_utils::<...>, +// expecting our users to abstain from relying on them, exporting internal +// macros at the top level, like `sqlparser::nest` was deemed too confusing). + +#[macro_export] macro_rules! nest { ($base:expr $(, $join:expr)*) => { TableFactor::NestedJoin(Box::new(TableWithJoins { From d9e044aabbeb90180ee37ce77651d3bc6c7e47f3 Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Mon, 12 Oct 2020 06:09:34 +0300 Subject: [PATCH 3/4] Extend one_statement_parses_to to also test parsing canonical SQL It was an omission of the original implementation. --- src/test_utils.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test_utils.rs b/src/test_utils.rs index 1c3985750..a9e35d9d2 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -69,13 +69,19 @@ impl TestedDialects { // Parser::parse_sql(&**self.dialects.first().unwrap(), sql) } - /// Ensures that `sql` parses as a single statement, optionally checking - /// that converting AST back to string equals to `canonical` (unless an - /// empty canonical string is provided). + /// Ensures that `sql` parses as a single statement and returns it. + /// If non-empty `canonical` SQL representation is provided, + /// additionally asserts that parsing `sql` results in the same parse + /// tree as parsing `canonical`, and that serializing it back to string + /// results in the `canonical` representation. pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement { let mut statements = self.parse_sql_statements(&sql).unwrap(); assert_eq!(statements.len(), 1); + if !canonical.is_empty() && sql != canonical { + assert_eq!(self.parse_sql_statements(&canonical).unwrap(), statements); + } + let only_statement = statements.pop().unwrap(); if !canonical.is_empty() { assert_eq!(canonical, only_statement.to_string()) From ad72cda6b0f1797cbd5444e58ba2b2c1a48e0114 Mon Sep 17 00:00:00 2001 From: Nickolay Ponomarev Date: Tue, 13 Oct 2020 09:29:16 +0300 Subject: [PATCH 4/4] [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 }`, 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 --- src/ast/query.rs | 7 +-- src/parser.rs | 60 ++++++++++++++++++++--- src/test_utils.rs | 7 +++ tests/sqlparser_common.rs | 9 +--- tests/sqlparser_snowflake.rs | 94 +++++++++++++++++++++++++----------- 5 files changed, 131 insertions(+), 46 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index e0dbe4c72..52c0fec27 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -242,9 +242,10 @@ pub enum TableFactor { }, /// Represents a parenthesized table factor. The SQL spec only allows a /// join expression (`(foo bar [ baz ... ])`) to be nested, - /// possibly several times, but the parser also accepts the non-standard - /// nesting of bare tables (`table_with_joins.joins.is_empty()`), so the - /// name `NestedJoin` is a bit of misnomer. + /// possibly several times. + /// + /// 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), } diff --git a/src/parser.rs b/src/parser.rs index 438c9f1ef..7d2242f76 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2156,14 +2156,58 @@ impl<'a> Parser<'a> { // 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 '('. - // Inside the parentheses we expect to find a table factor - // followed by some joins or another level of nesting. - let table_and_joins = self.parse_table_and_joins()?; - self.expect_token(&Token::RParen)?; - // The SQL spec prohibits derived and bare tables from appearing - // alone in parentheses. We don't enforce this as some databases - // (e.g. Snowflake) allow such syntax. - Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) + // Inside the parentheses we expect to find an (A) table factor + // followed by some joins or (B) another level of nesting. + let mut table_and_joins = self.parse_table_and_joins()?; + + if !table_and_joins.joins.is_empty() { + self.expect_token(&Token::RParen)?; + Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) // (A) + } 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 { let name = self.parse_object_name()?; // Postgres, MSSQL: table-valued functions: diff --git a/src/test_utils.rs b/src/test_utils.rs index a9e35d9d2..2fcacffa9 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -156,6 +156,13 @@ pub fn number(n: &'static str) -> Value { Value::Number(n.parse().unwrap()) } +pub fn table_alias(name: impl Into) -> Option { + Some(TableAlias { + name: Ident::new(name), + columns: vec![], + }) +} + pub fn table(name: impl Into) -> TableFactor { TableFactor::Table { name: ObjectName(vec![Ident::new(name.into())]), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 6bf3ad060..68b493c43 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20,7 +20,7 @@ #[macro_use] 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 sqlparser::ast::*; @@ -2130,13 +2130,6 @@ fn parse_cross_join() { ); } -fn table_alias(name: impl Into) -> Option { - Some(TableAlias { - name: Ident::new(name), - columns: vec![], - }) -} - #[test] fn parse_joins_on() { fn join_with_constraint( diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 694603a7e..1b1aaec9b 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -20,6 +20,7 @@ use test_utils::*; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; +use sqlparser::parser::ParserError; use sqlparser::tokenizer::*; #[test] @@ -72,42 +73,81 @@ fn test_snowflake_single_line_tokenize() { } #[test] -fn test_sf_derives_single_table_in_parenthesis() { - // Nesting a subquery in parentheses is non-standard, but supported in Snowflake SQL - let sql = "SELECT * FROM ((SELECT 1) AS t)"; - let select = snowflake_and_generic().verified_only_select(sql); - let from = only(select.from); - assert_eq!( - from.relation, - TableFactor::NestedJoin(Box::new(TableWithJoins { - relation: TableFactor::Derived { - lateral: false, - subquery: Box::new(snowflake_and_generic().verified_query("SELECT 1")), - alias: Some(TableAlias { - name: "t".into(), - columns: vec![], - }) - }, - joins: Vec::new(), - })) +fn test_sf_derived_table_in_parenthesis() { + // Nesting a subquery in an extra set of parentheses is non-standard, + // but supported in Snowflake SQL + snowflake_and_generic().one_statement_parses_to( + "SELECT * FROM ((SELECT 1) AS t)", + "SELECT * FROM (SELECT 1) AS t", + ); + snowflake_and_generic().one_statement_parses_to( + "SELECT * FROM (((SELECT 1) AS t))", + "SELECT * FROM (SELECT 1) AS t", ); } #[test] fn test_single_table_in_parenthesis() { // Parenthesized table names are non-standard, but supported in Snowflake SQL - let sql = "SELECT * FROM (a NATURAL JOIN (b))"; - let select = snowflake_and_generic().verified_only_select(sql); - let from = only(select.from); + snowflake_and_generic().one_statement_parses_to( + "SELECT * FROM (a NATURAL JOIN (b))", + "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 - let sql = "SELECT * FROM (a NATURAL JOIN ((b)))"; - let select = snowflake_and_generic().verified_only_select(sql); - let from = only(select.from); + 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) c ) )", + "SELECT * FROM (a NATURAL JOIN b AS c)", + ); - assert_eq!(from.relation, nest!(table("a"), nest!(nest!(table("b"))))); + 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 {