Перейти к основному содержанию
clickhouse-c — это header-only C-клиент для ClickHouse собственного протокола. Исходный код и справочник по каждому заголовочному файлу доступны в репозитории GitHub. В отличие от более высокоуровневых клиентов, он намеренно берет на себя минимум. Основной заголовочный файл декодирует и кодирует блоки формата Native через переданный вами I/O-колбэк. За сокет, TLS-контекст, аллокатор, повторные попытки и пул соединений отвечаете вы. Благодаря этому библиотека достаточно компактна для встраивания: одно только подключение clickhouse.h не добавляет зависимостей на этапе компоновки, кроме libc.
Эта библиотека находится в активной разработке. Версия v1 декодирует основные типы ClickHouse. Сообщайте об ограничениях или недостающей функциональности через issue tracker. Однако имейте в виду, что часть функциональности в этой библиотеке отсутствует намеренно.

Чего библиотека не делает

Это намеренно не входит в её задачи. Реализуйте это в своём приложении или с помощью родственной библиотеки:
  • HTTP-протокол. Используйте libcurl напрямую для HTTP-интерфейса.
  • Разрешение DNS-имён, переключение конечных точек при отказе, пул соединений, повторные попытки и задержка между ними.
  • Управление жизненным циклом TLS-контекста. Реализация на OpenSSL использует SSL, к которому вы уже подключились.
  • Многопоточность. Каждый chc_client по замыслу однопоточный.
  • Асинхронный I/O внутри библиотеки. Блокирующий клиент синхронно вызывает chc_io.read. Для клиента с циклом событий, который сам не выполняет I/O, используйте клиент без I/O.

Как организована библиотека

clickhouse-c поставляется в виде плоского набора заголовочных файлов. Каждый заголовок содержит и объявления, и реализацию, защищённые сигнальным макросом. Выбирайте заголовки, которые нужны вашей сборке.

Обязательная настройка сервера

Декодер считывает из wire печатные имена типов, поэтому они должны кодироваться как текст. ClickHouse по умолчанию записывает их в текстовом виде, но зафиксируйте эту настройку в своих запросах, чтобы профиль сервера или сеанса, который переключает её на бинарный формат, не нарушил декодирование:

Добавление в проект

Устанавливать пакет не нужно, поэтому заголовочные файлы следует добавить в дерево проекта через Git-подмодуль или просто скопировать. Ровно одна единица трансляции определяет CHC_IMPLEMENTATION и подключает реализацию; все остальные единицы включают те же заголовочные файлы только для объявлений.
Определите CHC_PROVIDE_STDLIB_ALLOC перед включением clickhouse.h, чтобы использовать chc_alloc_stdlib. Определите CHC_NO_LZ4 или CHC_NO_ZSTD для clickhouse-compression.h, чтобы исключить зависимости от lz4/zstd.

Подключение по TCP

Чтобы работать с ClickHouse server, вы сами настраиваете сокет, оборачиваете его в chc_io и передаёте в chc_client_init, который синхронно выполняет процедуру рукопожатия Hello. Библиотека не занимается DNS, переключением при отказе, повторным подключением или пулом соединений — это обязанность вызывающего кода.
Каждый chc_client однопоточный и инкапсулирует одно соединение. Библиотека синхронно вызывает функции обратного вызова chc_io; что именно они делают на нижнем уровне (epoll, io_uring, WaitLatchOrSocket) — зависит от вас.

Выполнение запроса

Отправьте запрос, затем считывайте пакеты до CHC_PKT_END_OF_STREAM. Используйте chc_client_send_query_ex, чтобы передать обязательную настройку сервера; обычный chc_client_send_query отправляет пустой список настроек и использует значения сервера по умолчанию.
Исключения сервера поступают в виде пакетов CHC_PKT_EXCEPTION, а не как не-OK возврат из chc_client_recv_packet. Не-OK возвращается только при сбоях транспортного уровня. Первый пакет CHC_PKT_DATA в результате — это блок заголовка, описывающий схему и содержащий ноль строк; затем следуют блоки данных. chc_packet_clear освобождает блок или исключение пакета — чтобы вместо этого взять владение на себя, сначала обнулите эти поля в пакете.

Чтение данных столбцов

Блоки имеют столбцовую организацию. У каждого столбца есть физическая структура, которую возвращает chc_column_layout и по которой выполняется дальнейшая обработка; объявленный тип столбца возвращает chc_block_column_type. Составные структуры могут быть вложенными, поэтому чтение Nullable(Array(String)) означает, что нужно снять обёртку Nullable, пройти по смещениям массива, а затем вырезать строковые данные. Пример чтения обычных числовых, строковых и столбцов с типом Nullable:
Данные CHC_COL_FIXED при передаче имеют порядок little-endian; на хостах с big-endian вы сами выполняете байтовую перестановку для многобайтовых целых чисел. Смещения и ключи LowCardinality уже приводятся к порядку байтов хоста при декодировании. UUID — это две little-endian половины UInt64, IPv4 — 4-байтовое little-endian целое число, а IPv6 использует network byte order. Тики DateTime64 отсчитываются в UTC — timezone в типе служит лишь как metadata. При приёме данных от недоверенного peer вызывайте chc_column_validate для каждого столбца перед обходом его содержимого. chc_block_read не проверяет инварианты между полями, такие как смещения массивов и ключи LowCardinality, поэтому иначе поддельный block может привести к чтению за пределами внутренних столбцов.

Вставка данных

