clickhouse-c é um cliente C header-only para o ClickHouse protocolo nativo.
O código-fonte e a referência de cada cabeçalho estão no repositório do GitHub.
Ao contrário dos clientes de nível mais alto, ele faz pouca coisa por você de forma intencional. O cabeçalho principal decodifica e
codifica blocos no formato Native por meio de um callback de E/S fornecido por você. Você é responsável
pelo socket, contexto de TLS, alocador, tentativas e pool de conexões. Isso o torna pequeno o suficiente para
ser embutido: incluir apenas clickhouse.h não traz dependências de linkedição 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.
Mas entenda que a ausência de certas funcionalidades nesta biblioteca é intencional.
O que a biblioteca não faz
- Protocolo HTTP. Encapsule o libcurl diretamente 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
SSLao qual você já se conectou. - Uso de threads. Cada
chc_clienté single-threaded por definição. - E/S assíncrona na biblioteca. O cliente bloqueante chama
chc_io.readde forma síncrona. Para um cliente orientado a loop de eventos que não faz nenhuma E/S por conta própria, use o cliente sem E/S.
Como a biblioteca é organizada
clickhouse-c é fornecido como um conjunto plano 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.
| Cabeçalho | Finalidade | Flags de linkedição |
|---|---|---|
clickhouse.h | Núcleo: tipos, erros, alocador, vtable de E/S, parser de nomes de tipos, leitor e gravador de bloco | — |
clickhouse-client.h | Loop de pacotes TCP: Hello, Query, Data, EndOfStream, Exception, Progress, Pong | — |
clickhouse-async.h | Cliente sem E/S: o mesmo loop de pacotes, acionado pelo envio de bytes pelo chamador, sem socket | — |
clickhouse-compression.h | Layout de frame comprimido, CityHash128, despacho de codec e adaptadores LZ4/ZSTD | -llz4 -lzstd |
clickhouse-posix-io.h | Backend de E/S sobre read(2)/write(2) bloqueantes | — |
clickhouse-openssl.h | Backend de E/S sobre SSL_read/SSL_write | -lssl -lcrypto |
Configuração obrigatória do servidor
Adicionando isso ao seu projeto
CHC_IMPLEMENTATION e inclui a implementação;
todas as outras unidades incluem os mesmos cabeçalhos apenas para as declarações.
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 via TCP
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 pooling — isso fica a cargo de quem faz a chamada.
chc_client é de thread única e encapsula uma conexão. A biblioteca chama os
callbacks de chc_io de forma síncrona; o que esses callbacks fazem por baixo dos panos (epoll, io_uring,
WaitLatchOrSocket) fica a seu critério.
Executando uma consulta
CHC_PKT_END_OF_STREAM. Use chc_client_send_query_ex para
anexar a configuração de servidor necessária; a versão simples de chc_client_send_query envia uma
lista vazia de configurações e herda as configurações padrão do servidor.
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
chc_column_layout, com
base no qual você faz o despacho; 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,
recortar os dados de string.
| Layout | Acessores |
|---|---|
CHC_COL_FIXED | chc_column_fixed_data(c, &elem_size) — n_rows * elem_size bytes em little-endian |
CHC_COL_STRING | chc_column_string_data(c), chc_column_string_offsets(c) — offsets[i] é o fim exclusivo da linha i na ordem de bytes do host; a linha 0 começa em 0 |
CHC_COL_NULLABLE | chc_column_null_map(c) (um byte por linha, 1 = NULL), chc_column_nullable_inner(c) |
CHC_COL_ARRAY | chc_column_array_offsets(c) (fins cumulativos), chc_column_array_values(c); Map é decodificado como Array(Tuple(K, V)) |
CHC_COL_TUPLE | chc_column_tuple_arity(c), chc_column_tuple_child(c, i) — cada filho tem o mesmo número de linhas |
CHC_COL_LOW_CARDINALITY | chc_column_lc_key_size(c) (1/2/4/8), chc_column_lc_keys(c), chc_column_lc_dict(c); o slot 0 do Dicionário é o valor padrão |
CHC_COL_FIXED são little-endian no wire; em hosts big-endian, você mesmo deve inverter a ordem dos bytes
dos 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 usa
network byte order. Os ticks de DateTime64 são UTC — o fuso horário no tipo é apenas metadata.
Ao fazer 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 acabar lendo além dos limites da coluna interna.
Inserindo dados
chc_block_builder e, em seguida, passe-o para chc_client_send_data. O builder registra
ponteiros em vez de copiar os dados, portanto os slabs das colunas devem permanecer válidos após 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_block_builder_append_fixed recebe n_rows * elem_size bytes little-endian;
chc_block_builder_append_string recebe offsets finais exclusivos cumulativos na ordem de bytes do host sobre uma
slab compactada. Encaminhar o builder por meio de chc_client_send_data, em vez do
chc_block_write de nível mais baixo, permite que o client defina as opções do bloco com base na revisão negociada e aplique
compressão.
Compressão
chc_client_opts. O cliente descomprime os
pacotes de dados de entrada e comprime os de saída. O cabeçalho de compressão inclui adaptadores LZ4 e ZSTD;
cada inicialização preenche apenas seus próprios slots, então chame ambas para dar suporte a qualquer um deles.
chc_codec;
a vtable é 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 SNI nem chama SSL_connect /
SSL_shutdown. Quando chc_io.read é acionado, o handshake já deve ter sido concluído.
check_cancel, verificado entre leituras, e um
limite de tempo de leitura via chc_openssl_io_set_deadline / chc_posix_io_set_deadline.
Cliente sem E/S (assíncrono)
clickhouse-async.h é uma variante sem E/S do cliente TCP para loops de eventos. Ele nunca acessa um
socket: você fornece os bytes recebidos 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 bloco 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 todo envio e recebimento. Quando um parse ultrapassa os 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 retoma no meio do bloco.
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 aceita alguns deles, chame chc_async_consume_out com essa contagem; uma
gravação parcial é aceitável. Na entrada, envie as leituras do socket para chc_async_submit. Os envios nunca bloqueiam nem aplicam
backpressure, então monitore o tamanho pendente de saída e pare de emitir envios quando ele ficar grande demais.
Um driver liburing funcional está em
test/test_async_uring.c.
Memória e o alocador
chc_alloc, então a alocação segue o esquema usado pelo host.
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
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 do chamador contém a mensagem legível por pessoas. A biblioteca nunca aloca
um erro no heap.
chc_err. Eles chegam pelo fluxo de pacotes como
CHC_PKT_EXCEPTION, carregando 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 compatíveis
Int8–Int256,UInt8–UInt256Float32,Float64,BFloat16BoolDecimal32,Decimal64,Decimal128,Decimal256Date,Date32,DateTime,DateTime64,Time,Time64String,FixedString(N)UUID,IPv4,IPv6Enum8,Enum16Nullable(T),Array(T),Tuple(...),Map(K, V),Nested(...)LowCardinality(T)IntervalQBit(...)Point,Ring,Polygon,MultiPolygonSimpleAggregateFunction(f, T), que é decodificada como seuTinternoJSONeObject('json'), como colunasStringcom serialização de string (veja abaixo)
JSON e Object('json') são decodificados somente quando a consulta define output_format_native_write_json_as_string=1.
Cada linha chega como um documento JSON em uma coluna CHC_COL_STRING, então os acessores de string o leem;
o builder grava o mesmo formato com chc_block_builder_append_json_string. Qualquer outra versão de serialização
de JSON retorna CHC_ERR_TYPE, informando o nome da configuração.
Variant, Dynamic, AggregateFunction ainda não são decodificados e retornam CHC_ERR_TYPE;
converta-os para String no servidor como fallback.