Skip to content

Add ClickHouse and chDB readers - #2

Merged
alexey-milovidov merged 2 commits into
mainfrom
clickhouse-reader
Sep 5, 2026
Merged

Add ClickHouse and chDB readers#2
alexey-milovidov merged 2 commits into
mainfrom
clickhouse-reader

Conversation

@alexey-milovidov

@alexey-milovidov alexey-milovidov commented Sep 5, 2026

Copy link
Copy Markdown
Member

Summary

Integrates ggsql with ClickHouse. Two readers behind new default-on clickhouse and chdb features share one implementation (ClickHouseSqlReader<T: Transport> in src/reader/clickhouse/):

  • clickhouse://[user[:password]@]host[:port][/database][?setting=…] (clickhouses:// for TLS) talks to a server's HTTP interface with plain requests and exchanges data as Arrow IPC streams (FORMAT ArrowStream), so no driver is needed. Unknown URI parameters are forwarded as ClickHouse settings; host, user and password fall back to CLICKHOUSE_HOST / CLICKHOUSE_USER / CLICKHOUSE_PASSWORD.
  • chdb://[path] runs the embedded chDB engine in-process. libchdb is loaded at runtime via libloading (the same approach as the ODBC driver manager), so the build has no new native dependency and a binary works with or without the library installed. chDB is also a new CacheBackend (chdb+<primary>://, --cache chdb), so a ClickHouse setup never depends on DuckDB.

Each reader owns one session, so the executor's temporary tables and SET statements persist across statements and stat transforms (binning, quantiles, KDE) run on the engine. A read-only account (e.g. play.clickhouse.com) is detected on connect with a CREATE TEMPORARY TABLE probe; reader_from_uri then keeps intermediate tables in an embedded chDB cache automatically, so the plain clickhouse:// URI just works there.

Type handling

ClickHouse's Arrow output loses some of its own types (DateTimeUInt32, Enum → codes, UUID/IPv4/IPv6/wide integers → bytes, Decimal → an Arrow decimal the pipeline does not consume). Every SELECT/WITH is DESCRIBEd first and, when needed, wrapped in SELECT * REPLACE (…) converting those columns server-side. Timestamps are normalized to naive microseconds. Arrow batches from ClickHouse are LZ4-compressed by default (a read-only account cannot change that), hence arrow/ipc_compression.

Dialect

Nullable(…) cast targets (ClickHouse cannot cast NULL to a non-nullable type), TEMPORARY temp tables, quantiles via quantileExactInclusive (inline, no correlated subquery), greatest/least arguments cast to Float64 (no UInt64/Float64 supertype), and a Memory-engine memo table for the caching layer through new cache_meta_* SqlDialect hooks. ClickHouse 26.8 or newer is assumed; there are deliberately no shims for older servers.

Portable fixes found along the way

  • The Vega-Lite writer rescaled timestamps that had already been cast to microseconds, overflowing for any non-microsecond source (only reachable with non-DuckDB readers).
  • Density and boxplot stat SQL now alias qualified projections (grid."g" AS "g"); ClickHouse otherwise names an unaliased cte.col projection cte.col.

Also

CLI --cache chdb, Jupyter kernel connection display names, Positron connection drivers for ClickHouse and chDB, docs (doc/get_started/tooling/cli.qmd), CHANGELOG, CLAUDE.md notes. GGSQL_CLICKHOUSE_TRACE=1 prints every statement sent.

Testing

  • cargo test -p ggsql --lib: 1835 passed (with GGSQL_CLICKHOUSE_URI and GGSQL_CHDB_LIBRARY set, the 34 ClickHouse/chDB live tests run against a local ClickHouse 26.9 server and libchdb 26.7; without them they skip). cargo test -p ggsql-cli -p ggsql-jupyter pass. cargo +1.86 build -p ggsql (MSRV) passes. cargo fmt, cargo clippy --all-targets clean for the new code (one pre-existing warning in geographic.rs tests untouched). npm run check-types passes for the VS Code extension.
  • 15 demo charts rendered end to end (Vega-Lite → PNG via vl-convert): 8 against a local server (Date/Enum/LowCardinality/Decimal/DateTime64/UUID/IPv4 columns; line, dodge bar, histogram, boxplot, facets, smooth), 3 on chdb://memory over a Parquet file (tile heatmap, grouped density, area), and 4 on clickhouses://explorer@play.clickhouse.com:443 through the automatic chDB cache (github_events, uk_price_paid, hackernews, opensky).

Notes for reviewers

  • libchdb allows one connection per process; all chdb:// readers on the same path share it, and the chDB tests are serialized on a mutex because the executor's temp-table names are per process.

🤖 Generated with Claude Code

alexey-milovidov and others added 2 commits September 5, 2026 21:58
Two readers behind the new default-on `clickhouse` and `chdb` features,
sharing one implementation (`ClickHouseSqlReader<T: Transport>`):

- `clickhouse://[user[:password]@]host[:port][/database][?setting=…]`
  (`clickhouses://` for TLS) talks to a server's HTTP interface with plain
  requests and exchanges data as Arrow IPC streams, so no driver is needed.
  Unknown URI parameters are forwarded as ClickHouse settings; host, user
  and password fall back to CLICKHOUSE_HOST/USER/PASSWORD.
- `chdb://[path]` runs the embedded chDB engine in-process. libchdb is
  loaded at runtime via libloading (same approach as the ODBC driver
  manager), so the build has no new native dependency. chDB is also a new
  `CacheBackend` (`chdb+<primary>://`, `--cache chdb`), so a ClickHouse
  setup needs no other engine.

Type handling: every SELECT is DESCRIBEd first and, when ClickHouse's Arrow
output would lose the type (DateTime → UInt32, Enum → codes, UUID/IP/wide
integers → bytes, Decimal), wrapped in `SELECT * REPLACE (…)` converting
those columns server-side; timestamps are normalized to naive microseconds.

Read-only accounts (e.g. play.clickhouse.com) are detected on connect via a
CREATE TEMPORARY TABLE probe; `reader_from_uri` then keeps the executor's
intermediate tables in an embedded chDB cache automatically.

Dialect: Nullable(…) cast targets, TEMPORARY temp tables, quantiles via
quantileExactInclusive (inline, no correlated subquery), greatest/least
cast to Float64 (no UInt64/Float64 supertype), Memory-engine memo table
for the caching layer via new `cache_meta_*` SqlDialect hooks, and a new
`sql_null_safe_equals` hook because older ClickHouse only accepts
IS NOT DISTINCT FROM in JOIN ON.

Portable fixes found along the way: the Vega-Lite writer rescaled
timestamps that were already converted to microseconds (overflow for any
non-microsecond source); density and boxplot stat SQL now alias qualified
projections, which ClickHouse otherwise names `cte.col`.

Also: CLI `--cache chdb`, Jupyter kernel connection names, Positron
connection drivers for ClickHouse and chDB, docs and CHANGELOG.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Remove the `sql_null_safe_equals` dialect hook and the dialect threading it
required in the density stat SQL: ClickHouse 26.8+ accepts
`IS NOT DISTINCT FROM` in any clause and supports correlated subqueries, so
no compatibility spelling is needed. Document the version requirement.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant