Implicit aliases

Right now `sqlparser-rs` inject the `AS` keyword in front of aliases
unconditionally. As reported by #1875 or #1784 this leads to problems on
Oracle databases.  This patch preserves the original absence / presence of the
keyword (implicit/explicit aliases) in "table-factor" position when rendered
via `Display`.

1. Some more effort could be invested to apply the same behavior for
`SelectItem`s (ie. projections in queries) and further nodes of the AST with
an alias for which `AS` is optional. To unify the implementation within the
parser and for clients, representing aliases could then be exposed not as pure
`Ident`s but maybe as something as:

```rust
struct Alias {
  explicit: bool,
  name: Ident,
}

impl Deref for Alias {
  type Target = Ident;
  ...
}

impl From<Alias> for Ident {
  ...
}
```

2. The parser could be instructed / configured (either by ParserOptions or
through a Dialect setting) to always produce "explicit" alias tokens. This
would then always produce the `AS` keyword when render via `Display`. Ideally,
there would be a `VisitorMut::visit_(mut_)alias` and clients could easily apply
their own preference.

3. I'd greatly appreciate a critical look since my know-how regarding
different DBs is quite limited. I hope I've not broken any of the existing
dialects and also hope this PR helps "preserving the syntax round trip".
This commit is contained in:
Petr Novotnik 2025-11-23 20:11:55 +01:00
parent 2b8e99c665
commit f4ca760489
10 changed files with 202 additions and 269 deletions

View file

@ -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<TableAliasColumnDef>,
}
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))?;
}

View file

@ -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)))
}
}

View file

@ -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,

View file

@ -368,8 +368,9 @@ pub fn single_quoted_string(s: impl Into<String>) -> Value {
Value::SingleQuotedString(s.into())
}
pub fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
pub fn table_alias(explicit: bool, name: impl Into<String>) -> Option<TableAlias> {
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<String>, alias: impl Into<String>) -> TableFactor {
pub fn table_with_alias(
name: impl Into<String>,
with_as_keyword: bool,
alias: impl Into<String>,
) -> 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,

View file

@ -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(),

View file

@ -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");

View file

@ -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

View file

@ -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"),
}
);
}

View file

@ -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")

View file

@ -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 <table-factor>");
}
}
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 <table-factor>");
}
}
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,