From cca174c40c25714cda4c6b32379bf88ca2670b65 Mon Sep 17 00:00:00 2001 From: Ben Herzberg Date: Thu, 10 Sep 2026 17:53:34 +0300 Subject: [PATCH] Postgres: Support aliases for JOIN USING --- src/ast/query.rs | 5 +++++ src/ast/spans.rs | 6 ++++++ src/parser/mod.rs | 9 ++++++++- tests/sqlparser_postgres.rs | 8 ++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 296e4e8ca6..ebb8f49986 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2701,6 +2701,9 @@ impl fmt::Display for Join { JoinConstraint::Using(attrs) => { write!(f, " USING({})", display_comma_separated(attrs)) } + JoinConstraint::UsingAlias(columns, alias) => { + write!(f, " USING({}) AS {alias}", display_comma_separated(columns)) + } _ => Ok(()), } } @@ -2890,6 +2893,8 @@ pub enum JoinConstraint { On(Expr), /// `USING(...)` list of column names. Using(Vec), + /// `USING(...) AS alias` list of column names with an alias for the joined columns. + UsingAlias(Vec, Ident), /// `NATURAL` join (columns matched automatically). Natural, /// No constraint specified (e.g. `CROSS JOIN`). diff --git a/src/ast/spans.rs b/src/ast/spans.rs index cb06b78990..e947652938 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2304,6 +2304,12 @@ impl Spanned for JoinConstraint { match self { JoinConstraint::On(expr) => expr.span(), JoinConstraint::Using(vec) => union_spans(vec.iter().map(|i| i.span())), + JoinConstraint::UsingAlias(columns, alias) => union_spans( + columns + .iter() + .map(|i| i.span()) + .chain(iter::once(alias.span)), + ), JoinConstraint::Natural => Span::empty(), JoinConstraint::None => Span::empty(), } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2171548f91..1d06b9db3a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -17738,7 +17738,14 @@ impl<'a> Parser<'a> { Ok(JoinConstraint::On(constraint)) } else if self.parse_keyword(Keyword::USING) { let columns = self.parse_parenthesized_qualified_column_list(Mandatory, false)?; - Ok(JoinConstraint::Using(columns)) + if self.parse_keyword(Keyword::AS) { + Ok(JoinConstraint::UsingAlias( + columns, + self.parse_identifier()?, + )) + } else { + Ok(JoinConstraint::Using(columns)) + } } else { Ok(JoinConstraint::None) //self.expected_ref("ON, or USING after JOIN", self.peek_token_ref()) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index dfd883eb4d..9df57ae3b9 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -9918,3 +9918,11 @@ fn parse_non_reserved_keywords_as_table_alias() { )); } } + +#[test] +fn parse_join_using_alias() { + pg().one_statement_parses_to( + "SELECT * FROM t1 JOIN t2 USING (id) AS joined_cols", + "SELECT * FROM t1 JOIN t2 USING(id) AS joined_cols", + ); +}