From f42fd06be046b0ef1d2e107ab5d71403b109162d Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 14 Oct 2026 10:29:54 -0500 Subject: [PATCH 1/3] feat: add execute_sql_cached binding to CachingReader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exposes the Rust CachingReader's compute surface (execute_sql_cached, present since ggsql 0.5.2) to Python. Setup statements that ggsql's derived SQL depends on must run on the private cache connection where that SQL executes — e.g. INSTALL/LOAD of DuckDB extensions such as spatial. Routing them through execute_sql sends them to the primary instead, where locked-down connections reject them and they take no effect on the cache. This is needed by querychat's opt-in "maps" capability to prewarm the DuckDB spatial extension on the compute surface (posit-dev/querychat#323). --- src/lib.rs | 27 +++++++++++++++++++++++++++ tests/test_ggsql.py | 18 ++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e1a4bc9..306af66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -741,6 +741,33 @@ impl PyCachingReader { df_to_py(py, &df) } + /// Execute a SQL query against the cache backend (compute surface). + /// + /// All ggsql-generated derived SQL runs on the private in-memory cache + /// connection, so this is the surface to use for statements that must + /// take effect there — e.g. `INSTALL spatial; LOAD spatial`, which the + /// spatial geom requires on the cache connection. Routing such + /// statements through `execute_sql` would send them to the primary data + /// source instead, where they typically fail (locked-down connections) + /// and do nothing for the cache. + /// + /// Parameters + /// ---------- + /// sql : str + /// The SQL statement to execute on the cache connection. + /// + /// Returns + /// ------- + /// pyarrow.Table + /// The query result as a pyarrow Table. + fn execute_sql_cached(&self, py: Python<'_>, sql: &str) -> PyResult> { + let df = self + .inner + .execute_sql_cached(sql) + .map_err(|e| PyErr::new::(e.to_string()))?; + df_to_py(py, &df) + } + /// Execute a ggsql query and return the visualization specification. /// /// Base reads hit the (memoized) primary; derived tables are diff --git a/tests/test_ggsql.py b/tests/test_ggsql.py index 3d47051..ceb57c9 100644 --- a/tests/test_ggsql.py +++ b/tests/test_ggsql.py @@ -1062,6 +1062,24 @@ def test_execute_sql(self): table = reader.execute_sql("SELECT 1 AS x") assert table.num_rows == 1 + def test_execute_sql_cached(self): + """execute_sql_cached runs on the cache, never touching the primary. + + This is the surface for per-connection setup that ggsql's derived + SQL depends on (e.g. INSTALL/LOAD of DuckDB extensions), which + would fail or take no effect on a locked-down primary. + """ + primary = _CountingReader() + reader = ggsql.CachingReader(primary) + table = reader.execute_sql_cached("SELECT 42 AS x") + assert table.column("x").to_pylist() == [42] + assert primary.calls == 0 + + def test_execute_sql_cached_error(self): + reader = ggsql.CachingReader("duckdb://memory") + with pytest.raises(ValueError): + reader.execute_sql_cached("SELECT * FROM __ggsql_missing__") + def test_register_and_query(self): reader = ggsql.CachingReader("duckdb://memory") reader.register("mytab", pa.table({"x": [1, 2], "y": [3, 4]})) From 91ee220dcab61f03b6dd83a1b56ac22b2ebe6b2c Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 14 Oct 2026 11:08:41 -0500 Subject: [PATCH 2/3] docs: changelog entry for CachingReader.execute_sql_cached --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab860a7..6967088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +- New `CachingReader.execute_sql_cached()` method that runs SQL on the reader's private in-memory cache connection — the compute surface where all ggsql-generated derived SQL executes — instead of the primary data source. This is the surface for per-connection setup that derived SQL depends on: e.g. `INSTALL`/`LOAD` of DuckDB extensions such as `spatial`, which would fail on a locked-down primary and take no effect on the cache even where it succeeds. - Enabled the crate's `spatial` feature, so `DRAW spatial` map layers now render through the Vega-Lite writer instead of raising "Spatial visualization requires the 'spatial' feature to be enabled". Spatial layers require DuckDB's `spatial` extension at runtime: ggsql issues `LOAD spatial` automatically but never `INSTALL` it, so run `INSTALL spatial` once per machine (e.g. via `DuckDBReader.execute_sql()`; the download is cached under `~/.duckdb/extensions`). Works with WKB geometry columns and the built-in `ggsql:world` dataset, including named map projections such as `PROJECT TO equal_earth`. ### Changed From 74ea2a37f2c7b07c4554ad3d6412b37c76da8e10 Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 14 Oct 2026 11:11:32 -0500 Subject: [PATCH 3/3] docs: reorder changelog entries --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6967088..9b9677e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ ### Added -- New `CachingReader.execute_sql_cached()` method that runs SQL on the reader's private in-memory cache connection — the compute surface where all ggsql-generated derived SQL executes — instead of the primary data source. This is the surface for per-connection setup that derived SQL depends on: e.g. `INSTALL`/`LOAD` of DuckDB extensions such as `spatial`, which would fail on a locked-down primary and take no effect on the cache even where it succeeds. - Enabled the crate's `spatial` feature, so `DRAW spatial` map layers now render through the Vega-Lite writer instead of raising "Spatial visualization requires the 'spatial' feature to be enabled". Spatial layers require DuckDB's `spatial` extension at runtime: ggsql issues `LOAD spatial` automatically but never `INSTALL` it, so run `INSTALL spatial` once per machine (e.g. via `DuckDBReader.execute_sql()`; the download is cached under `~/.duckdb/extensions`). Works with WKB geometry columns and the built-in `ggsql:world` dataset, including named map projections such as `PROJECT TO equal_earth`. +- New `CachingReader.execute_sql_cached()` method that runs SQL on the reader's private in-memory cache connection — the compute surface where all ggsql-generated derived SQL executes — instead of the primary data source. This is the surface for per-connection setup that derived SQL depends on: e.g. `INSTALL`/`LOAD` of DuckDB extensions such as `spatial`, which would fail on a locked-down primary and take no effect on the cache even where it succeeds. ### Changed