Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/dialect/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,9 @@ impl Dialect for DatabricksDialect {
fn supports_bang_not_operator(&self) -> bool {
true
}

/// See <https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-cte>
fn supports_cte_without_as(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,8 @@ impl Dialect for GenericDialect {
fn supports_comma_separated_trim(&self) -> bool {
true
}

fn supports_cte_without_as(&self) -> bool {
true
}
}
11 changes: 11 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,17 @@ pub trait Dialect: Debug + Any {
fn supports_comma_separated_trim(&self) -> bool {
false
}

/// Returns true if the dialect supports the `AS` keyword being
/// optional in a CTE definition. For example:
/// ```sql
/// WITH cte_name (SELECT ...)
/// ```
///
/// [Databricks](https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-cte)
fn supports_cte_without_as(&self) -> bool {
false
}
}

/// Operators for which precedence must be defined.
Expand Down
98 changes: 54 additions & 44 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14060,64 +14060,74 @@ impl<'a> Parser<'a> {
})
}

/// Parse a CTE (`alias [( col1, col2, ... )] AS (subquery)`)
/// Parse a CTE (`alias [( col1, col2, ... )] [AS] (subquery)`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a way to remove some duplications in this function, it would be great. It's not critical, though IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the review! I merged the AS and column-defs paths, is it better?

pub fn parse_cte(&mut self) -> Result<Cte, ParserError> {
let name = self.parse_identifier()?;

let mut cte = if self.parse_keyword(Keyword::AS) {
let mut is_materialized = None;
if dialect_of!(self is PostgreSqlDialect) {
if self.parse_keyword(Keyword::MATERIALIZED) {
is_materialized = Some(CteAsMaterialized::Materialized);
} else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
is_materialized = Some(CteAsMaterialized::NotMaterialized);
let as_optional = self.dialect.supports_cte_without_as();

// If AS is optional, first try to parse `name (query)` directly
if as_optional && !self.peek_keyword(Keyword::AS) {
if let Some((query, closing_paren_token)) = self.maybe_parse(|p| {
p.expect_token(&Token::LParen)?;
let query = p.parse_query()?;
let closing_paren_token = p.expect_token(&Token::RParen)?;
Ok((query, closing_paren_token))
})? {
let mut cte = Cte {
alias: TableAlias {
explicit: false,
name,
columns: vec![],
},
query,
from: None,
materialized: None,
closing_paren_token: closing_paren_token.into(),
};
if self.parse_keyword(Keyword::FROM) {
cte.from = Some(self.parse_identifier()?);
}
return Ok(cte);
}
self.expect_token(&Token::LParen)?;

let query = self.parse_query()?;
let closing_paren_token = self.expect_token(&Token::RParen)?;
}

let alias = TableAlias {
explicit: false,
name,
columns: vec![],
};
Cte {
alias,
query,
from: None,
materialized: is_materialized,
closing_paren_token: closing_paren_token.into(),
}
// Determine column definitions and consume AS
let columns = if self.parse_keyword(Keyword::AS) {
vec![]
} else {
let columns = self.parse_table_alias_column_defs()?;
self.expect_keyword_is(Keyword::AS)?;
let mut is_materialized = None;
if dialect_of!(self is PostgreSqlDialect) {
if self.parse_keyword(Keyword::MATERIALIZED) {
is_materialized = Some(CteAsMaterialized::Materialized);
} else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
is_materialized = Some(CteAsMaterialized::NotMaterialized);
}
if as_optional {
let _ = self.parse_keyword(Keyword::AS);
} else {
self.expect_keyword_is(Keyword::AS)?;
}
self.expect_token(&Token::LParen)?;
columns
};

let query = self.parse_query()?;
let closing_paren_token = self.expect_token(&Token::RParen)?;
let mut is_materialized = None;
if dialect_of!(self is PostgreSqlDialect) {
if self.parse_keyword(Keyword::MATERIALIZED) {
is_materialized = Some(CteAsMaterialized::Materialized);
} else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) {
is_materialized = Some(CteAsMaterialized::NotMaterialized);
}
}

let alias = TableAlias {
self.expect_token(&Token::LParen)?;
let query = self.parse_query()?;
let closing_paren_token = self.expect_token(&Token::RParen)?;

let mut cte = Cte {
alias: TableAlias {
explicit: false,
name,
columns,
};
Cte {
alias,
query,
from: None,
materialized: is_materialized,
closing_paren_token: closing_paren_token.into(),
}
},
query,
from: None,
materialized: is_materialized,
closing_paren_token: closing_paren_token.into(),
};
if self.parse_keyword(Keyword::FROM) {
cte.from = Some(self.parse_identifier()?);
Expand Down
27 changes: 27 additions & 0 deletions tests/sqlparser_databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,3 +651,30 @@ fn parse_numeric_prefix_identifier() {

databricks().verified_stmt("SELECT * FROM a.b.1c");
}

#[test]
fn parse_cte_without_as() {
databricks_and_generic().one_statement_parses_to(
"WITH cte (SELECT 1) SELECT * FROM cte",
"WITH cte AS (SELECT 1) SELECT * FROM cte",
);

databricks_and_generic().one_statement_parses_to(
"WITH a AS (SELECT 1), b (SELECT 2) SELECT * FROM a, b",
"WITH a AS (SELECT 1), b AS (SELECT 2) SELECT * FROM a, b",
);

databricks_and_generic().one_statement_parses_to(
"WITH cte (col1, col2) (SELECT 1, 2) SELECT * FROM cte",
"WITH cte (col1, col2) AS (SELECT 1, 2) SELECT * FROM cte",
);

databricks_and_generic().verified_query("WITH cte AS (SELECT 1) SELECT * FROM cte");

databricks_and_generic()
.verified_query("WITH cte (col1, col2) AS (SELECT 1, 2) SELECT * FROM cte");

assert!(all_dialects_where(|d| !d.supports_cte_without_as())
.parse_sql_statements("WITH cte (SELECT 1) SELECT * FROM cte")
.is_err());
}
Loading