Соберите блок с помощью chc_block_builder, затем передайте его в chc_client_send_data. Построитель блоков сохраняет указатели вместо копирования, поэтому срезы столбцов должны жить дольше, чем длится отправка. INSERT отправляет запрос, ожидает блок заголовка от сервера, отправляет один или несколько блоков данных, а затем отправляет пустой блок, чтобы завершить поток.
chc_block_builder_append_fixed принимает n_rows * elem_size байт в формате little-endian; chc_block_builder_append_string принимает накопительные конечные смещения exclusive в порядке байтов хоста для упакованного slab. Передача построителя блоков через chc_client_send_data, а не через более низкоуровневый chc_block_write, позволяет клиенту задавать параметры блока в соответствии с согласованной ревизией и применять сжатие.

Сжатие

Передайте режим сжатия и настроенный кодек в chc_client_opts. Клиент распаковывает входящие пакеты данных и сжимает исходящие. В заголовочном файле сжатия предусмотрены адаптеры LZ4 и ZSTD; каждая инициализация заполняет только свои слоты, поэтому вызовите обе, чтобы поддерживать любой вариант.
Чтобы использовать библиотеку сжатия, для которой проект не предоставляет биндинг, самостоятельно заполните структуру chc_codec; vtable объявлена в clickhouse-compression.h.

TLS

clickhouse-openssl.h предоставляет chc_io backend-соединение поверх SSL_read/SSL_write. OpenSSL настраиваете и управляете им вы: библиотека никогда не создаёт SSL_CTX, не проверяет сертификаты, не задаёт SNI и не вызывает SSL_connect / SSL_shutdown. К моменту вызова chc_io.read рукопожатие уже должно быть завершено.
ClickHouse Cloud и другие развертывания с поддержкой TLS используют собственный протокол на порту 9440. Оба backend-соединения поддерживают необязательный callback check_cancel, который опрашивается между операциями чтения, а также deadline чтения через chc_openssl_io_set_deadline / chc_posix_io_set_deadline.

Клиент без I/O (async)

clickhouse-async.h — это вариант TCP-клиента без I/O для циклов событий. Он никогда не обращается к сокету: вы передаёте полученные байты и забираете байты, которые он хочет отправить, самостоятельно управляя epoll, io_uring или WaitLatchOrSocket. Параметры, типы пакетов и построитель блоков — те же, что и у блокирующего клиента. chc_async_client_init не выполняет I/O и не может блокироваться. После этого рукопожатие работает как возобновляемая машина состояний, как и каждая операция отправки и приёма. Когда разбор выходит за пределы переданных вами байтов, вызов возвращает CHC_WOULD_BLOCK вместо блокировки — передайте больше входящих байтов и вызовите его снова, и parser продолжит работу с середины блока.
Ваш pump передаёт байты в обоих направлениях. Для исходящего трафика chc_async_pending_out возвращает указатель и длину для байтов в очереди; после того как сокет примет часть данных, вызовите chc_async_consume_out с этим количеством — частичная запись допустима. Для входящего трафика передавайте данные, прочитанные из сокета, в chc_async_submit. Отправка никогда не блокируется и не создаёт обратного давления, поэтому следите за длиной очереди исходящих данных и прекращайте отправку, когда она становится слишком большой. Рабочий драйвер liburing находится в test/test_async_uring.c.

Память и аллокатор

Каждая точка входа принимает vtable chc_alloc, поэтому память выделяется по той схеме, которую использует хост.
Определите CHC_PROVIDE_STDLIB_ALLOC перед включением clickhouse.h и вызовите chc_alloc_stdlib() для стандартного аллокатора на основе malloc.

Ошибки и Исключения сервера

Функции возвращают CHC_OK (0) или ненулевой код CHC_ERR_*. Код является возвращаемым значением; chc_err, размещённый в стеке вызывающей стороны, содержит человекочитаемое сообщение. Библиотека никогда не выделяет память под ошибку в куче.
Ошибки запросов на стороне сервера — это не сбои chc_err. Они приходят в потоке пакетов как CHC_PKT_EXCEPTION и содержат серверные code, display_text и stack_trace. Проверку chc_err следует оставлять для сбоев транспорта, протокола и декодирования.

Поддерживаемые типы данных

Компонент чтения блока декодирует:
  • Int8Int256, UInt8UInt256
  • Float32, Float64, BFloat16
  • Bool
  • Decimal32, Decimal64, Decimal128, Decimal256
  • Date, Date32, DateTime, DateTime64, Time, Time64
  • String, FixedString(N)
  • UUID, IPv4, IPv6
  • Enum8, Enum16
  • Nullable(T), Array(T), Tuple(...), Map(K, V), Nested(...)
  • LowCardinality(T)
  • Interval
  • QBit(...)
  • Point, Ring, Polygon, MultiPolygon
  • SimpleAggregateFunction(f, T), который декодируется как внутренний T
  • JSON и Object('json') как столбцы String при строковой сериализации (см. ниже)
JSON и Object('json') декодируются, только если запрос задаёт output_format_native_write_json_as_string=1. Каждая строка поступает как отдельный JSON-документ в столбце CHC_COL_STRING, поэтому его читают строковые аксессоры; построитель записывает ту же структуру с помощью chc_block_builder_append_json_string. Любая другая версия JSON- сериализации возвращает CHC_ERR_TYPE с указанием настройки. Variant, Dynamic, AggregateFunction пока не декодируются и возвращают CHC_ERR_TYPE; в качестве резервного варианта приведите их к String на стороне сервера.
Последнее изменение 2 июля 2026 г.