clickhouse-c est un client C header-only pour le protocole natif de ClickHouse.
Le code source et la référence de chaque en-tête se trouvent dans le dépôt GitHub.
Contrairement aux clients de plus haut niveau, il en fait délibérément très peu pour vous. L’en-tête principal décode et
encode des blocs au format Native via une fonction de rappel d’E/S que vous fournissez. Vous gérez
vous-même le socket, le contexte TLS, l’allocateur, les nouvelles tentatives et le pool de connexions. Cela le rend assez compact pour
être embarqué : inclure uniquement clickhouse.h n’ajoute aucune dépendance à l’édition de liens au-delà de libc.
Cette bibliothèque est en cours de développement actif. La v1 décode les principaux types ClickHouse.
Signalez les limitations ou les fonctionnalités manquantes via le suivi des issues.
Gardez toutefois à l’esprit que l’absence de certaines fonctionnalités est intentionnelle.
Ce que la bibliothèque ne fait pas
- Protocole HTTP. Encapsulez directement libcurl pour l’interface HTTP.
- Résolution DNS, basculement de point de terminaison, pool de connexions, nouvelles tentatives et backoff.
- Cycle de vie du contexte TLS. Le backend OpenSSL utilise un
SSLque vous avez déjà établi. - Gestion des threads. Chaque
chc_clientest monothread par conception. - E/S asynchrones au sein de la bibliothèque. Le client bloquant appelle
chc_io.readde manière synchrone. Pour un client piloté par une boucle d’événements qui ne réalise lui-même aucune E/S, utilisez le ioless client.
Organisation de la bibliothèque
clickhouse-c est fourni sous la forme d’un ensemble plat de fichiers d’en-tête. Chaque en-tête contient à la fois les déclarations et l’implémentation,
protégées par une macro sentinelle. Choisissez les en-têtes nécessaires à votre build.
Paramètre de serveur requis
L’ajouter à votre projet
CHC_IMPLEMENTATION et inclut l’implémentation ;
toutes les autres unités incluent les mêmes fichiers d’en-tête uniquement pour les déclarations.
CHC_PROVIDE_STDLIB_ALLOC avant d’inclure clickhouse.h pour utiliser chc_alloc_stdlib.
Définissez CHC_NO_LZ4 ou CHC_NO_ZSTD dans clickhouse-compression.h pour supprimer les dépendances à lz4/zstd.
Connexion via TCP
chc_io, puis le transmettez
à chc_client_init, qui exécute le handshake Hello de manière synchrone. La bibliothèque ne gère ni le DNS,
ni le failover, ni la reconnexion, ni le pool de connexions — cela relève de l’appelant.
chc_client est mono-thread et encapsule une connexion. La bibliothèque appelle les fonctions de rappel chc_io
de manière synchrone ; ce que ces fonctions de rappel font en arrière-plan (epoll, io_uring,
WaitLatchOrSocket) dépend de vous.
Exécuter une requête
CHC_PKT_END_OF_STREAM. Utilisez chc_client_send_query_ex pour
ajouter le paramètre serveur requis ; la version simple chc_client_send_query envoie une
liste de paramètres vide et hérite des paramètres par défaut du serveur.
CHC_PKT_EXCEPTION, et non comme un retour non-OK de
chc_client_recv_packet. Seules les défaillances au niveau du transport renvoient non-OK. Le premier paquet CHC_PKT_DATA
d’un résultat est un bloc d’en-tête décrivant le schéma avec zéro ligne ; les blocs de données suivent.
chc_packet_clear libère le bloc ou l’exception du paquet — définissez d’abord ces champs du paquet sur
null pour en prendre possession à la place.
Lecture des données de colonnes
chc_column_layout, sur
lequel vous effectuez le dispatch ; son type déclaré provient de chc_block_column_type. Les layouts composés sont imbriqués. Ainsi,
lire un Nullable(Array(String)) consiste à déballer le nullable, parcourir les offsets du tableau, puis
à découper les données de chaîne.
Un lecteur pour les colonnes numériques simples, de chaînes et nullables :
CHC_COL_FIXED sont en little-endian dans le format binaire ; sur les hôtes big-endian, vous devez permuter vous-même les octets des entiers multioctets.
Les offsets et les clés LowCardinality sont déjà convertis dans l’ordre natif de l’hôte au moment du décodage.
Les UUIDs se composent de deux moitiés UInt64 little-endian, IPv4 est un entier little-endian sur 4 octets, et IPv6 est en network byte order.
Les ticks DateTime64 sont en UTC — le timezone indiqué dans le type n’est qu’une métadonnée.
Lors de l’ingestion depuis un pair non fiable, appelez chc_column_validate sur chaque colonne avant de la parcourir.
chc_block_read ne valide pas les invariants entre champs, comme les offsets de tableau et les
clés LowCardinality, de sorte qu’un bloc falsifié pourrait sinon lire au-delà des limites de la colonne interne.
Insertion de données
chc_build_*, ajoutez-les à un chc_block_builder, puis passez-le à
chc_client_send_data. Le builder utilise le stockage fourni par l’appelant et enregistre des pointeurs au lieu de
copier les données ; le stockage, les arbres de colonnes, les types, les noms et les slabs doivent donc rester valides jusqu’à la fin de l’envoi. Un INSERT envoie
la requête, attend le bloc d’en-tête du serveur, envoie un ou plusieurs blocs de données, puis envoie un bloc vide pour terminer le flux.
chc_build_fixed prend n_rows * elem_size octets en little-endian ; chc_build_string prend des
offsets de fin cumulatifs exclusifs dans l’ordre des octets de l’hôte sur un slab compact. Les helpers renvoient des nœuds de colonne par
valeur. Imbriquez-les pour correspondre au type : par exemple, passez un nœud fixe ou chaîne à
chc_build_nullable, passez ce résultat à chc_build_array, puis ajoutez la racine du tableau. Tuple,
LowCardinality, Map et les colonnes Geo utilisent le même arbre : Map est Array(Tuple(K, V)).
Toutes les colonnes d’un block doivent avoir le même nombre de lignes au niveau supérieur. Le writer vérifie l’arbre par rapport
au type ClickHouse analysé, mais l’appelant doit dimensionner le stockage chc_block_col pour chaque ajout.
Vous pouvez aussi ajouter directement une colonne décodée depuis chc_block_column pour la réencoder, ou appeler
chc_block_write_cols avec un tableau chc_block_col pour vous passer du builder. Faire passer le builder par
chc_client_send_data plutôt que par chc_block_write, de niveau inférieur, permet au client de définir les options du block
à partir de la revision négociée et d’appliquer la compression.
Compression
chc_client_opts. Le client décompresse les
paquets Data entrants et compresse les paquets sortants. L’en-tête de compression inclut les adaptateurs LZ4 et ZSTD ;
chaque initialisation ne renseigne que ses propres emplacements, appelez donc les deux pour prendre en charge l’un comme l’autre.
chc_codec ;
la vtable est déclarée dans clickhouse-compression.h.
TLS
clickhouse-openssl.h fournit un backend chc_io basé sur SSL_read/SSL_write. Vous pilotez OpenSSL :
la bibliothèque ne crée jamais de SSL_CTX, ne vérifie pas les certificats, ne configure pas le SNI et n’appelle pas SSL_connect /
SSL_shutdown. Lorsque chc_io.read se déclenche, le handshake doit déjà être terminé.
check_cancel facultative, sondée entre les lectures, ainsi qu’une
échéance de lecture via chc_openssl_io_set_deadline / chc_posix_io_set_deadline.
Client ioless (async)
clickhouse-async.h est une variante ioless du client TCP conçue pour les boucles d’événements. Il n’accède jamais à un
socket : vous lui fournissez les octets reçus et récupérez ceux qu’il veut envoyer, en pilotant vous-même epoll,
io_uring ou WaitLatchOrSocket. Les options, les types de paquets et le block builder sont les
mêmes que pour le client bloquant.
chc_async_client_init n’effectue aucune E/S et ne peut pas bloquer. Le handshake s’exécute ensuite sous la forme d’une
machine à états reprenable, tout comme chaque envoi et chaque réception. Lorsque l’analyse dépasse les octets que vous avez fournis, l’appel
renvoie CHC_WOULD_BLOCK au lieu de bloquer — fournissez davantage d’octets entrants et relancez l’appel, et l’analyseur
reprendra au milieu du bloc.
pump déplace les octets dans les deux sens. En sortie, chc_async_pending_out renvoie un pointeur et une longueur
vers les octets en file d’attente ; une fois qu’une partie a été acceptée par le socket, appelez chc_async_consume_out avec ce nombre ; une
écriture partielle est acceptée. En entrée, transmettez à chc_async_submit les données lues sur le socket. Les envois ne bloquent jamais et n’exercent pas de
backpressure ; surveillez donc la longueur des données en attente de sortie et cessez d’émettre des envois lorsqu’elle devient trop importante.
Un driver liburing fonctionnel se trouve dans
test/test_async_uring.c.
Mémoire et l’allocateur
chc_alloc, de sorte que l’allocation s’appuie sur le mécanisme utilisé par l’hôte.
CHC_PROVIDE_STDLIB_ALLOC avant d’inclure clickhouse.h et appelez chc_alloc_stdlib() pour utiliser un
allocateur standard basé sur malloc.
Erreurs et exceptions du server
CHC_OK (0) ou un code CHC_ERR_* non nul. Le code est la valeur de retour ; un
chc_err alloué sur la pile de l’appelant contient le message compréhensible par l’être humain. La bibliothèque n’alloue jamais
d’erreur sur le tas.
chc_err. Elles arrivent dans le flux de paquets sous la forme de
CHC_PKT_EXCEPTION, avec le code, le display_text et le stack_trace du serveur. Réservez les vérifications
chc_err aux défaillances de transport, de protocole et de décodage.
Types de données pris en charge
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), qui est décodé comme sonTinterneJSONetObject('json'), en colonnesStringavec la sérialisation chaîne (voir ci-dessous)
JSON et Object('json') sont décodés avec la sérialisation chaîne ; définissez
output_format_native_write_json_as_string=1 sur la requête. Chaque ligne prise en charge arrive sous la forme d’un
document JSON dans une colonne CHC_COL_STRING. Construisez la même structure avec chc_build_string ;
le writer émet le préfixe requis par le type analysé.
Variant, Dynamic, AggregateFunction ne sont pas encore décodés et renvoient CHC_ERR_TYPE ;
convertissez-les en String côté serveur en solution de repli.