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/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/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 7d9636068..2fcacffa9 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::*; @@ -63,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()) @@ -143,3 +155,26 @@ pub fn expr_from_projection(item: &SelectItem) -> &Expr { 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())]), + 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/sqlparser_common.rs b/tests/sqlparser_common.rs index 6b032d926..68b493c43 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -18,12 +18,14 @@ //! sqlparser regardless of the chosen dialect (i.e. it doesn't conflict with //! dialect-specific parsing rules). -use matches::assert_matches; +#[macro_use] +mod test_utils; +use test_utils::{all_dialects, expr_from_projection, join, number, only, table, table_alias}; +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}; #[test] fn parse_insert_values() { @@ -2128,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( @@ -2282,31 +2277,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 +2307,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 +2446,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_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 c1aa41db3..1b1aaec9b 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -9,9 +9,18 @@ // 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. + +#![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] +mod test_utils; +use test_utils::*; + use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SnowflakeDialect}; -use sqlparser::test_utils::*; +use sqlparser::parser::ParserError; use sqlparser::tokenizer::*; #[test] @@ -63,6 +72,84 @@ fn test_snowflake_single_line_tokenize() { assert_eq!(expected, tokens); } +#[test] +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 + 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)", + ); +} + +#[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)", + ); + + 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)", + ); + + 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 { TestedDialects { dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})], 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/test_utils/mod.rs b/tests/test_utils/mod.rs new file mode 100644 index 000000000..f224314b9 --- /dev/null +++ b/tests/test_utils/mod.rs @@ -0,0 +1,34 @@ +// 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. + +// 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 { + relation: $base, + joins: vec![$(join($join)),*] + })) + }; +}