FORMAT Native 流量,涵盖 query、query_np、query_df 及其块流式与行流式变体,以及包括 insert_df 在内的插入操作。Arrow 相关方法使用 FORMAT Arrow,不受影响;raw 查询、raw 插入以及非 Native 格式同样不受影响。
该编解码器目前为 Experimental 功能,需显式启用,默认仍使用 Python 编解码器。
安装
编译后的编解码器以名为clickhouse-connect-core 的独立 wheel 包形式发布,该包提供 _ch_core 扩展模块。若要进行评估,请将该编解码器与 PyArrow 一并安装:
query_np、query_df、它们的流式变体,以及 query(..., use_numpy=True)。若缺少 PyArrow,native_codec="rust" 会记录一条警告,并改用 Python 编解码器执行这些查询;而 native_codec="rust_strict" 则会引发 NotSupportedError。
对于只使用标准 Python 行结果、行块或列块流以及插入操作,而不需要 NumPy 或 Pandas 输出的应用而言,单独安装 rust extra 仍然足够轻量。这些路径不需要 PyArrow:
NotSupportedError,并在错误信息中给出此安装命令。
启用 编解码器
通过native_codec 客户端选项选择编解码器:
默认值也可通过
native_codec 通用设置或 CLICKHOUSE_CONNECT_NATIVE_CODEC 环境变量指定。优先次序为:客户端 keyword argument 最高,其次是通用设置,最后是环境变量。
对于 interface="chdb" 的客户端,该选项将被忽略,此类客户端始终使用 Python 编解码器。
何时使用 Rust 编解码器
Rust 编解码器最适用于包含文本、容器和复杂类型 (如String、LowCardinality、Map、Array、JSON、Decimal 和 UUID) 的大型 DataFrame 结果。此外,它对大型行块流和列块流、并发查询工作负载以及批量插入也有帮助。
对于小结果集以及受网络带宽制约的查询,性能提升可能并不明显。扁平的数值型结果在 Python 编解码器中已经走了高效的批量处理路径,因此收益同样有限。
对于非常宽或全数值型的结果,目前使用 Rust 编解码器时,缓冲式 query() 调用可能更慢,且峰值内存占用更高。这类工作负载建议优先使用 query_df、query_row_block_stream 或 query_column_block_stream,流式处理可将内存占用保持在可控范围内。
在采用该编解码器之前,请针对自身的工作负载进行基准测试。测量时请使用 native_codec="rust_strict",这样遇到不受支持的选项或缺失的依赖时会直接抛出异常,而不会悄悄将查询转由 Python 处理。
Fallback 规则
Fallback 决策在读取或发送任何字节之前就已完成,因此绝不会出现流中途切换编解码器的情况。对于查询,该选择发生在读取 response body 之前。对于插入,只有当所有列类型都受支持时才会选用 Rust 编码器,否则整个 insert 都在 Python 编解码器上执行。 当naive_datetime_insert="server" 生效时,rust 会将包含任何 DateTime 或 DateTime64 列的插入操作路由到 Python 编解码器,以便应用声明的列时区或服务器时区。rust_strict 则会拒绝这种组合。默认的 naive_datetime_insert="local" 模式仍使用 Rust 编码器。
driver 内部的 metadata 查询,包括 SQLAlchemy dialect 的 reflection 语句,在所有模式下都会静默使用 Python 编解码器。
Rust 编解码器检测到格式错误的 Native payloads 时会引发 DataError。
Versioning
clickhouse-connect-core 的版本与 clickhouse-connect 相互独立。驱动通过 rust extra 声明兼容的版本范围,模块则导出一个 binding API 版本号;当选择 Rust 编解码器时,驱动会对该版本号进行校验。如果已安装的 wheel 对当前驱动而言版本过旧,创建 client 时会引发 NotSupportedError,并在其中给出升级命令:
clickhouse-connect-core 发行版一同发布,只需将 core wheel 升级到兼容版本即可获取。而对驱动的 Rust 集成所做的更改,则需要升级 clickhouse-connect。
已知行为差异
Rust 编解码器的目标是与 Python 编解码器逐单元保持一致。目前已知存在以下差异。- 对于
Variant列,query_np和query_df返回的结果是普通 Python 对象,而非 numpy 标量值。值相同,但单元类型不同。 - 包含
Time64的Dynamic值在 Rust 的query_np和query_df结果中会物化为datetime.timedelta。在标度为 0、3、6 和 9 时,这些值与 Python 编解码器的numpy.timedelta64单元相等,但单元类型不同;在其他标度下,Rust 编解码器返回datetime.timedelta,而 Python 编解码器会引发ProgrammingError,因为 NumPy 没有匹配的单位。Rust 解码后不会向驱动暴露 Dynamic 成员元数据,因此当需要 NumPy 单元类型或对不受支持的标度进行校验时,请使用native_codec="python"。 - 对于
query_df,Python 编解码器可能会将存储在 JSON shared data 中的复合类型值转为字符串。Rust 编解码器返回解码后的对象,与两种编解码器的query_np结果一致。 - 若
LowCardinality备选类型位于按单元物化的容器中 (例如Array(Variant(...))) ,生成的单元值虽然相等,但不会像 Python 编解码器那样共享每个字典 slot 的对象标识。 - 含有一个或多个 elements 的
Nullable(Tuple(...))列在 Rust 编解码器上可正确解码。Python 编解码器会误读这种 layout,因此应以 Rust 的结果为准。两种编解码器都支持Nullable(Tuple())。 - 对于 Rust 路径未实现的查询选项 (例如按查询自定义的
query_formats) ,rust_strict会直接拒绝,而不会悄然改变行为。 - Rust 的插入转换和校验错误可能引发
DataError,而 Python 编解码器引发的是ValueError,且消息文本可能不同。例如无效的Time和Time64值、IPv6 地址、QBit dimensions、FixedString长度、Float64字符串,以及尝试向Tuple()列插入 elements 等情况。 - Rust 编码器会拒绝用于
FixedString(N)的b"",也会拒绝用于整数列的数字字符串 (如"2") 。而 Python 编解码器会对空的FixedString值做零填充,并对数字字符串进行强制转换。 - 对于某些
Dynamicshared variant 值,Python 编解码器会将其保留为 raw bytes,而 Rust 编解码器会将其解码为对应的 Python 类型。例如,存储的Date在 Rust 中返回datetime.date,在 Python 中则返回二进制值。