Skip to content

Commit ba2d566

Browse files
committed
Add ibis SQL example
1 parent d0b585b commit ba2d566

4 files changed

Lines changed: 90 additions & 6 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ DRAW line MAPPING month AS x, total AS y
147147

148148
---
149149

150-
## Public API (`src/api.rs`)
150+
## Public API
151151

152152
### Quick Start
153153

@@ -190,7 +190,6 @@ let json = writer.render(&spec)?;
190190

191191
**`Spec`** - Result of `reader.execute()`, ready for rendering:
192192

193-
- `render(writer)` - Generate output (Vega-Lite JSON)
194193
- `plot()` - Resolved plot specification
195194
- `metadata()` - Rows, columns, layer count
196195
- `warnings()` - Validation warnings from execution

ggsql-python/README.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ Result of `reader.execute()`, containing resolved visualization ready for render
158158

159159
**Methods:**
160160

161-
- `render(writer: VegaLiteWriter) -> str` - Generate Vega-Lite JSON
162161
- `metadata() -> dict` - Get `{"rows": int, "columns": list[str], "layer_count": int}`
163162
- `sql() -> str` - The executed SQL query
164163
- `visual() -> str` - The VISUALISE clause
@@ -292,6 +291,53 @@ class AdvancedReader:
292291

293292
Native readers like `DuckDBReader` use an optimized fast path, while custom Python readers are automatically bridged via IPC serialization.
294293

294+
### Ibis Reader Example
295+
296+
[Ibis](https://ibis-project.org/) provides a unified Python API for SQL operations across multiple backends. Here's how to create an ibis-based custom reader:
297+
298+
```python
299+
import ggsql
300+
import polars as pl
301+
import ibis
302+
303+
class IbisReader:
304+
"""Custom reader using ibis as the SQL backend."""
305+
306+
def __init__(self, backend="duckdb"):
307+
if backend == "duckdb":
308+
self.con = ibis.duckdb.connect()
309+
elif backend == "sqlite":
310+
self.con = ibis.sqlite.connect()
311+
# Add other backends as needed
312+
313+
def execute_sql(self, sql: str) -> pl.DataFrame:
314+
return self.con.con.execute(sql).pl()
315+
316+
def supports_register(self) -> bool:
317+
return True
318+
319+
def register(self, name: str, df: pl.DataFrame) -> None:
320+
self.con.create_table(name, df.to_arrow(), overwrite=True)
321+
322+
def unregister(self, name: str) -> None:
323+
self.con.drop_table(name)
324+
325+
# Usage
326+
reader = IbisReader()
327+
df = pl.DataFrame({
328+
"date": ["2024-01-01", "2024-01-02", "2024-01-03"],
329+
"revenue": [100, 150, 120],
330+
})
331+
reader.register("sales", df)
332+
333+
spec = ggsql.execute(
334+
"SELECT * FROM sales VISUALISE date AS x, revenue AS y DRAW line",
335+
reader
336+
)
337+
writer = ggsql.VegaLiteWriter()
338+
print(writer.render(spec))
339+
```
340+
295341
## Development
296342

297343
### Keeping in sync with the monorepo

ggsql-python/tests/test_ggsql.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616

1717
import ggsql
1818

19+
# Optional dependency for ibis test
20+
try:
21+
import ibis
22+
23+
HAS_IBIS = hasattr(ibis, "duckdb")
24+
except ImportError:
25+
HAS_IBIS = False
26+
1927

2028
class TestValidate:
2129
"""Tests for validate() function."""
@@ -496,3 +504,37 @@ def execute_sql(self, sql: str) -> pl.DataFrame:
496504
assert len(reader.execute_calls) > 0
497505
# All calls should be valid SQL strings
498506
assert all(isinstance(sql, str) for sql in reader.execute_calls)
507+
508+
@pytest.mark.skipif(not HAS_IBIS, reason="ibis not installed")
509+
def test_custom_reader_ibis(self):
510+
"""Test custom reader using ibis as backend."""
511+
512+
class IbisReader:
513+
def __init__(self):
514+
self.con = ibis.duckdb.connect()
515+
516+
def execute_sql(self, sql: str) -> pl.DataFrame:
517+
return self.con.con.execute(sql).pl()
518+
519+
def supports_register(self) -> bool:
520+
return True
521+
522+
def register(self, name: str, df: pl.DataFrame) -> None:
523+
self.con.create_table(name, df.to_arrow(), overwrite=True)
524+
525+
def unregister(self, name: str) -> None:
526+
self.con.drop_table(name)
527+
528+
reader = IbisReader()
529+
df = pl.DataFrame({"x": [1, 2, 3], "y": [10, 20, 30]})
530+
reader.register("mydata", df)
531+
532+
spec = ggsql.execute(
533+
"SELECT * FROM mydata VISUALISE x, y DRAW point",
534+
reader,
535+
)
536+
537+
assert spec.metadata()["rows"] == 3
538+
writer = ggsql.VegaLiteWriter()
539+
json_output = writer.render(spec)
540+
assert "point" in json_output

src/doc/API.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,6 @@ class Validated:
477477

478478
```python
479479
class Spec:
480-
def render(self, writer: VegaLiteWriter) -> str:
481-
"""Render to output format."""
482-
483480
def metadata(self) -> dict:
484481
"""Get metadata as dict with keys: rows, columns, layer_count."""
485482

0 commit comments

Comments
 (0)