From 1cf560e34c0051d3fa43781aa756465ad4086c3c Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:04:53 -0500 Subject: [PATCH 1/3] Add Dialect::supports_cte_materialization and enable it for DuckDB --- src/dialect/duckdb.rs | 4 ++++ src/dialect/mod.rs | 14 ++++++++++++++ src/dialect/postgresql.rs | 4 ++++ src/parser/mod.rs | 2 +- tests/sqlparser_common.rs | 21 +++++++++++++++++++++ 5 files changed, 44 insertions(+), 1 deletion(-) 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..01cd527a51 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 @@ -2262,6 +2272,10 @@ mod tests { self.0.supports_group_by_expr() } + fn supports_cte_materialization(&self) -> bool { + self.0.supports_cte_materialization() + } + fn supports_in_empty_list(&self) -> bool { self.0.supports_in_empty_list() } 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..009f8295d7 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20682,3 +20682,24 @@ 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"); + 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(); + } + } +} From 0942c6fa2e77afaf65eaef619b1b2cd454605eea Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:15:19 -0500 Subject: [PATCH 2/3] Drop the untested WrappedDialect forwarding --- src/dialect/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 01cd527a51..bcb13c2d5c 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -2272,10 +2272,6 @@ mod tests { self.0.supports_group_by_expr() } - fn supports_cte_materialization(&self) -> bool { - self.0.supports_cte_materialization() - } - fn supports_in_empty_list(&self) -> bool { self.0.supports_in_empty_list() } From 420fab634b8aa95909fb4422d926aadbd93975f9 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sat, 17 Oct 2026 10:05:39 -0500 Subject: [PATCH 3/3] Test DuckDB CTE materialization explicitly Co-Authored-By: LucaCappelletti94 <7738570+LucaCappelletti94@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- tests/sqlparser_common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 009f8295d7..a3e9a45b26 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20694,6 +20694,7 @@ fn parse_cte_materialization() { ), ] { 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() {