Description
Le formatRowBinary analyse les données ligne par ligne, au format binaire.
Les lignes et les valeurs se succèdent, sans séparateurs.
Comme les données sont au format binaire, le délimiteur après FORMAT RowBinary est strictement défini comme suit :
- N’importe quel nombre de caractères d’espacement :
' '(espace - code0x20)'\t'(tabulation - code0x09)'\f'(saut de page - code0x0C)
- Suivi d’exactement une séquence de retour à la ligne :
- style Windows
"\r\n" - ou style Unix
'\n'
- style Windows
- Immédiatement suivi de données binaires.
Ce format est moins efficace que le format Native, car il est basé sur les lignes.
Format binaire des types de données
Les données peuvent ensuite être examinées avec un éditeur hexadécimal.LEB128 non signé (Little Endian Base 128)
Un encodage d’entier non signé à largeur variable en little-endian, utilisé pour représenter la longueur de types de données de taille variable tels queString, Array et Map. Vous trouverez un exemple d’implémentation sur la page wiki de LEB128.
(U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
Tous les types entiers sont encodés sur un nombre d’octets approprié en little-endian. Les types signés (Int8 à Int256) utilisent la représentation en complément à deux. La plupart des langages permettent d’extraire ce type d’entiers à partir de tableaux d’octets, à l’aide d’outils intégrés ou de bibliothèques bien connues. Pour Int128/Int256 et UInt128/UInt256, qui dépassent la taille des entiers prise en charge nativement par la plupart des langages, une désérialisation personnalisée peut être nécessaire.
Bool
Les valeurs booléennes sont encodées sur un seul octet et peuvent être désérialisées commeUInt8.
0correspond àfalse1correspond àtrue
Float32, Float64
Little-endian : nombres à virgule flottante codés sur 4 octets pourFloat32 et 8 octets pour Float64. Comme pour les entiers, la plupart des langages fournissent les outils appropriés pour désérialiser ces valeurs.
BFloat16
BFloat16 (Brain Floating Point) est un format à virgule flottante sur 16 bits qui offre la plage de Float32 avec une précision réduite, ce qui le rend utile pour les charges de travail de machine learning. Le format de transmission correspond essentiellement aux 16 bits de poids fort d’une valeur Float32. Si votre langage ne le prend pas en charge nativement, le moyen le plus simple de le gérer consiste à lire et écrire des valeurs en UInt16, puis à les convertir depuis et vers Float32 : Pour convertir BFloat16 en Float32 (pseudocode) :BFloat16 :
Decimal32, Decimal64, Decimal128, Decimal256
Les types Decimal sont représentés sous forme d’entiers little-endian de la largeur en bits correspondante.Decimal32- 4 octets, ouInt32.Decimal64- 8 octets, ouInt64.Decimal128- 16 octets, ouInt128.Decimal256- 32 octets, ouInt256.
trunc effectue une troncature vers zéro (et non une division avec arrondi à l’inférieur, qui diffère pour les valeurs négatives), et scale est le nombre de chiffres après la virgule. Par exemple, pour Decimal(10, 2) (l’équivalent de Decimal32(2)), l’échelle est 2, et la valeur 12345 sera représentée sous la forme (123, 45).
La sérialisation nécessite l’opération inverse :
String
Les chaînes ClickHouse sont des séquences arbitraires d’octets. Il n’est pas nécessaire qu’elles soient en UTF-8 valide. Le préfixe de longueur correspond à la longueur en octets, et non au nombre de caractères. Encodage en deux parties :- Un entier de longueur variable (LEB128) qui indique la longueur de la chaîne en octets.
- Les octets bruts de la chaîne.
foobar sera encodée sur sept octets, comme suit :
FixedString
Contrairement àString, FixedString a une longueur fixe, définie dans le schéma. Il est encodé sous forme d’une séquence d’octets, complétée par des octets nuls en fin de chaîne si la valeur est plus courte que N.
Lors de la lecture d’une
FixedString, les octets nuls de fin peuvent correspondre soit au remplissage, soit à de véritables caractères \0 dans les données ; il est impossible de les distinguer dans la représentation binaire transmise. ClickHouse lui-même conserve les N octets tels quels.FixedString(3) vide ne contient que des octets nuls de remplissage :
FixedString(3) non vide contenant la chaîne de caractères hi :
FixedString(3) non vide contenant la chaîne bar :
Date
Stocké sous forme deUInt16 (deux octets), représentant le nombre de jours depuis le 1970-01-01.
Plage de valeurs prise en charge : [1970-01-01, 2149-06-06].
Exemples de valeurs sous-jacentes pour Date :
Date32
Stocké au formatInt32 (quatre octets) représentant le nombre de jours avant ou après 1970-01-01.
Plage de valeurs prise en charge : [0000-01-01, 9999-12-31].
Exemples de valeurs sous-jacentes pour Date32 :
DateTime
Stocké au formatUInt32 (quatre octets), représentant le nombre de secondes depuis 1970-01-01 00:00:00 UTC.
Syntaxe :
DateTime ou DateTime('UTC').
La valeur binaire est toujours un décalage par rapport à l’epoch UTC. Le fuseau horaire ne modifie pas l’encodage. En revanche, le fuseau horaire a bien une incidence sur la façon dont les chaînes de caractères sont interprétées lors de l’insertion : l’insertion de
'2024-01-15 10:30:00' dans une colonne DateTime('America/New_York') stocke une valeur d’epoch différente de celle obtenue en insérant la même chaîne dans une colonne DateTime('UTC'), car la chaîne est interprétée comme une heure locale dans le fuseau horaire de la colonne. Sur le wire, dans les deux cas, il s’agit simplement de secondes d’epoch UInt32.[1970-01-01 00:00:00, 2106-02-07 06:28:15].
Exemples de valeurs sous-jacentes pour DateTime :
DateTime64
Stocké sous la forme d’unInt64 (huit octets), représentant le nombre de ticks avant ou après 1970-01-01 00:00:00 UTC. La résolution des ticks est définie par le paramètre precision ; voir la syntaxe ci-dessous :
precision est un entier compris entre 0 et 9. En général, seules les valeurs suivantes sont utilisées : 3 (millisecondes), 6 (microsecondes),
9 (nanosecondes).
Exemples de définitions DateTime64 valides : DateTime64(0), DateTime64(3), DateTime64(6, 'UTC') ou DateTime64(9, 'Europe/Amsterdam').
Comme pour
DateTime, la valeur binaire est toujours un décalage par rapport à l’epoch UTC. Le fuseau horaire affecte la façon dont les chaînes sont interprétées lors de l’insertion (voir la note DateTime), mais l’encodage lui-même correspond toujours à des ticks Int64 depuis l’epoch UTC.Int64 sous-jacente du type DateTime64 peut être interprétée comme le nombre d’unités suivantes avant ou après l’epoch UNIX :
DateTime64(0)- secondes.DateTime64(3)- millisecondes.DateTime64(6)- microsecondes.DateTime64(9)- nanosecondes.
[0000-01-01 00:00:00, 9999-12-31 23:59:59.999999999] (pour une précision jusqu’à 7 ; les précisions 8 et 9 sont plus limitées, voir la note ci-dessous).
Exemples de valeurs sous-jacentes pour DateTime64 :
DateTime64(3): la valeur1546300800000représente2019-01-01 00:00:00 UTC.DateTime64(6): la valeur1705314600123456représente2024-01-15 10:30:00.123456 UTC.DateTime64(9): la valeur1705314600123456789représente2024-01-15 10:30:00.123456789 UTC.
Comme la plage sous-jacente des ticks
Int64 est plus limitée à des précisions plus élevées, la valeur maximale prise en charge diminue : avec une précision de 8, elle est de 4892-10-07 et avec une précision de 9 (nanosecondes), elle est de 2262-04-11 23:47:16 UTC.Time
Stocké au formatInt32, représentant une valeur temporelle en secondes. Les valeurs négatives sont valides.
Plage de valeurs prise en charge : [-999:59:59, 999:59:59] (c.-à-d. [-3599999, 3599999] secondes).
À l’heure actuelle, le paramètre
enable_time_time64_type doit être défini sur 1 pour utiliser Time ou Time64.Time :
Time64
Stocké en interne sous forme deDecimal64 (lui-même stocké sous forme de Int64), il représente une valeur d’heure avec des fractions de seconde, avec une précision configurable. Les valeurs négatives sont autorisées.
Syntaxe :
precision est un entier compris entre 0 et 9. Valeurs courantes : 3 (millisecondes), 6 (microsecondes), 9 (nanosecondes).
Plage de valeurs prise en charge : [-999:59:59.xxxxxxxxx, 999:59:59.xxxxxxxxx].
À l’heure actuelle, le paramètre
enable_time_time64_type doit être défini sur 1 pour utiliser Time ou Time64.Int64 sous-jacente représente des fractions de seconde mises à l’échelle par 10^precision.
Exemples de valeurs sous-jacentes pour Time64 :
Types d’intervalle
Tous les types d’intervalle sont stockés sous forme deInt64 (huit octets, little-endian). La valeur représente le nombre d’unités de temps correspondantes. Les valeurs négatives sont valides.
Les types d’intervalle sont : IntervalNanosecond, IntervalMicrosecond, IntervalMillisecond, IntervalSecond, IntervalMinute, IntervalHour, IntervalDay, IntervalWeek, IntervalMonth, IntervalQuarter, IntervalYear.
Le nom du type d’intervalle (par ex.,
IntervalSecond vs IntervalDay) détermine l’unité de la valeur stockée. L’encodage binaire est toujours le même.Enum8, Enum16
Stocké sous la forme d’un seul octet (Enum8 == Int8) ou de deux octets (Enum16 == Int16) représentant l’index de la valeur enum dans la définition de l’enum. Notez que le type de stockage est signé — les valeurs enum peuvent être négatives (par ex., Enum8('a' = -128, 'b' = 0)).
Un Enum peut être défini simplement, comme ceci :
\', ainsi que les caractères spéciaux comme = qui peuvent apparaître à l’intérieur de chaînes entre guillemets.
UUID
Représenté sous la forme d’une séquence de 16 octets. L’UUID est stocké sous la forme de deux valeursUInt64 little-endian : les 8 premiers octets de la représentation UUID standard sont inversés octet par octet, et les 8 octets suivants le sont indépendamment.
Par exemple, pour l’UUID 61f0c404-5cb3-11e7-907b-a6006ad3dba0 :
- Représentation standard en octets :
61 f0 c4 04 5c b3 11 e7|90 7b a6 00 6a d3 db a0 - Première moitié inversée (LE UInt64) :
e7 11 b3 5c 04 c4 f0 61 - Seconde moitié inversée (LE UInt64) :
a0 db d3 6a 00 a6 7b 90
UUID :
61f0c404-5cb3-11e7-907b-a6006ad3dba0est représenté comme suit :
- L’UUID par défaut
00000000-0000-0000-0000-000000000000est représenté sous la forme de 16 octets nuls :
IPv4
Stocké sur quatre octets sous la forme deUInt32 en ordre des octets little-endian. Notez que cela diffère de l’ordre traditionnel des octets réseau (big-endian) couramment utilisé pour les adresses IP. Exemples de valeurs sous-jacentes pour IPv4 :
IPv6
Stockée sur 16 octets, en big-endian / ordre des octets réseau (octet de poids fort en premier). Exemples de valeurs sous-jacentes pourIPv6 :
Nullable
Un type de données nullable est encodé comme suit :- Un seul octet indique si la valeur est
NULLou non :0x00signifie que la valeur n’est pasNULL.0x01signifie que la valeur estNULL.
- Si la valeur n’est pas
NULL, le type de données sous-jacent est encodé normalement. Si la valeur estNULL, aucun octet supplémentaire n’est écrit pour le type sous-jacent.
Nullable(UInt32) :
LowCardinality
Dans le format RowBinary, le marqueur de faible cardinalité n’affecte pas le format binaire. Par exemple,LowCardinality(String) est encodé de la même manière qu’un String classique.
Une colonne peut être définie comme
LowCardinality(Nullable(T)), mais il n’est pas possible de la définir comme Nullable(LowCardinality(T)) - cela entraînera toujours une erreur du serveur.1 pour autoriser la plupart des types de données dans LowCardinality afin d’améliorer la couverture.
Array
Un tableau est encodé de la manière suivante :- Un entier à longueur variable (LEB128) indiquant le nombre d’éléments du tableau.
- Les éléments du tableau, encodés de la même manière que le type de données sous-jacent.
UInt32 :
Un tableau peut contenir des valeurs de type Nullable, mais le tableau lui-même ne peut pas être de type Nullable.
Tuple
Un tuple est encodé avec tous ses éléments placés les uns à la suite des autres dans leur format binaire respectif, sans métadonnées ni délimiteurs supplémentaires.Map
Une map peut être vue comme unArray(Tuple(K, V)), où K est le type de clé et V le type de valeur. La map est encodée comme suit :
- Un entier de longueur variable (LEB128) indiquant le nombre d’éléments de la map.
- Les éléments de la map sous forme de paires clé-valeur, encodés selon leurs types correspondants.
String et des valeurs UInt32 :
Il est possible d’avoir des valeurs de type Map avec des structures profondément imbriquées, telles que
Map(String, Map(Int32, Array(Nullable(String)))), qui seront encodées de façon similaire à celle décrite ci-dessus.Variant
Ce type représente une union d’autres types de données. Le typeVariant(T1, T2, ..., TN) signifie que chaque ligne de ce type possède une valeur de type T1, T2, …, TN ou aucune de ces valeurs (valeur NULL).
Prenons l’exemple suivant :
NULL est codée avec un octet de discriminant de 0xFF :
Variant.
Dynamic
Le typeDynamic peut contenir des valeurs de n’importe quel type, dont le type est déterminé à l’exécution. Dans le format RowBinary, chaque valeur est auto-descriptive : la première partie correspond à la spécification du type dans ce format. Le contenu suit ensuite, avec l’encodage de la valeur tel qu’il est décrit dans ce document. Pour analyser une valeur, il suffit donc d’utiliser l’index de type pour déterminer le bon parseur, puis de réutiliser l’analyse RowBinary déjà implémentée ailleurs.
BinaryTypeIndex est un octet unique qui identifie le type. Consultez la référence ici pour connaître les indices de type et les paramètres.
Une valeur Dynamic NULL est encodée avec BinaryTypeIndex 0x00 (le type Nothing), sans octet supplémentaire :
JSON
Le type JSON encode les données en deux catégories distinctes :- Chemins typés - Chemins déclarés avec des types explicites dans le schéma (par ex.
JSON(user_id UInt32, name String)) - Chemins dynamiques/Chemins de débordement lorsque la limite de chemins dynamiques est dépassée - Chemins détectés à l’exécution et stockés avec le type
Dynamic. L’encodage de la valeur est précédé de la définition du type.
Les chemins sont sérialisés en trois groupes, écrits de manière séquentielle : les chemins typés, les chemins dynamiques, puis les chemins de données partagées (overflow). Les chemins typés et dynamiques sont écrits dans un ordre implementation-defined (déterminé par l’itération interne de la table de hachage), tandis que les chemins de données partagées sont écrits par ordre alphabétique. Les lecteurs ne doivent pas supposer un ordre particulier des chemins. Le désérialiseur distribue chaque chemin par son nom, et non par sa position.
Chaque ligne JSON au format RowBinary est sérialisée de la manière suivante :
JSON(user_id UInt32, active Bool)
Ligne : {"user_id": 42, "active": true}
Encodage binaire (hexadécimal avec annotations) :
JSON(user_id UInt32, active Bool)
Ligne : {"user_id": 42, "active": true, "name": "Alice"}
Encodage binaire (hexadécimal avec annotations) :
JSON(score Nullable(Int32))
Row: {"score": null }
Encodage binaire (hexadécimal avec annotations) :
JSON(name String)
Ligne : {"name": null}
Encodage binaire :
JSON(id UInt64)
Ligne : {"id": 100, "metadata": null}
Encodage binaire :
metadata avec une valeur NULL n’est pas inclus car les chemins dynamiques ne sont sérialisés que lorsqu’ils sont non-NULL. C’est une différence clé par rapport aux chemins typés.
4. Objets JSON imbriqués :
Schéma : JSON()
Ligne : {"user": {"name": "Bob", "age": 30}}
Encodage binaire (hexadécimal avec annotations) :
user.name au lieu d’une structure imbriquée).
Alternative : JSON en mode String
Avec le paramètre output_format_binary_write_json_as_string=1, les colonnes JSON sont sérialisées en une seule chaîne de texte JSON au lieu d’un format binaire structuré. Il existe un paramètre correspondant pour écrire dans des colonnes JSON, input_format_binary_read_json_as_string. Le choix du paramètre dépend ici de l’endroit où vous souhaitez analyser le JSON : côté client ou côté serveur.
Types Geo
Geo est une catégorie de types de données qui représentent des données géographiques. Elle comprend :Point- sous la forme deTuple(Float64, Float64).MultiPoint- sous la forme deArray(Point)ouArray(Tuple(Float64, Float64)).Ring- sous la forme deArray(Point)ouArray(Tuple(Float64, Float64)).Polygon- sous la forme deArray(Ring)ouArray(Array(Tuple(Float64, Float64))).MultiPolygon- sous la forme deArray(Polygon)ouArray(Array(Array(Tuple(Float64, Float64)))).LineString- sous la forme deArray(Point)ouArray(Tuple(Float64, Float64)).MultiLineString- sous la forme deArray(LineString)ouArray(Array(Tuple(Float64, Float64))).
RowBinaryWithNamesAndTypes contiendront les alias de ces types, par exemple Point, MultiPoint, Ring, Polygon, MultiPolygon, LineString et MultiLineString.
Geometry
Geometry est un type Variant capable de contenir n’importe lequel des types Geo listés ci-dessus. Dans le format binaire, il est encodé exactement comme un Variant, avec un octet de discriminant indiquant quel type Geo suit.
Les indices du discriminant pour Geometry sont :
Structure du format binaire :
Point sous forme de Geometry :
Ring sous forme de Geometry :
Nested
Le format binaire deNested dépend du paramètre flatten_nested.
flatten_nested = 1 (par défaut)
Avec le paramètre par défaut, Nested est aplati sous forme de tableaux indépendants. Chaque sous-colonne devient une colonne Array distincte avec un nom séparé par des points :
DESCRIBE TABLE foo affiche les colonnes aplaties :
flatten_nested = 0
Avec flatten_nested = 0, Nested est conservé sous la forme d’une seule colonne de type Array(Tuple(...)). Le nom de la colonne n’est pas séparé par des points :
DESCRIBE TABLE foo affiche une seule colonne :
Array(Tuple(String, Int32)) : un préfixe indiquant la longueur du tableau, puis les champs du tuple de chaque élément dans l’ordre :
SimpleAggregateFunction
SimpleAggregateFunction(func, T) est encodé de façon identique à son type de données sous-jacent T. Le nom de la fonction d’agrégation n’a aucun effet sur le format binaire.
Par exemple, SimpleAggregateFunction(max, UInt32) est encodé de la même manière qu’un simple UInt32 :
SimpleAggregateFunction(max, UInt32), mais dans la représentation binaire, la valeur n’est qu’un UInt32 :
AggregateFunction
AggregateFunction(func, T) stocke l’état intermédiaire complet d’une fonction d’agrégation. Contrairement à SimpleAggregateFunction, qui stocke elle aussi un état intermédiaire mais l’encode de façon identique au type de données sous-jacent, AggregateFunction stocke un blob binaire opaque dont le format est propre à chaque fonction d’agrégation.
Le format interne varie selon la fonction. Voici quelques exemples simples :
countState — stocke le nombre sous forme de VarUInt (LEB128) :
sumState — stocke la somme accumulée dans un entier de taille fixe. La taille dépend du type de l’argument (UInt64 pour les arguments entiers) :
minState / maxState — stocke un octet de drapeau suivi de la valeur dans le type sous-jacent. Le drapeau vaut 0x00 pour un état vide (aucune valeur rencontrée) ou 0x01 lorsqu’une valeur est présente :
Les fonctions plus complexes comme
uniq, quantile ou groupArray utilisent des formats propres à leur implémentation. Si vous devez lire ou écrire ces états, consultez le code source de ClickHouse pour la fonction concernée.aggregate_function_input_format vaut value ou array, une colonne AggregateFunction(func, T) est lue comme une valeur de type T (ou un Tuple des types des arguments s’il y en a plusieurs), ou comme un Array de telles valeurs, encodée exactement comme ce type l’est en RowBinary. L’état est construit à partir des valeurs côté serveur. Par exemple, avec aggregate_function_input_format = 'array', une cellule de AggregateFunction(avg, UInt32) est un Array(UInt32) : une longueur LEB128 suivie des éléments.
QBit
QBit est un type de vecteur permettant une recherche efficace avec différents niveaux de précision. En interne, il est stocké dans un format transposé. Dans le format binaire, QBit est simplement un Array du type d’élément sous-jacent (Int8, Float32, Float64 ou BFloat16). L’optimisation de transposition des bits pour le stockage s’effectue côté serveur, et non dans le protocole RowBinary.
Syntaxe :
element_type est Int8, Float32, Float64 ou BFloat16, et dimension est la dimension fixe du vecteur. Le paramètre stride, facultatif, contrôle uniquement la façon dont les plans de bits sont regroupés dans les flux de stockage côté serveur ; il n’affecte pas le format binaire RowBinary, qui correspond toujours au tableau complet de dimension éléments.
Format binaire : identique à Array(element_type) :
QBit(Float32, 4) contenant [1.0, 2.0, 3.0, 4.0] :
Paramètres du format
Les paramètres suivants sont communs à tous les formats de typeRowBinary.