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 TABLEso replicated deployments avoid theCode: 517 CANNOT_ASSIGN_ALTERrace. Helpers render valid SQL in offline--sqlmode. #839 - Per-query ClickHouse settings in SQLAlchemy.
execution_options(settings={...})now forwards per-query settings through the dialect and DB-API cursor for Core andtext()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 asselectinloadand 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 existingch_join()factory is unchanged. #827 - Typed ClickHouse select.
cc_sqlalchemy.select()returns aClickHouseSelectexposingch_join,final,sample,array_join,prewhere, andlimit_byas typed methods, so static type checkers accept them without suppressions. The standardsqlalchemy.select()path is unchanged. #837 typedread format forVariantcolumns. When two members of aVariantshare a Python type, such asVariant(Float32, Float64), reading with thetypedformat wraps each value as aTypedVariantcarrying both the value and itstype_name, and these feed straight back into inserts. Enable per query withquery_formats={'Variant': 'typed'}or globally withset_read_format('Variant', 'typed'). The defaultnativeformat is unchanged. #825
Bug Fixes
- Fixed corruption of
QBitcolumns 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 truthyQuerySummarythat madeif result:misleading. #865- DB API
Cursor.executemanyno longer falls off the bulk-insert fast path when an INSERT names backtick-quoted dotted columns such as the wire form ofNestedsub-columns.unescape_identifiernow removes backtick quoting from compound identifiers correctly, which previously degraded the operation to slow per-row execution and could raiseProgrammingErrorwith dict rows and pyformat placeholders. #820
SQLAlchemy Bug Fixes
- MergeTree engine key clauses
order_by,partition_by,primary_key,sample_by, andttlnow accept arbitrary SQL expressions such ascol.desc(),func.cityHash64(a, b),tuple_(...), and interval TTL expressions, in scalar and list forms. Expression engines round-trip throughrepr()for Alembic autogeneration. #845 has_database()now usesEXISTS DATABASEinstead of queryingsystem.databases. On servers from 25.10 through 26.4,system.databasesomitted DataLakeCatalog and other remote databases by default, sohas_database()reportedFalsefor databases that actually exist. #849MATERIALIZEDandALIAScolumns now keep theircomment,codec, andttloptions in generated DDL, and column clauses are emitted in the order ClickHouse requires,COMMENTthenCODECthenTTL. This also fixes a pre-existing case where a column combining a codec with a comment produced invalid SQL. #856ClickHouseSelectnow keeps its typed ClickHouse chainables after column-shape methods such asadd_columns(),with_only_columns(),column(), andreduce_columns().cc_sqlalchemy.select()also works on SQLAlchemy 1.4. #844- Wrapping a ClickHouse type in a SQLAlchemy
TypeDecoratorno longer raisesTypeError: result_processor() takes 0 positional arguments but 2 were givenwhen reading results. #847 - The Alembic
op.rename_tablenow emitsRENAME TABLE old TO newinstead of theALTER TABLE old RENAME TO newform ClickHouse rejects. Standard SQLAlchemy indexes are filtered from ClickHouse autogenerate output, andColumn(index=True),Index(...),op.create_index, andop.drop_indexraise a clear Alembic error before partially applying DDL. Useop.add_clickhouse_indexandop.drop_clickhouse_indexfor data-skipping indexes. #839 - Fixed several Alembic ClickHouse DDL helper edge cases: raw SQL fragments containing
:nameare no longer parsed as bind parameters, dictionary comments escape backslashes correctly, explicit schemas are honored for legal dotted table names,CREATE MATERIALIZED VIEWno longer accepts a misleadingclickhouse_settingssuffix, and custom ClickHouse operation objects render through autogenerate instead of raisingValueError. #839
Upgrade notes
QBitdimension greater than 8. Values written by earlier clients were stored incorrectly on the wire. After upgrading, re-insert any affectedQBitdata. #866command()return value. A read that returns no rows now yields an empty string rather than a truthyQuerySummary. Code that relied oncommand()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