Skip to content

pg_clickhouse と chdb のアップデート: エンコーディング、ネスト、データ型

image 512x512 1
2026年9月30日 · 14分で読む

GitHub および PGXN にて公開された pg_clickhouse v0.11.0 と chdb extension v0.1.2 は、異種データベース間の互換性に対する私たちの徹底した注力をさらに推し進めるものです。こうした機能強化の多くは、ヘッダーオンリーの C ライブラリである clickhouse-c および pg-clickhouse-c に由来しています。今回のリリースに含まれる変更点から、注目の 3 つをご紹介します。

文字エンコーディングへの対応

まずは文字エンコーディングです。chdb 拡張機能の紹介記事向けのベンチマークを開発している過程で、pg-clickhouse-c がテキストカラムの文字エンコーディングを検証していないことに気付きました。同僚の Philip がすぐさまライブラリにパッチを適用し、テキストベースや JSON ベース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 コマンドに同様のオプションが用意されています。どちらの拡張機能でも、以下のようなエラーへの対処が可能です。

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 エンコーディングのデータベースでは、不正なバイトが `` に置き換えられます。

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

他のデータベースエンコーディングでは、問題のある文字は単に削除されます。

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;

これにより、バイナリデータがバイト単位でそのまま保持されます。

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

ただし、テキストへの変換は失敗することにご注意ください。

インターバル型のサポート

ClickHouse は、IntervalNanosecond、IntervalHour、IntervalDay、IntervalYear など、多彩な インターバル型 をサポートしています。以前のリリースでは pg_clickhouse がこれらの型をサポートしておらず、それらを使用している ClickHouse テーブルを インポート しようとするとエラーが返されていました。

今後はその心配はありません。pg_clickhouse v0.11.0 および chdb_hook 0.1.2 では、これらの型が Postgres の interval カラムとしてインポートされます。たとえば、以下の duration カラムのように IntervalMillisecond を使用している ClickHouse テーブルがあるとします。

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 カラムを持つテーブルを作成します。

カラム型Null 許容
req_idbigintnot null
start_attimestamp(6) with time zonenot null
durationintervalnot null
resourcetextnot null
methodtextnot null
node_idbigintnot null
responseintegernot null

もちろんプッシュダウンも機能します。たとえば、昨日の終わりまでに「完了した」すべてのトランザクションをカウントしたいとしましょう。開始時刻に duration を加算するだけです。

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 上で実行されるリモートクエリが表示されており、ClickHouse 側で実行されるよう start_at + duration が明白にプッシュダウンされていることが分かります(当然ながら COUNT() 集約2も同様です)。

同じパターンが chdb_hook v0.1.2 にも適用され、chDB の インターバル型 を Postgres の interval 値としてインポートします。両拡張機能とも、インターバル型を代わりに bigint としてインポートすることも可能です。単に外部テーブルまたはコピー先テーブルを duration bigint で作成するだけで、あとは拡張機能が処理します。

ネスト構造のサポート強化

pg_clickhouse の HTTP ドライバーは v0.1 から、バイナリドライバーは v0.3 から JSON 型 をサポートしています。しかし、以下のように JSON プロパティアクセサーをプッシュダウンすることはできても、

SELECT * FROM things ORDER BY data ->> 'name';

ClickHouse 側でエラーが返されていました。

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 が適切にプッシュダウンされるようになりました。

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 は、(フィールドごとに別々の配列カラムを作成するのではなく)Tuple(serial UInt32, order_id String) の配列としてフォーマットされた単一の goals カラムを作成するよう ClickHouse に指示します。従来はどちらの拡張機能もこの構造をサポートしていませんでした。現在では 2 つのマッピング方法が提供されています。

デフォルトでは、IMPORT FOREIGN SCHEMA ならびに chdb_hook の COPY および CREATE TABLE コマンドは、フラット化されていない Nested カラムを 2 次元のテキスト配列にマッピングします。

