Header-only C client for the ClickHouse Native wire format. One core header, plus optional follow-up headers for TCP, compression, codecs, and I/O backends.
Designed first for embedding inside a PostgreSQL extension — palloc arena,
siglongjmp error path — but with no PG-specific code in the library itself.
Including clickhouse.h alone gives a pure block decoder over caller-supplied
chc_io, usable for reading clickhouse-local's FORMAT Native from a pipe
with no TCP, no compression, no link-time deps beyond libc.
Decode a clickhouse local query without TCP or compression.
/* demo.c — cc -std=c11 -O2 -I. demo.c -o demo */
#define CHC_PROVIDE_STDLIB_ALLOC
#define CHC_IMPLEMENTATION
#include "clickhouse.h"
#include "clickhouse-posix-io.h"
#include <stdio.h>
#include <unistd.h>
int main(void) {
/* Spawn `clickhouse local --format Native -q "..."` and read its stdout
via a pipe; see examples/minimal_decode.c for the full plumbing. */
int fd = /* read end of pipe from child */;
chc_alloc al = chc_alloc_stdlib();
chc_posix_io state;
chc_io io;
chc_posix_io_init(&state, &io, fd, NULL, NULL);
chc_block_opts opts = {0}; /* clickhouse-local: no BlockInfo, no custom serialization */
for (;;) {
chc_block *block = NULL;
chc_err err = {0};
if (chc_block_read(&io, &al, &opts, &block, &err) != CHC_OK) {
fprintf(stderr, "decode: %s\n", err.msg);
return 1;
}
if (!block) break;
for (size_t r = 0; r < chc_block_n_rows(block); r++)
for (size_t c = 0; c < chc_block_n_columns(block); c++)
/* dispatch on chc_column_layout(chc_block_column(block, c)) */;
chc_block_destroy(block, &al);
}
}Runnable end-to-end version: examples/minimal_decode.c.
Required server setting (Query packet for TCP, -- flag for clickhouse local):
output_format_native_encode_types_in_binary_format = 0
Without it, the server emits binary type tags & chc_block_read returns
CHC_ERR_TYPE.
clickhouse-c is distributed as a flat set of headers:
each header contains both declarations & implementation, guarded by
a sentinel macro. Exactly one translation unit per consumer defines the
implementation macro & includes headers; everyone else includes for
declarations only.
/* consumer/src/clickhouse.c — the one TU that links the library in */
#define CHC_IMPLEMENTATION
#include "clickhouse.h"
#include "clickhouse-posix-io.h"
/* …include clickhouse-client.h, clickhouse-compression.h here for the
features you use. */Other TUs include only the public declarations:
#include "clickhouse.h"
#include "clickhouse-posix-io.h"
/* no CHC_IMPLEMENTATION here */For PostgreSQL extension embedding, see
examples/pg_alloc_thunks.c for palloc/repalloc/pfree
wiring; a stdlib malloc/realloc/free helper is available behind
#define CHC_PROVIDE_STDLIB_ALLOC before including clickhouse.h.
| Header | Purpose | Links |
|---|---|---|
clickhouse.h |
Core: types, errors, chc_alloc, chc_io, type-name parser, block reader & writer |
— |
clickhouse-client.h |
TCP packet loop: Hello / Query / Data / EOS / Exception / Progress / Pong | — |
clickhouse-compression.h |
Compressed-frame layout, CityHash128, chc_codec dispatch, LZ4 & ZSTD adapters (opt out with CHC_NO_LZ4 / CHC_NO_ZSTD) |
-llz4 -lzstd |
clickhouse-posix-io.h |
chc_io over blocking read(2)/write(2) with EINTR loop & cancel hook |
— |
clickhouse-openssl.h |
chc_io over SSL_read/SSL_write |
-lssl -lcrypto |
Each follow-up header is independent; pick what your build needs.
Per-header reference in doc/; inline declarations & comments in the headers themselves. Worked examples in examples/.
Use test.sh to run tests:
./test.shIf errors report missing headers or linker errors, use CFLAGS and LDFLAGS
to point to the appropriate directories, e.g., using Homebrew:
CFLAGS=-I/opt/homebrew/include LDFLAGS=-L/opt/homebrew/lib ./test.shThe script runs individual tests in test. To run individual tests,
pass their base names (without the trailing .c) as arguments:
./test.sh test_cancel test_block_decode- HTTP protocol — wrap libcurl directly.
- DNS, endpoint round-robin, connection pooling, retry/backoff.
- SSL/TLS context lifecycle.
chc_iocallbacks; caller drives OpenSSL. - Threading. Each
chc_clientis single-threaded by design. - Async I/O. Caller's
chc_io.readcan do whatever it wants under the hood (epoll, io_uring,WaitLatchOrSocket), but the library calls it synchronously. Variant/Dynamic/JSON/AggregateFunctiondecoding in v1. The upstream wire format is still shifting in 25.x / 26.x.