NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #639 most downloaded on PyPI
ClickHouse Database Core Driver for Python, Pandas, and Superset
Last release 12 days ago
22 Sep 2026
Ships on a steady schedule
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
3 versions withdrawn
withdrawn after publishing
4 years old
147 releases · first in 2022
One column per quarter.
ClickHouse Connect 1.9.0 adds native async SQLAlchemy support, Rust codec performance improvements, and fixes across client cleanup, SQLAlchemy, Alemb
ClickHouse Connect 1.9.0 adds native async SQLAlchemy support, Rust codec performance improvements, and fixes across client cleanup, SQLAlchemy, Alembic, and inserts. It includes all changes from the three 1.9.0 release candidates.
clickhousedb+async:// with create_async_engine() for buffered Core and ORM execution, DDL, reflection, and Alembic migrations through run_sync(). Use the native AsyncClient for streaming and bulk inserts. #576Insert.values() supports dictionaries, tuples, and SQL expressions, including Pandas to_sql(method="multi"). #1024pairs reads preserve duplicate keys and the order returned by the server. Dictionary output remains the default, and inserts still require dictionaries. #949StringDtype results use Arrow buffers directly without first creating Python strings.LIMIT 0 no longer cause query results to be discarded. Metadata probes that unexpectedly return rows raise InternalError without replaying the query; use raw_query() for those queries. #925executemany() preserves SQL expressions and sends one request per parameter set. Earlier writes remain committed if a later request fails. Use Client.insert() for Native bulk throughput.setuptools>=77.0.3. #996pip install clickhouse-connectFor async SQLAlchemy:
pip install "clickhouse-connect[sqlalchemy-async]"See the SQLAlchemy guide and full changelog, including the RC1, RC2, and RC3 sections.
This stable release includes all changes from 1.9.0rc1, 1.9.0rc2, and 1.9.0rc3. See those entries below for the full async SQLAlchemy feature set, Rust codec performance improvements, and fixes included in 1.9.0.
clickhouse-connect[sqlalchemy-async] and use clickhousedb+async:// with create_async_engine(). It supports buffered Core and ORM execution, DDL and reflection through run_sync(), and async Alembic environments. Use the native AsyncClient for streaming and bulk inserts. Closes #576.pairs format, which returns a list of key/value tuples and preserves duplicate keys and the sequence returned by the server. The default remains a dictionary. The format also applies to nested Maps and NumPy/Pandas results. Native Map inserts still require dictionaries and raise DataError for pair lists. Closes #949.insert_arrow / insert_df_arrow now quote table and database identifiers the same way insert() does, so hyphenated and other names that need backquotes work on both the sync and async clients. Closes #1014.LIMIT 0 because of // comments, quoted identifiers, or escaped string contents no longer take the columns-only metadata path and discard returned rows. Metadata probes now confirm a real trailing LIMIT 0 against the bound SQL, including chDB, while binary-bound queries use the normal Native path. If a probe still returns rows, as with some UNION, EXCEPT, or EXPLAIN queries, the client raises InternalError after one execution without replaying the query. Use raw_query() to retrieve results for those queries. This addresses the false metadata-probe cases in #925.license_expression field, and the deprecated License :: OSI Approved :: Apache Software License classifier has been removed. Built metadata carries License-Expression: Apache-2.0 and License-File: LICENSE. Building from source now requires setuptools>=77.0.3. Closes #996.This is the third release candidate for 1.9.0. Please try the native async SQLAlchemy support in your applications and report any issues.
This is the third release candidate for 1.9.0. Please try the native async SQLAlchemy support in your applications and report any issues.
pip install "clickhouse-connect[sqlalchemy-async]==1.9.0rc3"This RC brings the latest bug fixes from main into the 1.9 preview.
include_schemas=True, and version-table updates and deletes with an empty schema. #1045settings= construction for simple SQLAlchemy table engines. SummingMergeTree engines now preserve explicit summing columns through reflection and Alembic. #946socket_options are also honored. #1044If you use Alembic, please also test migration generation and upgrades with your table definitions.
Memory, Log, StripeLog, TinyLog, Null, and Set engines now accept the zero-argument and settings= constructor calls emitted by Alembic. SummingMergeTree engines now accept keyword-only columns and preserve explicit summing columns through reflection and Alembic. Existing positional arguments and engine inheritance remain compatible. Closes #946.SO_SNDBUF to 256 KiB, which disabled automatic buffer sizing and could slow large uploads. The operating system now sizes the buffer. The pool manager helpers also honor explicit socket_options instead of replacing them with defaults. Closes #1044.include_schemas=True and version_table_schema is unset. Offline mode now skips the current database lookup. Online version table updates and deletes also work when version_table_schema="". Closes #1045.This is the second release candidate for 1.9.0. We're continuing to seek feedback on the native async SQLAlchemy support introduced in RC1. Please try
This is the second release candidate for 1.9.0. We're continuing to seek feedback on the native async SQLAlchemy support introduced in RC1. Please try it in your applications and report any issues.
pip install "clickhouse-connect[sqlalchemy-async]==1.9.0rc2"This RC adds Rust codec performance improvements from main. It contains no new async SQLAlchemy changes.
If you use the Rust codec, please also test small concurrent queries and string-heavy pandas results.
StringDtype columns for query_df and query_df_stream directly from its Arrow buffers instead of materializing Python strings first. Output values and dtypes are unchanged. String columns that contain invalid UTF-8 keep the existing hex rendering.This is the first release candidate for clickhouse-connect 1.9.0. Please test the native async SQLAlchemy dialect in your applications and report resu
This is the first release candidate for clickhouse-connect 1.9.0. Please test the native async SQLAlchemy dialect in your applications and report results.
pip install "clickhouse-connect[sqlalchemy-async]==1.9.0rc1"For async connections, follow the guide's async setup and use the environment above.
clickhousedb+async:// with create_async_engine(). The dialect supports buffered Core and ORM execution, query settings, read formats, server-side parameters, and direct access to the native AsyncClient.run_sync(). Offline Alembic compilation is also supported, with a checked-in async environment example.Insert.values() statements now support dictionaries, tuples, and SQL expressions. This also enables Pandas to_sql(method="multi"). #1024SQLAlchemy results are buffered. Server-side cursors and AsyncConnection.stream() are unsupported at this time. AsyncSession.stream() returns a buffered result. Use the native client for streaming and bulk inserts. Async executemany sends one request per parameter set. Pooled connections generate distinct session IDs by default. Fixed session IDs require a single-connection pool or external serialization.
connect_timeout. Connection timeouts receive one retry for queries and replayable insert bodies; read timeouts, connector errors, certificate errors, and raw generator/file bodies remain non-retryable. #1013, #1012DateTime64 SQL binds and literals preserve fractional seconds, including nested arrays and tuples. Declared SQLAlchemy types must match the server schema. #1030Date and Date32 inserts accept timezone-aware datetimes and mixed date/datetime values while preserving their calendar dates. #1031executemany() preserves parameterized INSERT statements and their SQL semantics. These inserts send one request per parameter set, so earlier rows remain committed after a later failure. Row counts aggregate server summaries or report -1 when unavailable. SQLAlchemy retains Native bulk inserts for compiler-generated plain INSERT statements, and the established placeholder-less DB-API Native form remains supported. Use Client.insert() for explicit Native bulk throughput. #930, #932, #934ping() normalize trailing slashes in proxy_path.SELECT 1; closed sessions are replaced while reusable sessions survive execution errors.OperationalError instances and expose available ClickHouse error details.Please include your Python, SQLAlchemy, and SQLModel versions when reporting feedback, along with any cancellation, pooling, startup/shutdown, or resource-warning issues.
clickhouse-connect[sqlalchemy-async] and use clickhousedb+async:// with create_async_engine(). The first release supports buffered Core and ORM execution, inserts, per-query settings and read formats, server-side parameters, DDL and reflection through run_sync(), Alembic online migrations through AsyncConnection.run_sync(), offline Alembic compilation, and direct access to the native AsyncClient. A checked-in async Alembic environment demonstrates both migration paths. Alembic now adds its integration tag to async client User-Agent headers. SQLAlchemy pool pre-ping uses its standard SELECT 1 check. Closed native sessions are invalidated and replaced, while execution-time server and transport errors keep reusable open HTTP sessions. Pooled connections use distinct generated ClickHouse session IDs by default. A fixed session ID requires a single-connection pool or external serialization. Server-side cursors and AsyncConnection.stream() remain unsupported. AsyncSession.stream() returns a buffered result, and the native client provides streaming APIs for large results. Async executemany inserts issue one request per parameter set instead of using the Native bulk insert protocol. For bulk data, borrow the pool-owned driver_connection and call AsyncClient.insert(). Naive datetime values on the executemany path use naive_datetime_binding. Typed SQLAlchemy DateTime64 binds preserve fractional seconds with client-side and server-side parameters; untyped client-side parameters retain their existing formatting.Insert.values() statements now compile and execute. Rows can be dictionaries, tuples in table column order, or rows containing SQL expressions, with client-side or server-side bind parameters. This also enables Pandas to_sql(method="multi"). See the SQLAlchemy documentation for column selection rules and the bind parameter ceiling that applies to server-side parameters on ClickHouse 26.4 and newer. Closes #1024.connect_timeout. The timeout still covers DNS, TCP, TLS, and proxy connection setup after a pool slot is available. Closes #1013.DateTime64 values, including nested arrays and tuples. DateTime formatting, timezone handling, and Native bulk inserts retain their existing behavior. SQLAlchemy column types must match the server schema: declaring DateTime64 over a server DateTime column can now raise conversion errors, including in IN comparisons. Closes #1030.Date and Date32 columns now accept timezone-aware datetimes and mixed date and datetime values. They preserve each value's calendar date without converting its timezone, including in nullable and nested columns. Low cardinality columns also preserve different calendar dates when their datetime values represent the same instant. Closes #1031.Decimal columns and aggregates no longer raises an invalid precision or scale error. Expressions preserve configured operand types and existing numeric promotion behavior. Closes #1027.Cursor.executemany() now preserves the supplied INSERT statement and applies each parameter set through the normal SQL binding path. Expressions, literals, reordered or repeated named binds, target modifiers, INSERT SELECT, quoted identifiers, and server-side coercion are no longer discarded by a heuristic Native rewrite. Parameterized DB-API inserts issue one HTTP request per parameter set, so rows written before a later failure remain committed. Use Client.insert() when one Native block and its throughput are required. INSERT row counts now aggregate the server's written_rows summaries and report -1 when no reliable count is available. SQLAlchemy keeps Native bulk inserts for compiler-generated plain INSERT statements, while raw SQL and expression-bearing statements use the SQL path. The established placeholder-less INSERT INTO table (columns) VALUES form also keeps its Native compatibility path. In that form, doubled %% in identifiers follows the pyformat contract and sends a literal % identifier. Closes #930, #932, and #934.PoolManager. Creating and closing clients inside workers no longer retains one unused manager per client. Closes #1016.raw_insert with bytes or strings. Raw generator and file bodies still raise the timeout because redirects may have consumed them. Socket read timeouts, connector errors, and certificate errors remain non-retryable. Closes #1012.ProgrammingError before network I/O when a live aiohttp session is used from a different event loop. Close the client or dispose its SQLAlchemy engine in the owning loop before transfer when possible. If that loop has already closed, perform cleanup in the current loop before reuse, then reopen a directly reused client with _initialize().ping() calls now normalize a trailing slash in proxy_path, so a path such as /clickhouse/ sends /clickhouse/ping instead of /clickhouse//ping.AsyncClient.close(), context-manager exit, or explicit connection-pool rotation now force-closes the
detached aiohttp transport before propagating cancellation. Failed graceful session cleanup receives the same
fallback. Automatic connection-age rotation retires the old session in owned background cleanup, so cancellation
of the triggering request cannot interrupt or replay another request still using that session. Requests interrupted
by an explicitly force-closed session are not retried, preventing teardown from replaying an insert body. Concurrent
close callers do not inherit one another's cancellation while they wait on the same retired session cleanup.AsyncClient insert while its Native serializer is blocked on the bounded request-body queue now
shuts down the queue before awaiting the serializer. Async-generator cleanup no longer waits indefinitely, and
reusable insert contexts release their data and serializer error state after every attempt. External task
cancellation remains cancellation even when the serializer concurrently reports an error.AsyncClient query while its Native response is being handed to the parser now closes the response
source and releases its session lease. Streaming startup and columns-only response reads also close and release
their responses when interrupted, so later client shutdown no longer waits indefinitely for those abandoned queries.AsyncClient now closes its newly created aiohttp session when token provider or token installation fails, or when
cancellation interrupts token resolution or server initialization. These paths no longer leak the session when
create_async_client exits without returning a client. Initialization is serialized per client so cancellation of
one overlapping context entry cannot close a session initialized successfully by another.functools.partial wrappers, now work when asyncio debug mode is enabled.
Synchronous providers still run outside the event loop, and synchronous callables that return an awaitable remain
supported. If cancellation wins while a synchronous provider is still running, a late native coroutine result is
closed instead of emitting a never-awaited coroutine warning.create_client and create_async_client now convert string-valued pure boolean options from a DSN or generic_args,
including on and off, so false values no longer act as truthy strings. Both factories report invalid boolean and
numeric strings as ProgrammingError. The async factory preserves fractional timeout values and also
accepts connector_limit, connector_limit_per_host, and keepalive_timeout from those sources without passing
duplicate constructor keywords. Explicit non-None connector arguments take precedence over generic_args, which
take precedence over the DSN. None falls through to the next source and then the documented defaults.except clickhouse_connect.dbapi.Error now catches errors raised by the driver, and SQLAlchemy wraps them in the matching DBAPIError subclass instead of allowing them to escape its DB-API exception handling. StreamFailureError is now also an OperationalError, while remaining catchable by its existing class. Mid-stream failures now expose the numeric ClickHouse error code, and the symbolic error name when error details are enabled.This release introduces an experimental Rust codec for faster ClickHouse Native query decoding and insert encoding. It also expands ClickHouse type su
This release introduces an experimental Rust codec for faster ClickHouse Native query decoding and insert encoding. It also expands ClickHouse type support and includes several SQLAlchemy, Alembic, Native format, and packaging fixes.
The Rust codec is opt in. The existing Python and Cython codec is still the default.
The new native_codec option covers query, query_np, query_df, their streaming variants, and inserts including insert_df.
client = clickhouse_connect.get_client(
host="localhost",
native_codec="rust",
)native_codec="rust" falls back to Python for unsupported paths. Use native_codec="rust_strict" while evaluating or benchmarking so unsupported paths raise instead.
The Rust codec is currently most useful for:
Small results and flat numeric tables may see less benefit at the moment. Buffered query() calls on very wide or numeric results may also currently be slower. Benchmark your own workload before adopting the codec.
For evaluation, install the Rust codec with PyArrow:
pip install --upgrade "clickhouse-connect[rust,arrow]"Note that PyArrow is required for Rust NumPy and Pandas output paths in 1.8. Applications using standard Python rows, block streams, and inserts can use the smaller installation:
pip install --upgrade "clickhouse-connect[rust]"The compiled codec is published separately as clickhouse-connect-core 0.2.0. Existing installations do not install it automatically. The codec, packaging, and dependency set are currently experimental.
Geometry and MultiPoint in both codecs. Inserting MultiPoint requires ClickHouse 26.8 or later. #1018Interval* types.Time64 parsing, reflection, query, and insert support across server-valid precisions.Tuple() Native serialization, including nested and nullable forms. #971Variant member ordering that could write Native insert data under the wrong member type.Variant reflection. #989TypeDecorator and with_variant(). #984native_codec="rust_strict". #1019show_clickhouse_errors.CLICKHOUSE_CONNECT_REQUIRE_C and CLICKHOUSE_CONNECT_SKIP_CYTHON. #994Install the standard Python driver with:
pip install --upgrade clickhouse-connectPlease report Rust codec feedback and compatibility issues at:
https://github.com/ClickHouse/clickhouse-connect/issues
Full Changelog: v1.7.2...v1.8.0
MultiPoint type in the Python and Rust codecs, including containers and SQLAlchemy reflection. Inserting MultiPoint values requires ClickHouse 26.8 or later. Geometry now supports the MultiPoint member added in ClickHouse 26.8 at Native discriminator 6 while preserving the original discriminators 0 through 5. Geometry also supports the typed query format when selected with the Geometry type key. Variant format settings do not apply to Geometry. The Rust extra now requires clickhouse-connect-core>=0.2.0,<0.3.pip install "clickhouse-connect[rust,arrow]" for evaluation because its NumPy and Pandas output paths require PyArrow in 1.8. The lean rust extra remains available for standard Python row queries, block streams, and inserts. A missing PyArrow dependency now logs a warning before native_codec="rust" falls back to Python.native_codec="rust_strict". Dialect metadata statements are marked as driver-internal, so they decode with the Python codec in every codec mode instead of tripping the strict query_formats check. Compatible ordinary statements still use the Rust codec.show_clickhouse_errors. Mid-stream server errors return the generic message when the setting is False and drop the server version trailer when it is "scrub", matching the Python codec on both the sync and async clients.Dynamic shared-value decoding. See the Rust codec documentation for the full list of known behavior differences.See the 1.8.0rc1, 1.8.0rc2, and 1.8.0rc3 entries below for the other changes included in 1.8.0.
This is the third release candidate for clickhouse-connect 1.8.0. Please test it with your workloads and report any problems.
This is the third release candidate for clickhouse-connect 1.8.0. Please test it with your workloads and report any problems.
RC3 brings the Rust release branch up to date with work merged after 1.7.2 and adds further Python/Rust codec parity fixes. The Rust codec remains experimental and opt-in. The Python codec remains
the default. The compiled clickhouse-connect-core wheel is unchanged from RC2 at version 0.1.0.
Install RC3 with the optional Rust codec:
pip install "clickhouse-connect[rust]==1.8.0rc3"Or install RC3 with the standard Python codec only:
pip install "clickhouse-connect==1.8.0rc3"Enable the Rust codec when creating a client:
client = clickhouse_connect.get_client(
host="...",
native_codec="rust",
)Use native_codec="rust_strict" to reject unsupported Rust paths instead of falling back to Python.
Rust NumPy and Pandas queries now support Time64(0) with second resolution, including nullable, container, Nested, Variant, and typed JSON paths.
Unsupported statically declared Time64 scales now raise the same ProgrammingError under the Rust and Python codecs instead of leaking a KeyError.
Time64 now accepts every server-valid precision from 0 through 9 for parsing, reflection, Native queries, and DataFrame inserts. NumPy and Pandas query output remains limited to scales 0, 3, 6, and 9.
Negative Time64 timedelta inserts and NumPy timedelta magnitudes are now stored correctly. Out-of-range NumPy values are rejected instead of wrapping.
Pandas Timedelta row inserts now follow normal timedelta conversion. Nullable DataFrame columns insert missing values as NULL on Pandas 3.
Dynamic(Time64) retains documented experimental limitations because Dynamic member metadata is erased before driver conversion.
CLICKHOUSE_CONNECT_REQUIRE_C=1 makes extension build failures fatal for CI and redistributable wheels.CLICKHOUSE_CONNECT_SKIP_CYTHON=1 continues to request an explicit pure-Python build.Please report RC problems at:
https://github.com/ClickHouse/clickhouse-connect/issues
JSON columns can now declare ClickHouse typed paths with typed_paths={...} or simple keyword shorthand, plus dynamic path and type limits and plain and regular expression skip rules. JSON arguments use the same canonical ordering as server reflection. Alembic autogeneration renders configured JSON types as valid Python and round-trips them without a follow-up type migration. Closes #981.JSON type names now use the server's canonical argument ordering, omit explicit default limits, and expose decoded skip_paths and skip_regexps. This changes the .name and skips values reflected to all driver users.Time64 now accepts every server-valid precision from 0 through 9 for parsing, reflection, native queries, and DataFrame inserts. NumPy and Pandas queries remain limited to precisions 0, 3, 6, and 9 and raise ProgrammingError otherwise.Dynamic type names now preserve the max_types argument.Enum definitions are accepted and canonicalized to Enum8 or Enum16 by value range.Time64(0) with second resolution, including typed JSON paths. Unsupported statically declared Time64 scales, including nullable, container, Nested, Variant, and typed JSON types, now raise the same ProgrammingError as the Python codec instead of leaking a KeyError. The remaining Dynamic(Time64) cell type and validation limitations are documented.Tuple() definitions no longer parse as if they contain a phantom element, and their columns now consume and emit the Native format's per-row marker bytes. Root, nested, named, array-wrapped, and nullable empty tuples now query and insert without misaligning adjacent columns, corrupting values, or failing insert block sizing. Closes #971.SimpleAggregateFunction and AggregateFunction columns, including aggregate names that collide with Python builtins and arguments containing named Tuple types. Closes #992.Variant columns, and Alembic autogeneration can compare and round-trip them. Closes #989.Nested and named Tuple columns, including when they are inside another container. Generated upgrades preserve field names and no longer produce repeated type migrations. Closes #988.CLICKHOUSE_CONNECT_REQUIRE_C=1 makes an extension build failure fatal for CI and redistributable wheels. The default fallback now covers only compiler and linker failures, and a missing Cython outside skip mode fails the build. A default-mode fallback wheel keeps its platform and interpreter tags instead of being tagged py3-none-any. CLICKHOUSE_CONNECT_SKIP_CYTHON=1 still skips the extensions entirely, and setting both flags is an error. Closes #994.Variant members to the server's canonical order. Previously a client-declared Variant type with non-canonical member order wrote native insert data under the wrong member types.Time64 negative timedelta inserts previously stored incorrect values. Time64 NumPy timedelta inserts previously stored wrong magnitudes. Both now store correct tick values, and out-of-range NumPy values are rejected instead of incorrectly wrapping.Timedelta values in Time and Time64 row inserts now convert like timedelta. Nullable timedelta DataFrame columns now insert missing values as NULL on Pandas 3 instead of raising TypeError.TypeDecorator and with_variant() types through the active ClickHouse dialect. Dialect-aware types such as a decorator that selects DateTime64(6, 'UTC') no longer fall back to generic DATETIME in CREATE TABLE and related column DDL. Closes #984.get_client(host="2001:db8::1") connects instead of producing an unusable URI where the first colon of the address reads as the port separator. Applies to both the sync and async clients. Closes #998.alembic extra now requires alembic>=1.18. Earlier versions satisfied the package metadata but failed when importing the ClickHouse Alembic integration because the priority-dispatch API it uses was added in Alembic 1.18. See #983.This is the second release candidate for testing the optional Rust codec. RC2 updates the driver onto the 1.7.2 stable release, incorporating all of i
This is the second release candidate for testing the optional Rust codec. RC2 updates the driver onto the 1.7.2 stable release, incorporating all of its bug fixes. The Rust codec itself is unchanged from RC1.
Please test this release with your workloads and report any issues. Install it with a pinned version:
pip install "clickhouse-connect[rust]==1.8.0rc2"
FORMAT clauses inside the statement, including trailing whitespace and comments. Insert detection now follows SQL token rules. Fixesproxy_path values exactly. Fixes #963./ when using a forwarding HTTP proxy. Fixes #951.$ where ClickHouse permits it, while preserving the driver's raw binary binding convention. Fixes [#936](https://github.com/ClickHouse/literal_binds, DDL string clauses, comments, and Alembic comment operations now use ClickHouse-compatible backslash escaping. Fixes [#975](https://github.com/ClickHouse/clickhouse-union(), intersect(), and except_() now emit explicit DISTINCT operations, preserving SQLAlchemy semantics. Use their _all() counterparts when duplicate-preserving behavior isSelect.with_hint() table hints now emit SAWarning instead of being silently ignored. Fixes #974.The optional Rust codec remains experimental. The default python codec is unchanged.
To enable Rust with Python fallback:
client = clickhouse_connect.get_client(
host="...",
native_codec="rust",
)For testing, native_codec="rust_strict" raises on unsupported paths instead of falling back.
For this release candidate:
pip install "clickhouse-connect[rust]==1.8.0rc2"
For the latest stable release:
pip install clickhouse-connect
Follow-up release candidate to 1.8.0rc1, rebased on 1.7.2 so all bug fixes from that stable release are included. The optional Rust codec itself is unchanged.
Rust NumPy and Pandas queries now support Time64(0) with second resolution, including nullable, container, Nested, Variant, and typed JSON paths.
Unsupported statically declared Time64 scales now raise the same ProgrammingError under the Rust and Python codecs instead of leaking a KeyError .
Time64 now accepts every server-valid precision from 0 through 9 for parsing, reflection, Native queries, and DataFrame inserts. NumPy and Pandas query output remains limited to scales 0, 3, 6, and 9.
Negative Time64 timedelta inserts and NumPy timedelta magnitudes are now stored correctly. Out-of-range NumPy values are rejected instead of wrapping.
Pandas Timedelta row inserts now follow normal timedelta conversion. Nullable DataFrame columns insert missing values as NULL on Pandas 3.
Dynamic(Time64) retains documented experimental limitations because Dynamic member metadata is erased before driver conversion.
SQLAlchemy JSON columns can declare typed paths, dynamic path and type limits, and plain or regular-expression skip rules. Alembic autogeneration preserves configured JSON types. Closes #981 .
Dynamic type names preserve their max_types argument.
Generic Enum definitions are accepted and canonicalized to Enum8 or Enum16 according to their value range.
Type parsing now handles double-quoted identifiers and quoted string literals consistently.
Source builds no longer swallow unrelated packaging failures and retry them as pure Python builds.
CLICKHOUSE_CONNECT_REQUIRE_C=1 makes extension build failures fatal for CI and redistributable wheels.
CLICKHOUSE_CONNECT_SKIP_CYTHON=1 continues to request an explicit pure-Python build.
Missing Cython outside skip mode now fails clearly, and setting both build flags is rejected. Closes #994 .
Please report RC problems at: https://github.com/ClickHouse/clickhouse-connect/issues
Assets 2 Loading
There was an error while loading. Please reload this page .
All reactions
This is a release candidate for testing the new optional Rust codec. Please try it and report any issues. Install it with a pinned version:
This is a release candidate for testing the new optional Rust codec. Please try it and report any issues. Install it with a pinned version:
pip install "clickhouse-connect[rust]==1.8.0rc1"
This release adds an experimental native_codec client option that selects the codec used for FORMAT Native query decode and insert encode.
python is the default and uses the existing codec, so nothing changes unless you opt in.rust prefers the compiled Rust codec and falls back to the Python codec for unsupported options and types (which should be (hopefully) rare).rust_strict raises instead of falling back and is the recommended mode for testing.The compiled codec is published as the separate clickhouse-connect-core wheel, installed automatically by the [rust] extra. Wheels cover CPython 3.10 through 3.14 on Linux x86_64 and aarch64 for glibc 2.28 and newer plus musl, macOS, and Windows x64 and arm64. Results and dtypes match the Python codec, and the Arrow methods are unaffected.
To enable it on a client:
client = clickhouse_connect.get_client(host=..., native_codec="rust")This is early access for benchmarking and testing. See the rust-codec documentation page for details.
Everything in the 1.7.0 and 1.7.1 stable releases.
For this release candidate: pip install "clickhouse-connect[rust]==1.8.0rc1"
For the latest stable release: pip install clickhouse-connect
native_codec client option that selects the codec for FORMAT Native query decode and insert encode. python is the default and uses the existing codec. rust prefers the compiled Rust codec and falls back to the Python codec for unsupported options and types, while rust_strict raises instead of falling back. Python codec parity is the target, with known differences documented on the rust-codec page. The Arrow methods are unaffected. The compiled codec ships as the separate clickhouse-connect-core wheel, installed with pip install clickhouse-connect[rust]. See the rust-codec documentation page for details. This is early access for benchmarking and is not yet a supported path.Geometry type. Point values use 2-tuples and the other geometry members use nested lists. SQLAlchemy reflection exposes the public Geometry type.Interval* types as signed 64-bit counts in the interval type's unit.Queries ending in a semicolon now keep client-appended FORMAT clauses inside the statement, including trailing whitespace and comments. Insert detection now follows SQL token rules. Fixes #903 .
The async client now preserves explicit proxy_path values exactly. Fixes #963 .
The synchronous client now normalizes an empty request path to / when using a forwarding HTTP proxy. Fixes #951 .
Server-side query parameter names can now contain $ where ClickHouse permits it, while preserving the driver's raw binary binding convention. Fixes [ #936 ]( https://github.com/ClickHouse/
ClickHouse type literals containing percent signs now compile safely alongside bound parameters. Fixes #966 .
Generic literal_binds , DDL string clauses, comments, and Alembic comment operations now use ClickHouse-compatible backslash escaping. Fixes [ #975 ]( https://github.com/ClickHouse/clickhouse- connect/issues/975).
union() , intersect() , and except_() now emit explicit DISTINCT operations, preserving SQLAlchemy semantics. Use their _all() counterparts when duplicate-preserving behavior is intended. Fixes #973 .
Applicable Select.with_hint() table hints now emit SAWarning instead of being silently ignored. Fixes #974 .
This patch release fixes SQLAlchemy reflection and SQL generation, query formatting, server-side parameter names, and HTTP proxy path handling.
This patch release fixes SQLAlchemy reflection and SQL generation, query formatting, server-side parameter names, and HTTP proxy path handling.
Engine now support get_columns() and reflect_table() on SQLAlchemy 2.x. Reflection also honors include_columns and exclude_columns. Closes #967.FORMAT clauses correctly. Insert detection also follows SQL token rules. Closes #903.proxy_path values without adding extra slashes. Closes #963./ request path through forwarding HTTP proxies when no proxy_path is configured. Closes #951.$ in valid parameter names while preserving raw binary binding and ambiguity checks. Closes #936.TypeDecorator and with_variant(). Closes #965.union(), intersect(), and except_() now emit explicit DISTINCT operations. Use the corresponding _all() methods when duplicate-preserving behavior is required. Closes #973.Select.with_hint() now emits SAWarning when an applicable table hint would otherwise be ignored. Generated SQL remains unchanged. Closes #974.Full Changelog: v1.7.1...v1.7.2
pip install clickhouse-connectEngine can now call get_columns() and reflect_table() directly on SQLAlchemy 2.x. These methods now acquire and reuse one connection for each reflection operation, while inspectors already bound to a Connection continue to reuse it. Table reflection also honors positional include_columns and exclude_columns filters passed by SQLAlchemy instead of silently reflecting every column. Closes #967.FORMAT clause inside the statement, including when the semicolon is followed by whitespace or a trailing comment. This fixes query, query_arrow, and raw_query with fmt for both sync and async clients. A lone directly trailing semicolon keeps the existing fast binding path, and inserts carrying inline data are never passed through the SQL lexer. Insert detection now follows SQL token rules, so quoted text such as ' INSERT INTO ' in a SELECT no longer misroutes the query, and identifiers named insert are not mistaken for the keyword. Closes #903.proxy_path when constructing request URLs. It previously appended / unconditionally, changing /clickhouse to /clickhouse/ and /clickhouse/ to /clickhouse//, which could break exact-path proxy routing. Bare authority URLs still use /. Closes #963./ when no proxy_path is configured, so requests routed through a forwarding HTTP proxy (http_proxy/HTTP_PROXY) use the normal absolute-form request-target (http://host:8123/?query=...) instead of the RFC-valid but non-normalized http://host:8123?query=..., which some proxies reject with HTTP 400 and others forward with the query string silently dropped. Direct connections are unaffected because urllib3 already normalizes the empty path, and an explicit proxy_path is left exactly as-is. This matches the async client, which already sent the path. Closes #951.$ in server-valid parameter names such as {id$x:Int32} or {$x$:String}. Previously these names were missed, which omitted their server-side values and could also drop DateTime64 precision and timezone hints. Placeholder detection is otherwise unchanged from 1.x. A $name$ dictionary key with a buffer value such as bytes, bytearray, or memoryview stays a raw binary bind. A non-buffer value for such a key can bind through a single {name:Type} placeholder, and ambiguous or repeated uses of the name raise ProgrammingError. SQLAlchemy server_side_params accepts the same names and rejects the reserved $name$ form. Closes #936.TypeDecorator wrappers and with_variant() render ClickHouse literals with proper quoting and escaping. Closes #965.literal_binds strings, string DEFAULT, MATERIALIZED, ALIAS, and TTL clauses, CREATE comments, and Alembic table and column comment operations. Backslash values now round-trip verbatim instead of being reinterpreted or terminating a quoted literal. ClickHouse-native literal processors and percent handling are unchanged. If custom TypeDecorator.process_literal_param or UserDefinedType code pre-escaped backslashes as a workaround, remove that workaround because the dialect now applies ClickHouse escaping. Closes #975.union(), intersect(), and except_() now compile to explicit UNION DISTINCT, INTERSECT DISTINCT, and EXCEPT DISTINCT, preserving SQLAlchemy's duplicate-removing semantics instead of relying on ClickHouse defaults. Their union_all(), intersect_all(), and except_all() counterparts remain explicit ALL operations. Users relying on previous duplicate-preserving behavior from union_default_mode='ALL' or ClickHouse's default intersect_default_mode='ALL' and except_default_mode='ALL' should switch to the corresponding _all() method. Closes #973.Select.with_hint() now emits an SAWarning when an applicable table hint would otherwise be silently ignored. The generated SQL remains unchanged for 1.x compatibility. Applications that promote SAWarning to an error will now stop at compilation instead of executing without the requested hint. Use the typed final(), sample(), prewhere(), and limit_by() methods for those ClickHouse clauses. Raw with_statement_hint() tail directives remain supported. Closes #974.SQLAlchemy 2.1 compatibility : Identifier quoting forwarded the deprecated force argument to IdentifierPreparer.quote , which SQLAlchemy 2.1 removed,…
This is a patch release with two SQLAlchemy compatibility fixes.
force argument to IdentifierPreparer.quote, which SQLAlchemy 2.1 removed, causing any dialect use to raise TypeError on 2.1.0b3. The parent call now passes only the identifier. The optional force parameter remains available on the ClickHouse preparer for direct callers. Closes #954.clickhouse_materialized, clickhouse_alias, or clickhouse_ttl options raised AttributeError on SQLAlchemy 1.4 because it called a rendering helper that only exists in 2.0. The helper is now implemented locally. This appears to have been broken since 1.1.0.Full Changelog: v1.7.0...v1.7.1
pip install clickhouse-connect
clickhouse-connect 1.7.0 adds SQLAlchemy support for JSON subcolumns and materialized CTEs, introduces more control over error messages and naive date
clickhouse-connect 1.7.0 adds SQLAlchemy support for JSON subcolumns and materialized CTEs, introduces more control over error messages and naive datetime inserts, and fixes issues across parameter binding, streaming, JSON decoding, DB-API, and SQLAlchemy.
column["segment"], column.subcolumn(...), and the typed json_subcolumn(...) helper. #899.cte(..., materialized=True) and cc_sqlalchemy.cte(...). This requires ClickHouse 26.3 or later with enable_materialized_cte and the analyzer enabled. #900show_clickhouse_errors="scrub" preserves useful server error details while removing the server URL and version trailer. Transport and streaming errors follow the same setting. Invalid values now raise ProgrammingError. #344naive_datetime_insert setting controls whether naive Python datetime values use the client host timezone or the column and server timezone. The default remains "local" for compatibility. #938%2E JSON key encodings.datetime.time and datetime.timedelta query parameters now bind correctly for Time and Time64, including nested, negative, extended-duration, timezone-aware, and nanosecond values. SQLAlchemy inserts, comparisons, and literal_binds now support these values too. #919Binary, Date, Time, Timestamp, and ticks-based constructors. This also fixes SQLAlchemy LargeBinary inserts. #919DateTime64 values before the Unix epoch now serialize to the correct second. #938None values in arrays, tuples, and map-formatted maps now render as SQL NULL. #879FixedString columns are now padded correctly. #880system.settings, including custom role settings, are now forwarded to ClickHouse for validation. #530String format is configured as bytes. #920LIMIT handling. #928Cursor.description now reports accurate top-level nullability and handles empty-result metadata probes more safely. #902, #907, #909StreamFailureError when TLS queries fail mid-stream.datetime.time and datetime.timedelta parameters are now quoted by the driver. Remove manual quotes around existing %(name)s placeholders. #919common.set_setting("naive_datetime_binding", "legacy") to restore the previous host-timezone conversion behavior. #938pip install clickhouse-connectcolumn["segment"], column.subcolumn("segment", type_=...), and the statically typed json_subcolumn(...) helper. Nested paths compile as independently quoted dotted identifiers, and typed access uses CAST. Closes #899.show_clickhouse_errors now accepts "scrub" in addition to True/False. Scrub mode keeps the SQL exception text and symbolic name (for example UNKNOWN_TABLE) while stripping the server URL and trailing (version ...) trailer from client exception messages. Transport errors and mid-stream StreamFailureError messages honor the same setting. When error detail is disabled (False), the displayed exception string is generic. The chDB backend now uses the same generic text as HTTP, without its former trailing period. This setting governs str(exc) only. Transport errors remain attached as __cause__, so tracebacks can still contain the original host, URL, or library error text. Historical string booleans still work, but non-boolean values such as integers and unrecognized strings now raise ProgrammingError. Addresses the middle ground requested in #344.cc_sqlalchemy.select(...).cte("name", materialized=True) emits WITH name AS MATERIALIZED (...), so a CTE referenced more than once is computed once instead of being inlined and re-executed at each reference. A module-level cc_sqlalchemy.cte(statement, "name", materialized=True) does the same for a statement built with the standard sqlalchemy.select. The keyword renders only on the ClickHouse dialect. The server materializes the CTE only when the experimental enable_materialized_cte setting is also enabled for the query and the analyzer is enabled. Materialized CTEs require ClickHouse 26.3 or later. The SQLAlchemy helpers reject recursive=True with materialized=True because ClickHouse does not support recursive materialized CTEs. Closes #900.naive_datetime_insert setting for Python object inserts, including naive ISO strings accepted by DateTime64. Set it to "server" to interpret a naive datetime in the timezone declared by the DateTime or DateTime64 column, or in the server timezone when the column has no timezone. The default remains "local" in 1.x and preserves the existing host-local conversion. This setting does not change datetime64-dtype NumPy and Pandas columns. Use naive_datetime_binding to control naive datetime query parameters. See #938.common.readonly value for servers older than 19.17 and always attempts guarded Native protocol negotiation, retaining the existing proxy-safe fallback. JSON inserts no longer fall back to String serialization for 24.8 and 24.9 servers. The module attribute clickhouse_connect.datatypes.dynamic.json_serialization_format remains importable for compatibility but assigning it no longer changes insert behavior. The generated cast_string_to_dynamic_use_inference default no longer depends on the obsolete allow_experimental_json_type setting. The global common.readonly option is deprecated and retained as a no-op. The default local Docker server is now ClickHouse 25.8.datetime.time and datetime.timedelta query parameters are now rendered as quoted literals. A time value was previously rendered without quotes and the server rejected it, so some queries added the quotes in the query text as a workaround, for example WHERE t = '%(t)s'. Those queries now produce a doubled quote and fail. Remove the manual quotes and bind the value normally. See #919.datetime query parameters now bind as wall time instead of being interpreted in the client host timezone. Previously a naive value passed through astimezone for server-side {name:DateTime} parameters and DT64Param values, so the same query could match different rows depending on the timezone of the machine running it. Only workloads that bind naive datetime parameters with a non-UTC host timezone or a non-UTC target timezone are affected. Environments where both the host and the bind target are UTC see no change, and client-side % parameters against a UTC server were already sent verbatim. Two changes are observable. First, on a non-UTC host with a UTC target, server-side parameters and DT64Param values no longer shift, which corrects silently wrong results. Second, when the bind target is a non-UTC timezone, a naive value now means wall time in that timezone instead of the instant implied by the client local timezone, which can change matched rows for code that relied on the old conversion. A related consequence is that inserting a naive datetime and then filtering with the same naive value no longer matches on a non-UTC host, because the insert path still interprets naive values as host local time. #938 tracks unifying insert semantics. Timezone-aware datetimes are unchanged and still convert to the target bind timezone. Set common.set_setting("naive_datetime_binding", "legacy") to restore the previous behavior exactly. To make a naive value represent a specific instant under either mode, attach the intended tzinfo before binding.% now compile safely in statements with bound parameters. The DB-API bulk INSERT path also restores escaped percent signs in table and column names, so executemany keeps using one bulk insert instead of falling back to row-by-row execution or sending the wrong identifier. This includes %2E JSON key encodings used with json_type_escape_dots_in_keys.datetime.time and datetime.timedelta query parameters now bind as a quoted HH:MM:SS[.ffffff] literal for Time and Time64 columns. This fixes client-side %(name)s binding, timezone-aware time values, timedelta values, and values nested in arrays and tuples. A naive scalar time at the top level of a server-side {name:Time} bind already worked and is unchanged. A timedelta may be negative and may exceed 24 hours, and a pandas Timedelta with sub-microsecond nanoseconds formats a nine digit fraction. Plain Time accepts only whole seconds. Addresses the Time parameter failure in #919.Time and Time64 columns now accept datetime.time and datetime.timedelta values in inserts and comparisons, and render correctly with literal_binds. The types inherit from the SQLAlchemy Interval type, which converted every bound value to an epoch datetime that the server rejected and coerced comparison values to its DateTime implementation. Reads still return timedelta. Part of #919.Binary, Date, Time, Timestamp, DateFromTicks, TimeFromTicks, and TimestampFromTicks. SQLAlchemy LargeBinary inserts no longer raise AttributeError. Addresses the Binary constructor failure in #919.DateTime64 values before the Unix epoch now serialize with the correct second. The serializer truncated negative timestamps toward zero before adding the fractional component, which shifted affected values forward by one second. This affected Python datetime values and accepted ISO strings in both naive datetime insert modes. See #938.Variant, Tuple, Nested, or typed JSON column type whose element is an Enum with an escaped single quote in a value name no longer corrupts the escape sequence and fails while re-parsing the element type. Closes #878.None nested inside an Array or Tuple, or inside a Map when dict_parameter_format="map", now renders as the SQL NULL keyword instead of the \N sentinel used for top-level values. Top-level scalar None binds are unchanged. Closes #879.b"" into a non-nullable FixedString(N) column now zero-pads to N bytes instead of raising DataError, matching the existing string and nullable-bytes write paths. Closes #880.system.settings for the current user (including custom settings declared CHANGEABLE_IN_READONLY on a role) are now forwarded to ClickHouse instead of raising ProgrammingError: Setting ... is unknown or readonly. The client cannot discover those settings without extra privileges, so the server is treated as authoritative. Setting invalid_setting_action to drop still drops them, so a single settings dict stays portable across server versions. Known readonly settings still honor invalid_setting_action, and reserved HTTP request parameter names such as query, user, default_format, and the param_ bound-parameter namespace still raise a client-side ProgrammingError because they are not settings. Closes #530.String decoding, so set_default_formats("String", "bytes") no longer turns reflected database, table, or column names into bytes. The Alembic startup current database lookup uses the same internal format. Alembic version table queries do not use the internal override and remain affected by a global String bytes format. Closes #920.remove_sql_comments replaced it with nothing, so SELECT/*c*/number FROM numbers(9) became the single token SELECTnumber, stopped looking like a SELECT, and the client side query_limit was silently dropped, while SELECT number FROM numbers(9)/*c*/LIMIT 1 became numbers(9)LIMIT 1, hid the real LIMIT, and the client appended a second one that the server rejected with Code: 62. A removed block comment now leaves a single space behind, and the trailing LIMIT 0 check that routes a query to the columns only metadata probe accepts any whitespace between LIMIT and 0 instead of exactly one space, so LIMIT /*c*/0 keeps reaching that probe. A -- line comment is unchanged, its terminating newline was already kept. Closes #928.__exception__<tag> and <tag>__exception__, but the server separates __exception__ from the tag with a CRLF on both markers (__exception__\r\n<tag> ... <tag>\r\n__exception__), so the scan never matched and the exception block was only recovered by the last-chunk fallback in NativeTransform.parse_response. When the block spanned a transport-chunk boundary that fallback saw just a fragment and surfaced a truncated or garbled error instead of the real ClickHouse exception. Both the pure Python and compiled Cython buffers are corrected. Closes #915.Cursor.description now reports the result type's top-level nullability instead of hardcoding null_ok=True, including the implicit null values supported by Variant, Dynamic, and SimpleAggregateFunction over a nullable element type. Existing type_code values are unchanged, and types whose nullability is unknown report None. The empty-result metadata probe also recognizes leading ClickHouse comments, including nested block comments, and is best effort, so a failed probe leaves description empty instead of raising after the original query succeeded. Closes #902, #907, and #909.Date, DateTime, and DateTime64 values in shared data, both as scalars and inside arrays, now decode as well. Closes #897.AsyncClient no longer tears down the aiohttp response from the parser's executor thread when a query fails mid-stream. The synchronous cleanup cancelled the producer task and closed the response directly, which raced with the event loop handling the server's connection abort and could surface an AttributeError from asyncio's SSL shutdown on TLS connections instead of the real StreamFailureError. Cleanup is now scheduled onto the event loop with call_soon_threadsafe.clickhouse-connect 1.6.0 adds an experimental in-process chDB backend, resolves several sync/async client parity bugs, and replaces the zstandard depe
clickhouse-connect 1.6.0 adds an experimental in-process chDB backend, resolves several sync/async client parity bugs, and replaces the zstandard dependency with the standard library/a backport. Internally, the sync and async HTTP clients were unified onto a shared backend core, which is what enables the chDB backend and fixes the async issues below.
get_client(interface='chdb') or a chdb:// DSN returns a standard client that runs queries against an embedded chDB engine instead of a ClickHouse server, supporting the full query, insert, streaming, and Arrow client surface. Use the path argument or a chdb:///on/disk/path DSN for a persistent database. Requires the chdb package, installable with pip install clickhouse-connect[chdb]. chDB allows one engine per process, has no async client, and does not support external data. (#872)AsyncClient initialization no longer overwrites user-supplied session settings with generated defaults which no matches the sync client. (#872)AsyncClient created with both client certificates and an access token now sends the mutual TLS authentication headers and the Authorization: Bearer header together which now also matches the sync client. (#872)additional_table_filters no longer crash with DB::Exception: Cannot parse quoted string when passed through query()'s settings parameter. Closes #501.client_protocol_version capability probe errors on the sync client; it now falls back gracefully and logs at debug level, matching the async client.zstandard dependency with the stdlib compression.zstd module (Python 3.14+) and backports.zstd (Python 3.10-3.13), giving a single consistent call surface across all supported Python versions. zstd compression remains fully supported on all standard Python installs. Closes #577.asyncclient.py shrank by roughly 1,200 lines of duplicated logic). This is an internal change with no public API surface, but it's the basis for the chDB backend and the sync/async parity fixes above. (#872)pip install clickhouse-connectAsyncClient initialization no longer overwrites user-supplied session settings with generated defaults. A client created with settings={'date_time_input_format': 'basic'} previously had that value replaced by the generated best_effort default. User settings now always win, matching the sync client.AsyncClient created with both client certificates and an access token now sends the mutual TLS authentication headers and the Authorization: Bearer header together, matching the sync client. The certificates previously suppressed the token at construction, while the token_provider option re-added its token right after initialization, so the two async token paths disagreed with each other. The server resolves the credential precedence.additional_table_filters no longer crash with DB::Exception: Cannot parse quoted string when passed through query()'s settings parameter. The value was rendered with Python's own str()/repr() of the dict, which mixes single and double quotes and is not valid ClickHouse map-literal syntax; it is now rendered as a properly single-quoted, escaped ClickHouse map literal. Closes #501.BFloat16 row inserts are now stored instead of being written as 0.Array(Dynamic) column no longer raises ZeroDivisionError when a sampled row holds an empty array. The insert block size estimate now treats an empty sample as minimal instead of dividing by its length.client_protocol_version capability probe errors on the sync client. The client falls back to running without the newer native protocol features and logs the probe failure at debug level, matching the async client.get_client(interface='chdb') or a chdb:// DSN returns a standard client that runs queries against an embedded chDB engine instead of a ClickHouse server, supporting the full query, insert, streaming, and Arrow client surface. Use the path argument or a chdb:///on/disk/path DSN for a persistent database. Requires the chdb package, installable with pip install clickhouse-connect[chdb]. chDB allows one engine per process, has no async client, and does not support external data.zstandard dependency with the stdlib compression.zstd module (Python 3.14+) and backports.zstd (Python 3.10-3.13), which provides the same API as the stdlib module. This gives a single consistent call surface across all supported Python versions and removes a dependency that diverges from the standard library. zstd compression remains fully supported on all standard Python installs. In the rare case of a custom CPython 3.14+ interpreter compiled without zstd support, the driver now still imports, drops zstd from the advertised compression methods, and raises a clear error only if zstd is explicitly requested. Closes #577.This is a feature release focused on the SQLAlchemy and Alembic integration, alongside two important correctness fixes on the core driver. Alembic gai
This is a feature release focused on the SQLAlchemy and Alembic integration, alongside two important correctness fixes on the core driver. Alembic gains first-class support for ClickHouse-specific DDL, SQLAlchemy gains per-query settings and typed ClickHouse query chainables, and Variant columns gain a new lossless read format. On the driver side, this release fixes a data-corruption bug when decoding large varints on the compiled path and a QBit corruption bug for dimensions greater than 8.
ALTER TABLE so replicated deployments avoid the Code: 517 CANNOT_ASSIGN_ALTER race. Helpers render valid SQL in offline --sql mode. #839execution_options(settings={...}) now forwards per-query settings through the dialect and DB-API cursor for Core and text() statements, execute, executemany, and the bulk-insert path. Settings set at the connection or engine level compose with per-statement settings, and per-statement values take precedence, so a connection-level default applies to implicit ORM queries such as selectinload and lazy loads. #838, #846Select.ch_join() lets ClickHouse JOIN modifiers be written in normal SQLAlchemy chaining style. It takes the strictness modifiers ALL, ANY, ASOF, SEMI, and ANTI, the GLOBAL distribution modifier, plus USING and CROSS as keyword arguments. The existing ch_join() factory is unchanged. #827cc_sqlalchemy.select() returns a ClickHouseSelect exposing ch_join, final, sample, array_join, prewhere, and limit_by as typed methods, so static type checkers accept them without suppressions. The standard sqlalchemy.select() path is unchanged. #837typed read format for Variant columns. When two members of a Variant share a Python type, such as Variant(Float32, Float64), reading with the typed format wraps each value as a TypedVariant carrying both the value and its type_name, and these feed straight back into inserts. Enable per query with query_formats={'Variant': 'typed'} or globally with set_read_format('Variant', 'typed'). The default native format is unchanged. #825QBit columns with a dimension greater than 8 on native inserts and reads. Data written by earlier clients was stored incorrectly and should be re-inserted. See the upgrade note below. #866command() now returns an empty string for a read that produces an empty result set, instead of a truthy QuerySummary that made if result: misleading. #865Cursor.executemany no longer falls off the bulk-insert fast path when an INSERT names backtick-quoted dotted columns such as the wire form of Nested sub-columns. unescape_identifier now removes backtick quoting from compound identifiers correctly, which previously degraded the operation to slow per-row execution and could raise ProgrammingError with dict rows and pyformat placeholders. #820order_by, partition_by, primary_key, sample_by, and ttl now accept arbitrary SQL expressions such as col.desc(), func.cityHash64(a, b), tuple_(...), and interval TTL expressions, in scalar and list forms. Expression engines round-trip through repr() for Alembic autogeneration. #845has_database() now uses EXISTS DATABASE instead of querying system.databases. On servers from 25.10 through 26.4, system.databases omitted DataLakeCatalog and other remote databases by default, so has_database() reported False for databases that actually exist. #849MATERIALIZED and ALIAS columns now keep their comment, codec, and ttl options in generated DDL, and column clauses are emitted in the order ClickHouse requires, COMMENT then CODEC then TTL. This also fixes a pre-existing case where a column combining a codec with a comment produced invalid SQL. #856ClickHouseSelect now keeps its typed ClickHouse chainables after column-shape methods such as add_columns(), with_only_columns(), column(), and reduce_columns(). cc_sqlalchemy.select() also works on SQLAlchemy 1.4. #844TypeDecorator no longer raises TypeError: result_processor() takes 0 positional arguments but 2 were given when reading results. #847op.rename_table now emits RENAME TABLE old TO new instead of the ALTER TABLE old RENAME TO new form ClickHouse rejects. Standard SQLAlchemy indexes are filtered from ClickHouse autogenerate output, and Column(index=True), Index(...), op.create_index, and op.drop_index raise a clear Alembic error before partially applying DDL. Use op.add_clickhouse_index and op.drop_clickhouse_index for data-skipping indexes. #839:name are no longer parsed as bind parameters, dictionary comments escape backslashes correctly, explicit schemas are honored for legal dotted table names, CREATE MATERIALIZED VIEW no longer accepts a misleading clickhouse_settings suffix, and custom ClickHouse operation objects render through autogenerate instead of raising ValueError. #839QBit dimension greater than 8. Values written by earlier clients were stored incorrectly on the wire. After upgrading, re-insert any affected QBit data. #866command() return value. A read that returns no rows now yields an empty string rather than a truthy QuerySummary. Code that relied on command() for a read always being truthy should check the returned value explicitly. #865pip install clickhouse-connect
Full Changelog: v1.4.2...v1.5.0
op.rename_table now emits RENAME TABLE old TO new. It previously emitted the standard ALTER TABLE old RENAME TO new, which ClickHouse rejects. Standard SQLAlchemy indexes are now filtered from ClickHouse autogenerate output, and Column(index=True), Index(...), op.create_index, and op.drop_index raise a clear Alembic error before partially applying DDL. Use the ClickHouse-specific op.add_clickhouse_index and op.drop_clickhouse_index helpers for data-skipping indexes. Part of #839.:name are no longer parsed as SQLAlchemy bind parameters, dictionary comments now escape backslashes correctly, explicit schemas are honored for legal dotted table names, CREATE MATERIALIZED VIEW no longer accepts a misleading clickhouse_settings suffix that ClickHouse stores inside the SELECT definition, and custom ClickHouse operation objects now render through Alembic autogenerate instead of raising ValueError. Part of #839.ClickHouseSelect now keeps its typed ClickHouse chainables after column-shape methods such as add_columns(), with_only_columns(), column(), and reduce_columns(). cc_sqlalchemy.select() also works on SQLAlchemy 1.4. Closes #844.TypeDecorator no longer raises TypeError: result_processor() takes 0 positional arguments but 2 were given when reading results. Closes #847.order_by, partition_by, primary_key, sample_by, ttl) now accept arbitrary SQL expressions such as col.desc(), func.cityHash64(a, b), tuple_(...), and interval TTL expressions, in scalar and list forms. Expressions were previously either rejected with a TypeError in list form or rendered through the wrong dialect in scalar form. Plain strings, text(), and bare Column inputs render exactly as before, and expression engines round-trip through repr() for Alembic autogeneration. Closes #845.has_database() now uses EXISTS DATABASE instead of querying system.databases. On ClickHouse servers from 25.10 through 26.4 system.databases omitted DataLakeCatalog and other remote databases by default, so has_database() reported False for databases that actually exist and broke schema-existence checks. EXISTS DATABASE consults the database catalog directly and is correct on every server version. Closes #849.MATERIALIZED and ALIAS columns now keep their comment, codec, and ttl options in generated DDL. Column clauses are also now emitted in the order ClickHouse requires, COMMENT then CODEC then TTL, which fixes a separate pre-existing case where any column that combined a codec with a comment produced invalid SQL that the server rejected. DEFAULT, MATERIALIZED, and ALIAS remain mutually exclusive. Closes #856.Cursor.executemany no longer silently falls off the bulk insert fast path when an INSERT names backtick-quoted dotted columns, the wire form of Nested sub-columns such as `directory`.`id`. unescape_identifier stripped only the outermost backtick pair, so the normalized column names kept their inner backticks and never matched the row dict keys. The comparison always failed, so the operation degraded to slow per-row execution, and with dict rows and pyformat placeholders it could raise ProgrammingError. unescape_identifier now removes backtick quoting from compound identifiers correctly. Closes #820.command() now returns an empty string for a read that produces an empty result set, instead of a truthy QuerySummary that made if result: misleading. Closes #865.QBit columns with a dimension greater than 8 no longer corrupt the vector. Data written by earlier clients was stored incorrectly and should be re-inserted. Closes #866.ALTER TABLE, so replicated deployments avoid the Code: 517 CANNOT_ASSIGN_ALTER race that separate statements can trigger. Helpers render valid SQL in offline --sql mode, and helpers whose signature includes clickhouse_settings render that mapping as an inline SETTINGS clause. Closes #839.Select.ch_join() so ClickHouse JOIN modifiers can be written in normal SQLAlchemy chaining style instead of nesting the ch_join() helper inside select_from(). It takes the strictness modifiers ALL, ANY, ASOF, SEMI, and ANTI, the GLOBAL distribution modifier, plus USING and CROSS, all as keyword arguments, and chains so multi-join queries stay readable. The existing ch_join() factory is unchanged. Closes #827.cc_sqlalchemy.select() which returns a ClickHouseSelect. It exposes the ClickHouse chainable modifiers such as ch_join, final, sample, array_join, prewhere, and limit_by as typed methods so static type checkers accept them without suppressions. The existing sqlalchemy.select() path keeps working unchanged. Closes #837.execution_options(settings={...}) now forwards per-query ClickHouse settings through the dialect and DB-API cursor instead of silently ignoring them. This works for Core and text() statements, connection-level execution options, execute, executemany, and the bulk-insert path, while continuing to use the existing client settings validation. Settings set at the connection or engine level compose with per-statement settings, with the per-statement value taking precedence for any key set at both levels, so a connection or engine level default applies to every execution including implicit ORM queries such as selectinload and lazy attribute loads without overriding explicit per-query settings. Closes #838 and #846.typed read format for Variant columns. When two members of a Variant share a Python type, such as Variant(DateTime, DateTime64(3)) or Variant(Float32, Float64), the decoded value alone did not record which member produced it, so the originating ClickHouse type was unrecoverable. Reading with the typed format wraps each value as a TypedVariant carrying both the value and its type_name, and these values feed straight back into inserts. The default read format stays native and returns bare values as before, so existing behavior is unchanged. Enable it per query with query_formats={'Variant': 'typed'} or globally with set_read_format('Variant', 'typed'). Closes #825.Patch release with a single bug fix on top of 1.4.1. No new features and no breaking changes.
Patch release with a single bug fix on top of 1.4.1. No new features and no breaking changes.
Bug Fixes
Full Changelog: v1.4.1...v1.4.2
This is a patch release with two bug fixes. One restores correct Alembic autogenerate output for non-ClickHouse dialects after the ClickHouse integrat
This is a patch release with two bug fixes. One restores correct Alembic autogenerate output for non-ClickHouse dialects after the ClickHouse integration is imported. The other completes the return-type annotations on the async client so callers running mypy in strict mode no longer get errors.
CreateTableOp, AddColumnOp, and DropTableOp were registered as process-wide replacements with no dialect guard, because Alembic renderers have no per-dialect dispatch. Any non-ClickHouse autogenerate run in the same process then used the ClickHouse renderers, which dropped the nullable argument from columns whose nullability was not set explicitly and injected cc_sqlalchemy imports. The renderers now fall back to Alembic's built-in rendering for non-ClickHouse dialects. Closes #832.AsyncClient methods now carry the return-type annotations their sync Client counterparts already had. close, close_connections, query_np, query_df, query_arrow, set_client_setting, and set_access_token were missing them, so downstream projects running mypy with --disallow-untyped-calls got no-untyped-call errors on calls like await client.close() once the package began shipping py.typed in 1.4.0. The async client surface is now fully annotated. This is a type-only change with no runtime effect. Closes #831.pip install clickhouse-connect
Importing clickhouse-connect no longer emits a DeprecationWarning for the array 'u' type code. This also lets projects that run with -W error import t…
This is a minor release. It adds experimental free-threading support, ships type information for downstream type checkers, and fixes bugs across the core client, the DB-API cursor, and the SQLAlchemy dialect.
py.typed marker so downstream type checkers can use its annotations. #692None for port and database. create_client, create_async_client, connect(), and the DB-API Connection constructor now accept None to request the driver's defaults, in addition to omitting them. The internal sentinel values are still accepted, so this is backward compatible. #801QueryResult.query_id now returns an empty string instead of None when the server reported no query id. This matches QuerySummary and keeps the property consistently typed as str.QueryResult.first_item and QueryResult.first_row now return None for an empty result set instead of raising IndexError. #824Cursor.executemany now resets rowcount and reports the number of inserted rows after a bulk insert, and appends the insert summary to cursor.summary. Passing a generator as seq_of_parameters no longer raises TypeError. The bulk-insert optimization is skipped for non-indexable iterables and falls through to the row-by-row path as PEP 249 requires.StreamFailureError, carrying the server-side error message when ClickHouse reported one. #802Client.insert_arrow and AsyncClient.insert_arrow no longer drop the transport_settings argument. It was passed positionally into the compression parameter, so transport settings were ignored. It is now forwarded correctly.command now raises ProgrammingError when binary parameter binds are combined with command data or external data, instead of placing binary content into the URL query string. This applies to both the sync and async clients.DeprecationWarning for the array 'u' type code. This also lets projects that run with -W error import the package. #815Nullable and LowCardinality DDL helpers are now functions that return a concrete ChSqlaType, so Array(LowCardinality(String)) no longer raises a spurious mypy error. Runtime behavior is unchanged. #819Client.insert, AsyncClient.insert, and raw_insert now require column_names to be a Sequence rather than a bare Iterable. A one-shot iterator such as a generator already failed at runtime because the column names are measured and iterated more than once, so the type hint now matches the real requirement.column_names as a generator or other one-shot iterator, switch to a Sequence such as a list or tuple. This path already failed at runtime.pip install clickhouse-connect
QueryResult.first_item and QueryResult.first_row now return None for an empty result set instead of raising IndexError. Both properties indexed element [0] without checking the row count, so they crashed when a query returned no rows. They now short-circuit on an empty result in both row-oriented and column-oriented mode. Closes #824.Cursor.executemany now correctly resets rowcount and reports the number of inserted rows after a bulk insert. Previously, rowcount retained the value from the previous operation. The insert summary is also appended to cursor.summary, consistent with the non-bulk path. In addition, passing a generator as seq_of_parameters no longer raises TypeError; the bulk-insert optimisation is now skipped for non-indexable iterables and the operation falls through to the row-by-row path as PEP 249 requires.StreamFailureError, carrying the server-side error message when ClickHouse reported one. Closes #802.Client.insert_arrow and AsyncClient.insert_arrow no longer drop the transport_settings argument. It was passed positionally into the compression parameter, so transport settings were ignored and a non-empty value corrupted the request. It is now forwarded correctly.command now raises ProgrammingError when binary parameter binds are combined with command data or external data, instead of placing binary content into the URL query string. This applies to both the sync and async clients.DeprecationWarning for the array 'u' type code. The compiled buffer module built an unused array.array('u', []) template at import time. The 'u' (wchar_t) code was deprecated and is scheduled for removal in 3.16, where this would have become an ImportError. The template is removed, which also lets projects that run with -W error import the package. Closes #815.Nullable and LowCardinality DDL helpers are now functions that return a concrete ChSqlaType. They were classes whose __new__ returned a different type, which type checkers cannot model, so Array(LowCardinality(String)) raised a spurious mypy error despite working at runtime. Runtime behavior is unchanged. Closes #819.ResponseBuffer.read_uint64 no longer uses a module level scratch buffer for its big-endian byte swap, which was the one piece of shared mutable state in the C modules. Building from source now requires Cython 3.1 or later. The CI test matrix now runs the full suite on free-threaded Python 3.14t as a non-blocking job. Free-threading support remains experimental.QueryResult.query_id now returns an empty string instead of None when the server reported no query id. This matches QuerySummary and makes the property consistently typed as str.py.typed marker is included so downstream type checkers can use the package annotations. Closes #692.create_client, create_async_client, connect(), and the DB-API Connection constructor now accept None for port and database to request the driver's defaults, in addition to omitting them. Previously the public type hints exposed the internal sentinel values 0 for port and "__default__" for database, so callers had no clean way to say "not specified". None is now the documented, typed way to do that at every level. The old sentinel values are still accepted, so the change is backward compatible. Closes #801.Client.insert, AsyncClient.insert, and raw_insert now require column_names to be a Sequence rather than a bare Iterable. A one-shot iterator such as a generator already failed at runtime because the column names are measured and iterated more than once, so the type hint now matches the real requirement.This release adds structured error codes on raised exceptions, Windows ARM64 wheels, and a set of bug fixes for the DB API cursor and server-side bind
This release adds structured error codes on raised exceptions, Windows ARM64 wheels, and a set of bug fixes for the DB API cursor and server-side bind parameters.
code attribute with the ClickHouse error code and a name attribute with the symbolic name such as UNKNOWN_TABLE on DatabaseError and OperationalError. Callers can branch on exc.code instead of parsing the message string. code is set even when show_clickhouse_errors is disabled. Both default to None when unavailable, such as on transport errors. Closes #786.win_arm64 on CPython 3.10 through 3.14, including the free-threaded 3.14 build. Closes #785.IN list or a high-dimensional vector embedding no longer produces a URL that intermediaries like nginx, AWS ALB, or CloudFront reject with HTTP 414. Queries using binary parameter binds are never promoted automatically and only use form encoding when form_encode_query_params=True is set. Applies to both sync and async clients. Closes #740.Cursor.executemany no longer raises AttributeError when rows are passed as sequences instead of mappings. PEP 249 allows sequence rows, and consumers like Airflow's DbApiHook.insert_rows(executemany=True) pass tuples.uuid.UUID, IPv4Address, and IPv6Address values nested inside Array, Tuple, or Map server-side bind parameters are now quoted, fixing IN lists of UUIDs bound to {name:Array(String)} being rejected by the server. Closes #791._64 is no longer renamed when the query binds that exact name, fixing placeholders like {param_64:DateTime64(6)} being left unbound. SQLAlchemy hit this with server_side_params=True once a statement reached 64 anonymous parameters. The documented _64 convention still works as before. Closes #790.pip install --upgrade clickhouse-connect
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v1.2.0...v1.3.0
win_arm64 wheels for CPython 3.10 through 3.14, including the free-threaded 3.14 build. Closes #785.DatabaseError and OperationalError carry a numeric code attribute with the ClickHouse error code and a name attribute with the symbolic name such as UNKNOWN_TABLE, so callers can branch on exc.code instead of parsing the message string. code is set even when show_clickhouse_errors is disabled. name is only set when error detail is enabled. Both default to None when unavailable, such as on transport errors. Closes #786.Cursor.executemany no longer raises AttributeError: 'tuple' object has no attribute 'keys' when rows are passed as sequences instead of mappings. The bulk insert optimization assumed every row was a dict, but PEP 249 allows seq_of_parameters to contain sequences, and cursor.execute already accepted positional parameters. Sequence rows now use the same bulk insert path, taking column names from the INSERT statement when present. This fixes consumers like Airflow's DbApiHook.insert_rows(executemany=True), which passes tuples.IN list or a high-dimensional vector embedding could produce a URL that HTTP intermediaries such as nginx, AWS ALB, and CloudFront reject with HTTP 414. The client now routes parameters to the POST body once their encoded length passes a threshold, which keeps the URL small. Setting form_encode_query_params=True still forces form encoding for all queries. Queries using binary parameter binds are never promoted automatically and only use form encoding when the flag is set. This does not change the server's per-value size limit, which is governed by http_max_field_value_size. Applies to both sync and async clients. Closes #740.uuid.UUID, IPv4Address, and IPv6Address values nested inside Array, Tuple, or Map server-side bind parameters are now quoted, matching client-side parameter formatting. Previously they rendered unquoted, so an IN list of UUIDs bound to {name:Array(String)} (as produced by SQLAlchemy Column.in_ with server_side_params=True) was rejected by the server with Code: 26 ... cannot be parsed as Array(String). Closes #791._64 is no longer renamed when the query binds that exact name. bind_query treated any trailing _64 as a DateTime64 precision hint and stripped it before looking at the query, so a placeholder like {param_64:DateTime64(6)} was left unbound and the server rejected the query. SQLAlchemy hits this with server_side_params=True once a statement reaches 64 anonymous parameters. The suffix is now only treated as a hint when the full name does not appear as a placeholder in the query, which leaves the documented _64 convention working as before. Closes #790.This release adds opt-in server-side bind parameters for the SQLAlchemy dialect, a token_provider client option for refreshable access tokens, and cus
This release adds opt-in server-side bind parameters for the SQLAlchemy dialect, a token_provider client option for refreshable access tokens, and custom HTTP headers on client creation. It also ships a batch of SQLAlchemy reflection and type fixes along with several correctness fixes in query parameter handling and DSN parsing.
% in a DSNThe dsn passed to create_client / create_async_client now percent-decodes the username, password, and database. This lets you supply credentials that contain reserved characters in URL-encoded form, so pass%20word decodes to pass word.
If your DSN contains a literal %, you must now escape it as %25. A DSN credential like pa%ss will otherwise be misread. This is the one behavior change in this release that can affect existing setups, so check your connection strings before upgrading. A DSN with a username and no password now also sends an empty password rather than the literal string None. Closes #713.
create_engine(url, server_side_params=True). The dialect then emits ClickHouse native {name:Type} / {name:Array(Type)} placeholders instead of client-side string interpolation. Off by default. Closes #735.token_provider client option. Sync and async. It accepts a callable that returns an access token string. The callable is invoked once for the initial token and again to fetch a fresh token whenever the server rejects the current one on an authentication failure, retrying the request once. Mutually exclusive with access_token and username/password.headers. A new headers option on create_client / create_async_client attaches custom headers to every request, including the initialization queries sent during client creation. Useful for HTTP gateways that require auth headers such as Cloudflare Access service tokens.datetime bound to a server-side {name:DateTime64(...)} placeholder now keeps its sub-second precision instead of being truncated to seconds. The declared parameter type drives this, so no _64 name suffix or manual DT64Param wrapper is needed, and it applies through Array and Tuple hints. Plain DateTime binds are unchanged. Closes #739.bytes / bytearray query parameters now render as ClickHouse string literals, each byte as \xHH, instead of the Python repr. This fixes inserts into FixedString / String columns through the SQLAlchemy dialect. Closes #777.-- line comments that have no following space when classifying queries, so a DDL with a leading --sql-style comment is routed as a command instead of raising StreamFailureError. Closes #499.MetaData.reflect() and Inspector.get_multi_columns() work.UUID, IPv4 / IPv6, JSON, Nested, geometry types, and AggregateFunction now return concrete python_type classes instead of None, matching SQLAlchemy's TypeEngine.python_type contract.Array now subclasses sqlalchemy.types.ARRAY and exposes item_type.pip install clickhouse-connect
create_engine(url, server_side_params=True). The dialect then emits ClickHouse native {name:Type} / {name:Array(Type)} placeholders instead of client-side string interpolation. Off by default. Closes #735.token_provider client option (sync and async). It accepts a callable returning an access token string; the callable is invoked once for the initial token and again to fetch a fresh token whenever the server rejects the current one (authentication failure), retrying the request once. Mutually exclusive with access_token and username/password.headers option to create_client/create_async_client for attaching custom HTTP headers to every request, including the initialization queries sent during client creation. Useful for HTTP gateways that require auth headers such as Cloudflare Access service tokens.datetime bound to a server-side {name:DateTime64(...)} placeholder now keeps its sub-second precision instead of being truncated to seconds. The declared parameter type drives this, so no _64 name suffix or manual DT64Param wrapper is needed, and it applies through Array and Tuple hints. Plain DateTime binds are unchanged. Closes #739.-- line comments that have no following space when classifying queries, so a DDL with a leading --sql-style comment is routed as a command instead of raising StreamFailureError. Closes #499.MetaData.reflect() and Inspector.get_multi_columns() work.UUID, IPv4/IPv6, JSON, Nested, geometry types, AggregateFunction, etc.) now return concrete python_type classes instead of None, matching SQLAlchemy's TypeEngine.python_type contract.Array now subclasses sqlalchemy.types.ARRAY and exposes item_type.bytes/bytearray query parameters now render as ClickHouse string literals (each byte as \xHH) instead of the Python repr, fixing inserts into FixedString/String columns through the SQLAlchemy dialect. Closes #777.dsn passed to create_client/create_async_client now percent-decodes the username, password, and database, so credentials containing reserved characters can be supplied URL-encoded (pass%20word becomes pass word). A literal % in a DSN must now be written as %25. A DSN with a username and no password now sends an empty password rather than the literal string None. Closes #713.This patch release fixes a handful of async-client connectivity bugs and a SHOW ROW POLICIES query routing issue. Recommended for all users on 1.1.0,
This patch release fixes a handful of async-client connectivity bugs and a SHOW ROW POLICIES query routing issue. Recommended for all users on 1.1.0, especially those running the async client behind a proxy or against keep-alive-prone pools.
ping() now routes through the configured proxy, matching _raw_request. Previously the proxy was omitted, so ping() falsely returned False on networks where the server was only reachable via the proxy. Closes #757.query("SHOW ROW POLICIES") / query("SHOW POLICIES") by routing these non-tabular statements without appending FORMAT Native. Empty row-policy SHOW results now return "" instead of QuerySummary. Closes #761.ClientOSError or ClientConnectionResetError, fixing large async inserts on killed pooled connections. Closes #763.BrokenPipeError (in addition to ConnectionResetError), matching the async behavior.pip install clickhouse-connect
If you're upgrading from a 0.15.x or earlier release, please read MIGRATION.md for the 1.0 breaking changes first.
This is the first minor release on the 1.x line. 1.1.0 consolidates everything from the 1.1.0a1 and 1.1.0a2 alphas i.e. the Alembic/SQLAlchemy integration work and adds a handful of bug fixes on top of 1.0.1.
If you're upgrading from a 0.15.x or earlier release, please read MIGRATION.md for the 1.0 breaking changes first.
This is the first clickhouse-connect release to ship Alembic integration. SQLAlchemy and Alembic each have a large surface area, and ClickHouse has plenty of dialect-specific quirks, so this release should best understood as a focused first cut. The common autogenerate/upgrade/downgrade flows for tables, columns, engines, dictionaries, comments, and operation-level settings rather than an exhaustive port of every Alembic feature. If you hit a gap, an inconvenience, or unexpected behavior, please open an issue with a repro. Enhancement requests are equally welcome and will help prioritize what to focus on.
IF EXISTS guards, column placement with AFTER, and operation-level clickhouse_settings. ClickHouse table engines (MergeTree, ReplacingMergeTree, etc.) and dictionaries are preserved through the migration lifecycle. Install via pip install clickhouse-connect[alembic]. See clickhouse_connect/cc_sqlalchemy/alembic/WORKED_EXAMPLE.md for an end-to-end walkthrough.Dictionary type support. Reflect, create, and drop ClickHouse dictionaries through SQLAlchemy and Alembic.PREWHERE, LIMIT BY, and lambda expressions are now chainable on select() for ClickHouse-specific queries.ARRAY JOIN label preservation. ARRAY JOIN aliases now survive through compilation, fixing label-loss issues with parallel array expansion.clickhouse-sqlalchemy users. New cc_sqlalchemy.types and cc_sqlalchemy.engines import-compatible modules ease migration from clickhouse-sqlalchemy. See clickhouse_connect/cc_sqlalchemy/MIGRATING_FROM_CLICKHOUSE_SQLALCHEMY.md.aiohttp>=3.9.0. This is required to support TLS SNI override via server_host_name, because aiohttp added the per-request server_hostname option in 3.9.alembic extra now requires alembic>=1.16 (previously >=1.9), to expose the IF EXISTS / IF NOT EXISTS operation kwargs used by the integration.server_host_name now also overrides the TLS SNI / certificate hostname, matching the sync client. Previously the async path only applied it to the HTTP Host header, so connecting to host A while presenting SNI B (the 0.x pool_mgr=urllib3.PoolManager(server_hostname=...) pattern, useful for ClickHouse Cloud VPC endpoints reached via external DNS) was not expressible against the new aiohttp-based client. Closes #752.retries budget on connection-error retries in _raw_request instead of only retrying once. Previously both the sync and async clients gated network-error retries on attempts == 1, so two consecutive aiohttp.ServerDisconnectedErrors (or ConnectionResetErrors on the sync path) surfaced as OperationalError even when query_retries would have allowed another attempt. Read paths will now drain query_retries. Insert/command paths still get one retry. Sync raw_query and raw_stream, the foundation for query_arrow & query_arrow_stream now also pass query_retries so they match their async counterparts. Adds a 0.1 * attempts backoff between connection-error retries to match the 429/503/504 branch. Closes #754.quote_identifier now re-escapes inputs that start and end with ` or " but contain unescaped inner occurrences of the same quote character, instead of passing them through unchanged. Validly pre-quoted identifiers like backslash or doubled-quote escaping still pass through untouched. Closes #737.op.add_column(..., clickhouse_settings={...}) now works through the public Alembic operations API. Previously the stock Operations.add_column proxy had a fixed signature that rejected clickhouse_settings with TypeError, even though the underlying impl accepted it. Rendered AddColumnOp migrations also preserve extra ClickHouse kwargs on round-trip, so autogenerated scripts containing clickhouse_settings=... survive regeneration.CREATE TABLE / ADD COLUMN are rendered inline instead of emitting separate COMMENT ON COLUMN statements (which ClickHouse rejects). Table comments are now emitted in generated DDL, reflected for no-op autogenerate, and changed or dropped via ALTER TABLE ... MODIFY COMMENT. Inspector.get_table_comment(...) now raises NoSuchTableError for missing tables instead of silently returning {"text": None}.SETTINGS clauses. Previously settings like MergeTree(settings={"storage_policy": "hot_cold"}) or op.add_column(..., clickhouse_settings={"mutations_sync": "2"}) emitted unquoted SQL (storage_policy = hot_cold), which ClickHouse rejected. Numeric and boolean settings are unchanged.settings on reflection. build_engine() previously hardcoded engine.settings = {} even when the reflected DDL contained a SETTINGS clause, so callers reading engine.settings after reflection saw an empty dict. settings is now populated from the parsed engine kwargs, decoding ClickHouse string-literal escapes (\\, \', \n, etc.) and preserving float-valued settings as floats providing round-trip parity with the construction path.Inspector error messages from get_table_metadata() now report the resolved database name i.e. from currentDatabase() when schema was not provided instead of literal None.FINAL, SAMPLE, PREWHERE, and LIMIT BY modifiers when a select() is built from ORM-mapped attributes e.g. select(Event.id) rather than Core columns. Previously the ORM compile path rebuilt the inner Select via Select._create_raw_select, which dropped the modifier instance attributes, so the compiled SQL silently emitted no modifier. The compiler now falls back to compile_state.select_statement which is the original user-built Select to recover the modifiers. Closes #730.v1.1.0a1 (2026-05-06) - initial alembic / SQLAlchemy alphav1.1.0a2 (2026-05-07) - ORM modifier compile-path fixpip install clickhouse-connect
For Alembic integration:
pip install clickhouse-connect[alembic]
aiohttp>=3.9.0. This is required to support TLS SNI override via server_host_name, because aiohttp added the per-request server_hostname option in 3.9.alembic extra now requires alembic>=1.16 (previously >=1.9) so the documented IF EXISTS / IF NOT EXISTS Alembic operation kwargs are available.op.add_column(..., clickhouse_settings={...}) now works through the public Alembic operations API, and rendered AddColumnOp migrations preserve extra ClickHouse kwargs.CREATE TABLE / ADD COLUMN no longer emit rejected COMMENT ON COLUMN statements; table comments are now emitted in generated DDL, reflected for no-op autogenerate, and changed or dropped with ALTER TABLE ... MODIFY COMMENT.server_host_name now also overrides the TLS SNI / certificate hostname, matching the sync client. Previously the async path only applied it to the HTTP Host header, so connecting to host A while presenting SNI B (the 0.x pool_mgr=urllib3.PoolManager(server_hostname=...) pattern, useful for ClickHouse Cloud VPC endpoints reached via external DNS) was not expressible against the new aiohttp-based client. Both _raw_request and ping() now pass ssl=self._ssl_context, server_hostname=self.server_host_name per request when an SSL context is in use. Closes #752.retries budget on connection-error retries in _raw_request instead of only retrying once. Previously both the sync and async clients gated network-error retries on attempts == 1, so two consecutive aiohttp.ServerDisconnectedErrors (or ConnectionResetErrors on the sync path) surfaced as OperationalError even when query_retries would have allowed another attempt. Read paths now drain query_retries; insert/command paths still get one retry (retries=0 callers). Sync raw_query and raw_stream (the foundation for query_arrow / query_arrow_stream) now also pass query_retries so they match their async counterparts. Adds a 0.1 * attempts backoff between connection-error retries to match the 429/503/504 branch. Closes #754.quote_identifier now re-escapes inputs that start and end with ` or " but contain unescaped inner occurrences of the same quote character, instead of passing them through unchanged. Validly pre-quoted identifiers like backslash or doubled-quote escaping still pass through untouched. Closes #737.SETTINGS clauses. Previously settings like MergeTree(settings={"storage_policy": "hot_cold"}) or op.add_column(..., clickhouse_settings={"mutations_sync": "2"}) emitted unquoted SQL (storage_policy = hot_cold), which ClickHouse rejected. Numeric and boolean settings are unchanged.settings on reflection. build_engine() previously hardcoded engine.settings = {} even when the reflected DDL contained a SETTINGS clause, so callers reading engine.settings after reflection saw an empty dict. settings is now populated from the parsed engine kwargs, decoding ClickHouse string-literal escapes (\\, \', \n, etc.) and preserving float-valued settings as floats — round-trip parity with the construction path.Inspector error messages from get_table_metadata() now report the resolved database name i.e. from currentDatabase() when schema was not provided instead of literal None.Quick follow-up to `1.1.0a1`, rebased on 1.0.0rc3 so the rc3 insert-retry fix is included. Everything from 1.1.0a1 still applies. See that release for
Quick follow-up to 1.1.0a1, rebased on 1.0.0rc3 so the rc3 insert-retry fix is included. Everything from 1.1.0a1 still applies. See that release for the full preview overview.
pip install clickhouse-connect==1.1.0a2
# To use the Alembic integration:
pip install "clickhouse-connect[alembic]==1.1.0a2"
FINAL, SAMPLE, PREWHERE, and LIMIT BY were silently dropped when a select() was built from ORM-mapped attributes (e.g. select(Event.id)) rather than Core columns. The ORM compile path rebuilds the inner Select via Select._create_raw_select, which discarded the modifier instance attributes — so the compiled SQL emitted no modifier. The compiler now falls back to compile_state.select_statement to recover them. Closes #730.1.0.0rc3). Fixes intermittent Code: 62. Empty query. (SYNTAX_ERROR) on inserts when a pooled keep-alive connection is reset between attempts; the retry path now rebuilds the insert body instead of replaying an already-drained generator. Affects both sync and async clients. Closes #731.Please keep filing issues at https://github.com/ClickHouse/clickhouse-connect/issues with the alembic or sqlalchemy label.
See CHANGELOG.md for the complete entry.
Follow-up alpha to 1.1.0a1 with a fix for an ORM compile-path regression in the new ClickHouse Select modifiers, rebased on 1.0.0rc3 so the insert-retry fix from rc3 is also included.
FINAL, SAMPLE, PREWHERE, and LIMIT BY modifiers are now preserved when a select() is built from ORM-mapped attributes (e.g. select(Event.id)) rather than Core columns. Previously the ORM compile path rebuilt the inner Select via Select._create_raw_select, which dropped the modifier instance attributes, so the compiled SQL silently emitted no modifier. The compiler now falls back to compile_state.select_statement (the original user-built Select) to recover the modifiers. Closes #730.This is an alpha preview of the upcoming 1.1.0 release, published from the joe/alembic-integration-work branch for early testing of the new SQLAlchemy
This is an alpha preview of the upcoming 1.1.0 release, published from the joe/alembic-integration-work branch for early testing of the new SQLAlchemy and Alembic features. It includes everything in 1.0.0rc2 plus the additions listed below.
Pre-releases are not installed by default. Pin the exact version:
pip install clickhouse-connect==1.1.0a1
# To use the new Alembic integration:
pip install "clickhouse-connect[alembic]==1.1.0a1"
MergeTree, ReplacingMergeTree, etc.) and Dictionaries are preserved through the migration lifecycle. Supported operations include create/drop table, add/alter/drop/rename column, type and nullability changes, defaults, comments, IF EXISTS guards, column placement with AFTER, and operation-level clickhouse_settings. See WORKED_EXAMPLE.md for an end-to-end walkthrough.Dictionary type support in SQLAlchemy and Alembic.PREWHERE, LIMIT BY, and lambda expressions as Select constructs for ClickHouse-specific query shapes.ARRAY JOIN label preservation — aliases now survive through compilation, fixing label loss with parallel array expansion.clickhouse-sqlalchemy users — new cc_sqlalchemy.types and cc_sqlalchemy.engines import-compatible modules. See MIGRATING_FROM_CLICKHOUSE_SQLALCHEMY.md.This alpha exists to shake out the new SQLAlchemy/Alembic surface before it ships in 1.1.0 final. Please file issues at https://github.com/ClickHouse/clickhouse-connect/issues with the alembic or sqlalchemy label so they're easy to triage.
See CHANGELOG.md for the complete entry, plus everything inherited from 1.0.0rc2.
This is an alpha preview of the upcoming 1.1.0 release, published from the alembic integration branch for early testing. It includes everything in 1.0.0rc2 plus the items below. The new SQLAlchemy and Alembic APIs introduced here may change before 1.1.0 final.
IF EXISTS guards, column placement with AFTER, and operation-level clickhouse_settings. ClickHouse table engines (MergeTree, ReplacingMergeTree, etc.) and dictionaries are preserved through the migration lifecycle. Install via pip install clickhouse-connect[alembic]. See clickhouse_connect/cc_sqlalchemy/alembic/WORKED_EXAMPLE.md for an end-to-end walkthrough.Dictionary type support. Reflect, create, and drop ClickHouse dictionaries through SQLAlchemy and Alembic.PREWHERE, LIMIT BY, and lambda expressions are now available as chainable Select constructs for ClickHouse-specific query shapes.ARRAY JOIN label preservation. ARRAY JOIN aliases now survive through compilation, fixing label-loss issues with parallel array expansion.clickhouse-sqlalchemy users. New cc_sqlalchemy.types and cc_sqlalchemy.engines import-compatible modules ease migration from clickhouse-sqlalchemy. See clickhouse_connect/cc_sqlalchemy/MIGRATING_FROM_CLICKHOUSE_SQLALCHEMY.md.If you are upgrading from 0.15.x or earlier, see MIGRATION.md for the 1.0 breaking changes.
If you are upgrading from 0.15.x or earlier, see MIGRATION.md for the 1.0 breaking changes.
Fixed/UTC±HH:MM:SS timezones emitted by ClickHouse servers without an IANA tz database (in column types, the X-ClickHouse-Timezone header, and SELECT timezone()). Previously these raised ProgrammingError on any column read, parameter bind, or client init that touched one. The exact ±24:00:00 boundary remains rejected because Python's datetime.timezone cannot represent it. Closes #702.AsyncClient across concurrent coroutines previously raised RuntimeError: Session is closed (and related Connection reset / QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING cascades) whenever max_connection_age triggered a pool rotation while other tasks had requests in flight. close_connections() now installs the new session before retiring the old one, and waits for outstanding requests (including streaming responses) to release their lease before tearing it down. close() clears self._session so post-close calls fail with ProgrammingError instead of leaking aiohttp's RuntimeError. Closes #744.ca_cert="certifi" shorthand now resolves to certifi.where(), matching the sync client. Previously the async path passed the literal string to ssl_context.load_verify_locations, producing FileNotFoundError. Closes #742.ILIKE and NOT ILIKE expressions using native ClickHouse syntax instead of the generic SQLAlchemy lower(...) LIKE lower(...) fallback.The 1.0 release includes breaking changes accumulated across the rc1/rc2/rc3 cycle. If you are upgrading from a 0.15.x or earlier release, please read…
The first stable release of clickhouse-connect. 1.0.0 is identical in code to 1.0.0rc3. No changes have been merged since that tag.
Installation:
pip install clickhouse-connect
The 1.0 release includes breaking changes accumulated across the rc1/rc2/rc3 cycle. If you are upgrading from a 0.15.x or earlier release, please read MIGRATION.md for a guide to the changes and their replacements.
At a glance, the breaking changes are:
0.15.x is the last series supporting 3.9)pytz dependency replaced with stdlib zoneinfo (install the tzdata extra on slim Linux containers)utc_tz_aware -> tz_mode ("naive_utc" / "aware" / "schema")apply_server_timezone -> tz_source ("auto" / "server" / "local")preserve_pandas_datetime_resolution setting removed: DateTime/DateTime64 now return their natural numpy resolutionclickhouse_connect.get_async_client() (native aiohttp, installed via the async extra)1.0 includes substantial Cython read- and write-path improvements over the 0.15.x series:
DateTime/DateTime64 reads for naive UTC and UTC-equivalent timezones, via epoch arithmetic and the CPython datetime C API.memcpy.Map reads and writes, with reads avoiding the intermediate pair-tuple materialization.Decimal and BigDecimal reads, the decode path now builds values directly from the integer column via Decimal.scaleb instead of constructing intermediate strings per row.Notable fixes that shipped during the RC cycle:
Code: 62. Empty query. (SYNTAX_ERROR) on inserts when a pooled keep-alive connection is reset between attempts. (#731)ServerDisconnectedError("Server disconnected") on a pooled keep-alive connection idle-closed by the server."SharedVariant". (#712)InvalidStateError on early stream termination.text() in Inspector.get_schema_names() / get_table_names() so they work on SQLAlchemy 2.x.Bool type now accepts and forwards **kwargs, fixing ORM model use and Table.to_metadata(). (#705)CreateDatabase with engine="Replicated" now emits valid DDL.clickhouse_connect.__version__, following Python packaging conventions.numpy, pandas, pyarrow, polars) now applies to the async client as well.For the per-RC breakdown:
No code changes since 1.0.0rc3. See the 1.0.0rc1, 1.0.0rc2, and 1.0.0rc3 entries below for the full set of changes included in the 1.0.0 release.
Upgrading from a 0.15.x or earlier release? See MIGRATION.md for a guide to the breaking changes and their replacements.
If upgrading from 0.15.x, see the migration notes in rc1. All 1.0 breaking changes were introduced there. rc3 contains no new breaking changes.
This is a release candidate for clickhouse-connect 1.0.0. Please test thoroughly with your workloads and report any issues before the final release.
Installation:
pip install clickhouse-connect==1.0.0rc3
If upgrading from 0.15.x, see the migration notes in rc1. All 1.0 breaking changes were introduced there. rc3 contains no new breaking changes.
rc3 is a small bug-fix release on top of rc2. It fixes an intermittent SYNTAX_ERROR on inserts when a pooled keep-alive connection is reset between attempts.
Code: 62. Empty query. (SYNTAX_ERROR) on inserts when a pooled keep-alive connection is reset between attempts. The retry path now rebuilds the insert body instead of replaying an already-drained generator. Affects both sync and async clients. Closes #731If upgrading from 0.15.x, see the migration notes in rc1. All 1.0 breaking changes were introduced there. rc2 contains no new breaking changes.
This is a release candidate for clickhouse-connect 1.0.0. Please test thoroughly with your workloads and report any issues before the final release.
Installation:
pip install clickhouse-connect==1.0.0rc2
If upgrading from 0.15.x, see the migration notes in rc1. All 1.0 breaking changes were introduced there. rc2 contains no new breaking changes.
rc2 is primarily a performance release. There is order-of-magnitude faster DateTime/DateTime64 reads, Decimal reads, and fixed-width numpy inserts, plus significantly faster Map reads and writes.
DateTime and DateTime64 reads for naive UTC and UTC-equivalent timezones. The Cython read paths now decode via epoch arithmetic and construct datetime objects directly via the CPython datetime C API, bypassing datetime.fromtimestamp and the Python-level datetime(...) constructor. Also fixes Cython DateTime conversion bugs and expands epoch-arithmetic test coverage.Map reads and writes. The read path avoids materializing an intermediate pair tuple. The write path moves into a new Cython build_map_columns helper.write_native_col helper writes 1-D C-contiguous numpy arrays of matching dtype directly into the output buffer via memcpy, avoiding the per-element conversion the previous path required.Decimal and BigDecimal reads. The decode path no longer constructs intermediate strings per row, building values directly from the integer column via Decimal.scaleb.ServerDisconnectedError with the default "Server disconnected" message. The existing retry path covered "Connection reset" and "Remote end closed", but not the bare ServerDisconnectedError() produced by recent aiohttp versions, which surfaced as an OperationalError("Network Error: Server disconnected") on the first request after an idle period.Bool type now accepts and forwards **kwargs to the underlying SqlaBoolean constructor. SQLAlchemy's SchemaType machinery passes internal kwargs (e.g., _create_events) when copying or adapting the type during ORM model use or Table.to_metadata(), which previously raised a TypeError. Fixes #705CreateDatabase with engine="Replicated" now emits a closing ) after the (zoo_path, shard, replica) arguments, fixing previously invalid DDL on this path. The same arguments and the system.tables lookup in get_engine now go through bound parameters and the existing format_str helper instead of raw f-string interpolation.Removed the deprecated utc_tz_aware parameter entirely. Use tz_mode instead: "naive_utc" (default, was False), "aware" (was True), or "schema" (unchan…
This is a release candidate for clickhouse-connect 1.0.0. Please test thoroughly with your workloads and report any issues before the final release.
Installation:
pip install clickhouse-connect==1.0.0rc1
Python Version: Minimum Python version is now 3.10. If you're on Python 3.9, pin to clickhouse-connect<1.0 or upgrade Python.
Timezone Handling: The pytz dependency has been replaced with the standard library zoneinfo. On Windows, tzdata is pulled in automatically. On slim Linux containers, install with pip install clickhouse-connect[tzdata].
Async Client: The executor-based async client has been removed. Use clickhouse_connect.get_async_client() for a native aiohttp-based client. Install with pip install clickhouse-connect[async].
Pandas: Minimum pandas version is now 2.0.
pytz dependency in favor of the standard library zoneinfo. On Windows, tzdata is pulled in automatically. On slim Linux containers without a system tzdb, install pip install clickhouse-connect[tzdata].query_tz, column_tzs, or the server now surface zoneinfo.ZoneInfoNotFoundError internally (previously pytz.exceptions.UnknownTimeZoneError). User-visible ProgrammingError/log messages suggest the tzdata extra. Closes #714.utc_tz_aware parameter entirely. Use tz_mode instead: "naive_utc" (default, was False), "aware" (was True), or "schema" (unchanged). Closes #654, #665apply_server_timezone parameter entirely. Use tz_source instead: "auto" (default), "server" (was True), or "local" (was False).AsyncClient(client=...) constructor pattern, executor_threads, and executor parameters are no longer supported. Use clickhouse_connect.get_async_client() (or create_async_client()) which creates a native aiohttp-based async client directly. The pool_mgr parameter is also rejected on the async path. aiohttp remains an optional dependency, installed via pip install clickhouse-connect[async].AiohttpAsyncClient class has been renamed to AsyncClient and the module clickhouse_connect.driver.aiohttp_client has been removed. Import AsyncClient from clickhouse_connect.driver as before.NotSupportedError at import time. Non-pandas usage is unaffected. Closes #661preserve_pandas_datetime_resolution common setting. Datetime columns now always return their natural resolution, e.g. datetime64[s] for DateTime, datetime64[ms] for DateTime64(3), instead of coercing everything to datetime64[ns]. Closes #662"SharedVariant". ClickHouse's DataTypeVariant constructor sorts its members alphabetically by name, and discriminator bytes on the wire index into that sorted order. The client appended SharedVariant to the variant list without sorting, so affected paths were read as the wrong variant. Closes #712InvalidStateError exceptions on early stream termination. When breaking out of an async stream early, shutdown() scheduled a set_result callback for pending futures via call_soon_threadsafe, but Task.cancel() could cancel the future before the callback ran. The done-check is now deferred into the callback itself so it sees the actual future state at execution time.text() in ChClickHouseDialect.get_schema_names() and get_table_names(), so Inspector.get_schema_names() and get_table_names() work on SQLAlchemy 2.x instead of raising ObjectNotExecutableError.clickhouse_connect.__version__ (a string), following Python packaging conventions. The version remains single-sourced from clickhouse_connect/_version.py. Users can access version information via clickhouse_connect.__version__, importlib.metadata.version("clickhouse-connect"), or the clickhouse_connect.common.version() helper.numpy, pandas, pyarrow, polars) now applies to the async client as well, matching the pattern established in 0.15.0 for the sync client.generic_args parameter is now properly parsed on the async client creation path, matching the sync client behavior.copy=False parameter from Series(), concat(), and astype() calls. Updated datetime insert path to use vectorized numpy conversion instead of element-by-element nanosecond arithmetic..git-blame-ignore-revs. CI lint job no longer requires building C extensions or installing project dependencies, significantly reducing lint check time.Use timezone from parameter type hint instead of server_tz when formatting tz-aware datetimes in {param:Type} bind expressions. Fixes #697
server_tz when formatting tz-aware datetimes in {param:Type} bind expressions. Fixes #697server_tz when formatting tz-aware datetimes in {param:Type} bind expressions. Previously, bind_query always converted datetimes to the server timezone, ignoring explicit timezone declarations in type hints like DateTime64(6, 'UTC'). This caused incorrect query results when server_tz differed from the hint timezone. Handles LowCardinality, Nullable, and container type wrappers. Fixes #697Comprehensive ClickHouse JOIN support in SQLAlchemy via ch_join() with all strictness/distribution modifiers and USING syntax (#635, #636)
JOIN support in SQLAlchemy via ch_join() with all strictness/distribution modifiers and USING syntax (#635, #636)array_join() for parallel array expansion (#633)ReplicatedReplacingMergeTree, etc.) (#687)numpy, pandas, pyarrow, and polars, ~4x faster bare import time (#589).final() and .sample() silently overwriting each other when chained (#658)sqlalchemy.values() to emit ClickHouse VALUES table function syntax (#681)GraphiteMergeTree to properly quote config_section argumentpy.typed marker that was causing false type errors for mypy/pyright users (#691)Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.14.1...v0.15.0
ch_join() helper. All strictness modifiers (ALL, ANY, SEMI, ANTI, ASOF), the GLOBAL distribution modifier, and explicit CROSS JOIN are now available. Use with select_from() to generate ClickHouse-specific join syntax like GLOBAL ALL LEFT OUTER JOIN. Closes #635array_join() now supports multiple columns for parallel array expansion. Pass a list of columns and a matching list of aliases to generate ARRAY JOIN col1 AS a, col2 AS b, col3 AS c. Single-column usage is unchanged. Closes #633ch_join() now supports USING syntax via the new using parameter. Pass a list of column name strings to generate USING (col1, col2) instead of ON. This is important for FULL OUTER JOIN where USING merges the join column correctly while ON produces default values (0, '') for unmatched sides. Closes #636ReplicatedReplacingMergeTree, ReplicatedCollapsingMergeTree, ReplicatedVersionedCollapsingMergeTree, and ReplicatedGraphiteMergeTree. Closes #687import clickhouse_connect time. They are only imported when features that need them are actually used. The C/Numpy optimization bridge is also deferred. This speeds up bare import time of clickhouse-connect about 4X in environments where all four are installed. Closes #589py.typed marker file. The package does not have comprehensive type annotations, so the PEP 561 marker was causing false type errors for mypy/pyright users. Closes #691.final() and .sample() silently overwriting each other when chained. Both methods now store modifiers as custom attributes on the Select instance and render them during compilation, replacing the previous with_hint() approach that only allowed one hint per table. Chaining in either order (e.g. select(t).final().sample(0.1)) correctly produces FROM t FINAL SAMPLE 0.1. Also fixes rendering for aliased tables (FROM t AS u FINAL) and supports explicit table targeting in joins. Fixes #658sqlalchemy.values() to generate ClickHouse's VALUES table function syntax. The compiler now emits VALUES('col1 Type1, col2 Type2', ...) with the column structure as the first argument, instead of the standard SQL form that places column names after the alias. Generic SQLAlchemy types are mapped to ClickHouse equivalents (e.g. Integer to Int32, String to String). Also handles CTE usage by wrapping in SELECT * FROM VALUES(...). Fixes #681GraphiteMergeTree and ReplicatedGraphiteMergeTree to properly single-quote the config_section argument as ClickHouse requires.Fixed JSON and Dynamic column read paths to properly decode shared variant data instead of returning raw binary with discriminator byte prefixes. Clos
SELECT results so cursor.description is still populated when ClickHouse Native format returns no data blocks. This restores correct handling for empty result sets, including parameterized and limited queries. Closes #675CLICKHOUSE_CONNECT_USE_C=0 is exoplicitly set. Closes #676Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.14.0...v0.14.1
max_dynamic_paths or types exceed max_dynamic_types are now decoded from ClickHouse's binary variant encoding. Scalar types like integers, floats, strings, booleans, and nulls as well as nested objects are now fully decoded. Compound types like Array, Tuple, Map, DateTime, Date, Decimal, and UUID are not yet decoded and will be returned as raw bytes. Fixes #599, #615, and #674cursor.description is still populated when ClickHouse Native format returns no data blocks. This restores correct handling for empty result sets, including parameterized and limited queries. Closes #675driverc modules are used again unless CLICKHOUSE_CONNECT_USE_C=0 is set. Fix C/Python parity issues in streaming exception handling, FixedString string reads, nullable array helpers, and numpy conversion helpers, and expand CI and unit parity coverage to keep the optimized and pure-Python paths in sync. Addresses #676pivot in the Cython data conversion module to use tuple(zip(*...)) instead of a manual tuple-building loop which matches the pure-Python implementation and provides significant insert speedup.This release is primarily focused on preparing the path to 1.0.0. It introduces a handful of breaking changes and deprecation warnings for APIs that w…
This release is primarily focused on preparing the path to 1.0.0. It introduces a handful of breaking changes and deprecation warnings for APIs that will be removed or finalized in 1.0.0. If your code uses any of the deprecated parameters, you'll now see DeprecationWarnings with clear migration guidance and highly recommend addressing these before upgrading to 1.0.0 when it ships.
apply_server_timezone renamed to tz_source. Options are "auto" (the default), "server", or "local". The old parameter currently still works with a deprecation warning. https://github.com/ClickHouse/clickhouse-connect/pull/670utc_tz_aware renamed to tz_mode. Options are "naive_utc" (the default), "aware", or "schema". The old parameter still currently still works with a deprecation warning. https://github.com/ClickHouse/clickhouse-connect/pull/664Object('json') type. This was a legacy experimental JSON type has been removed in favor of the new JSON type in ClickHouse. https://github.com/ClickHouse/clickhouse-connect/pull/666pip install clickhouse_connect[async]==0.12.0rc1. A FutureWarning advertising this will now be emitted on creation of the (to be legacy) async client. https://github.com/ClickHouse/clickhouse-connect/pull/672SAMPLE in SQLAlchemy dialect. https://github.com/ClickHouse/clickhouse-connect/pull/656Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.13.0...v0.14.0
apply_server_timezone parameter to tz_source across Client and HttpClient. The new tz_source parameter accepts string values: "auto" (default, was None), "server" (was True or "always"), and "local" (was False). The old apply_server_timezone parameter is still accepted but emits a DeprecationWarning and will be removed in 1.0. Passing both tz_source and apply_server_timezone raises ProgrammingError. The "always" value (which had no distinct runtime behavior from True) maps to "server".utc_tz_aware parameter to tz_mode across Client, QueryContext, and all query methods. The new tz_mode parameter accepts string values: "naive_utc" (default, was False), "aware" (was True), and "schema" (unchanged). The old utc_tz_aware parameter is still accepted but emits a DeprecationWarning and will be removed in 1.0. Passing both tz_mode and utc_tz_aware raises ProgrammingError. Closes #654Object('json') type. This was the legacy experimental JSON type that has been superseded by the new JSON type in ClickHouse. Closes #556DeprecationWarning is emitted at import time for pandas 1.x users.SAMPLE clause in SQLAlchemy statements. Note: Due to a SQLAlchemy limitation, only one hint (SAMPLE or FINAL) can be applied per table; chaining both will silently ignore one. For now, this change enables use of sample(), but chaining with final() is not yet supported. Closes #634BREAKING CHANGE: Implement native write path for Variant data type with type-aware dispatching. Previously, all values inserted into a Variant column…
BREAKING CHANGE: Implement native write path for Variant data type with type-aware dispatching. Previously, all values inserted into a Variant column were stringified and sent to the server, which would store them in the String member if present, or attempt server-side conversion otherwise. Values are now serialized using their native ClickHouse types client-side (e.g. inserting 100 into Variant(Int64, String) stores Int64(100) instead of String("100")).
Key changes:
DataError instead of being stringified and
delegated to the server.typed_variant(value, 'TypeName') helper is provided for cases where automatic dispatch
cannot resolve the target type, such as when multiple variant members map to the same Python
type (e.g. Array(UInt32) vs Array(String)).Added utc_tz_aware="schema" mode which returns timezone-aware datetimes only when the server's column schema explicitly defines a timezone (e.g. DateTime('UTC')), and naive datetimes for bare DateTime columns. This matches the ClickHouse schema definition exactly. Not yet supported for Arrow-based query methods. Closes #645
Add type annotations to public API methods in Client, AsyncClient, HttpClient, and QueryResult. Ref #567
dict_add parameter typed as builtin any instead of typing.Any.UPDATE as a command so lightweight updates work correctly via client.query() and SQLAlchemy.GROUP BY now renders label aliases instead of full expressions which avoids circular reference errors when an alias shadows a source column name in ClickHouse.Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.11.0...v0.13.0
The old executor-based path still works but is now deprecated and will be removed after release candidate testing and benchmarking is completed.
This is a pre-release for testing and feedback on the new native async client built on aiohttp. Closes #141.
If you're using the sync client and have been looking for a reason to go async, this is a good opportunity to give it a shot. If you're already using AsyncClient, this is a drop-in upgrade. The API surface is identical but under the hood it's completely redesigned.
Previously, the AsyncClient was just the sync urllib3 client wrapped in a thread executor. This is a from-scratch async implementation with real async I/O, proper connection pooling, and a pipelined architecture that streams and processes response data concurrently rather than a read-then-parse pattern, providing a potentially significant performance increase depending on your workload. The old executor-based path still works but is now deprecated and will be removed after release candidate testing and benchmarking is completed.
aiohttp is now a required dependency for using the native async client, but its installation is not included by default. To install this release candidate with the required aiohttp for async use, install clickhouse_connect as follows:
pip install clickhouse-connect[async]==0.12.0rc1
import asyncio
import clickhouse_connect
async def main():
async with await clickhouse_connect.get_async_client(host="localhost") as client:
# create a test table
await client.command(
"CREATE TABLE IF NOT EXISTS test_example "
"(id UInt32, name String) "
"ENGINE MergeTree ORDER BY id"
)
# insert
data = [[1, "clickhouse"], [2, "joe"]]
await client.insert("test_example", data, column_names=["id", "name"])
# query
result = await client.query("SELECT id, name FROM test_example")
print(result.result_rows)
# cleanup
await client.command("DROP TABLE IF EXISTS test_example")
asyncio.run(main())
As you can see, the method names and signatures are the same as the sync client, you just await them. And for users of the the original async client, you don't have to change anything.
AsyncClientIf you're creating your async client like this, you're already on the new native implementation and no changes are needed:
client = await clickhouse_connect.get_async_client(host="localhost")
The only case where you'll still get the old executor-based wrapper is if you're explicitly constructing it from a sync client:
from clickhouse_connect.driver import AsyncClient
sync_client = clickhouse_connect.get_client(host="localhost")
async_client = AsyncClient(client=sync_client) # legacy path, now deprecated
If you're doing this, it will continue to work for a very short time but you'll see a DeprecationWarning. We recommend switching to get_async_client() when you get a chance so that you're using the native async client, not the deprecated executor-based client. The legacy path has been left in temporarily so users can benchmark it against the native version if they desire, as significant unexpected performance degradation would be very helpful feedback before we remove it entirely.
Please report issues on this repo with [async] in the title.
This release also includes everything from v0.11.0 release.
Add support for mid-stream exceptions. Closes #626
query_id from the client side as a UUID4 if it is not explicitly set. Closes #596cancel_http_readonly_queries_on_client_close setting for HTTP clients to ensure SELECT queries are cancelled on the server when the client disconnects. Closes #641OperationalError when ResponseSource hits network failure before any data is received. Previously, empty result would be returned. Closes #620InsertContext state was not reset on insert failure, leading to reuse errors when data was passed separately. Closes #616Etc/UCT, GMT, or other UTC-equivalent timezone names caused inconsistent behavior with utc_tz_aware=False. DateTime columns with explicit UTC timezones now correctly return naive datetimes when utc_tz_aware=False regardless of the specific UTC-equivalent timezone name returned by the server. Closes #629Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.10.0...v0.11.0
Python 3.9 EOL'd Oct 2025. Support for Python 3.9 is now softly deprecated and has been removed from our CI test matrix but
distribution wheels will continue to be built until the 1.0 release or until the builds naturally fail, whichever comes first.
A DeprecationWarning will now be displayed when initializing the client on Python 3.9. Users should plan to upgrade to
Python 3.10+ as 3.9 compatibility may break unexpectedly in future updates.
Etc/UCT, GMT, or other UTC-equivalent timezone names caused inconsistent behavior with utc_tz_aware=False. DateTime columns with explicit UTC timezones now correctly return naive datetimes when utc_tz_aware=False regardless of the specific UTC-equivalent timezone name returned by the server. Closes #629%) double encoding in SQLAlchemy string literals when using text() queries with formatDateTime and similar functions. The cursor now correctly unescapes %% back to % for non-parameterized queries. Closes #297cancel_http_readonly_queries_on_client_close setting for HTTP clients to ensure SELECT queries are cancelled on the server when the client disconnects. Closes #641Added SQLAlchemy core API support for ARRAY JOIN and FINAL modifier. Closes #579
ARRAY JOIN and FINAL modifier. Closes #579utc_tz_aware parameter to client and query methods to opt in to returning timezone-aware UTC objects for DateTime/DateTime64 columns. Default behavior remains the same and returns tz naive objects for backward compatibility.
executor parameter to AsyncClient constructor to allow passing a custom executor for async operations. This allows users to control the concurrency and thread pool used by the async client.DateTime and DateTime64 types caused by passing potentially ambiguous times to pd.DateTimeIndex constructor. Closes #585Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.9.2...v0.10.0
Updated python_requires to drop Python 3.8 and advertise support for 3.9–3.13
python_requires to drop Python 3.8 and advertise support for 3.9–3.13role as a field in the settings keyword argument to set a role for a specific queryFull Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.9.1...v0.9.2
Fixed typing issue that required numpy to be installed during clickhouse_connect import
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.9.0...v0.9.1
WARNING: BREAKING CHANGE — Removed support for sqlalchemy 1.3 which reached its EOL in 2021. The minimum required version is now 1.4.40.
read_format='native', the client will always return ipaddress.IPv6Address objects, even for IPv4-mapped addresses (e.g., "::ffff:192.168.1.1"). Previously, the client returned ipaddress.IPv4Address objects for these cases. This change enforces type consistency and avoids surprising implicit conversions. If your application requires IPv4 objects, you can explicitly convert using the ipv4_mapped attribute of IPv6Address.read_format='string', the client will always return IPv6 string representations, e.g., "::ffff:192.168.1.1" instead of "192.168.1.1", for the same reasons as above. If you require only the IPv4 string, you can parse or truncate this in your application code.query_df_arrow, query_df_arrow_stream, insert_df_arrow). This initial implementation provides basic dataframe conversion through the Arrow format, similar to how we support the pyarrow-backed pandas dataframes. Closes #111 and #542query_df_arrow(): returns a pandas DataFrame with PyArrow dtype backend. Note that Arrow data types are preserved without additional conversions.query_df_arrow_stream(): Streaming version of query_df_arrow() for processing large result sets.insert_df_arrow(): Optimized insertion method for pandas DataFrames with PyArrow backend, which should provide better performance than standard insert_df().DELETE in sqlalchemy. Closes #382SELECT/JOIN operations via SQLAlchemy's core API (table operations and explicit statements--not ORM sessions-based queries)rename_response_column (default None) that allows the user to define how response columns are automatically renamed according to a predefined scheme. Helpful for stripping alias prefixes, etc. in potentially complex queries. Closes #228send_integration_tags to False.form_encode_query_params=True when creating the client. This is particularly useful for queries with large parameter payloads that might exceed URL length limits.False) allowing pandas 2.x users to opt into (when set to True) using the additional pandas 2.x datetime64/timedelta64 resolutions of "s", "ms", "us". If set to False or using pandas 1.x, all datetime64/timedelta64 resolutions will be coerced to "ns". (See here for more info). Closes #165 and #531query_dfAsyncClient.settings typing to Optional[Dict[str, Any]] to accept None inputs.datetime.utcfromtimestamphttp.client when importing clickhouse_connect under certain circumstancesFull Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.18...v0.9.0
Fix SQLAlchemy execution error by using text() function by @lakako in https://github.com/ClickHouse/clickhouse-connect/pull/491
fetchall to only return rows from the current cursor location.fetchmany to respect size parameter.tests/unit_tests/test_driver/test_cursor.py) for testing cursor behaviorFull Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.17...v0.8.18
tests/unit_tests/test_driver/test_cursor.py) for testing cursor behaviortests/unit_tests/test_driver/test_cursor.py) for testing cursor behaviorfetchall to only return rows from the current cursor location.fetchmany to respect size parameter.Updates for 0.8.17 release by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/488
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.16...v0.8.17
transport_settings has been added to the Client query and insert methods. For the HTTP client (currently
the only option), this dictionary of string is directly translated into additional HTTP headers at a query level. This can
be used to provide additional proxy directives or other extra 'non-ClickHouse' information that is passed via headers.
Thanks to Paweł Szczur of PostHog for the original PR!https://big_proxy:8080/clickhouse). The new proxy_path get_client` argument can now be used to set that path. Closes https://github.com/ClickHouse/clickhouse-connect/issues/486Gg/update test matrix by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/464
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.15...v0.8.16
system.settings table.
Closes https://github.com/ClickHouse/clickhouse-connect/issues/469user_agent header is in ascii. Note this could lead to an incorrectly encoded os_user if the
os_user is not an Ascii string. Closes https://github.com/ClickHouse/clickhouse-connect/issues/484query_df and query_arrow and raise a more meaningful exception
if the required library is absent. Closes https://github.com/ClickHouse/clickhouse-connect/issues/477Update test_tls.py reference by @emmanuel-ferdman in https://github.com/ClickHouse/clickhouse-connect/pull/455
test_tls.py reference by @emmanuel-ferdman in https://github.com/ClickHouse/clickhouse-connect/pull/455Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.14...v0.8.15
close
function for the async client is now async to cleanly close down the pool. The recommended way to use an async client
is now within an AsyncContext. See the associated PR for details.
Thanks to ClickHouse core developer @pufit for the fix!Make all None by default arguments accept None value by @orian in https://github.com/ClickHouse/clickhouse-connect/pull/450
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.13...v0.8.14
fix: add default value for access token in HttpClient init by @lukasthalerINNIO in https://github.com/ClickHouse/clickhouse-connect/pull/448
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.12...v0.8.13
JWT auth support by @slvrtrn in https://github.com/ClickHouse/clickhouse-connect/pull/442
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.11...v0.8.12
access_token client configuration option for both sync and async clients.
The token can also be updated via the set_access_token method in the existing client instance.
NB: do not mix access token and username/password credentials in the configuration;
the client will throw an error if both are set.Add a write into file example by @slvrtrn in https://github.com/ClickHouse/clickhouse-connect/pull/437
DateTime64 type by @rnv812 in https://github.com/ClickHouse/clickhouse-connect/pull/440Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.10...v0.8.11
Update experimental JSON support by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/438
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.9...v0.8.10
Gg/release 0 8 9 by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/434
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.8...v0.8.9
Gg/fix source dist name by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/430
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.7...v0.8.8
Gg/release 0 8 7 by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/428
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.6...v0.8.7
Fix chunked streaming and wait_end_of_query bug by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/421
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.5...v0.8.6
wait_end_of_query for any streaming requests. Fixes https://github.com/ClickHouse/clickhouse-connect/issues/416Move type conversion to specific datatypes by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/415
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.4...v0.8.5
None and the column
required conversion to the numeric type (such as Python str to float). This has been fixed. Note that "mixed" Python
types in an insert data set will still throw an exception (i.e., Python strings and ints should not be combined into
the same column for insert. Closes https://github.com/ClickHouse/clickhouse-connect/issues/414Gg/release 0 8 4 by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/413
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.3...v0.8.4
INSERT INTO ... SELECT FROM ...) and send_progress_in_http_headers
is enabled to keep the HTTP connection open.Test workflow updates by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/406
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.2...v0.8.3
executor_threads argument to the get_async_client method. This controls the number of concurrent
threads that each AsyncClient has available for queries. Defaults to "number of CPU cores plus four". Closes
https://github.com/ClickHouse/clickhouse-connect/issues/407Arrow compression plus another http buffer tweak by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/404
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/V0.8.1...v0.8.2
lz4 or zstd. Closes https://github.com/ClickHouse/clickhouse-connect/issues/267.Clean up http buffer by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/402
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.8.0...V0.8.1
Include the column name in the error message for an unexpected NULL by @angusholder in https://github.com/ClickHouse/clickhouse-connect/pull/397
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.7.19...v0.8.0
{}. Other
forms of JSON data are not supportedmax_dynamic_paths number of elements (which defaults to 1024). This
will be fixed in a future release.Dynamic columns will always be the String representation of the Python value. This will be fixed
in a future release.This is the first time that a new clickhouse_connect features has been labeled "experimental", but these new
datatypes are complex and still experimental in ClickHouse server. Current test coverage for these types is also
quite limited. Please don't hesitate to report issues with the new types.
strict TLS mode, HTTPS connections require a client certificate even if that
certificate is not used for authentication. A new client parameter tls_mode='strict' can be used in this situation where
username/password authentication is being used with client certificates. Other valid values for the new tls_mode setting
are 'proxy' when TLS termination occurs at a proxy, and 'mutual' to specify mutual TLS authentication is used by
the ClickHouse server. If tls_mode is not set, and a client certificate and key are provided, mutual is assumed.SELECT FROM ... LIMIT 0 will no longer raise an exception. Closes https://github.com/ClickHouse/clickhouse-connect/issues/389.common setting
http_buffer_size. This is a fix in some cases of https://github.com/ClickHouse/clickhouse-connect/issues/399, but note that
slow processing of large queries will still cause connection and processing failures if the data cannot be buffered.DateTime64 type parameters when calling Client query methods through one of two approaches:
datetime.datetime value in the new DT64Param class, e.g. query = 'SELECT {p1:DateTime64(3)}' # Server side binding with dictionary
parameters={'p1': DT64Param(dt_value)}
query = 'SELECT %s as string, toDateTime64(%s,6) as dateTime' # Client side binding with list
parameters=['a string', DT64Param(datetime.now())]
_64 to the parameter name query = 'SELECT {p1:DateTime64(3)}, {a1:Array(DateTime(3))}' # Server side binding with dictionary
parameters={'p1_64': dt_value, 'a1_64': [dt_value1, dt_value2]}
This closes https://github.com/ClickHouse/clickhouse-connect/issues/396, see also the similar issue https://github.com/ClickHouse/clickhouse-connect/issues/212Fix insert large string by @bakwc in https://github.com/ClickHouse/clickhouse-connect/pull/388
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.7.18...v0.7.19
Don't throw exception if unable to get os_user by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/381
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.7.17...v0.7.18
client data in the HTTP User-Agent header could throw an exception. This
has been fixed (os_user will not be sent in those cases). Closes https://github.com/ClickHouse/clickhouse-connect/issues/380.Fix server_tz, add os_user by @genzgd in https://github.com/ClickHouse/clickhouse-connect/pull/378
Full Changelog: https://github.com/ClickHouse/clickhouse-connect/compare/v0.7.16...v0.7.17
send_os_user to False. Closes https://github.com/ClickHouse/clickhouse-connect/issues/371.Your coding agent can read these notes before it upgrades. Set up the MCP server →