diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index 28ee240260..ce72f578a8 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -24,6 +24,10 @@ pub struct DuckDbDialect; // In most cases the redshift dialect is identical to [`PostgresSqlDialect`]. impl Dialect for DuckDbDialect { + fn supports_cte_materialization(&self) -> bool { + true + } + fn supports_trailing_commas(&self) -> bool { true } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index d3778994dc..bcb13c2d5c 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -386,6 +386,16 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if the dialect supports `AS MATERIALIZED` and `AS NOT MATERIALIZED` + /// in a common table expression. + /// + /// ```sql + /// WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte + /// ``` + fn supports_cte_materialization(&self) -> bool { + false + } + /// Returns true if the dialects supports `group sets, roll up, or cube` expressions. fn supports_group_by_expr(&self) -> bool { false diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index 3bec6ceba3..65bfd46a70 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -73,6 +73,10 @@ const AND_PREC: u8 = 20; const OR_PREC: u8 = 10; impl Dialect for PostgreSqlDialect { + fn supports_cte_materialization(&self) -> bool { + true + } + fn identifier_quote_style(&self, _identifier: &str) -> Option { Some('"') } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index cd88170971..ec3e09cab6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15349,7 +15349,7 @@ impl<'a> Parser<'a> { }; let mut is_materialized = None; - if dialect_of!(self is PostgreSqlDialect) { + if self.dialect.supports_cte_materialization() { if self.parse_keyword(Keyword::MATERIALIZED) { is_materialized = Some(CteAsMaterialized::Materialized); } else if self.parse_keywords(&[Keyword::NOT, Keyword::MATERIALIZED]) { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 66824165ca..a3e9a45b26 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20682,3 +20682,25 @@ fn parse_array_suffix_after_nested_type() { }) .verified_stmt("SELECT CAST(NULL AS MAP> ARRAY)"); } + +#[test] +fn parse_cte_materialization() { + for (directive, expected) in [ + ("", None), + ("MATERIALIZED ", Some(CteAsMaterialized::Materialized)), + ( + "NOT MATERIALIZED ", + Some(CteAsMaterialized::NotMaterialized), + ), + ] { + let sql = format!("WITH c AS {directive}(SELECT 1) SELECT * FROM c"); + TestedDialects::new(vec![Box::new(DuckDbDialect {})]).verified_query(&sql); + let query = all_dialects_where(|d| d.supports_cte_materialization()).verified_query(&sql); + assert_eq!(query.with.unwrap().cte_tables[0].materialized, expected); + if expected.is_some() { + all_dialects_where(|d| !d.supports_cte_materialization()) + .parse_sql_statements(&sql) + .unwrap_err(); + } + } +}