From 2dfc3ba42dccee191c9f2ee8c6544d6f50184f9b Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 May 2026 14:53:03 +0200 Subject: [PATCH 1/3] Fix CREATE + VISUALISE FROM producing invalid double-wrapped SQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit transform_global_sql concatenated side-effect SQL (CREATE, INSERT, …) with the VISUALISE FROM injection, then the caller wrapped the whole thing in another CREATE TABLE AS, producing invalid SQL like: CREATE TABLE __ggsql_global__ AS CREATE TEMP TABLE data … Split the return into side-effect statements (executed directly) and the queryable part (wrapped as the global temp table). Co-Authored-By: Claude Opus 4.6 --- src/execute/cte.rs | 70 ++++++++++++++++++++++++++++++++++++---------- src/execute/mod.rs | 37 ++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/execute/cte.rs b/src/execute/cte.rs index 0ab34848..0e6dcc5e 100644 --- a/src/execute/cte.rs +++ b/src/execute/cte.rs @@ -210,15 +210,15 @@ pub fn split_with_query(source_tree: &SourceTree) -> Option<(String, String)> { Some((cte_prefix, trailing)) } -/// Transform global SQL for execution with temp tables +/// Transform global SQL for execution with temp tables. /// -/// If the SQL has a WITH clause followed by SELECT, extracts just the SELECT -/// portion and transforms CTE references to temp table names. -/// For SQL without WITH clause, just transforms any CTE references. +/// Returns statements to execute directly as side effects (CREATE, INSERT, …) +/// and an optional query whose result should be wrapped as the global temp +/// table. pub fn transform_global_sql( source_tree: &SourceTree, materialized_ctes: &HashSet, -) -> Option { +) -> (Vec, Option) { // Try to extract trailing SELECT (WITH...SELECT or direct SELECT) let select_sql = split_with_query(source_tree) .map(|(_, select)| select) @@ -229,16 +229,58 @@ pub fn transform_global_sql( }); if let Some(select_sql) = select_sql { - Some(transform_cte_references(&select_sql, materialized_ctes)) - } else if does_consume_cte(source_tree) { - // Non-SELECT executable SQL (CREATE, INSERT, UPDATE, DELETE) - // OR VISUALISE FROM (which injects SELECT * FROM ) - // Extract SQL (with injection if VISUALISE FROM) and transform CTE references - let sql = source_tree.extract_sql()?; - Some(transform_cte_references(&sql, materialized_ctes)) + return ( + vec![], + Some(transform_cte_references(&select_sql, materialized_ctes)), + ); + } + + if !does_consume_cte(source_tree) { + return (vec![], None); + } + + // We have non-SELECT executable SQL (CREATE, INSERT, …) and/or + // VISUALISE FROM. Split them: side-effect statements run directly, + // VISUALISE FROM becomes the queryable part. + // + // Only actual statements (CREATE, INSERT, …) are side effects — a bare + // WITH clause without a trailing statement is not executable on its own + // (its CTEs are already materialized separately). + let root = source_tree.root(); + + let side_effect_stmts = r#" + (sql_statement + [(create_statement) + (insert_statement) + (update_statement) + (delete_statement)] @stmt) + "#; + let side_effects: Vec = source_tree + .find_texts(&root, side_effect_stmts) + .into_iter() + .map(|s| transform_cte_references(s.trim(), materialized_ctes)) + .filter(|s| !s.is_empty()) + .collect(); + + let viz_from_query = source_tree + .find_text( + &root, + r#"(visualise_statement (from_clause (table_ref) @table))"#, + ) + .map(|table| { + let q = format!("SELECT * FROM {}", table); + transform_cte_references(&q, materialized_ctes) + }); + + if !side_effects.is_empty() || viz_from_query.is_some() { + (side_effects, viz_from_query) } else { - // No executable SQL (just CTEs) - None + // does_consume_cte was true but we found no specific statements or + // VISUALISE FROM — fall back to extract_sql as the query. + let query = source_tree + .extract_sql() + .map(|s| transform_cte_references(&s, materialized_ctes)); + (vec![], query) } } diff --git a/src/execute/mod.rs b/src/execute/mod.rs index 8a1a3911..ef67a919 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -1063,16 +1063,22 @@ pub fn prepare_data_with_reader(query: &str, reader: &dyn Reader) -> Result Date: Fri, 1 May 2026 14:55:02 +0200 Subject: [PATCH 2/3] add news bullet --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b3e140..199ffbab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [Unreleased] +### Fixed + +- Side effects like `CREATE TEMP TABLE` before the `VISUALISE` statement are now + separated from directly feeding into the visualisation data (#415) + ## 0.3.0 - 2026-04-29 ### Added From 254f400ef0af5af31fe7a46bece90a0c475b5056 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 May 2026 15:11:41 +0200 Subject: [PATCH 3/3] cargo fmt --- src/execute/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/execute/mod.rs b/src/execute/mod.rs index ef67a919..2962e085 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -1065,8 +1065,7 @@ pub fn prepare_data_with_reader(query: &str, reader: &dyn Reader) -> Result