Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ jobs:

- name: Run tests
shell: bash
# Bound the step: a hung extension download once stalled Windows jobs
# indefinitely (tests themselves finish in seconds).
timeout-minutes: 10
run: pytest tests/ -v

# The abi3 wheel is identical across Python legs; keep just one for
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## UNRELEASED

### Added

- 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

- Data now crosses the Python↔Rust boundary via the zero-copy Arrow C Data Interface instead of Arrow IPC serialization, reducing memory copies and peak RSS for large results (on a 1M-row × 50-col register+read microbenchmark: ~5.7× faster, ~1 GB lower peak RSS). Set `GGSQL_ARROW_IPC=1` to restore the previous IPC behavior. The minimum pyarrow version is now 15.
Expand Down
126 changes: 125 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.26", features = ["extension-module", "abi3-py310"] }
arrow = { version = "58", default-features = false, features = ["ipc", "ffi"] }
ggsql = { version = "0.5.2", default-features = false, features = ["duckdb", "vegalite", "builtin-data", "svg", "pdf", "hep"] }
ggsql = { version = "0.5.2", default-features = false, features = ["duckdb", "vegalite", "builtin-data", "svg", "pdf", "hep", "spatial"] }

[features]
default = []
51 changes: 51 additions & 0 deletions tests/test_ggsql.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,57 @@ def test_render_multi_layer(self):
assert "layer" in spec_dict


class TestSpatial:
"""Tests for spatial (map) layers via the VegaLiteWriter."""

@pytest.fixture(scope="class", autouse=True)
def install_spatial_extension(self):
"""Install DuckDB's spatial extension (the ggsql crate only LOADs it).

The binary is cached per DuckDB version/platform under
~/.duckdb/extensions, so this downloads at most once per machine.
"""
reader = ggsql.DuckDBReader("duckdb://memory")
try:
reader.execute_sql("INSTALL spatial")
except Exception as e:
pytest.skip(f"DuckDB spatial extension unavailable (offline?): {e}")

def test_render_spatial_builtin_world(self):
reader = ggsql.DuckDBReader("duckdb://memory")
spec = reader.execute("VISUALISE FROM ggsql:world DRAW spatial")
writer = ggsql.VegaLiteWriter()

result = writer.render(spec)
spec_dict = json.loads(result)
marks = [
layer["mark"]["type"] if isinstance(layer["mark"], dict) else layer["mark"]
for layer in spec_dict["layer"]
]
assert "geoshape" in marks

def test_render_spatial_choropleth(self):
reader = ggsql.DuckDBReader("duckdb://memory")
spec = reader.execute(
"VISUALISE FROM ggsql:world DRAW spatial MAPPING population AS fill"
)
writer = ggsql.VegaLiteWriter()

result = writer.render(spec)
assert "geoshape" in result

def test_render_chart_spatial(self):
reader = ggsql.DuckDBReader("duckdb://memory")
spec = reader.execute("VISUALISE FROM ggsql:world DRAW spatial")
writer = ggsql.VegaLiteWriter()

chart = writer.render_chart(spec)
assert isinstance(chart, altair.TopLevelMixin)
# The chart survives Altair-level round-tripping (this is how the
# spec reaches the browser widget)
assert "projection" in chart.to_dict()


class TestRenderAltairDataFrameConversion:
"""Tests for DataFrame handling in render_altair()."""

Expand Down
Loading