Skip to main content
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

Ces éléments sont volontairement hors du périmètre. Gérez-les dans votre application ou avec une bibliothèque associée :
  • 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 SSL que vous avez déjà établi.
  • Gestion des threads. Chaque chc_client est monothread par conception.
  • E/S asynchrones au sein de la bibliothèque. Le client bloquant appelle chc_io.read de 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

Le décodeur lit sur le wire des noms de type affichables, ils doivent donc être encodés sous forme de texte. ClickHouse les écrit sous forme de texte par défaut, mais épinglez ce paramètre dans vos requêtes afin qu’un profil de serveur ou de session qui le définit en binaire ne puisse pas compromettre le décodage :

L’ajouter à votre projet

Il n’y a pas de paquet à installer, vous devez donc intégrer les fichiers d’en-tête à votre arborescence via un sous-module Git ou en les copiant. Une seule unité de traduction définit 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.
Définissez 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

Pour communiquer avec un serveur ClickHouse, vous configurez vous-même le socket, l’encapsulez dans un 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.
Chaque 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

Envoyez la requête, puis lisez les paquets jusqu’à 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.
Les exceptions du serveur arrivent sous forme de paquets 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

Les blocs sont orientés par colonne. Chaque colonne possède un layout physique, renvoyé par 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 :
Les données 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

Construisez des colonnes avec les helpers 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

Indiquez un mode de compression et un codec renseigné dans 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.
Pour utiliser une bibliothèque de compression pour laquelle le projet ne fournit pas de binding, définissez vous-même un 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é.
ClickHouse Cloud et les autres déploiements où TLS est activé utilisent le protocole natif sur le port 9440. Les deux backends acceptent une fonction de rappel 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.
Votre 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

Chaque point d’entrée prend en paramètre une vtable chc_alloc, de sorte que l’allocation s’appuie sur le mécanisme utilisé par l’hôte.
Définissez 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

Les fonctions renvoient 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.
Les erreurs de requête côté serveur ne sont pas des erreurs 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

Le lecteur de blocs décode :
  • 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), qui est décodé comme son T interne
  • JSON et Object('json'), en colonnes String avec 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.
Dernière modification le 23 juillet 2026