clickhouse-c は、ClickHouse の ネイティブプロトコル 向けのヘッダーオンリー C クライアントです。
ソースコードと各ヘッダーのリファレンスは、GitHub リポジトリ にあります。
上位レベルのクライアントとは異なり、このクライアントは意図的に多くのことを行いません。中核となるヘッダーは、ユーザーが提供する I/O コールバックを介して Native フォーマットのブロックをデコードおよびエンコードします。ソケット、TLS コンテキスト、アロケータ、再試行、接続プーリングはユーザー側で管理します。そのため埋め込みに適した小ささを実現しており、clickhouse.h だけをインクルードしても、リンク時の依存関係は libc を除いて発生しません。
このライブラリは現在も活発に開発されています。v1 では ClickHouse の主要な型をデコードします。
制限事項や不足している機能は、issue tracker から報告してください。
ただし、このライブラリには設計上あえて備えていない機能があることをご理解ください。
ライブラリが行わないこと
- HTTPプロトコル。HTTP インターフェイス を使用する場合は、libcurl を直接ラップしてください。
- DNS 名前解決、エンドポイントのフェイルオーバー、接続プーリング、再試行、バックオフ。
- TLS コンテキストのライフサイクル。OpenSSL バックエンドは、すでに接続済みの
SSLを使用します。 - スレッド処理。各
chc_clientは設計上シングルスレッドです。 - ライブラリ内部での非同期 I/O。ブロッキングクライアント は
chc_io.readを同期的に呼び出します。ライブラリ自身では I/O を行わない event-loop client が必要な場合は、I/O を行わないクライアント を使用してください.
ライブラリの構成
clickhouse-c は、フラットなヘッダーファイル群として提供されます。各ヘッダーには宣言と実装の両方が含まれており、
センチネルマクロで保護されています。ビルドに必要なヘッダーを選択してください。
必須のサーバー設定
プロジェクトへの追加
CHC_IMPLEMENTATION を定義して実装を取り込む翻訳単位は、必ず1つだけにしてください。
それ以外のすべての翻訳単位では、宣言だけを得るために同じヘッダーファイルをインクルードします。
chc_alloc_stdlib を使用するには、clickhouse.h をインクルードする前に CHC_PROVIDE_STDLIB_ALLOC を定義してください。
clickhouse-compression.h で lz4/zstd への依存関係をなくすには、CHC_NO_LZ4 または CHC_NO_ZSTD を定義してください。
TCP 経由で接続する
chc_io でラップして chc_client_init に渡します。これにより、Hello ハンドシェイクが同期的に実行されます。DNS、フェイルオーバー、再接続、プーリングはライブラリでは処理しないため、これらは呼び出し側で対応する必要があります。
chc_client はシングルスレッドで動作し、1 つの接続をラップします。ライブラリは chc_io
コールバックを同期的に呼び出します。これらのコールバックが内部で何を行うか (epoll、io_uring、
WaitLatchOrSocket) は、使用者に委ねられます。
クエリの実行
CHC_PKT_END_OF_STREAM に達するまでパケットを読み続けます。必須のサーバー設定を
指定するには chc_client_send_query_ex を使用してください。chc_client_send_query のみを使用すると空の
設定リストが送信され、サーバーのデフォルト設定がそのまま適用されます。
CHC_PKT_EXCEPTION パケットとして返され、
chc_client_recv_packet の non-OK の戻り値として返されるわけではありません。non-OK が返るのはトランスポート層の障害時だけです。結果の最初の CHC_PKT_DATA
パケットは、スキーマを記述した 0 行のヘッダーブロックで、その後にデータブロックが続きます。
chc_packet_clear はパケット内の block または exception を解放します。代わりに所有権を引き取る場合は、まず
パケット上のそれらのフィールドを null にしてください。
カラムデータの読み取り
chc_column_layout が返す物理レイアウトがあり、
それに応じて処理を分岐します。宣言上の型は chc_block_column_type から取得します。複合レイアウトは入れ子になるため、
Nullable(Array(String)) を読み取るには、Nullable をアンラップし、配列のオフセットをたどってから、
文字列データを切り出します。
プレーンな数値、文字列、Nullable カラム用のリーダー:
CHC_COL_FIXED データは、送信時のバイト表現ではリトルエンディアンです。ビッグエンディアンのホストでは、多バイト
整数のバイト順を自分で入れ替える必要があります。offsets と LowCardinality のキーは、デコード時点で
すでにホストバイトオーダーに変換されています。UUIDs はリトルエンディアンの UInt64 を 2 つ並べたもので、IPv4 は 4 バイトのリトルエンディアン整数、IPv6 は
ネットワークバイトオーダーです。DateTime64 の ticks は UTC であり、型に含まれる timezone はメタデータにすぎません。
信頼できないピアから取り込む際は、各カラムを走査する前に chc_column_validate を呼び出して
ください。chc_block_read は、配列の offsets や
LowCardinality のキーといったフィールド間の不変条件を検証しないため、
そうしないと、偽造された block によって内部カラムの境界を超えて読み取られるおそれがあります。
データの挿入
chc_build_* ヘルパーでカラムを構築し、それらを chc_block_builder に追加してから、
chc_client_send_data に渡します。ビルダーは呼び出し元が用意したストレージを使用し、データをコピーするのではなく
ポインターを保持するため、ストレージ、カラムツリー、型、名前、スラブは送信処理が完了するまで有効である必要があります。INSERT では
クエリを送信し、サーバーのヘッダーブロックを待ってから、1 つ以上のデータブロックを送信し、最後に
空のブロックを送信してストリームを終了します。
chc_build_fixed は n_rows * elem_size の リトルエンディアン バイト列を受け取り、chc_build_string は packed されたスラブ上の、ホストバイトオーダーで表された
排他的な終端 offset の累積値を受け取ります。helpers はカラムノードを値として返します。
型に合わせてそれらをネストしてください。たとえば、fixed または string ノードを
chc_build_nullable に渡し、その結果を chc_build_array に渡して、array のルートを append します。Tuple、
LowCardinality、Map、および Geo カラムは同じツリーを使います。Map は Array(Tuple(K, V)) です。
block 内のすべてのカラムは、トップレベルの行数が同じでなければなりません。writer はツリーを
parse 済みの ClickHouse 型と照合しますが、呼び出し側は append のたびに各 chc_block_col のストレージサイズを確保する必要があります。
また、chc_block_column から取得したデコード済みのカラムを直接 append して再エンコードすることも、chc_block_col 配列を指定して
chc_block_write_cols を呼び出し、builder を省略することもできます。builder を下位レベルの chc_block_write ではなく
chc_client_send_data 経由で渡すことで、client はネゴシエートされたリビジョンに基づいて block
オプションを設定し、圧縮を適用できます。
圧縮
chc_client_opts に圧縮モードと使用するコーデックを渡します。クライアントは受信した
Dataパケットを伸長し、送信するパケットを圧縮します。圧縮ヘッダーには LZ4 と ZSTD のアダプターが用意されています。
各 init はそれぞれ自身のスロットしか埋めないため、どちらにも対応するには両方を呼び出してください。
chc_codec を自分で実装してください;
vtable は clickhouse-compression.h で宣言されています。
TLS
clickhouse-openssl.h は、SSL_read/SSL_write を介した chc_io バックエンドを提供します。OpenSSL の制御は利用者側で行います。
このライブラリが SSL_CTX を作成したり、証明書を検証したり、SNI を設定したり、SSL_connect /
SSL_shutdown を呼び出したりすることはありません。chc_io.read が発火する時点で、ハンドシェイクは完了している必要があります。
check_cancel コールバックと、
chc_openssl_io_set_deadline / chc_posix_io_set_deadline による読み取りデッドラインに対応しています。
Ioless (async) クライアント
clickhouse-async.h は、イベントループ向け TCP クライアントの ioless バリアントです。ソケットには一切触れません。受信したバイト列を渡し、送信したいバイト列を取り出して、epoll、io_uring、WaitLatchOrSocket は自分で駆動します。オプション、パケットタイプ、ブロックビルダーはブロッキングクライアントと同じです。
chc_async_client_init は I/O を行わず、ブロックもできません。その後のハンドシェイクは再開可能なステートマシンとして動作し、送信と受信もすべて同様です。パースが渡したバイト列の範囲を超えると、その呼び出しはブロックする代わりに CHC_WOULD_BLOCK を返します。さらに受信バイト列を渡してもう一度呼び出すと、パーサーはブロックの途中から再開します。
pump は双方向にバイトをやり取りします。Outbound では、chc_async_pending_out がキュー済みのバイトへのポインターと長さを返します。ソケットがその一部を受け付けたら、そのバイト数を指定して chc_async_consume_out を呼び出します。部分書き込みでも問題ありません。Inbound では、ソケットから読み取ったデータを chc_async_submit に渡します。送信ではブロックもバックプレッシャーの適用も発生しないため、pending-out の長さを監視し、大きくなりすぎたら送信の発行を止めてください。
動作する liburing ドライバーは
test/test_async_uring.c
にあります。
メモリとアロケータ
chc_alloc vtable を受け取るため、メモリ割り当てにはホスト側で使われている方式がそのまま使われます。
clickhouse.h をインクルードする前に CHC_PROVIDE_STDLIB_ALLOC を定義し、標準の malloc ベースのアロケータを使用するには chc_alloc_stdlib() を呼び出します。
エラーとサーバー例外
CHC_OK (0) または 0 以外の CHC_ERR_* コードを返します。コードが戻り値であり、
呼び出し元のスタックに確保された chc_err に可読なメッセージが格納されます。ライブラリが
エラーをヒープに割り当てることはありません。
chc_err の失敗ではありません。これらはパケットストリーム上で
CHC_PKT_EXCEPTION として届き、サーバーの code、display_text、stack_trace を伴います。chc_err の確認は、
トランスポート、プロトコル、デコードの失敗に対してのみ行ってください。
サポートされているデータ型
Int8–Int256,UInt8–UInt256Float32,Float64,BFloat16BoolDecimal32,Decimal64,Decimal128,Decimal256Date,Date32,DateTime,DateTime64,Time,Time64String,FixedString(N)UUID,IPv4,IPv6Enum8,Enum16Nullable(T),Array(T),Tuple(...),Map(K, V),Nested(...)LowCardinality(T)IntervalQBit(...)Point,Ring,Polygon,MultiPolygonSimpleAggregateFunction(f, T)。これは内部のTとしてデコードされますJSONとObject('json')。文字列シリアライゼーションではStringカラムとしてデコードされます (下記を参照)
JSON と Object('json') は文字列シリアライゼーションでデコードされます。クエリで
output_format_native_write_json_as_string=1 を設定してください。サポートされる各行は
CHC_COL_STRING カラム内の 1 つの JSON ドキュメントとして渡されます。同じ構造を chc_build_string で構築します。
writer は、解析された型に必要なプレフィックスを出力します。
Variant、Dynamic、AggregateFunction はまだデコードされず、CHC_ERR_TYPE を返します。
フォールバックとして、これらはサーバー側で String にキャストしてください。