Add clickhouse-connect backend#593
Open
ShawnChen-Sirius wants to merge 8 commits into
Open
Conversation
The client.chdb namespace exposes chDB-only capabilities that a remote ClickHouse server has no equivalent for — the Python() table function (query_python), Python UDF registration, the native DB-API cursor, and the session path — without bloating clickhouse-connect's base Client. On an HTTP client there is no .chdb attribute, so accessing it raises AttributeError by design. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
… factory ChdbClient is a clickhouse-connect Client subclass (relocated and adapted from clickhouse-connect PR #753): it overrides only the transport-shaped methods plus the zero-copy Arrow fast paths (query_arrow / query_arrow_stream / query_df ride chDB's in-process Arrow buffer) and inherits the public API, type system, settings, Native parser and error model unchanged. supports_zero_copy_arrow = True. Assigning client.database lazily issues USE so unqualified names resolve as they do over HTTP, without breaking the create-database-then-use bootstrap. Two integration-suite-driven robustness features: * A process-wide refcounted connection cache keyed by chdb path. chdb-core's embedded engine permits only one active engine instance per process per data directory; a second chdb.connect() on the same path while the first is open deadlocks in the C layer. The cache lets many ChdbClient instances share one underlying connection and only closes it on the last release. * weakref.finalize on every stream wrapper (_ChdbStreamSource / _ChdbStreamFile / _ChdbArrowStreamSource), guaranteeing the per-connection lock is released even when a test errors out without calling close(). Without this a leaked stream holds the shared connection lock and every subsequent operation deadlocks. AsyncChdbClient wraps the sync client on a thread executor; ChdbBackend is the entry-point factory. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
…endency Registers chDB with clickhouse-connect through the clickhouse_connect.backends entry-point group and adds clickhouse-connect as an optional dependency. Discovery is automatic — clickhouse-connect's get_client(backend="chdb") finds this entry point at runtime; clickhouse-connect itself never imports chdb. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
tests/clickhouse_connect/test_cc_backend.py is the clickhouse-connect PR #753 suite relocated and adapted to backend="chdb" (105 embedded-only assertions: query, insert, streaming, Arrow, numpy, settings, DB-API, async, error mapping). test_parity asserts the same clickhouse-connect call returns the same result on the chDB backend and a real ClickHouse server (38 checks across a type and operation matrix), skipped when no server is configured. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
…on chDB Runs clickhouse-connect's own test suite against the embedded chDB backend by redirecting create_client (its single client chokepoint). Skip handling is entirely data-driven (no pytest markers): an empirically-built nodeid blacklist (skip_list.txt) skips capabilities the embedded engine genuinely cannot support, plus an optional strict-xfail file for documented divergences. The job gates: any failure that is neither skipped nor a documented divergence is a real regression. The skip list was built by running clickhouse-connect's full integration suite twice (against a real ClickHouse 26.5.3.41 server and against the embedded chDB engine) and including only cases that pass on HTTP and genuinely cannot pass embedded. With the list applied the chDB run is fully green (PASS=346, FAIL=0, ERROR=0, SKIP=181) across all 34 integration files. Eight categories cover the differences: external_data, HTTP session_id/aiohttp pool, transport attributes, RBAC, query_id/summary, DateTime/Arrow formatting, server-formatted output, streaming/arrow batching. README documents the version-subscription policy (personal fork branch -> CC main -> CC release) and the procedure for re-curating the list on each CC version bump. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Three hard gates against the chDB backend, run in this repo (clickhouse-connect's own CI never touches chDB): the relocated backend suite (embedded), output parity against a version-aligned ClickHouse 26.5 server, and clickhouse-connect's own suite over the curated-green file set with skip_list.txt applied. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Contributor
Author
|
@chibugai review it |
There was a problem hiding this comment.
Pull request overview
Adds a new clickhouse-connect execution backend powered by embedded chDB, enabling existing clickhouse-connect client code to run unchanged without an HTTP server.
Changes:
- Introduces
ChdbClient/AsyncChdbClientbackend implementation plus aclient.chdbextension namespace for chDB-only capabilities. - Adds comprehensive backend and parity test suites plus an upstream-suite harness with data-driven skip/xfail lists.
- Registers the backend via Python entry points and adds an optional
clickhouse-connectdependency; adds CI workflow gates.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
chdb/cc_backend.py |
Implements the clickhouse-connect backend over embedded chDB, including streaming and Arrow fast paths. |
chdb/cc_extension.py |
Adds client.chdb namespace for chDB-only features (Python table function, UDFs, cursor, session path). |
pyproject.toml |
Adds optional dependency and registers the backend via clickhouse_connect.backends entry point. |
tests/clickhouse_connect/conftest.py |
Adds shared test fixtures/timezone pinning for deterministic results. |
tests/clickhouse_connect/test_cc_backend.py |
Adds relocated/adapted backend behavior tests (embedded-only). |
tests/clickhouse_connect/test_parity.py |
Adds parity assertions comparing embedded chDB backend vs real ClickHouse server outputs. |
scripts/cc_upstream_suite/* |
Adds harness to run clickhouse-connect’s upstream suite against chDB with skip/xfail data files. |
.github/workflows/clickhouse-connect-backend.yml |
Adds CI workflow running backend tests, parity tests, and upstream suite gate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
chibugai
reviewed
Jun 23, 2026
ShawnChen-Sirius
added a commit
to ShawnChen-Sirius/chdb
that referenced
this pull request
Jun 23, 2026
Squashed response to PR chdb-io#593 review and the CI failure that the CC PR's registry is not in the released clickhouse-connect 1.3.0 yet: CI workflow - Install clickhouse-connect from the chDB-author fork branch (ShawnChen-Sirius/clickhouse-connect@feat/pluggable-backend-registry) instead of the PyPI 1.3.0 release that does not yet carry the backend registry. README's "Version subscription policy" already documents this phased rollout. - Add pytest-timeout to the install list (used by the upstream-suite harness). Backend (chdb/cc_backend.py) - Wrap the rest of ChdbClient.__init__ after _acquire_chdb_connection in a try/except that releases the refcount on failure -- closes the leak where an exception during super().__init__ / set_client_setting / extension import would leave the shared :memory: connection pinned forever. - Validate every setting name against a strict identifier pattern before interpolating it into SET / SETTINGS clauses; reject non-identifier names with ProgrammingError. - Escape the temp-file path interpolated into INSERT INTO ... FROM INFILE '<path>' the same way value literals are escaped (handles single-quote / backslash in TMPDIR). - Honor the public use_database=False flag in raw_query and raw_stream by threading a keyword through _exec_raw_query that skips the lazy USE binding. - Switch _ChdbStreamFile's accumulator from bytes (O(n^2) repeated concat) to bytearray with extend / del slice (amortized O(n)). Extension (chdb/cc_extension.py) - query_python now acquires the per-connection lock (self._client._lock) around the conn.query call; the previous version only held the extension-level globals lock, which would race with concurrent regular queries on the shared chdb connection. Tests - conftest.py: guard time.tzset() behind hasattr() so the module imports on Windows. - test_cc_backend.py::test_database_parameter_switches_default: use a tmp_path chdb data file instead of literal "other_db" / "scoped_test" names so the test cannot collide with the session-shared :memory: connection state. - test_cc_backend.py::test_raw_insert_decompresses_pre_compressed_payload: importorskip lz4.frame and zstandard so the parametrization gracefully skips when an optional compression library is missing. Upstream-suite harness - run_upstream_suite.py: wrap the work in try/finally so the temp checkout is cleaned up on any error path; detect a pre-existing root conftest.py in the checkout and refuse to overwrite it; default CHDB_UPSTREAM_SUITE_PATH to a per-invocation tempdir so consecutive runs do not inherit each other's databases on disk. - _force_chdb_conftest.py: expand the comment on _CHDB_PATH default to document the shared-engine-across-tests trade-off and how cross-test isolation already works through clickhouse-connect's session-scoped random test_database fixture. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
ShawnChen-Sirius
added a commit
to ShawnChen-Sirius/chdb
that referenced
this pull request
Jun 23, 2026
Squashed response to PR chdb-io#593 review and the CI failure that the CC PR's registry is not in the released clickhouse-connect 1.3.0 yet: CI workflow - Install clickhouse-connect from the chDB-author fork branch (ShawnChen-Sirius/clickhouse-connect@feat/pluggable-backend-registry) instead of the PyPI 1.3.0 release that does not yet carry the backend registry. README's "Version subscription policy" already documents this phased rollout. - Add pytest-timeout to the install list (used by the upstream-suite harness). Backend (chdb/cc_backend.py) - Wrap the rest of ChdbClient.__init__ after _acquire_chdb_connection in a try/except that releases the refcount on failure -- closes the leak where an exception during super().__init__ / set_client_setting / extension import would leave the shared :memory: connection pinned forever. - Validate every setting name against a strict identifier pattern before interpolating it into SET / SETTINGS clauses; reject non-identifier names with ProgrammingError. - Escape the temp-file path interpolated into INSERT INTO ... FROM INFILE '<path>' the same way value literals are escaped (handles single-quote / backslash in TMPDIR). - Honor the public use_database=False flag in raw_query and raw_stream by threading a keyword through _exec_raw_query that skips the lazy USE binding. - Switch _ChdbStreamFile's accumulator from bytes (O(n^2) repeated concat) to bytearray with extend / del slice (amortized O(n)). Extension (chdb/cc_extension.py) - query_python now acquires the per-connection lock (self._client._lock) around the conn.query call; the previous version only held the extension-level globals lock, which would race with concurrent regular queries on the shared chdb connection. Tests - conftest.py: guard time.tzset() behind hasattr() so the module imports on Windows. - test_cc_backend.py::test_database_parameter_switches_default: use a tmp_path chdb data file instead of literal "other_db" / "scoped_test" names so the test cannot collide with the session-shared :memory: connection state. - test_cc_backend.py::test_raw_insert_decompresses_pre_compressed_payload: importorskip lz4.frame and zstandard so the parametrization gracefully skips when an optional compression library is missing. Upstream-suite harness - run_upstream_suite.py: wrap the work in try/finally so the temp checkout is cleaned up on any error path; detect a pre-existing root conftest.py in the checkout and refuse to overwrite it; default CHDB_UPSTREAM_SUITE_PATH to a per-invocation tempdir so consecutive runs do not inherit each other's databases on disk. - _force_chdb_conftest.py: expand the comment on _CHDB_PATH default to document the shared-engine-across-tests trade-off and how cross-test isolation already works through clickhouse-connect's session-scoped random test_database fixture. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
f3d94f7 to
4dd85b0
Compare
ShawnChen-Sirius
added a commit
to ShawnChen-Sirius/chdb
that referenced
this pull request
Jun 23, 2026
Squashed response to PR chdb-io#593 review and the CI failure that the CC PR's registry is not in the released clickhouse-connect 1.3.0 yet: CI workflow - Install clickhouse-connect from the chDB-author fork branch (ShawnChen-Sirius/clickhouse-connect@feat/pluggable-backend-registry) instead of the PyPI 1.3.0 release that does not yet carry the backend registry. README's "Version subscription policy" already documents this phased rollout. - Add pytest-timeout to the install list (used by the upstream-suite harness). Backend (chdb/cc_backend.py) - Wrap the rest of ChdbClient.__init__ after _acquire_chdb_connection in a try/except that releases the refcount on failure -- closes the leak where an exception during super().__init__ / set_client_setting / extension import would leave the shared :memory: connection pinned forever. - Validate every setting name against a strict identifier pattern before interpolating it into SET / SETTINGS clauses; reject non-identifier names with ProgrammingError. - Escape the temp-file path interpolated into INSERT INTO ... FROM INFILE '<path>' the same way value literals are escaped (handles single-quote / backslash in TMPDIR). - Honor the public use_database=False flag in raw_query and raw_stream by threading a keyword through _exec_raw_query that skips the lazy USE binding. - Switch _ChdbStreamFile's accumulator from bytes (O(n^2) repeated concat) to bytearray with extend / del slice (amortized O(n)). Extension (chdb/cc_extension.py) - query_python now acquires the per-connection lock (self._client._lock) around the conn.query call; the previous version only held the extension-level globals lock, which would race with concurrent regular queries on the shared chdb connection. Tests - conftest.py: guard time.tzset() behind hasattr() so the module imports on Windows. - test_cc_backend.py::test_database_parameter_switches_default: use a tmp_path chdb data file instead of literal "other_db" / "scoped_test" names so the test cannot collide with the session-shared :memory: connection state. - test_cc_backend.py::test_raw_insert_decompresses_pre_compressed_payload: importorskip lz4.frame and zstandard so the parametrization gracefully skips when an optional compression library is missing. Upstream-suite harness - run_upstream_suite.py: wrap the work in try/finally so the temp checkout is cleaned up on any error path; detect a pre-existing root conftest.py in the checkout and refuse to overwrite it; default CHDB_UPSTREAM_SUITE_PATH to a per-invocation tempdir so consecutive runs do not inherit each other's databases on disk. - _force_chdb_conftest.py: expand the comment on _CHDB_PATH default to document the shared-engine-across-tests trade-off and how cross-test isolation already works through clickhouse-connect's session-scoped random test_database fixture. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
4dd85b0 to
e85e4e4
Compare
Squashed response to PR chdb-io#593 review and the CI failure that the CC PR's registry is not in the released clickhouse-connect 1.3.0 yet: CI workflow - Install clickhouse-connect from the chDB-author fork branch (ShawnChen-Sirius/clickhouse-connect@feat/pluggable-backend-registry) instead of the PyPI 1.3.0 release that does not yet carry the backend registry. README's "Version subscription policy" already documents this phased rollout. - Add pytest-timeout to the install list (used by the upstream-suite harness). Backend (chdb/cc_backend.py) - Wrap the rest of ChdbClient.__init__ after _acquire_chdb_connection in a try/except that releases the refcount on failure -- closes the leak where an exception during super().__init__ / set_client_setting / extension import would leave the shared :memory: connection pinned forever. - Validate every setting name against a strict identifier pattern before interpolating it into SET / SETTINGS clauses; reject non-identifier names with ProgrammingError. - Escape the temp-file path interpolated into INSERT INTO ... FROM INFILE '<path>' the same way value literals are escaped (handles single-quote / backslash in TMPDIR). - Honor the public use_database=False flag in raw_query and raw_stream by threading a keyword through _exec_raw_query that skips the lazy USE binding. - Switch _ChdbStreamFile's accumulator from bytes (O(n^2) repeated concat) to bytearray with extend / del slice (amortized O(n)). Extension (chdb/cc_extension.py) - query_python now acquires the per-connection lock (self._client._lock) around the conn.query call; the previous version only held the extension-level globals lock, which would race with concurrent regular queries on the shared chdb connection. Tests - conftest.py: guard time.tzset() behind hasattr() so the module imports on Windows. - test_cc_backend.py::test_database_parameter_switches_default: use a tmp_path chdb data file instead of literal "other_db" / "scoped_test" names so the test cannot collide with the session-shared :memory: connection state. - test_cc_backend.py::test_raw_insert_decompresses_pre_compressed_payload: importorskip lz4.frame and zstandard so the parametrization gracefully skips when an optional compression library is missing. Upstream-suite harness - run_upstream_suite.py: wrap the work in try/finally so the temp checkout is cleaned up on any error path; detect a pre-existing root conftest.py in the checkout and refuse to overwrite it; default CHDB_UPSTREAM_SUITE_PATH to a per-invocation tempdir so consecutive runs do not inherit each other's databases on disk. - _force_chdb_conftest.py: expand the comment on _CHDB_PATH default to document the shared-engine-across-tests trade-off and how cross-test isolation already works through clickhouse-connect's session-scoped random test_database fixture. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
e85e4e4 to
ec35c4c
Compare
Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> Match clickhouse-connect's switch from get_client(backend="chdb", path=...) to a DSN-scheme entry point. The chDB backend code itself (ChdbBackend, ChdbClient, AsyncChdbClient) is unchanged -- it still accepts the same path / settings kwargs; clickhouse-connect now parses chdb://memory or chdb:///path/to/db into those kwargs before invoking the factory. Call-site changes: - tests/clickhouse_connect/test_cc_backend.py + test_parity.py: add a tiny _chdb_dsn helper and switch every get_client / get_async_client / dbapi.connect call to the URI form. - scripts/cc_upstream_suite/_force_chdb_conftest.py: build the chDB DSN from CHDB_UPSTREAM_SUITE_PATH and route create_client / create_async_client through it. - chdb/cc_backend.py + chdb/cc_extension.py: update module docstrings' usage examples to the URI form.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR makes chDB usable as a clickhouse-connect execution backend, so existing clickhouse-connect code runs unchanged against the embedded chDB engine instead of an HTTP server. It is the chDB-side half of the design that follows up on ClickHouse/clickhouse-connect#753; the matching clickhouse-connect change is ClickHouse/clickhouse-connect#811 and is required for this PR to function (it adds the entry-point mechanism this PR registers against).
What this PR adds (6 commits, ~3,400 lines, all in this repo)
chdb/cc_backend.py—ChdbClientis a thinclickhouse_connect.driver.Clientsubclass that overrides only the transport-shaped methods (raw_query/raw_stream/raw_insert/command/_query_with_context/data_insert) plus a true zero-copy Arrow fast path (query_arrow/query_arrow_stream/query_dfride chDB's in-process Arrow buffer through PyArrow's C Data Interface).AsyncChdbClientwraps the sync client viarun_in_executor.ChdbBackendis the entry-point factory.chdb/cc_extension.py— theclient.chdbnamespace for chDB-only capabilities:query_python(...)(thePython()table function for in-process DataFrames),register_function(...)(Python UDFs),cursor()(chDB's native DB-API cursor),session_path. Absent on HTTP clients by design — accessing.chdbon an HTTP client raisesAttributeError.pyproject.toml— registers chDB with clickhouse-connect via theclickhouse_connect.backendsentry-point group, and adds clickhouse-connect as an optional dependency (clickhouse-connect>=1.3.0; will be bumped once the matching CC version is released).tests/clickhouse_connect/test_cc_backend.py— 105 tests relocated from clickhouse-connect PR #753, adapted tobackend=\"chdb\".tests/clickhouse_connect/test_parity.py— 38 chDB-vs-real-server output-parity assertions across primitives, dates, decimals, arrays, maps, tuples, enums, UUIDs, IPs, fixed strings, aggregates, group-by and datetime64.scripts/cc_upstream_suite/— harness that runs clickhouse-connect's own integration suite againstChdbBackendby redirectingcreate_clientat import time. The skip handling is entirely data-driven (nopytest.markcapability markers — kept clickhouse-connect's repo minimal):skip_list.txtlists nodeid substrings for cases the embedded engine genuinely cannot satisfy (HTTP transport / RBAC /external_data/ Arrow-vs-Native dtype divergence / etc.)..github/workflows/clickhouse-connect-backend.yml— three CI gates against the chDB backend: the relocated #753 suite (embedded), parity against a real ClickHouse 26.5 server (service container), and clickhouse-connect's own integration suite over a curated-green file set.A few real-world fixes the integration-suite run forced:
cc_backend.py. chdb-core's embedded engine permits only one active engine instance per process per data directory; a secondchdb.connect()on the same path while the first is open deadlocks in the C layer. The cache lets manyChdbClientinstances share one underlying connection.weakref.finalizeon every stream wrapper. Guarantees the per-connection lock is released even when a test errors out without callingclose().USEonclient.database = X. Over HTTP this is a per-request parameter and assigning a not-yet-created database is fine; chDB is session-scoped and needsUSE, so we apply it lazily before the next operation, which preserves the common bootstrap of "create database, then use it".Testing
Empirical, no pytest markers:
skip_list.txtapplied)Of the 423 cases passing on the HTTP baseline, 320 (76%) pass byte-identically on chDB; the remaining 103 are categorized in
skip_list.txt:headers/ compression / port)GRANT/CREATE ROLE/ row policies)query_id/ per-query summary headersREADME.mddocuments the version-subscription policy (personal fork branch → CC main → CC release) and the procedure to re-curate the skip list on each clickhouse-connect release.User demo
For chDB-only features:
From the user's point of view it is still the normal clickhouse-connect API; the chDB backend is selected with one keyword and chDB-only capabilities are reachable through an explicit, opt-in namespace.
Release ordering
This PR depends on clickhouse-connect/#811 being merged and released. Once that's available, the
clickhouse-connect>=1.3.0pin inpyproject.tomlwill be bumped to the version that ships the registry.