@@ -14060,7 +14060,7 @@ impl<'a> Parser<'a> {
1406014060 })
1406114061 }
1406214062
14063- /// Parse a CTE (`alias [( col1, col2, ... )] AS (subquery)`)
14063+ /// Parse a CTE (`alias [( col1, col2, ... )] [AS] (subquery)`)
1406414064 pub fn parse_cte(&mut self) -> Result<Cte, ParserError> {
1406514065 let name = self.parse_identifier()?;
1406614066
@@ -14091,32 +14091,67 @@ impl<'a> Parser<'a> {
1409114091 closing_paren_token: closing_paren_token.into(),
1409214092 }
1409314093 } else {
14094- let columns = self.parse_table_alias_column_defs()?;
14095- self.expect_keyword_is(Keyword::AS)?;
14096- let mut is_materialized = None;
14097- if dialect_of!(self is PostgreSqlDialect) {
14098- if self.parse_keyword(Keyword::MATERIALIZED) {
14099- is_materialized = Some(CteAsMaterialized::Materialized);
14100- } else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
14101- is_materialized = Some(CteAsMaterialized::NotMaterialized);
14094+ // If AS is optional, first try to parse `name (query)` directly,
14095+ // similar to how IN expression disambiguates subquery vs list.
14096+ let as_optional = self.dialect.supports_cte_without_as();
14097+ let opt_query = if as_optional {
14098+ self.maybe_parse(|p| {
14099+ p.expect_token(&Token::LParen)?;
14100+ let query = p.parse_query()?;
14101+ let closing_paren_token = p.expect_token(&Token::RParen)?;
14102+ Ok((query, closing_paren_token))
14103+ })?
14104+ } else {
14105+ None
14106+ };
14107+ match opt_query {
14108+ Some((query, closing_paren_token)) => {
14109+ let alias = TableAlias {
14110+ explicit: false,
14111+ name,
14112+ columns: vec![],
14113+ };
14114+ Cte {
14115+ alias,
14116+ query,
14117+ from: None,
14118+ materialized: None,
14119+ closing_paren_token: closing_paren_token.into(),
14120+ }
1410214121 }
14103- }
14104- self.expect_token(&Token::LParen)?;
14122+ None => {
14123+ let columns = self.parse_table_alias_column_defs()?;
14124+ if as_optional {
14125+ let _ = self.parse_keyword(Keyword::AS);
14126+ } else {
14127+ self.expect_keyword_is(Keyword::AS)?;
14128+ }
14129+ let mut is_materialized = None;
14130+ if dialect_of!(self is PostgreSqlDialect) {
14131+ if self.parse_keyword(Keyword::MATERIALIZED) {
14132+ is_materialized = Some(CteAsMaterialized::Materialized);
14133+ } else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
14134+ is_materialized = Some(CteAsMaterialized::NotMaterialized);
14135+ }
14136+ }
14137+ self.expect_token(&Token::LParen)?;
1410514138
14106- let query = self.parse_query()?;
14107- let closing_paren_token = self.expect_token(&Token::RParen)?;
14139+ let query = self.parse_query()?;
14140+ let closing_paren_token = self.expect_token(&Token::RParen)?;
1410814141
14109- let alias = TableAlias {
14110- explicit: false,
14111- name,
14112- columns,
14113- };
14114- Cte {
14115- alias,
14116- query,
14117- from: None,
14118- materialized: is_materialized,
14119- closing_paren_token: closing_paren_token.into(),
14142+ let alias = TableAlias {
14143+ explicit: false,
14144+ name,
14145+ columns,
14146+ };
14147+ Cte {
14148+ alias,
14149+ query,
14150+ from: None,
14151+ materialized: is_materialized,
14152+ closing_paren_token: closing_paren_token.into(),
14153+ }
14154+ }
1412014155 }
1412114156 };
1412214157 if self.parse_keyword(Keyword::FROM) {
0 commit comments