Skip to main content
clickhouse-c — это C-клиент, состоящий только из заголовочных файлов, для ClickHouse собственного протокола. Исходный код и справочная информация по каждому заголовочному файлу доступны в репозитории GitHub. В отличие от более высокоуровневых клиентов, он намеренно берёт на себя минимум. Основной заголовочный файл декодирует и кодирует блоки формата Native через предоставленный вами callback ввода-вывода. Управление сокетом, TLS-контекстом, аллокатором, retries и пулом соединений остаётся на вашей стороне. Благодаря этому библиотека достаточно мала для встраивания: подключение одного лишь clickhouse.h не добавляет зависимостей на этапе линковки, кроме libc.
Эта библиотека находится в активной разработке. Версия v1 декодирует основные типы ClickHouse. Сообщайте об ограничениях или недостающей функциональности через трекер issues. Однако имейте в виду, что часть функциональности в этой библиотеке отсутствует намеренно.

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

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

Как устроена библиотека

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

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

Декодер считывает печатные имена типов из передаваемых по сети данных, поэтому они должны быть закодированы как текст. 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, вы самостоятельно настраиваете сокет, оборачиваете его в chc_io и передаёте в chc_client_init, который синхронно выполняет рукопожатие Hello. Библиотека не занимается DNS, failover, повторным подключением или pooling — это задача вызывающей стороны.
Каждый 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 представляются как две половины UInt64 в little-endian, IPv4 — как 4-байтовое little-endian-целое число, а IPv6 — в сетевом порядке байтов. Тики DateTime64 — в UTC; часовой пояс в типе — это лишь метаданные. При приёме данных от недоверенного peer вызывайте chc_column_validate для каждого столбца, прежде чем обходить его. chc_block_read не проверяет межполевые инварианты, такие как смещения массивов и ключи LowCardinality, поэтому без такой проверки поддельный блок может прочитать данные за пределами границ внутренних столбцов.

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

Соберите столбцы с помощью вспомогательных функций chc_build_*, добавьте их в chc_block_builder, а затем передайте его в chc_client_send_data. chc_block_builder использует хранилище, предоставленное вызывающей стороной, и сохраняет указатели, а не копирует данные, поэтому хранилище, деревья столбцов, типы, имена и slabs должны существовать дольше, чем длится отправка. INSERT отправляет запрос, ожидает заголовочный блок от сервера, отправляет один или несколько блоков данных, а затем отправляет пустой блок, чтобы завершить поток.
chc_build_fixed принимает n_rows * elem_size байт в формате little-endian; chc_build_string принимает накопительные исключающие конечные смещения в порядке байт хоста для упакованного slab. Вспомогательные функции возвращают узлы столбцов по значению. Вкладывайте их в соответствии с типом: например, передайте фиксированный или строковый узел в chc_build_nullable, затем передайте результат в chc_build_array и добавьте корневой узел массива. Tuple, LowCardinality, Map и Geo-столбцы используют одно и то же дерево: Map — это Array(Tuple(K, V)). Все столбцы в блоке должны иметь одинаковое количество строк на верхнем уровне. writer проверяет дерево на соответствие разобранному типу ClickHouse, но вызывающая сторона должна выделить хранилище chc_block_col для каждого append. Вы также можете напрямую добавить декодированный столбец из chc_block_column, чтобы повторно закодировать его, или вызвать chc_block_write_cols с массивом chc_block_col, чтобы обойтись без builder. Использование builder через chc_client_send_data, а не через низкоуровневый chc_block_write, позволяет client задавать параметры блока на основе согласованной ревизии и применять сжатие.

Сжатие

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

TLS

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

Ioless (async) клиент

clickhouse-async.h — это ioless-вариант TCP-клиента для циклов событий. Он вообще не работает с сокетом: вы передаёте полученные байты и считываете байты, которые клиент хочет отправить, самостоятельно управляя epoll, io_uring или WaitLatchOrSocket. Параметры, типы пакетов и построитель блоков здесь те же, что и у блокирующего клиента. chc_async_client_init не выполняет I/O и не может блокироваться. После этого рукопожатие работает как возобновляемая машина состояний; то же самое относится к каждой операции отправки и получения. Когда разбор выходит за пределы переданных вами байтов, вызов возвращает CHC_WOULD_BLOCK вместо блокировки — передайте больше входящих байтов и вызовите функцию снова, и парсер продолжит работу с середины блока.
Ваш pump перемещает байты в обоих направлениях. Для исходящего потока chc_async_pending_out возвращает указатель и длину для байтов в очереди; после того как сокет примет часть данных, вызовите chc_async_consume_out с этим значением — частичная запись допустима. Для входящего потока передавайте в chc_async_submit данные, прочитанные из сокета. Отправка никогда не блокируется и не создает обратного давления, поэтому следите за длиной pending-out и прекращайте отправку, когда она становится слишком большой. Рабочий драйвер 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_build_string; модуль записи добавляет префикс, необходимый для разобранного типа. Variant, Dynamic, AggregateFunction пока не декодируются и возвращают CHC_ERR_TYPE; в качестве обходного варианта приводите их к String на стороне server.
Последнее изменение 23 июля 2026 г.