diff --git a/src/ast/query.rs b/src/ast/query.rs index 95f980181..c302ef967 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -2703,6 +2703,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(()), } } @@ -2892,6 +2895,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 945cbb098..16682c50b 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2309,6 +2309,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 b8238843a..28320e149 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -17864,7 +17864,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 b97c55df3..e4d42f58d 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10138,3 +10138,34 @@ fn parse_bitstring_literal_escaping() { pg_and_generic().verified_stmt("SELECT B''''"); pg_and_generic().verified_stmt("SELECT B'it''s'"); } + +#[test] +fn parse_join_using_alias() { + let sql = "SELECT * FROM t1 JOIN t2 USING (id) AS joined_cols"; + let dialect = PostgreSqlDialect {}; + let mut parser = Parser::new(&dialect).try_with_sql(sql).unwrap(); + let statement = parser.parse_statement().unwrap(); + let Statement::Query(query) = statement else { + panic!("Expected a query statement"); + }; + let SetExpr::Select(select) = *query.body else { + panic!("Expected a SELECT query"); + }; + let join = &select.from[0].joins[0]; + + assert_eq!( + JoinOperator::Join(JoinConstraint::UsingAlias( + vec![ObjectName::from(vec![Ident::new("id")])], + Ident::new("joined_cols"), + )), + join.join_operator + ); + assert_eq!( + Span::new(Location::new(1, 33), Location::new(1, 51)), + join.join_operator.span() + ); + assert_eq!( + "SELECT * FROM t1 JOIN t2 USING(id) AS joined_cols", + select.to_string() + ); +}