clickhouse-c es un client de C de solo cabecera para el protocolo nativo de ClickHouse.
El código fuente y la referencia de cada cabecera están en el repositorio de GitHub.
A diferencia de los clients de más alto nivel, deliberadamente hace muy poco por ti. La cabecera principal decodifica y
codifica bloques en formato Native mediante una callback de E/S que tú proporcionas. Tú te encargas
del socket, el contexto TLS, el asignador de memoria, los reintentos y el pooling de conexiones. Eso hace que sea lo bastante pequeño como para
integrarse: incluir solo clickhouse.h no añade dependencias de enlazado aparte de libc.
Esta biblioteca está en desarrollo activo. La v1 decodifica los tipos básicos de ClickHouse.
Informa sobre limitaciones o funcionalidades faltantes mediante el rastreador de issues.
Ten en cuenta, sin embargo, que a esta biblioteca le falta funcionalidad por diseño.
Lo que la biblioteca no hace
- Protocolo HTTP. Envuelva libcurl directamente para la interfaz HTTP.
- Resolución DNS, failover de endpoints, pooling de conexiones, reintentos y backoff.
- Ciclo de vida del contexto TLS. El backend de OpenSSL usa un
SSLque ya debe estar conectado. - Hilos. Cada
chc_clientes monohilo por diseño. - E/S asíncrona dentro de la biblioteca. El client bloqueante llama a
chc_io.readde forma síncrona. Para un client de bucle de eventos que no realiza ninguna E/S por sí mismo, use el client sin E/S.
Cómo se organiza la biblioteca
clickhouse-c se distribuye como un conjunto simple de archivos de cabecera. Cada cabecera incluye tanto las declaraciones como la implementación,
protegidas por una macro centinela. Selecciona las cabeceras que necesite tu compilación.
| Cabecera | Propósito | Flags de enlace |
|---|---|---|
clickhouse.h | Núcleo: tipos, errores, asignador, vtable de E/S, parser de nombres de tipos, lector y escritor de bloques | — |
clickhouse-client.h | Bucle de paquetes TCP: Hello, Query, Data, EndOfStream, Exception, Progress, Pong | — |
clickhouse-async.h | client sin E/S: el mismo bucle de paquetes, controlado por el envío de bytes por parte del llamador, sin socket | — |
clickhouse-compression.h | Estructura de tramas comprimidas, CityHash128, despacho de codecs y adaptadores LZ4/ZSTD | -llz4 -lzstd |
clickhouse-posix-io.h | Backend de E/S sobre read(2)/write(2) en modo bloqueante | — |
clickhouse-openssl.h | Backend de E/S sobre SSL_read/SSL_write | -lssl -lcrypto |
Configuración del servidor requerida
Añadirlo a tu proyecto
CHC_IMPLEMENTATION e incluye la implementación;
todas las demás unidades incluyen las mismas cabeceras solo para las declaraciones.
CHC_PROVIDE_STDLIB_ALLOC antes de incluir clickhouse.h para usar chc_alloc_stdlib.
Define CHC_NO_LZ4 o CHC_NO_ZSTD para clickhouse-compression.h para prescindir de la dependencia de lz4/zstd.
Conexión por TCP
chc_io y pasárselo a chc_client_init, que ejecuta el handshake Hello de forma síncrona. La biblioteca no se encarga de DNS, failover, reconexión ni pooling; eso queda a cargo del llamador.
chc_client es monohilo y encapsula una conexión. La biblioteca invoca los
callbacks de chc_io de forma síncrona; lo que hagan internamente esos callbacks (epoll, io_uring,
WaitLatchOrSocket) depende de usted.
Ejecutar una consulta
CHC_PKT_END_OF_STREAM. Use chc_client_send_query_ex para
incluir la configuración de servidor requerida; chc_client_send_query, sin más, envía una
lista de configuraciones vacía y hereda los valores predeterminados del servidor.
CHC_PKT_EXCEPTION, no como un valor distinto de OK devuelto por
chc_client_recv_packet. Solo los fallos de nivel de transporte devuelven un valor no OK. El primer paquete CHC_PKT_DATA
de un resultado es un bloque de encabezado que describe el esquema con cero filas; después vienen los bloques de datos.
chc_packet_clear libera el bloque o la excepción del paquete; primero establezca esos campos del paquete en NULL para
quedarse con la propiedad en su lugar.
Lectura de datos de columnas
chc_column_layout, y el despacho se hace en función de ella; su tipo declarado proviene de chc_block_column_type. Las disposiciones compuestas se anidan, por lo que leer un Nullable(Array(String)) implica desenvolver el Nullable, recorrer los desplazamientos del array y, después, segmentar los datos de texto.
| Disposición | Accesores |
|---|---|
CHC_COL_FIXED | chc_column_fixed_data(c, &elem_size) — n_rows * elem_size bytes en orden little-endian |
CHC_COL_STRING | chc_column_string_data(c), chc_column_string_offsets(c) — offsets[i] es el final exclusivo de la fila i en el orden de bytes del host; la fila 0 empieza en 0 |
CHC_COL_NULLABLE | chc_column_null_map(c) (un byte por fila, 1 = NULL), chc_column_nullable_inner(c) |
CHC_COL_ARRAY | chc_column_array_offsets(c) (finales acumulados), chc_column_array_values(c); Map se decodifica como Array(Tuple(K, V)) |
CHC_COL_TUPLE | chc_column_tuple_arity(c), chc_column_tuple_child(c, i) — cada hijo tiene el mismo número de filas |
CHC_COL_LOW_CARDINALITY | chc_column_lc_key_size(c) (1/2/4/8), chc_column_lc_keys(c), chc_column_lc_dict(c); la ranura 0 del diccionario es el valor predeterminado |
CHC_COL_FIXED son little-endian en la transmisión; en hosts big-endian debes intercambiar manualmente el orden de bytes de los
enteros de varios bytes. Los desplazamientos y las claves de LowCardinality ya se convierten al
orden del host durante la decodificación. Los UUIDs constan de dos mitades UInt64 little-endian, IPv4 es un entero little-endian de 4 bytes, e IPv6 está en
orden de bytes de red. Los ticks de DateTime64 son UTC; la zona horaria del tipo es solo metadatos.
Al ingestar desde un peer no confiable, llama a chc_column_validate en cada columna antes de recorrerla.
chc_block_read no valida invariantes entre campos, como los desplazamientos de los arrays y las
claves de LowCardinality, por lo que un bloque falsificado podría leer fuera de los límites de las columnas internas.
Inserción de datos
chc_block_builder y luego páselo a chc_client_send_data. El constructor registra
punteros en lugar de copiar los datos, por lo que los bloques de memoria de las columnas deben seguir siendo válidos hasta después del envío. Un INSERT envía la consulta,
espera el bloque de cabecera del servidor, envía uno o más bloques de datos y luego envía un bloque vacío para
terminar el flujo.
chc_block_builder_append_fixed recibe n_rows * elem_size bytes en formato little-endian;
chc_block_builder_append_string recibe desplazamientos finales exclusivos acumulativos en el orden de bytes del host sobre un
slab packed. Enviar el builder a través de chc_client_send_data en lugar de la función de nivel inferior
chc_block_write permite que el client establezca las opciones del block a partir de la revisión negociada y aplique
compresión.
Compresión
chc_client_opts. El client descomprime los
paquetes Data entrantes y comprime los salientes. El encabezado de compresión incluye adaptadores LZ4 y ZSTD;
cada inicialización solo completa sus propios campos, así que llame a ambas para admitir cualquiera de los dos.
chc_codec;
la vtable está declarada en clickhouse-compression.h.
TLS
clickhouse-openssl.h proporciona un backend chc_io basado en SSL_read/SSL_write. OpenSSL se maneja externamente:
la biblioteca nunca crea un SSL_CTX, verifica certificados, configura SNI ni llama a SSL_connect /
SSL_shutdown. Para cuando se invoque chc_io.read, el handshake debe haberse completado.
check_cancel, que se consulta entre lecturas, y un
tiempo límite de lectura mediante chc_openssl_io_set_deadline / chc_posix_io_set_deadline.
Client ioless (asíncrono)
clickhouse-async.h es una variante ioless del client TCP para bucles de eventos. Nunca toca un
socket: tú le entregas los bytes que has recibido y extraes los bytes que quiere enviar, gestionando epoll,
io_uring o WaitLatchOrSocket por tu cuenta. Las opciones, los tipos de paquetes y el constructor de bloques son los
mismos que en el client con bloqueo.
chc_async_client_init no realiza ninguna E/S y no puede bloquearse. El handshake se ejecuta después como una
máquina de estados reanudable, al igual que cada envío y recepción. Cuando el parser supera los bytes que has proporcionado, la
llamada devuelve CHC_WOULD_BLOCK en lugar de bloquearse: proporciona más bytes de entrada y vuelve a llamar, y el
parser se reanuda a mitad del bloque.
pump mueve bytes en ambos sentidos. En sentido saliente, chc_async_pending_out devuelve un puntero y una longitud
de los bytes en cola; después de que el socket acepte algunos, llama a chc_async_consume_out con esa cantidad; no pasa nada si la
escritura es parcial. En sentido entrante, pasa las lecturas del socket a chc_async_submit. Los envíos nunca se bloquean ni aplican
backpressure, así que vigila la longitud de salida pendiente y deja de enviar cuando crezca demasiado.
Hay un driver funcional de liburing en
test/test_async_uring.c.
La memoria y el asignador
chc_alloc, por lo que la asignación depende del mecanismo que use el host.
CHC_PROVIDE_STDLIB_ALLOC antes de incluir clickhouse.h y llame a chc_alloc_stdlib() para usar un
asignador estándar basado en malloc.
Errores y excepciones del servidor
CHC_OK (0) o un código CHC_ERR_* distinto de cero. El código es el valor de retorno; un
chc_err asignado en la pila del llamador contiene el mensaje legible para humanos. La biblioteca nunca asigna
un error en el heap.
chc_err. Llegan por el flujo de paquetes como
CHC_PKT_EXCEPTION, con el code, display_text y stack_trace del servidor. Reserve la
comprobación de chc_err para los fallos de transporte, protocolo y decodificación.
Tipos de datos compatibles
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 se decodifica como suTinternaJSONyObject('json'), como columnasStringcon serialización de cadenas (consulte más abajo)
JSON y Object('json') solo se decodifican cuando la consulta establece output_format_native_write_json_as_string=1.
Cada fila llega como un documento JSON en una columna CHC_COL_STRING, por lo que los accesores de cadenas pueden leerlo;
el constructor escribe la misma estructura con chc_block_builder_append_json_string. Cualquier otra versión de serialización JSON
devuelve CHC_ERR_TYPE e indica el nombre de la configuración.
Variant, Dynamic, AggregateFunction aún no se decodifican y devuelven CHC_ERR_TYPE;
conviértalos a String del lado del servidor como alternativa.