FORMAT Native traffic, which covers query, query_np, query_df, their block and row streaming variants, and inserts including insert_df. The Arrow methods use FORMAT Arrow and are unaffected, as are raw queries, raw inserts, and non-Native formats.
The codec is experimental and opt in. The Python codec remains the default.
Installation
The compiled codec ships as a separate wheel namedclickhouse-connect-core, which provides the _ch_core extension module. For evaluation, install the codec and PyArrow together:
query_np, query_df, their streaming variants, and query(..., use_numpy=True). Without PyArrow, native_codec="rust" logs a warning and runs those queries with the Python codec. native_codec="rust_strict" raises NotSupportedError instead.
The rust extra alone stays lean for applications that use standard Python row results, row or column block streams, and inserts without requesting NumPy or Pandas output. Those paths do not require PyArrow:
NotSupportedError naming this install command.
Enabling the codec
Select the codec with thenative_codec client option:
The default can also be seeded with the
native_codec common setting or the CLICKHOUSE_CONNECT_NATIVE_CODEC environment variable. Precedence is the client keyword argument, then the common setting, then the environment variable.
The option is ignored for interface="chdb" clients, which always use the Python codec.
When to use the Rust codec
The Rust codec is most useful for large DataFrame results with text, container, and complex types such asString, LowCardinality, Map, Array, JSON, Decimal, and UUID. It can also help large row and column block streams, concurrent query workloads, and bulk inserts.
Small results and network-bound queries may see little change. Flat numeric results already use efficient bulk paths in the Python codec, so they may also see less benefit.
Buffered query() calls on very wide or all-numeric results can currently be slower and use more peak memory with the Rust codec. Prefer query_df, query_row_block_stream, or query_column_block_stream for those workloads. Streaming keeps memory bounded.
Benchmark your own workload before adopting the codec. Use native_codec="rust_strict" while measuring so an unsupported option or missing dependency raises instead of silently routing the query to Python.
Fallback rules
Fallback decisions are made before any bytes are read or sent, so there is never a mid-stream codec switch. For queries the choice happens before the response body is consumed. For inserts the Rust encoder is only selected when every column type is supported, otherwise the whole insert runs on the Python codec. Whennaive_datetime_insert="server" is active, rust routes an insert containing any DateTime or DateTime64 column to the Python codec so the declared column timezone or server timezone is applied. rust_strict rejects that combination. The default naive_datetime_insert="local" mode continues to use the Rust encoder.
Driver-internal metadata queries, including SQLAlchemy dialect reflection statements, always use the Python codec, silently, in every mode.
Malformed Native payloads detected by the Rust codec raise DataError.
Versioning
clickhouse-connect-core versions independently of clickhouse-connect. The driver declares a compatible range through the rust extra, and the module exports a binding API version that the driver checks when a Rust codec is selected. If the installed wheel is too old for the driver, client creation raises a NotSupportedError naming the upgrade command:
clickhouse-connect-core releases and can be picked up with a wheel upgrade alone, without waiting for a clickhouse-connect release.
Known behavior differences
The Rust codec targets cell for cell parity with the Python codec. The following differences are known.query_npandquery_dfresults forVariantcolumns contain plain Python objects rather than numpy scalar values. The values are equal, the cell types differ.Dynamicvalues that containTime64materialize asdatetime.timedeltain Rustquery_npandquery_dfresults. At scales 0, 3, 6, and 9 the values equal the Python codec’snumpy.timedelta64cells, but the cell types differ. At other scales the Rust codec returnsdatetime.timedeltawhile the Python codec raisesProgrammingErrorbecause NumPy has no matching unit. Dynamic member metadata is not exposed to the driver after Rust decoding, so usenative_codec="python"when NumPy cell types or unsupported-scale validation are required.- For
query_df, the Python codec may stringify compound values stored in JSON shared data. The Rust codec returns decoded objects, matching both codecs’query_npresults. - A
LowCardinalityalternative inside a container that materializes per cell, such asArray(Variant(...)), produces value-equal cells that do not share the per-dictionary-slot object identity the Python codec exhibits. Nullable(Tuple(...))columns with one or more elements decode correctly on the Rust codec. The Python codec misreads this layout and the Rust result is the reference behavior. Both codecs supportNullable(Tuple()).rust_strictrejects query options the Rust path does not implement, such as custom per-queryquery_formats, rather than silently changing behavior.- Rust insert conversion and validation errors can raise
DataErrorwhere the Python codec raisesValueError, and the message text can differ. Examples include invalidTimeandTime64values, IPv6 addresses, QBit dimensions,FixedStringlengths,Float64strings, and attempts to insert elements into aTuple()column. - The Rust encoder rejects
b""forFixedString(N)and numeric strings such as"2"for integer columns. The Python codec zero-fills an emptyFixedStringvalue and coerces numeric strings. - The Rust codec decodes some
Dynamicshared-variant values to their Python types when the Python codec leaves the value as raw bytes. For example, a storedDatecan return asdatetime.datefrom Rust and as a binary value from Python.