ClickHouse C: a header-only C client for the ClickHouse native protocol

ClickHouse C: a header-only C client for the ClickHouse native protocol

clickhouse-c is a header-only C client for the ClickHouse Native wire format, which the README describes as the Native Protocol over TCP carrying Native Format blocks. It ships as one core header, clickhouse.h, plus optional follow-up headers for TCP, compression, codecs and I/O backends. Each follow-up header is independent, so a project picks only what its build needs.

The library is designed for embedding inside PostgreSQL extensions, with a palloc arena and longjmp in mind, but it contains no PostgreSQL-specific code. Including clickhouse.h alone gives a pure block decoder over a caller-supplied chc_io. That is enough to read the Native-format output of clickhouse-local from a pipe, with no TCP, no compression and no link-time dependencies beyond libc. The README's demo spawns clickhouse local with the Native format, initialises a reader over the pipe with chc_in_init, then loops on chc_block_read, walking rows and columns with chc_block_n_rows and chc_block_n_columns and freeing each block. It compiles with cc -std=c11 -O2, and a runnable version lives in examples/minimal_decode.c.

One server setting is required: output_format_native_encode_types_in_binary_format = 0. It goes in the Query packet for TCP, or is passed as a flag to clickhouse local. Without it the server emits binary type tags and chc_block_read returns CHC_ERR_TYPE.

Each header holds both declarations and implementation, guarded by a sentinel macro. Exactly one translation unit per consumer defines CHC_IMPLEMENTATION and includes the headers; every other unit includes declarations only. A stdlib malloc/realloc/free helper is available behind CHC_PROVIDE_STDLIB_ALLOC. For palloc/repalloc/pfree wiring inside a real PostgreSQL extension, the README points to pg_clickhouse and pg_stat_ch. The clickhouse-client.h header supports ClickHouse 23.3 and newer.

The README lists explicit non-goals. HTTP is out of scope (wrap libcurl directly), as are DNS, endpoint round-robin, connection pooling and retry/backoff. The library does not manage the SSL/TLS context: chc_io callbacks let the caller drive OpenSSL. Each chc_client is single-threaded by design. The blocking client calls chc_io.read synchronously, and the callback may use epoll, io_uring or WaitLatchOrSocket underneath; for an ioless client driven by caller byte submission in an event loop, the README points to clickhouse-async.h. Decoding of Variant, Dynamic, JSON and AggregateFunction is not supported in v1, because the upstream wire format is still shifting in 25.x and 26.x.

Tests run with ./test.sh, or with individual base names such as test_cancel and test_block_decode. CFLAGS and LDFLAGS can point at header and library directories, for example Homebrew paths. test_client_tcp spawns a clickhouse server from PATH; setting CHC_TEST_PORT tests against a running server instead, for example the clickhouse/clickhouse-server:23.3.22.3 docker image on port 19233. tools/capture.py proxies such a server to record replay fixtures like test/ch23_3_sparse.bin. Per-header reference is in doc/ and worked examples are in examples/.

Key facts

  • clickhouse-c is a header-only C client for the ClickHouse Native wire format: one core header, clickhouse.h, plus optional headers for TCP, compression, codecs and I/O backends.
  • It is designed for embedding in PostgreSQL extensions (palloc arena, longjmp) but has no PostgreSQL-specific code; clickhouse.h alone decodes blocks from clickhouse-local through a pipe with no dependencies beyond libc.
  • The server must set output_format_native_encode_types_in_binary_format = 0, otherwise chc_block_read returns CHC_ERR_TYPE.
  • clickhouse-client.h supports ClickHouse 23.3 and newer.
  • HTTP, connection pooling, retries, TLS lifecycle and threading are non-goals, and v1 cannot decode Variant, Dynamic, JSON or AggregateFunction.

Why it matters

Speaking ClickHouse's native binary protocol from C normally means pulling in a larger client or reimplementing the wire format. This library packages the Native format and protocol as plain headers with no link-time dependencies beyond libc for the core decoder. The explicit design target is code that lives inside a PostgreSQL extension, where memory comes from a palloc arena and errors may unwind with longjmp, and the README names pg_clickhouse and pg_stat_ch as places to see that wiring in a real extension.

Who it affects

C and C++ developers who need to read from ClickHouse inside a constrained host, above all authors of PostgreSQL extensions. Anyone who wants to decode the Native-format output of clickhouse-local from a pipe also benefits, since the core header handles that case with no TCP or compression. It is of little use to people who simply want a general-purpose, batteries-included database driver, because pooling, retries, TLS and threading are left to the caller.

How to use it

Copy the headers into a project. In exactly one translation unit, define CHC_IMPLEMENTATION and include clickhouse.h along with any follow-up headers you need, such as clickhouse-posix-io.h, clickhouse-client.h or clickhouse-compression.h. Other units include the same headers without the macro. Define CHC_PROVIDE_STDLIB_ALLOC to get a stdlib allocator, or supply your own (palloc and friends in a PostgreSQL extension). Set output_format_native_encode_types_in_binary_format = 0 on the query. Start from examples/minimal_decode.c, consult doc/ for the per-header reference, and run ./test.sh to check the build.

How solid is it

The README is detailed about structure, setup, testing and limits. It includes a test suite run by ./test.sh, replay fixtures recorded with tools/capture.py, and a way to test against a running older server such as 23.3.22.3 in docker. The README states no version number or release date, no license, no benchmarks or performance figures, and no statement of production use. The README text names no authors, maintainers or organisation, and does not say the company behind ClickHouse officially supports the library.

Risks and caveats

The server setting is mandatory; forgetting it produces CHC_ERR_TYPE errors. Version 1 cannot decode Variant, Dynamic, JSON or AggregateFunction columns, and the README says the upstream wire format for them is still shifting in 25.x and 26.x. Each client is single-threaded by design. Only ClickHouse 23.3 and newer are supported by clickhouse-client.h. The header-only layout requires discipline: exactly one translation unit may define the implementation macro. Everything around the connection, including HTTP, DNS, pooling, retries and TLS, has to be built by the consumer.

“Without it, the server emits binary type tags & chc_block_read returns CHC_ERR_TYPE.”

— clickhouse-c README