Skip to content

Commit 2be0e8d

Browse files
committed
Support optional AS keyword in CTE definitions for Databricks
Databricks allows omitting the AS keyword in CTE definitions: `WITH cte (SELECT ...) SELECT * FROM cte` Add `supports_cte_without_as()` dialect method and enable it for Databricks and Generic dialects.
1 parent 6f8e7b8 commit 2be0e8d

File tree

5 files changed

+90
-2
lines changed

5 files changed

+90
-2
lines changed

src/dialect/databricks.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,9 @@ impl Dialect for DatabricksDialect {
9090
fn supports_optimize_table(&self) -> bool {
9191
true
9292
}
93+
94+
/// See <https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-cte>
95+
fn supports_cte_without_as(&self) -> bool {
96+
true
97+
}
9398
}

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,8 @@ impl Dialect for GenericDialect {
288288
fn supports_comma_separated_trim(&self) -> bool {
289289
true
290290
}
291+
292+
fn supports_cte_without_as(&self) -> bool {
293+
true
294+
}
291295
}

src/dialect/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,17 @@ pub trait Dialect: Debug + Any {
16641664
fn supports_comma_separated_trim(&self) -> bool {
16651665
false
16661666
}
1667+
1668+
/// Returns true if the dialect supports the `AS` keyword being
1669+
/// optional in a CTE definition. For example:
1670+
/// ```sql
1671+
/// WITH cte_name (SELECT ...)
1672+
/// ```
1673+
///
1674+
/// [Databricks](https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-cte)
1675+
fn supports_cte_without_as(&self) -> bool {
1676+
false
1677+
}
16671678
}
16681679

16691680
/// Operators for which precedence must be defined.

src/parser/mod.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

@@ -14090,9 +14090,33 @@ impl<'a> Parser<'a> {
1409014090
materialized: is_materialized,
1409114091
closing_paren_token: closing_paren_token.into(),
1409214092
}
14093+
} else if self.dialect.supports_cte_without_as()
14094+
&& self.peek_token().token == Token::LParen
14095+
&& self.is_query_start_token(&self.peek_nth_token(1))
14096+
{
14097+
self.expect_token(&Token::LParen)?;
14098+
let query = self.parse_query()?;
14099+
let closing_paren_token = self.expect_token(&Token::RParen)?;
14100+
14101+
let alias = TableAlias {
14102+
explicit: false,
14103+
name,
14104+
columns: vec![],
14105+
};
14106+
Cte {
14107+
alias,
14108+
query,
14109+
from: None,
14110+
materialized: None,
14111+
closing_paren_token: closing_paren_token.into(),
14112+
}
1409314113
} else {
1409414114
let columns = self.parse_table_alias_column_defs()?;
14095-
self.expect_keyword_is(Keyword::AS)?;
14115+
if self.dialect.supports_cte_without_as() {
14116+
let _ = self.parse_keyword(Keyword::AS);
14117+
} else {
14118+
self.expect_keyword_is(Keyword::AS)?;
14119+
}
1409614120
let mut is_materialized = None;
1409714121
if dialect_of!(self is PostgreSqlDialect) {
1409814122
if self.parse_keyword(Keyword::MATERIALIZED) {
@@ -14125,6 +14149,23 @@ impl<'a> Parser<'a> {
1412514149
Ok(cte)
1412614150
}
1412714151

14152+
fn is_query_start_token(&self, token: &TokenWithSpan) -> bool {
14153+
match &token.token {
14154+
Token::Word(w) => {
14155+
matches!(
14156+
w.keyword,
14157+
Keyword::SELECT
14158+
| Keyword::WITH
14159+
| Keyword::VALUES
14160+
| Keyword::VALUE
14161+
| Keyword::TABLE
14162+
) || (w.keyword == Keyword::FROM && self.dialect.supports_from_first_select())
14163+
}
14164+
Token::LParen => true,
14165+
_ => false,
14166+
}
14167+
}
14168+
1412814169
/// Parse a "query body", which is an expression with roughly the
1412914170
/// following grammar:
1413014171
/// ```sql

tests/sqlparser_databricks.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,3 +644,30 @@ fn parse_databricks_json_accessor() {
644644
"SELECT raw:store.bicycle.price::DOUBLE FROM store_data",
645645
);
646646
}
647+
648+
#[test]
649+
fn parse_cte_without_as() {
650+
databricks_and_generic().one_statement_parses_to(
651+
"WITH cte (SELECT 1) SELECT * FROM cte",
652+
"WITH cte AS (SELECT 1) SELECT * FROM cte",
653+
);
654+
655+
databricks_and_generic().one_statement_parses_to(
656+
"WITH a AS (SELECT 1), b (SELECT 2) SELECT * FROM a, b",
657+
"WITH a AS (SELECT 1), b AS (SELECT 2) SELECT * FROM a, b",
658+
);
659+
660+
databricks_and_generic().one_statement_parses_to(
661+
"WITH cte (col1, col2) (SELECT 1, 2) SELECT * FROM cte",
662+
"WITH cte (col1, col2) AS (SELECT 1, 2) SELECT * FROM cte",
663+
);
664+
665+
databricks_and_generic().verified_query("WITH cte AS (SELECT 1) SELECT * FROM cte");
666+
667+
databricks_and_generic()
668+
.verified_query("WITH cte (col1, col2) AS (SELECT 1, 2) SELECT * FROM cte");
669+
670+
assert!(all_dialects_where(|d| !d.supports_cte_without_as())
671+
.parse_sql_statements("WITH cte (SELECT 1) SELECT * FROM cte")
672+
.is_err());
673+
}

0 commit comments

Comments
 (0)