From 87c2474ba8012412f669bed772a4319fc454c21c Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Fri, 24 Apr 2026 10:05:12 +0200 Subject: [PATCH 1/2] fix materialization regression --- CHANGELOG.md | 6 ++++++ src/execute/cte.rs | 29 +++++++++-------------------- src/execute/mod.rs | 13 ++++++++++--- src/reader/mod.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/reader/sqlite.rs | 16 ++++++++++++++++ 5 files changed, 81 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd15e31..6f54be18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ handled auto-resizing in the plot pane. We now have a per-output-location path in the Jupyter kernel (#360) +### Changed + +- Reverted an earlier decision to materialize CTEs and the global query in Rust +before registering them back to the backend. We now keep the data purely on the +backend until the layer query as was always intended + ### Removed - Removed polars from dependency list along with all its transient dependencies. Rewrote DataFrame struct on top of arrow (#350) diff --git a/src/execute/cte.rs b/src/execute/cte.rs index dc4637f4..6abe56ab 100644 --- a/src/execute/cte.rs +++ b/src/execute/cte.rs @@ -146,28 +146,17 @@ pub fn materialize_ctes(ctes: &[CteDefinition], reader: &dyn Reader) -> Result = df.get_column_names(); - for (old, new) in current_names.iter().zip(cte.column_aliases.iter()) { - df = df.rename(old, new).map_err(|e| { - GgsqlError::ReaderError(format!( - "Failed to apply column alias '{}' for CTE '{}': {}", - new, cte.name, e - )) - })?; - } + let statements = reader.dialect().create_or_replace_temp_table_sql( + &temp_table_name, + &cte.column_aliases, + &transformed_body, + ); + for stmt in &statements { + reader.execute_sql(stmt).map_err(|e| { + GgsqlError::ReaderError(format!("Failed to materialize CTE '{}': {}", cte.name, e)) + })?; } - reader.register(&temp_table_name, df, true).map_err(|e| { - GgsqlError::ReaderError(format!("Failed to register CTE '{}': {}", cte.name, e)) - })?; - materialized.insert(cte.name.clone()); } diff --git a/src/execute/mod.rs b/src/execute/mod.rs index d9215b41..82c9e1c0 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -968,9 +968,16 @@ pub fn prepare_data_with_reader(query: &str, reader: &dyn Reader) -> Result Vec { + let body = wrap_with_column_aliases(body_sql, column_aliases); + vec![format!( + "CREATE OR REPLACE TEMP TABLE {} AS {}", + naming::quote_ident(name), + body + )] + } +} + +/// Wrap a body SQL in a CTE with a column alias list when aliases are present. +/// This is a portable way to rename the body's output columns without relying +/// on `CREATE TABLE t(a, b) AS ...` (which SQLite does not support). +pub(crate) fn wrap_with_column_aliases(body_sql: &str, column_aliases: &[String]) -> String { + if column_aliases.is_empty() { + return body_sql.to_string(); + } + let cols = column_aliases + .iter() + .map(|c| naming::quote_ident(c)) + .collect::>() + .join(", "); + format!( + "WITH __ggsql_aliased__({}) AS ({}) SELECT * FROM __ggsql_aliased__", + cols, body_sql + ) } pub struct AnsiDialect; diff --git a/src/reader/sqlite.rs b/src/reader/sqlite.rs index c50ce6b6..7023d04f 100644 --- a/src/reader/sqlite.rs +++ b/src/reader/sqlite.rs @@ -92,6 +92,22 @@ impl super::SqlDialect for SqliteDialect { table.replace('\'', "''") ) } + + /// SQLite does not support `CREATE OR REPLACE`, so emit a drop-then-create + /// pair. Column aliases are preserved portably via the default CTE wrapper. + fn create_or_replace_temp_table_sql( + &self, + name: &str, + column_aliases: &[String], + body_sql: &str, + ) -> Vec { + let qname = naming::quote_ident(name); + let body = super::wrap_with_column_aliases(body_sql, column_aliases); + vec![ + format!("DROP TABLE IF EXISTS {}", qname), + format!("CREATE TEMP TABLE {} AS {}", qname, body), + ] + } } /// SQLite database reader From 15578f75517cc6c30580cacd65e1d3c744a8f28f Mon Sep 17 00:00:00 2001 From: Thomas Lin Pedersen Date: Fri, 24 Apr 2026 10:06:26 +0200 Subject: [PATCH 2/2] add PR ref --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f54be18..17223518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ in the Jupyter kernel (#360) - Reverted an earlier decision to materialize CTEs and the global query in Rust before registering them back to the backend. We now keep the data purely on the -backend until the layer query as was always intended +backend until the layer query as was always intended (#363) ### Removed