カラム型
visit_idnumeric(20,0)
user_idnumeric(20,0)
goalstext[][]

これにより、Nested 値の各要素が、各型の文字列表現の配列にマッピングされます。

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 配列には 2 つのテキスト値を持つ配列が 2 つ含まれており、1 つ目が serial、2 つ目が order_id に対応します。この構造はデータ型を犠牲にしてデータを保持しますが、pg_clickhouse テーブルへの INSERT では、ClickHouse へ挿入する前に型が適切に変換されます。

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

しかし、より良い方法があります。pg_clickhouse v0.11.0 では、順序、型、名前が完全に一致している限り、Nested 値をカスタムの 複合型 (composite type) にマッピングすることも可能です。goals に定義された Nested 型を例にとると、

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 タプルが複合型へと変換されるようになります。

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() を使用しました。データがフラット化されていない(flatten_nested=0)構造で ClickHouse からエクスポートされている場合は、代わりに 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)
    $$
);

その他の更新内容

pg_clickhouse v0.11.0 には、言及しておく価値のある改善がほかにも多数含まれています。

  • 目ざとい読者の方ならすでにお気づきのことでしょう。インターバルのマッピング に加え、大きな整数型が適切な Postgres の数値型にマッピングされるようになり、その他多くの ClickHouse データ型も適切な Postgres の対応型にマッピングされるようになりました。

    ClickHousePostgreSQL
    Int128numeric(39,0)
    Int256numeric(77,0)
    UInt64numeric(20,0)
    UInt128numeric(39,0)
    UInt256numeric(78,0)
    BFloat16float4
    Timetime
    Time64(P)time
    Tuple(...)text[]
    Map(K,V)text[][]
    LineStringpath
    MultiLineStringpath[]
    MultiPolygonpolygon[][]
    Pointpoint
    Ringpolygon
    Polygonpolygon[]

    同じマッピングが chdb extension v0.1.2 にも適用されます。

  • v0.10.0 で非推奨となっていた従来の clickhouse_raw_query() 関数が削除されました。行を読み込むには clickhouse_query(server, sql) を、行を返さないステートメントを実行するには CALL clickhouse_perform(server, sql) を使用するようにコードを更新してください。

  • 本リリースでは PostgreSQL 13 のサポートが削除されました。Postgres コミュニティによるサポートは 2025年9月に終了しています。

  • コミュニティからのコントリビューション により、PostgreSQL の sha224()、sha256()、sha384()、sha512() 関数に加え、pgcrypto 拡張機能の digest() 関数のサポート対象となる定数アルゴリズム呼び出しのプッシュダウンが追加されました。

バグ修正を含む詳細については、pg_clickhouse の変更履歴 および chdb の変更履歴 の全容をご確認ください。各拡張機能は以下の通常のリポジトリから入手できます。pg_clickhouse はこちらです。

chdb 拡張機能はこちらから入手できます。

Footnotes

  1. もちろん、JSON は定義上 UTF-8 が推奨 されますが、例外もあります。Postgres 内の JSON データ は、常にデータベースのエンコーディングを使用しなければなりません。 ↩

  2. 残念ながら、ClickHouse のインターバル型自体はまだ集約をサポートしていないため、たとえば avg(duration) は失敗します。しかし、26.10 における avg および sum のサポートにご期待ください。 ↩

ClickHouse Managed Postgres を今すぐ始める

ClickHouse Managed Postgres を自社のデータで試してみませんか? ClickHouse Cloud は数分で使い始めることができ、300 ドル分の無料クレジットも提供しています。

サインアップ

この記事をシェア

  • Y Combinator icon
  • X icon
  • Bluesky icon
  • Facebook icon
  • LinkedIn icon

Subscribe to our newsletter

Stay informed on feature releases, product roadmap, support, and cloud offerings!

Follow us

XBlueskySlackGithubTelegramMeetupRSS