DeepSeek总结的pg_clickhouse 与 chdb 更新:编码、嵌套与类型

pg_clickhouse 与 chdb 更新:编码、嵌套与类型

发布日期:2026-09-30T16:29:40.227Z

作者:David Wheeler

分类:产品

摘要:最新的 pg_clickhouse 和 chdb 扩展版本改进了 ClickHouse 与 Postgres 之间的字符编码、间隔处理、嵌套数据、JSON 和类型映射。


pg_clickhouse 与 chdb 更新:编码、嵌套与类型

现已在 GitHub 和 PGXN 上发布,pg_clickhouse v0.11.0 和 chdb 扩展 v0.1.2 继续我们坚定不移地专注于跨数据库兼容性。其中大量增强源自我们的仅头文件 C 库 clickhouse-c 和 pg-clickhouse-c。让我们看看这些版本中仅有的三项更改。

好一个字符 {#what_a_character}

首先是字符编码。在开发 chdb 扩展文章的基准测试过程中,我发现 pg-clickhouse-c 没有验证文本列的字符编码。我的同事 Philip 迅速修补了该库,使其在任何基于文本或 JSON 的类型[1](#1)包含违反数据库编码的字节时抛出异常。

此修复随 chdb v0.1.1 发布,但我们推迟了 pg_clickhouse 的发布,以避免对任何拥有读取无效编码数据的现有外部表的用户造成错误。pg_clickhouse v0.11.0 添加了一个新的外部服务器选项 check_encoding,它提供编码错误处理器。选项包括:

  • fail(默认):抛出错误
  • remove:移除无效字节
  • replace:在 UTF-8 编码下,将无效字节替换为 Unicode 替换字符(�);对于其他编码,等同于 remove
  • truncate:在第一个无效字节处截断文本

chdb_hook v0.1.2 为其 COPY 和 CREATE TABLE 命令提供了相同的选项。两者都允许你解决类似以下的错误:

shell 复制代码
ERROR:  invalid byte sequence for encoding "UTF8": 0x81

更改 pg_clickhouse 服务器配置 check_encoding 以消除错误。最易读的将是 replace:

复制代码
ALTER SERVER ch_server_name OPTIONS (ADD check_encoding 'replace');

对于 chdb_hook,将其作为 COPY 或 CREATE TABLE 选项传入:

复制代码
CREATE TABLE logs () WITH (
    copy_from      = 's3://chdb-lakedata-public/logs/logs-2026-08-26.csv',
    format         = 'CSVWithNames',
    check_encoding = 'replace'
);

对于 UTF-8 编码的数据库,无效字节将被替换为 �:

shell 复制代码
try=# SELECT * FROM ch_table ORDER BY id;
 id |   name
----+---------
  1 | Barrack
  2 | Ale�y
  3 | Leopold
  4 | An�n�e

对于其他数据库编码,有问题的字符将被直接移除:

shell 复制代码
try=# SELECT * FROM ch_table ORDER BY id;
 id |   name
----+---------
  1 | Barrack
  2 | Aley
  3 | Leopold
  4 | Ann

另一方面,如果你需要保留字节兼容性,则需要将有问题的列映射为 bytea:

复制代码
ALTER FOREIGN TABLE ch_table ALTER name TYPE bytea;

这将保留逐字节的二进制数据:

shell 复制代码
try=# SELECT * FROM ch_table ORDER BY id;
id |      name
----+------------------
  1 | \x4261727261636b
  2 | \x416c650079
  3 | \x4c656f706f6c64
  4 | \x416e006e8165
(4 rows)

但请注意,转换为文本将会失败。

间隔有效 {#intervalid}

ClickHouse 支持众多间隔类型:IntervalNanosecond、IntervalHour、IntervalDay、IntervalYear,以及介于两者之间的所有类型。在以前的版本中,pg_clickhouse 不支持这些类型;尝试导入使用其中一种类型的 ClickHouse 表会返回错误。

不再如此。pg_clickhouse v0.11.0 和 chdb_hook 0.1.2 将这些类型导入为 Postgres interval 列。因此,给定一个使用例如 IntervalMillisecond 的 ClickHouse 表,如下面 duration 列所示:

复制代码
CREATE TABLE logs (
    req_id    Int64                NOT NULL,
    start_at  DateTime64(6, 'UTC') NOT NULL,
    duration  IntervalMillisecond  NOT NULL,
    resource  Text                 NOT NULL,
    method    Enum8('GET' = 1, 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH') NOT NULL,
    node_id   Int64                NOT NULL,
    response  Int32                NOT NULL
) ENGINE = MergeTree
  ORDER BY start_at;

导入时,pg_clickhouse 会创建一个带有 duration interval 列的表:

列 类型 可空
req_id bigint not null
start_at timestamp(6) with time zone not null
duration interval not null
resource text not null
method text not null
node_id bigint not null
response integer not null

当然,下推也有效。假设你想统计昨天一天结束前完成的所有事务。只需将持续时间加到开始时间:

shell 复制代码
try=# EXPLAIN (VERBOSE, COSTS OFF)
 SELECT COUNT(*)
   FROM logs
  WHERE start_at + duration < date_trunc('day', now());
                                                QUERY PLAN
-----------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(*))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT count(*) FROM "default".logs WHERE (((start_at + duration) < toStartOfDay(now64())))
(4 rows)

EXPLAIN (VERBOSE) 输出显示了在 ClickHouse 上执行的远程查询,它显然将 start_at + duration 下推到 ClickHouse 中执行(当然,还有 COUNT() 聚合[2](#2))。

同样的模式适用于 chdb_hook v0.1.2:它将 chDB 间隔类型导入为 Postgres interval 值。两个扩展也都允许将间隔类型改为导入为 bigint。只需创建带有 duration bigint 的外部表或复制目标表,扩展会完成其余工作。

嵌套本能 {#nesting_instinct}

pg_clickhouse 的 http 驱动自 v0.1 起就支持 JSON 类型,二进制驱动自 v0.3 起也支持。然而,尽管它会下推 JSON 属性访问器,例如:

复制代码
SELECT * FROM things ORDER BY data ->> 'name';

ClickHouse 会返回错误:

shell 复制代码
DB::Exception: Data types Variant/Dynamic are not allowed in ORDER BY keys, because it can lead to unexpected results.
Consider using a subcolumn with a specific data type instead

此错误源于 ClickHouse JSON 对象的实现:ClickHouse 想要知道 JSON 属性存在才能排序。ClickHouse 25.3+ 参数化子列保证了属性存在。一个示例:

复制代码
CREATE TABLE things (
    id    Int32 NOT NULL,
    data  JSON(
      id      UInt32,
      name    String,
      size    Enum('small', 'medium', 'large'),
      stocked Bool
    ) NOT NULL
) ENGINE = MergeTree PARTITION BY id ORDER BY (id);

以前,pg_clickhouse 无法导入参数化 JSON 列,但 v0.11.0(以及 chdb_hook v0.1.2)简单地将其映射为 jsonb(或 json),现在对属性的 ORDER BY 可以正确下推:

shell 复制代码
try=# SELECT * FROM things ORDER BY data ->> 'name';
 id |                              data
----+-----------------------------------------------------------------
  4 | {"id": 4, "name": "doodad", "size": "large", "stocked": false}
  3 | {"id": 3, "name": "gizmo", "size": "medium", "stocked": true}
  2 | {"id": 2, "name": "sprocket", "size": "small", "stocked": true}
  1 | {"id": 1, "name": "widget", "size": "large", "stocked": true}
(4 rows)

类似地,pg_clickhouse v0.11.0 和 chdb_hook v0.1.2 改进了对未展平 Nested 类型的支持,如下例所示:

复制代码
CREATE TABLE visits(
    visit_id  UInt64,
    user_id   UInt64,
    goals     Nested(
        serial    UInt32,
        order_id  String
    )
) ENGINE = MergeTree ORDER BY visit_id SETTINGS flatten_nested = 0;

flatten_nested=0 指示 ClickHouse 创建一个单一的 goals 列,格式为 Tuple(serial UInt32, order_id String) 的数组(而不是为每个字段单独的数组列)。以前,两个扩展都不支持这种结构。现在它们提供两种映射。

默认情况下,IMPORT FOREIGN SCHEMA 和 chdb_hook 的 COPY 与 CREATE TABLE 命令将未展平的 Nested 列映射为二维文本数组:

列 类型
visit_id numeric(20,0)
user_id numeric(20,0)
goals text\[\]\[\]

这会将 Nested 值中的每一项映射为每种类型文本表示的数组:

shell 复制代码
try=# SELECT * FROM nest_bin.visits WHERE visit_id < 3 ORDER BY visit_id;
 visit_id | user_id |      goals      
----------+---------+-----------------
        1 |       1 | {{1,xx},{2,yy}}
(1 row)

goals 数组包含两个数组,每个数组有两个文本值,第一个用于 serial,第二个用于 order_id。这种结构以牺牲数据类型为代价保留了数据,不过在 pg_clickhouse 表上执行 INSERT 会在插入 ClickHouse 之前正确转换类型:

复制代码
INSERT INTO visits
VALUES (2, 2, ARRAY[ ['3', 'aa'], ['4', 'bb'] ]);

但我们可以做得更好。pg_clickhouse v0.11.0 还允许将 Nested 值映射到自定义复合类型,只要顺序、类型和命名完全一致。给定为 goals 定义的 Nested 类型:

shell 复制代码
Tuple(serial UInt32, order_id String)

我们可以创建一个具有对应名称和类型的类型,并将其放入外部表:

复制代码
CREATE TYPE goal_type AS (serial bigint, order_id text);
ALTER FOREIGN TABLE visits ALTER goals TYPE goal_type[];

现在 Nested 元组转换为复合类型:

shell 复制代码
try=# SELECT * FROM nest_bin.visits WHERE visit_id < 3 ORDER BY visit_id;
 visit_id | user_id |        goals        
----------+---------+---------------------
        1 |       1 | {"(1,xx)","(2,yy)"}
        2 |       2 | {"(3,aa)","(4,bb)"}

当然我们也可以按此格式 INSERT 数据:

复制代码
INSERT INTO visits
VALUES (3, 3, ARRAY[row(5, 'jj'), row(6, 'zz')]::goal_type[]);

同样的模式适用于 chdb_hook v0.1.2:在处理从 ClickHouse 或 chDB 导出的嵌套数据时,Postgres 目标表可以使用多维值数组或适当结构的复合类型数组:

复制代码
CREATE TYPE event_status AS ENUM ('new', 'done');
CREATE TYPE event_point AS (x integer, y integer);
CREATE TYPE event_label AS (key text, value bigint);
CREATE TYPE event_item AS (id integer, name text);

CREATE TABLE events (
    status event_status,
    point  event_point,
    labels event_label[],
    items  event_item[]
);

然后在 COPY 查询中使用相应的数据类型定义(或依赖某个 *WithNamesAndTypes 格式)来导入数据。

复制代码
COPY events FROM 's3://chdb-lakedata-public/examples/events.parquet' (
    structure $$
        status Enum8('new' = 1, 'done' = 2),
        point  Tuple(Int32, Int32),
        labels Map(String, Int64),
        items  Array(Tuple(id Int32, name String))
    $$
);

这里我们为嵌套类型使用了 Array();如果数据是从 ClickHouse 以未展平(flatten_nested=0)结构导出的,你可以改用 Nested:

复制代码
COPY events FROM 's3://chdb-lakedata-public/examples/events.parquet' (
    structure $$
        status Enum8('new' = 1, 'done' = 2),
        point  Tuple(Int32, Int32),
        labels Map(String, Int64),
        items  Nested(id Int32, name String)
    $$
);

零碎事项 {#odds_and_ends}

pg_clickhouse v0.11.0 还附带了许多其他值得一提的改进:

  • 眼尖的读者无疑已经注意到,除了间隔映射之外,大整数类型现在映射到适当的 Postgres numeric,许多其他 ClickHouse 数据类型现在也映射到适当的 Postgres 对应类型:

    ClickHouse PostgreSQL
    Int128 numeric(39,0)
    Int256 numeric(77,0)
    UInt64 numeric(20,0)
    UInt128 numeric(39,0)
    UInt256 numeric(78,0)
    BFloat16 float4
    Time time
    Time64§ time
    Tuple(...) text\[\]
    Map(K,V) text\[\]\[\]
    LineString path
    MultiLineString path\[\]
    MultiPolygon polygon\[\]\[\]
    Point point
    Ring polygon
    Polygon polygon\[\]

    相同的映射适用于 chdb 扩展 v0.1.2。

  • 最初的 clickhouse_raw_query() 函数已在 v0.10.0 中弃用,现已被移除。请更新你的代码,使用 clickhouse_query(server, sql) 读取行,使用 CALL clickhouse_perform(server, sql) 运行不返回结果的语句。

  • 此版本放弃了对 PostgreSQL 13 的支持,该版本自 2025 年 9 月起已不再受 Postgres 社区支持。

  • 一项社区贡献添加了对 PostgreSQL sha224()、sha256()、sha384() 和 sha512() 函数的下推支持,以及对 pgcrypto 扩展 digest() 函数支持的常量算法调用。

查看完整的 pg_clickhouse 更改和 chdb 更改以了解更多细节,包括错误修复。然后从通常的地方获取它们。对于 pg_clickhouse:

对于 chdb 扩展:


立即开始使用 ClickHouse Managed Postgres

有兴趣了解 ClickHouse Managed Postgres 如何在你的数据上运作吗?几分钟内开始使用 ClickHouse Cloud,并获得 300 美元免费额度。

注册



  1. 是的,JSON 按定义偏好 UTF-8,除非它并非如此。Postgres 中的 JSON 数据必须始终使用数据库编码。 ↩︎

  2. 不幸的是,ClickHouse 间隔类型本身尚不支持聚合,因此例如 avg(duration) 会失败。但请关注 26.10 中的 avg 和 sum 支持。 ↩︎

相关推荐
傲世仙尊1 小时前
从HTTPS到MySQL-会话保持的攻防与数据库的真面目
数据库·mysql·https
jyOverQ1 小时前
MySQL 崩溃以后怎么恢复数据?redo log、undo log 与 Crash Recovery
数据库·mysql
盟道科技1 小时前
电商小程序购物车系统设计:缓存与数据库双写一致性实战
数据库·缓存·小程序
java1234_小锋1 小时前
【技术专题】Mysql8 数据库 - Mysql8 客户端sqlyog安装以及配置
数据库·sqlyog
奇牙coding1 小时前
Codex C接 配置教程:的 字段从迁移时必须写完整后缀,填旧值或省略 会静默回退默认模型
java·c语言·数据库·ai
Nturmoils1 小时前
别急着 JOIN,子查询有些场景更顺手
数据库
喜欢的名字被抢了1 小时前
09-Redis 进阶原理篇:单线程、多线程、过期、LRU-LFU、Fork 与 Lua
数据库·redis·lua
Omics Pro2 小时前
计算虚拟扰动:网络毒理+虚拟敲除
数据库·人工智能·算法·机器学习·自然语言处理
白帽攻防录10 小时前
SRC 挖洞:Roundcube 预认证 SQL 注入深度复盘,CVE-2026-48842 preg_replace 转义绕过怎么打穿邮件系统
网络·数据库·sql·网络安全·sql注入