github ClickHouse/clickhouse-connect v1.5.0

7 hours ago

clickhouse-connect 1.5.0

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.

Highlights

Features

  • Alembic support for ClickHouse-specific DDL. New runtime Alembic operations cover skip indexes, projections, table settings, materialized views, and dictionaries, including plural add/drop helpers for indexes and projections. The plural helpers emit a single comma-joined ALTER TABLE so replicated deployments avoid the Code: 517 CANNOT_ASSIGN_ALTER race. Helpers render valid SQL in offline --sql mode. #839
  • Per-query ClickHouse settings in SQLAlchemy. execution_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, #846
  • Chainable JOIN modifiers. A new Select.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. #827
  • Typed ClickHouse select. cc_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. #837
  • typed 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. #825

Bug Fixes

  • Fixed corruption of QBit 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. #866
  • The compiled Cython response buffer now decodes LEB128 varint values of 2^31 and larger correctly. Each 7-bit group was shifted in signed 32-bit arithmetic before being widened, so any varint of 2^31 or more was truncated or corrupted. This affected string and nullable string column lengths and every other varint read on the compiled path. The pure Python reader was already correct. #828
  • 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. #865
  • DB API Cursor.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. #820

SQLAlchemy Bug Fixes

  • MergeTree engine key clauses order_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. #845
  • has_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. #849
  • MATERIALIZED 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. #856
  • 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. #844
  • Wrapping a ClickHouse type in a SQLAlchemy TypeDecorator no longer raises TypeError: result_processor() takes 0 positional arguments but 2 were given when reading results. #847
  • The Alembic op.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
  • Fixed several Alembic ClickHouse DDL helper edge cases: raw SQL fragments containing :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. #839

Upgrade notes

  • QBit dimension greater than 8. Values written by earlier clients were stored incorrectly on the wire. After upgrading, re-insert any affected QBit data. #866
  • command() 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. #865

Installation

pip install clickhouse-connect

Full Changelog: v1.4.2...v1.5.0

Don't miss a new clickhouse-connect release

NewReleases is sending notifications on new releases.