Skip to content

[snowflake] Support FROM (table_name) alias #260

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,10 @@ pub enum TableFactor {
},
/// Represents a parenthesized table factor. The SQL spec only allows a
/// join expression (`(foo <JOIN> bar [ <JOIN> 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<TableWithJoins>),
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://stackoverflow.com/a/44541071/1026>
// External users are not supposed to rely on this module.
pub mod test_utils;
60 changes: 52 additions & 8 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
41 changes: 38 additions & 3 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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<String>) -> Option<TableAlias> {
Some(TableAlias {
name: Ident::new(name),
columns: vec![],
})
}

pub fn table(name: impl Into<String>) -> 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),
}
}
72 changes: 4 additions & 68 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -2128,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]
fn parse_joins_on() {
fn join_with_constraint(
Expand Down Expand Up @@ -2282,31 +2277,6 @@ fn parse_complex_join() {

#[test]
fn parse_join_nesting() {
fn table(name: impl Into<String>) -> 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!(
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
5 changes: 4 additions & 1 deletion tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
6 changes: 4 additions & 2 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
5 changes: 4 additions & 1 deletion tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading