Skip to main content
clickhouse-c é um cliente C header-only para o protocolo nativo do ClickHouse. O código-fonte e a referência de cada cabeçalho estão no repositório GitHub. Ao contrário dos clientes de mais alto nível, ele intencionalmente faz pouca coisa por você. O cabeçalho principal decodifica e codifica blocos no formato Native por meio de um callback de E/S fornecido por você. Ficam por sua conta o socket, o contexto TLS, o alocador, as novas tentativas e o pool de conexões. Isso o torna pequeno o suficiente para ser embutido: incluir apenas clickhouse.h não traz dependências de linkagem além da libc.
Esta biblioteca está em desenvolvimento ativo. A v1 decodifica os principais tipos do ClickHouse. Relate limitações ou funcionalidades ausentes por meio do rastreador de issues. Entenda, porém, que a ausência de certas funcionalidades nesta biblioteca é intencional.

O que a biblioteca não faz

Estes são objetivos deliberadamente fora do escopo. Trate-os na sua aplicação ou com uma biblioteca complementar:
  • Protocolo HTTP. Use o libcurl diretamente como wrapper para a interface HTTP.
  • Resolução de DNS, failover de endpoint, pool de conexões, retry e backoff.
  • Ciclo de vida do contexto TLS. O backend OpenSSL usa um SSL cuja conexão você já estabeleceu.
  • Threads. Cada chc_client foi projetado para usar uma única thread.
  • E/S assíncrona dentro da biblioteca. O cliente bloqueante chama chc_io.read de forma síncrona. Para um cliente de loop de eventos que não realiza nenhuma E/S por conta própria, use o ioless client.

Como a biblioteca é organizada

clickhouse-c é distribuída como um conjunto simples de cabeçalhos. Cada cabeçalho reúne declarações e implementação, protegidas por uma macro sentinela. Escolha os cabeçalhos de que sua compilação precisa.

Configuração obrigatória do servidor

O decodificador lê nomes de tipos legíveis a partir do wire, portanto eles precisam ser codificados como texto. O ClickHouse os escreve como texto por padrão, mas fixe essa configuração nas suas consultas para que um perfil de servidor ou de sessão que a defina como binária não possa prejudicar a decodificação:

Adicionando isso ao seu projeto

Não há pacote para instalar, então você deve incorporar os arquivos de cabeçalho à sua árvore de código-fonte por meio de um submódulo do Git ou de uma cópia. Exatamente uma unidade de tradução define CHC_IMPLEMENTATION e inclui a implementação; todas as outras unidades incluem os mesmos arquivos de cabeçalho apenas para declarações.
Defina CHC_PROVIDE_STDLIB_ALLOC antes de incluir clickhouse.h para usar chc_alloc_stdlib. Defina CHC_NO_LZ4 ou CHC_NO_ZSTD em clickhouse-compression.h para remover as dependências de lz4/zstd.

Conectando por TCP

Para se comunicar com um servidor ClickHouse, você mesmo configura o socket, o encapsula em um chc_io e o passa para chc_client_init, que executa o handshake Hello de forma síncrona. A biblioteca não faz DNS, failover, reconexão nem pool de conexões — isso é responsabilidade de quem faz a chamada.
Cada chc_client é de thread única e encapsula uma conexão. A biblioteca chama os callbacks chc_io de forma síncrona; o que esses callbacks fazem internamente (epoll, io_uring, WaitLatchOrSocket) fica a seu critério.

Executando uma consulta

Envie a consulta e, em seguida, consuma os pacotes até CHC_PKT_END_OF_STREAM. Use chc_client_send_query_ex para anexar a configuração de servidor necessária; chc_client_send_query, sem parâmetros extras, envia uma lista de configurações vazia e herda a configuração padrão do servidor.
As exceções do servidor chegam como pacotes CHC_PKT_EXCEPTION, não como um retorno não OK de chc_client_recv_packet. Somente falhas no nível de transporte retornam não OK. O primeiro pacote CHC_PKT_DATA de um resultado é um bloco de cabeçalho que descreve o esquema com zero linhas; os blocos de dados vêm em seguida. chc_packet_clear libera o bloco ou a exceção do pacote — primeiro defina esses campos do pacote como nulos para assumir a propriedade deles.

Leitura de dados de coluna

Os blocos são orientados a colunas. Cada coluna tem um layout físico, retornado por chc_column_layout, com base no qual você faz o encaminhamento; seu tipo declarado vem de chc_block_column_type. Layouts compostos são aninhados, então ler um Nullable(Array(String)) significa desempacotar o Nullable, percorrer os offsets do array e, em seguida, fatiar os dados da string. Um leitor para colunas numéricas simples, de string e Nullable:
Os dados CHC_COL_FIXED são little-endian on the wire; em hosts big-endian, você mesmo deve fazer o byte-swap de inteiros com vários bytes. Offsets e chaves de LowCardinality já são convertidos para a ordem do host no momento da decodificação. UUIDs são duas metades UInt64 little-endian, IPv4 é um inteiro little-endian de 4 bytes e IPv6 está em network byte order. Os ticks de DateTime64 são UTC — o timezone no tipo é apenas metadata. Ao fazer a ingestão a partir de um peer não confiável, chame chc_column_validate em cada coluna antes de percorrê- la. chc_block_read não valida invariantes entre campos, como offsets de array e chaves de LowCardinality, então um bloco forjado poderia, caso contrário, ler além dos limites da coluna interna.

Inserindo dados

Construa colunas com os helpers chc_build_*, adicione-as a um chc_block_builder e, em seguida, passe-o para chc_client_send_data. O builder usa armazenamento fornecido pelo chamador e armazena ponteiros em vez de copiar os dados, portanto o armazenamento, as árvores de colunas, os tipos, os nomes e os slabs devem permanecer válidos durante o envio. Um INSERT envia a consulta, aguarda o bloco de cabeçalho do servidor, envia um ou mais blocos de dados e, em seguida, envia um bloco vazio para encerrar o fluxo.
chc_build_fixed aceita n_rows * elem_size bytes em little-endian; chc_build_string aceita offsets finais cumulativos exclusivos na ordem de bytes do host sobre um slab compactado. Os helpers retornam nós de coluna por valor. Aninhe-os para corresponder ao tipo: por exemplo, passe um nó fixo ou de string para chc_build_nullable, passe esse resultado para chc_build_array e acrescente a raiz do array. Tuple, LowCardinality, Map e colunas geo usam a mesma árvore: Map é Array(Tuple(K, V)). Todas as colunas em um bloco devem ter a mesma contagem de linhas no nível superior. A rotina de gravação verifica a árvore em relação ao tipo do ClickHouse analisado, mas o chamador deve dimensionar o armazenamento chc_block_col para cada acréscimo. Você também pode acrescentar diretamente uma coluna decodificada de chc_block_column para recodificá-la, ou chamar chc_block_write_cols com um array chc_block_col para dispensar o builder. Passar o builder por meio de chc_client_send_data, em vez do chc_block_write de nível inferior, permite que o cliente defina as opções do bloco com base na revision negociada e aplique compressão.

Compressão

Passe um modo de compressão e um codec configurado em chc_client_opts. O cliente descomprime os pacotes Data recebidos e comprime os enviados. O cabeçalho de compressão inclui adaptadores LZ4 e ZSTD; cada inicialização preenche apenas seus próprios slots, então chame ambos para dar suporte a qualquer um dos dois.
Para usar uma biblioteca de compressão para a qual o projeto não fornece um binding, defina você mesmo um chc_codec; a vtable está declarada em clickhouse-compression.h.

TLS

clickhouse-openssl.h fornece um backend chc_io baseado em SSL_read/SSL_write. Você controla o OpenSSL: a biblioteca nunca cria um SSL_CTX, verifica certificados, define o SNI nem chama SSL_connect / SSL_shutdown. Quando chc_io.read é acionado, o handshake já deve ter sido concluído.
ClickHouse Cloud e outras implantações com TLS ativado usam o protocolo nativo na porta 9440. Ambos os backends aceitam um callback opcional check_cancel, consultado entre leituras, e um prazo de leitura por meio de chc_openssl_io_set_deadline / chc_posix_io_set_deadline.

Cliente ioless (async)

clickhouse-async.h é uma variante ioless do cliente TCP para loops de eventos. Ele nunca acessa um socket: você fornece os bytes que recebeu e drena os bytes que ele quer enviar, controlando epoll, io_uring ou WaitLatchOrSocket por conta própria. As opções, os tipos de pacote e o construtor de blocos são os mesmos do cliente bloqueante. chc_async_client_init não faz E/S e não pode bloquear. O handshake é executado depois como uma máquina de estados que pode ser retomada, assim como cada envio e recebimento. Quando o parser avança além dos bytes que você forneceu, a chamada retorna CHC_WOULD_BLOCK em vez de bloquear — forneça mais bytes de entrada e chame novamente, e o parser será retomado no meio do bloco.
Seu pump move bytes nos dois sentidos. Na saída, chc_async_pending_out retorna um ponteiro e o tamanho dos bytes enfileirados; depois que o socket aceitar parte deles, chame chc_async_consume_out com essa quantidade; uma escrita parcial é aceitável. Na entrada, passe as leituras do socket para chc_async_submit. Os envios nunca bloqueiam nem fazem backpressure, então monitore o tamanho pendente de saída e pare de enviar quando ele ficar grande demais. Um driver liburing funcional está em test/test_async_uring.c.

Memória e o alocador

Cada ponto de entrada recebe uma vtable chc_alloc, portanto a alocação fica a cargo do mecanismo usado pelo host.
Defina CHC_PROVIDE_STDLIB_ALLOC antes de incluir clickhouse.h e chame chc_alloc_stdlib() para usar um alocador padrão baseado em malloc.

Erros e exceções do servidor

As funções retornam CHC_OK (0) ou um código CHC_ERR_* diferente de zero. O código é o valor de retorno; um chc_err alocado na pilha pelo chamador contém a mensagem legível para humanos. A biblioteca nunca aloca um erro no heap.
Erros de consulta no servidor não são falhas chc_err. Eles chegam pelo fluxo de pacotes como CHC_PKT_EXCEPTION, trazendo code, display_text e stack_trace do servidor. Reserve a verificação de chc_err para falhas de transporte, protocolo e decodificação.

Tipos de dados suportados

O leitor de bloco decodifica:
  • 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), que é decodificado como seu T interno
  • JSON e Object('json'), como colunas String com serialização de string (veja abaixo)
JSON e Object('json') são decodificados com serialização de string; defina output_format_native_write_json_as_string=1 na consulta. Cada linha suportada chega como um documento JSON em uma coluna CHC_COL_STRING. Construa a mesma estrutura com chc_build_string; o gravador emite o prefixo exigido pelo tipo convertido. Variant, Dynamic, AggregateFunction ainda não são decodificados e retornam CHC_ERR_TYPE; converta-os para String no servidor como alternativa.
Última modificação em 23 de julho de 2026