diff --git a/src/ast/query.rs b/src/ast/query.rs index 33c92614f..16fc9ec0e 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1902,7 +1902,7 @@ impl fmt::Display for TableFactor { write!(f, " {sample}")?; } if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } if !index_hints.is_empty() { write!(f, " {}", display_separated(index_hints, " "))?; @@ -1932,7 +1932,7 @@ impl fmt::Display for TableFactor { NewLine.fmt(f)?; f.write_str(")")?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -1948,14 +1948,14 @@ impl fmt::Display for TableFactor { write!(f, "{name}")?; write!(f, "({})", display_comma_separated(args))?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } TableFactor::TableFunction { expr, alias } => { write!(f, "TABLE({expr})")?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -1973,13 +1973,13 @@ impl fmt::Display for TableFactor { } if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } if *with_offset { write!(f, " WITH OFFSET")?; } if let Some(alias) = with_offset_alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -1995,7 +1995,7 @@ impl fmt::Display for TableFactor { columns = display_comma_separated(columns) )?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -2014,7 +2014,7 @@ impl fmt::Display for TableFactor { write!(f, " WITH ({})", display_comma_separated(columns))?; } if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -2024,7 +2024,7 @@ impl fmt::Display for TableFactor { } => { write!(f, "({table_with_joins})")?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -2051,8 +2051,8 @@ impl fmt::Display for TableFactor { write!(f, " DEFAULT ON NULL ({expr})")?; } write!(f, ")")?; - if alias.is_some() { - write!(f, " AS {}", alias.as_ref().unwrap())?; + if let Some(alias) = alias { + write!(f, " {alias}")?; } Ok(()) } @@ -2075,8 +2075,8 @@ impl fmt::Display for TableFactor { name, display_comma_separated(columns) )?; - if alias.is_some() { - write!(f, " AS {}", alias.as_ref().unwrap())?; + if let Some(alias) = alias { + write!(f, " {alias}")?; } Ok(()) } @@ -2109,8 +2109,8 @@ impl fmt::Display for TableFactor { } write!(f, "PATTERN ({pattern}) ")?; write!(f, "DEFINE {})", display_comma_separated(symbols))?; - if alias.is_some() { - write!(f, " AS {}", alias.as_ref().unwrap())?; + if let Some(alias) = alias { + write!(f, " {alias}")?; } Ok(()) } @@ -2135,7 +2135,7 @@ impl fmt::Display for TableFactor { columns = display_comma_separated(columns) )?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) } @@ -2168,7 +2168,7 @@ impl fmt::Display for TableFactor { write!(f, ")")?; if let Some(alias) = alias { - write!(f, " AS {alias}")?; + write!(f, " {alias}")?; } Ok(()) @@ -2181,13 +2181,17 @@ impl fmt::Display for TableFactor { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct TableAlias { + /// Tells whether the alias was introduced with an explicit, preceding "AS" + /// keyword, e.g. `AS name`. Typically, the keyword is preceding the name + /// (e.g. `.. FROM table AS t ..`). + pub explicit: bool, pub name: Ident, pub columns: Vec, } impl fmt::Display for TableAlias { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.name)?; + write!(f, "{}{}", if self.explicit { "AS " } else { "" }, self.name)?; if !self.columns.is_empty() { write!(f, " ({})", display_comma_separated(&self.columns))?; } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index d54290b68..b50cc56d5 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -15,10 +15,13 @@ // specific language governing permissions and limitations // under the License. -use crate::ast::{ - ddl::AlterSchema, query::SelectItemQualifiedWildcardKind, AlterSchemaOperation, AlterTable, - ColumnOptions, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreateView, - ExportData, Owner, TypedString, +use crate::{ + ast::{ + ddl::AlterSchema, query::SelectItemQualifiedWildcardKind, AlterSchemaOperation, AlterTable, + ColumnOptions, CreateOperator, CreateOperatorClass, CreateOperatorFamily, CreateView, + ExportData, Owner, TypedString, + }, + tokenizer::TokenWithSpan, }; use core::iter; @@ -96,6 +99,12 @@ pub trait Spanned { fn span(&self) -> Span; } +impl Spanned for TokenWithSpan { + fn span(&self) -> Span { + self.span + } +} + impl Spanned for Query { fn span(&self) -> Span { let Query { @@ -2079,9 +2088,12 @@ impl Spanned for FunctionArgExpr { impl Spanned for TableAlias { fn span(&self) -> Span { - let TableAlias { name, columns } = self; - - union_spans(iter::once(name.span).chain(columns.iter().map(|i| i.span()))) + let TableAlias { + explicit: _, + name, + columns, + } = self; + union_spans(core::iter::once(name.span).chain(columns.iter().map(Spanned::span))) } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1fa7f796a..57d94bee1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -11140,10 +11140,15 @@ impl<'a> Parser<'a> { fn validator(explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool { parser.dialect.is_table_factor_alias(explicit, kw, parser) } + let explicit = self.peek_keyword(Keyword::AS); match self.parse_optional_alias_inner(None, validator)? { Some(name) => { let columns = self.parse_table_alias_column_defs()?; - Ok(Some(TableAlias { name, columns })) + Ok(Some(TableAlias { + explicit, + name, + columns, + })) } None => Ok(None), } @@ -12775,6 +12780,7 @@ impl<'a> Parser<'a> { let closing_paren_token = self.expect_token(&Token::RParen)?; let alias = TableAlias { + explicit: false, name, columns: vec![], }; @@ -12801,7 +12807,11 @@ impl<'a> Parser<'a> { let query = self.parse_query()?; let closing_paren_token = self.expect_token(&Token::RParen)?; - let alias = TableAlias { name, columns }; + let alias = TableAlias { + explicit: false, + name, + columns, + }; Cte { alias, query, diff --git a/src/test_utils.rs b/src/test_utils.rs index b6100d498..73d29312b 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -368,8 +368,9 @@ pub fn single_quoted_string(s: impl Into) -> Value { Value::SingleQuotedString(s.into()) } -pub fn table_alias(name: impl Into) -> Option { +pub fn table_alias(explicit: bool, name: impl Into) -> Option { Some(TableAlias { + explicit, name: Ident::new(name), columns: vec![], }) @@ -405,13 +406,14 @@ pub fn table_from_name(name: ObjectName) -> TableFactor { } } -pub fn table_with_alias(name: impl Into, alias: impl Into) -> TableFactor { +pub fn table_with_alias( + name: impl Into, + with_as_keyword: bool, + alias: impl Into, +) -> TableFactor { TableFactor::Table { name: ObjectName::from(vec![Ident::new(name)]), - alias: Some(TableAlias { - name: Ident::new(alias), - columns: vec![], - }), + alias: table_alias(with_as_keyword, alias), args: None, with_hints: vec![], version: None, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 15bf59cd3..f2b9f2aff 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -1690,7 +1690,7 @@ fn parse_table_identifiers() { fn parse_hyphenated_table_identifiers() { bigquery().one_statement_parses_to( "select * from foo-bar f join baz-qux b on f.id = b.id", - "SELECT * FROM foo-bar AS f JOIN baz-qux AS b ON f.id = b.id", + "SELECT * FROM foo-bar f JOIN baz-qux b ON f.id = b.id", ); assert_eq!( @@ -1766,7 +1766,7 @@ fn parse_join_constraint_unnest_alias() { .joins, vec![Join { relation: TableFactor::UNNEST { - alias: table_alias("f"), + alias: table_alias(true, "f"), array_exprs: vec![Expr::CompoundIdentifier(vec![ Ident::new("t1"), Ident::new("a") @@ -1841,10 +1841,7 @@ fn parse_merge() { assert_eq!( TableFactor::Table { name: ObjectName::from(vec![Ident::new("inventory")]), - alias: Some(TableAlias { - name: Ident::new("T"), - columns: vec![], - }), + alias: table_alias(true, "T"), args: Default::default(), with_hints: Default::default(), version: Default::default(), @@ -1859,10 +1856,7 @@ fn parse_merge() { assert_eq!( TableFactor::Table { name: ObjectName::from(vec![Ident::new("newArrivals")]), - alias: Some(TableAlias { - name: Ident::new("S"), - columns: vec![], - }), + alias: table_alias(true, "S"), args: Default::default(), with_hints: Default::default(), version: Default::default(), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index ba1e64488..3649e8d3f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -512,10 +512,7 @@ fn parse_update_set_from() { format_clause: None, pipe_operators: vec![], }), - alias: Some(TableAlias { - name: Ident::new("t2"), - columns: vec![], - }) + alias: table_alias(true, "t2") }, joins: vec![] }])), @@ -558,10 +555,7 @@ fn parse_update_with_table_alias() { TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("users")]), - alias: Some(TableAlias { - name: Ident::new("u"), - columns: vec![], - }), + alias: table_alias(true, "u"), args: None, with_hints: vec![], version: None, @@ -627,10 +621,14 @@ fn parse_update_or() { #[test] fn parse_select_with_table_alias_as() { + one_statement_parses_to( + "SELECT a, b, c FROM lineitem AS l (A, B, C)", + "SELECT a, b, c FROM lineitem AS l (A, B, C)", + ); // AS is optional one_statement_parses_to( "SELECT a, b, c FROM lineitem l (A, B, C)", - "SELECT a, b, c FROM lineitem AS l (A, B, C)", + "SELECT a, b, c FROM lineitem l (A, B, C)", ); } @@ -651,6 +649,7 @@ fn parse_select_with_table_alias() { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("lineitem")]), alias: Some(TableAlias { + explicit: true, name: Ident::new("l"), columns: vec![ TableAliasColumnDef::from_name("A"), @@ -851,10 +850,7 @@ fn parse_where_delete_with_alias_statement() { assert_eq!( TableFactor::Table { name: ObjectName::from(vec![Ident::new("basket")]), - alias: Some(TableAlias { - name: Ident::new("a"), - columns: vec![], - }), + alias: table_alias(true, "a"), args: None, with_hints: vec![], version: None, @@ -870,10 +866,7 @@ fn parse_where_delete_with_alias_statement() { Some(vec![TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("basket")]), - alias: Some(TableAlias { - name: Ident::new("b"), - columns: vec![], - }), + alias: table_alias(true, "b"), args: None, with_hints: vec![], version: None, @@ -6819,7 +6812,7 @@ fn parse_table_function() { ), expr ); - assert_eq!(alias, table_alias("a")) + assert_eq!(alias, table_alias(true, "a")) } _ => panic!("Expecting TableFactor::TableFunction"), } @@ -6916,10 +6909,7 @@ fn parse_unnest_in_from_clause() { &dialects, vec![TableWithJoins { relation: TableFactor::UNNEST { - alias: Some(TableAlias { - name: Ident::new("numbers"), - columns: vec![], - }), + alias: table_alias(true, "numbers"), array_exprs: vec![Expr::Identifier(Ident::new("expr"))], with_offset: true, with_offset_alias: None, @@ -6973,10 +6963,7 @@ fn parse_unnest_in_from_clause() { &dialects, vec![TableWithJoins { relation: TableFactor::UNNEST { - alias: Some(TableAlias { - name: Ident::new("numbers"), - columns: vec![], - }), + alias: table_alias(true, "numbers"), array_exprs: vec![Expr::Identifier(Ident::new("expr"))], with_offset: false, with_offset_alias: None, @@ -7266,14 +7253,14 @@ fn parse_joins_on() { only(&verified_only_select("SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2").from).joins, vec![join_with_constraint( "t2", - table_alias("foo"), + table_alias(true, "foo"), false, JoinOperator::Join, )] ); one_statement_parses_to( "SELECT * FROM t1 JOIN t2 foo ON c1 = c2", - "SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2", + "SELECT * FROM t1 JOIN t2 foo ON c1 = c2", ); // Test parsing of different join operators assert_eq!( @@ -7406,13 +7393,17 @@ fn parse_joins_using() { only(&verified_only_select("SELECT * FROM t1 JOIN t2 AS foo USING(c1)").from).joins, vec![join_with_constraint( "t2", - table_alias("foo"), + table_alias(true, "foo"), JoinOperator::Join, )] ); one_statement_parses_to( - "SELECT * FROM t1 JOIN t2 foo USING(c1)", "SELECT * FROM t1 JOIN t2 AS foo USING(c1)", + "SELECT * FROM t1 JOIN t2 AS foo USING(c1)", + ); + one_statement_parses_to( + "SELECT * FROM t1 JOIN t2 foo USING(c1)", + "SELECT * FROM t1 JOIN t2 foo USING(c1)", ); // Test parsing of different join operators assert_eq!( @@ -7536,7 +7527,7 @@ fn parse_natural_join() { // natural join another table with alias assert_eq!( only(&verified_only_select("SELECT * FROM t1 NATURAL JOIN t2 AS t3").from).joins, - vec![natural_join(JoinOperator::Join, table_alias("t3"))] + vec![natural_join(JoinOperator::Join, table_alias(true, "t3"))] ); let sql = "SELECT * FROM t1 natural"; @@ -7595,7 +7586,7 @@ fn parse_join_nesting() { relation: table("a"), joins: vec![join(table("b"))], }), - alias: table_alias("c"), + alias: table_alias(true, "c"), } ); assert_eq!(from.joins, vec![]); @@ -7634,6 +7625,7 @@ fn parse_ctes() { for (i, exp) in expected.iter().enumerate() { let Cte { alias, query, .. } = &sel.with.as_ref().unwrap().cte_tables[i]; assert_eq!(*exp, query.to_string()); + assert_eq!(false, alias.explicit); assert_eq!( if i == 0 { Ident::new("a") @@ -7711,6 +7703,7 @@ fn parse_recursive_cte() { assert_eq!(with.cte_tables.len(), 1); let expected = Cte { alias: TableAlias { + explicit: false, name: Ident { value: "nums".to_string(), quote_style: None, @@ -7783,10 +7776,7 @@ fn parse_derived_tables() { relation: TableFactor::Derived { lateral: false, subquery: Box::new(verified_query("(SELECT 1) UNION (SELECT 2)")), - alias: Some(TableAlias { - name: "t1".into(), - columns: vec![], - }), + alias: table_alias(true, "t1"), }, joins: vec![Join { relation: table_from_name(ObjectName::from(vec!["t2".into()])), @@ -9812,10 +9802,7 @@ fn parse_merge() { table, TableFactor::Table { name: ObjectName::from(vec![Ident::new("s"), Ident::new("bar")]), - alias: Some(TableAlias { - name: Ident::new("dest"), - columns: vec![], - }), + alias: table_alias(true, "dest"), args: None, with_hints: vec![], version: None, @@ -9875,14 +9862,7 @@ fn parse_merge() { format_clause: None, pipe_operators: vec![], }), - alias: Some(TableAlias { - name: Ident { - value: "stg".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "stg"), } ); assert_eq!(source, source_no_into); @@ -11078,10 +11058,7 @@ fn parse_pivot_table() { Pivot { table: Box::new(TableFactor::Table { name: ObjectName::from(vec![Ident::new("monthly_sales")]), - alias: Some(TableAlias { - name: Ident::new("a"), - columns: vec![] - }), + alias: table_alias(true, "a"), args: None, with_hints: vec![], version: None, @@ -11118,6 +11095,7 @@ fn parse_pivot_table() { ]), default_on_null: None, alias: Some(TableAlias { + explicit: true, name: Ident { value: "p".to_string(), quote_style: None, @@ -11127,7 +11105,7 @@ fn parse_pivot_table() { TableAliasColumnDef::from_name("c"), TableAliasColumnDef::from_name("d"), ], - }), + }) } ); assert_eq!(verified_stmt(sql).to_string(), sql); @@ -11226,10 +11204,7 @@ fn parse_unpivot_table() { let base_unpivot = Unpivot { table: Box::new(TableFactor::Table { name: ObjectName::from(vec![Ident::new("sales")]), - alias: Some(TableAlias { - name: Ident::new("s"), - columns: vec![], - }), + alias: table_alias(true, "s"), args: None, with_hints: vec![], version: None, @@ -11250,6 +11225,7 @@ fn parse_unpivot_table() { }) .collect(), alias: Some(TableAlias { + explicit: true, name: Ident::new("u"), columns: ["product", "quarter", "quantity"] .into_iter() @@ -11475,7 +11451,7 @@ fn parse_select_table_with_index_hints() { let sql = "SELECT * FROM T USE LIMIT 1"; let unsupported_dialects = all_dialects_where(|d| !d.supports_table_hints()); let select = unsupported_dialects - .verified_only_select_with_canonical(sql, "SELECT * FROM T AS USE LIMIT 1"); + .verified_only_select_with_canonical(sql, "SELECT * FROM T USE LIMIT 1"); assert_eq!( select.from, vec![TableWithJoins { @@ -11483,10 +11459,7 @@ fn parse_select_table_with_index_hints() { name: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier( Ident::new("T") )]), - alias: Some(TableAlias { - name: Ident::new("USE"), - columns: vec![], - }), + alias: table_alias(false, "USE"), args: None, with_hints: vec![], version: None, @@ -11515,10 +11488,7 @@ fn parse_pivot_unpivot_table() { table: Box::new(Unpivot { table: Box::new(TableFactor::Table { name: ObjectName::from(vec![Ident::new("census")]), - alias: Some(TableAlias { - name: Ident::new("c"), - columns: vec![] - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -11538,10 +11508,7 @@ fn parse_pivot_unpivot_table() { alias: None, }) .collect(), - alias: Some(TableAlias { - name: Ident::new("u"), - columns: vec![] - }), + alias: table_alias(true, "u"), }), aggregate_functions: vec![ExprWithAlias { expr: call("sum", [Expr::Identifier(Ident::new("population"))]), @@ -11565,10 +11532,7 @@ fn parse_pivot_unpivot_table() { }, ]), default_on_null: None, - alias: Some(TableAlias { - name: Ident::new("p"), - columns: vec![] - }), + alias: table_alias(true, "p"), } ); assert_eq!(verified_stmt(sql).to_string(), sql); @@ -16347,10 +16311,10 @@ fn parse_pipeline_operator() { "SELECT * FROM users |> LEFT JOIN orders USING(user_id, order_date)", ); - // join pipe operator with alias + // join pipe operator with alias (with an omitted "AS" keyword) dialects.verified_query_with_canonical( "SELECT * FROM users |> JOIN orders o ON users.id = o.user_id", - "SELECT * FROM users |> JOIN orders AS o ON users.id = o.user_id", + "SELECT * FROM users |> JOIN orders o ON users.id = o.user_id", ); dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders AS o ON users.id = o.user_id"); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index a947db49b..99a298f89 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -92,10 +92,31 @@ fn parse_mssql_single_quoted_aliases() { #[test] fn parse_mssql_delimited_identifiers() { - let _ = ms().one_statement_parses_to( + let s = ms().one_statement_parses_to( "SELECT [a.b!] [FROM] FROM foo [WHERE]", - "SELECT [a.b!] AS [FROM] FROM foo AS [WHERE]", + "SELECT [a.b!] AS [FROM] FROM foo [WHERE]", ); + if let Statement::Query(q) = s { + match &q.body.as_select().expect("not a SELECT").from[..] { + [from] => match &from.relation { + TableFactor::Table { name, alias, .. } => { + assert_eq!(&format!("{name}"), "foo"); + assert_eq!( + alias, + &Some(TableAlias { + explicit: false, + name: Ident::with_quote('[', "WHERE"), + columns: vec![] + }) + ); + } + _ => panic!("unexpected FROM type"), + }, + _ => panic!("unexpected number of FROMs"), + } + } else { + panic!("statement not parsed as QUERY"); + } } #[test] @@ -454,10 +475,7 @@ fn parse_mssql_openjson() { vec![TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("t_test_table")]), - alias: Some(TableAlias { - name: Ident::new("A"), - columns: vec![] - }), + alias: table_alias(true, "A"), args: None, with_hints: vec![], version: None, @@ -494,10 +512,7 @@ fn parse_mssql_openjson() { as_json: true } ], - alias: Some(TableAlias { - name: Ident::new("B"), - columns: vec![] - }) + alias: table_alias(true, "B") }, global: false, join_operator: JoinOperator::CrossApply @@ -514,10 +529,7 @@ fn parse_mssql_openjson() { vec![TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("t_test_table"),]), - alias: Some(TableAlias { - name: Ident::new("A"), - columns: vec![] - }), + alias: table_alias(true, "A"), args: None, with_hints: vec![], version: None, @@ -554,10 +566,7 @@ fn parse_mssql_openjson() { as_json: true } ], - alias: Some(TableAlias { - name: Ident::new("B"), - columns: vec![] - }) + alias: table_alias(true, "B") }, global: false, join_operator: JoinOperator::CrossApply @@ -574,10 +583,7 @@ fn parse_mssql_openjson() { vec![TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("t_test_table")]), - alias: Some(TableAlias { - name: Ident::new("A"), - columns: vec![] - }), + alias: table_alias(true, "A"), args: None, with_hints: vec![], version: None, @@ -614,10 +620,7 @@ fn parse_mssql_openjson() { as_json: false } ], - alias: Some(TableAlias { - name: Ident::new("B"), - columns: vec![] - }) + alias: table_alias(true, "B") }, global: false, join_operator: JoinOperator::CrossApply @@ -634,10 +637,7 @@ fn parse_mssql_openjson() { vec![TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("t_test_table")]), - alias: Some(TableAlias { - name: Ident::new("A"), - columns: vec![] - }), + alias: table_alias(true, "A"), args: None, with_hints: vec![], version: None, @@ -654,10 +654,7 @@ fn parse_mssql_openjson() { ), json_path: Some(Value::SingleQuotedString("$.config".into())), columns: vec![], - alias: Some(TableAlias { - name: Ident::new("B"), - columns: vec![] - }) + alias: table_alias(true, "B") }, global: false, join_operator: JoinOperator::CrossApply @@ -674,10 +671,7 @@ fn parse_mssql_openjson() { vec![TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("t_test_table")]), - alias: Some(TableAlias { - name: Ident::new("A"), - columns: vec![] - }), + alias: table_alias(true, "A"), args: None, with_hints: vec![], version: None, @@ -694,10 +688,7 @@ fn parse_mssql_openjson() { ), json_path: None, columns: vec![], - alias: Some(TableAlias { - name: Ident::new("B"), - columns: vec![] - }) + alias: table_alias(true, "B") }, global: false, join_operator: JoinOperator::CrossApply diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index bc5d48baa..e847d3edb 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -2638,10 +2638,7 @@ fn parse_update_with_joins() { TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("orders")]), - alias: Some(TableAlias { - name: Ident::new("o"), - columns: vec![] - }), + alias: table_alias(true, "o"), args: None, with_hints: vec![], version: None, @@ -2654,10 +2651,7 @@ fn parse_update_with_joins() { joins: vec![Join { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("customers")]), - alias: Some(TableAlias { - name: Ident::new("c"), - columns: vec![] - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -3716,10 +3710,7 @@ fn parse_json_table() { on_error: Some(JsonTableColumnErrorHandling::Null), }), ], - alias: Some(TableAlias { - name: Ident::new("t"), - columns: vec![], - }), + alias: table_alias(true, "t"), } ); } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index fbfa66588..a14ff5ecb 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5109,7 +5109,7 @@ fn parse_join_constraint_unnest_alias() { .joins, vec![Join { relation: TableFactor::UNNEST { - alias: table_alias("f"), + alias: table_alias(true, "f"), array_exprs: vec![Expr::CompoundIdentifier(vec![ Ident::new("t1"), Ident::new("a") diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index f187af1bd..22a632666 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -1197,17 +1197,17 @@ fn test_single_table_in_parenthesis() { 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)", + "SELECT * FROM (a NATURAL JOIN b c)", ); snowflake_and_generic().one_statement_parses_to( "SELECT * FROM (a NATURAL JOIN ((b)) c )", - "SELECT * FROM (a NATURAL JOIN b AS c)", + "SELECT * FROM (a NATURAL JOIN b c)", ); snowflake_and_generic().one_statement_parses_to( "SELECT * FROM (a NATURAL JOIN ( (b) c ) )", - "SELECT * FROM (a NATURAL JOIN b AS c)", + "SELECT * FROM (a NATURAL JOIN b c)", ); snowflake_and_generic().one_statement_parses_to( @@ -1215,9 +1215,13 @@ fn test_single_table_in_parenthesis_with_alias() { "SELECT * FROM (a NATURAL JOIN b AS c)", ); + snowflake_and_generic().one_statement_parses_to( + "SELECT * FROM (a as alias1 NATURAL JOIN ( (b) c ) )", + "SELECT * FROM (a AS alias1 NATURAL JOIN b 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)", + "SELECT * FROM (a alias1 NATURAL JOIN b c)", ); snowflake_and_generic().one_statement_parses_to( @@ -1226,7 +1230,7 @@ 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", "SELECT * FROM (a NATURAL JOIN b) AS c", ); @@ -3051,9 +3055,9 @@ fn asof_joins() { assert_eq!( query.from[0], TableWithJoins { - relation: table_with_alias("trades_unixtime", "tu"), + relation: table_with_alias("trades_unixtime", true, "tu"), joins: vec![Join { - relation: table_with_alias("quotes_unixtime", "qu"), + relation: table_with_alias("quotes_unixtime", true, "qu"), global: false, join_operator: JoinOperator::AsOf { match_condition: Expr::BinaryOp { @@ -3644,10 +3648,37 @@ fn test_sql_keywords_as_table_aliases() { "OPEN", ]; + fn assert_implicit_alias(mut select: Select, canonical_with_explicit_alias: &str) { + if let TableFactor::Table { alias, .. } = &mut select + .from + .get_mut(0) + .as_mut() + .expect("missing FROM") + .relation + { + let alias = alias.as_mut().expect("missing ALIAS"); + assert!(!alias.explicit); + alias.explicit = true; + assert_eq!(&format!("{select}"), canonical_with_explicit_alias); + } else { + panic!("unexpected FROM "); + } + } + + fn assert_no_alias(select: Select) { + if let TableFactor::Table { alias, .. } = + &select.from.first().expect("missing FROM").relation + { + assert_eq!(alias, &None); + } else { + panic!("unexpected FROM "); + } + } + for kw in unreserved_kws { snowflake().verified_stmt(&format!("SELECT * FROM tbl AS {kw}")); - snowflake().one_statement_parses_to( - &format!("SELECT * FROM tbl {kw}"), + assert_implicit_alias( + snowflake().verified_only_select(&format!("SELECT * FROM tbl {kw}")), &format!("SELECT * FROM tbl AS {kw}"), ); } @@ -3663,13 +3694,17 @@ fn test_sql_keywords_as_table_aliases() { } // LIMIT is alias - snowflake().one_statement_parses_to("SELECT * FROM tbl LIMIT", "SELECT * FROM tbl AS LIMIT"); + assert_implicit_alias( + snowflake().verified_only_select("SELECT * FROM tbl LIMIT"), + "SELECT * FROM tbl AS LIMIT", + ); + // LIMIT is not an alias - snowflake().verified_stmt("SELECT * FROM tbl LIMIT 1"); - snowflake().verified_stmt("SELECT * FROM tbl LIMIT $1"); - snowflake().verified_stmt("SELECT * FROM tbl LIMIT ''"); - snowflake().verified_stmt("SELECT * FROM tbl LIMIT NULL"); - snowflake().verified_stmt("SELECT * FROM tbl LIMIT $$$$"); + assert_no_alias(snowflake().verified_only_select("SELECT * FROM tbl LIMIT 1")); + assert_no_alias(snowflake().verified_only_select("SELECT * FROM tbl LIMIT $1")); + assert_no_alias(snowflake().verified_only_select("SELECT * FROM tbl LIMIT ''")); + assert_no_alias(snowflake().verified_only_select("SELECT * FROM tbl LIMIT NULL")); + assert_no_alias(snowflake().verified_only_select("SELECT * FROM tbl LIMIT $$$$")); } #[test] @@ -3911,14 +3946,7 @@ fn test_nested_join_without_parentheses() { table_with_joins: Box::new(TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("customers".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "c".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -3931,14 +3959,7 @@ fn test_nested_join_without_parentheses() { joins: vec![Join { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("products".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "p".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "p"), args: None, with_hints: vec![], version: None, @@ -3992,14 +4013,7 @@ fn test_nested_join_without_parentheses() { table_with_joins: Box::new(TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("customers".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "c".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -4012,14 +4026,7 @@ fn test_nested_join_without_parentheses() { joins: vec![Join { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("products".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "p".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "p"), args: None, with_hints: vec![], version: None, @@ -4073,14 +4080,7 @@ fn test_nested_join_without_parentheses() { table_with_joins: Box::new(TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("customers".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "c".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -4093,14 +4093,7 @@ fn test_nested_join_without_parentheses() { joins: vec![Join { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("products".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "p".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "p"), args: None, with_hints: vec![], version: None, @@ -4154,14 +4147,7 @@ fn test_nested_join_without_parentheses() { table_with_joins: Box::new(TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("customers".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "c".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -4174,14 +4160,7 @@ fn test_nested_join_without_parentheses() { joins: vec![Join { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("products".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "p".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "p"), args: None, with_hints: vec![], version: None, @@ -4235,14 +4214,7 @@ fn test_nested_join_without_parentheses() { table_with_joins: Box::new(TableWithJoins { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("customers".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "c".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "c"), args: None, with_hints: vec![], version: None, @@ -4255,14 +4227,7 @@ fn test_nested_join_without_parentheses() { joins: vec![Join { relation: TableFactor::Table { name: ObjectName::from(vec![Ident::new("products".to_string())]), - alias: Some(TableAlias { - name: Ident { - value: "p".to_string(), - quote_style: None, - span: Span::empty(), - }, - columns: vec![], - }), + alias: table_alias(true, "p"), args: None, with_hints: vec![], version: None,