Skip to content

Commit 6d4859f

Browse files
authored
feat: add execute_sql_cached binding to CachingReader (#14)
* feat: add execute_sql_cached binding to CachingReader 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). * docs: changelog entry for CachingReader.execute_sql_cached * docs: reorder changelog entries
1 parent c14eb16 commit 6d4859f

3 files changed

Lines changed: 46 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- 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`.
8+
- 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.
89

910
### Changed
1011

‎src/lib.rs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,33 @@ impl PyCachingReader {
741741
df_to_py(py, &df)
742742
}
743743

744+
/// Execute a SQL query against the cache backend (compute surface).
745+
///
746+
/// All ggsql-generated derived SQL runs on the private in-memory cache
747+
/// connection, so this is the surface to use for statements that must
748+
/// take effect there — e.g. `INSTALL spatial; LOAD spatial`, which the
749+
/// spatial geom requires on the cache connection. Routing such
750+
/// statements through `execute_sql` would send them to the primary data
751+
/// source instead, where they typically fail (locked-down connections)
752+
/// and do nothing for the cache.
753+
///
754+
/// Parameters
755+
/// ----------
756+
/// sql : str
757+
/// The SQL statement to execute on the cache connection.
758+
///
759+
/// Returns
760+
/// -------
761+
/// pyarrow.Table
762+
/// The query result as a pyarrow Table.
763+
fn execute_sql_cached(&self, py: Python<'_>, sql: &str) -> PyResult<Py<PyAny>> {
764+
let df = self
765+
.inner
766+
.execute_sql_cached(sql)
767+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
768+
df_to_py(py, &df)
769+
}
770+
744771
/// Execute a ggsql query and return the visualization specification.
745772
///
746773
/// Base reads hit the (memoized) primary; derived tables are

‎tests/test_ggsql.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,24 @@ def test_execute_sql(self):
10621062
table = reader.execute_sql("SELECT 1 AS x")
10631063
assert table.num_rows == 1
10641064

1065+
def test_execute_sql_cached(self):
1066+
"""execute_sql_cached runs on the cache, never touching the primary.
1067+
1068+
This is the surface for per-connection setup that ggsql's derived
1069+
SQL depends on (e.g. INSTALL/LOAD of DuckDB extensions), which
1070+
would fail or take no effect on a locked-down primary.
1071+
"""
1072+
primary = _CountingReader()
1073+
reader = ggsql.CachingReader(primary)
1074+
table = reader.execute_sql_cached("SELECT 42 AS x")
1075+
assert table.column("x").to_pylist() == [42]
1076+
assert primary.calls == 0
1077+
1078+
def test_execute_sql_cached_error(self):
1079+
reader = ggsql.CachingReader("duckdb://memory")
1080+
with pytest.raises(ValueError):
1081+
reader.execute_sql_cached("SELECT * FROM __ggsql_missing__")
1082+
10651083
def test_register_and_query(self):
10661084
reader = ggsql.CachingReader("duckdb://memory")
10671085
reader.register("mytab", pa.table({"x": [1, 2], "y": [3, 4]}))

0 commit comments

Comments
 (0)