Descripción
El formatoRowBinary analiza los datos fila por fila en formato binario.
Las filas y los valores se muestran consecutivamente, sin separadores.
Como los datos están en formato binario, el delimitador después de FORMAT RowBinary se especifica estrictamente de la siguiente manera:
- Cualquier cantidad de espacios en blanco:
' '(espacio - código0x20)'\t'(tabulación - código0x09)'\f'(salto de página - código0x0C)
- Seguido de exactamente una secuencia de salto de línea:
- estilo Windows
"\r\n" - o estilo Unix
'\n'
- estilo Windows
- Seguido inmediatamente de datos binarios.
Este formato es menos eficiente que el formato Native, ya que está basado en filas.
Formato de transmisión de los tipos de datos
A continuación, los datos pueden examinarse con un editor hexadecimal.LEB128 sin signo (Base 128 little-endian)
Una codificación de enteros sin signo de longitud variable en formato little-endian que se usa para codificar la longitud de tipos de datos de tamaño variable, comoString, Array y Map. Se puede encontrar una implementación de ejemplo en la página de Wikipedia sobre LEB128.
(U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
Todos los tipos enteros se codifican con el número de bytes correspondiente en formato little-endian. Los tipos con signo (Int8 a Int256) usan la representación en complemento a dos. La mayoría de los lenguajes permiten extraer estos enteros de arrays de bytes, ya sea con herramientas integradas o con bibliotecas conocidas. Para Int128/Int256 y UInt128/UInt256, que superan el tamaño de entero nativo de la mayoría de los lenguajes, puede ser necesaria una deserialización personalizada.
Bool
Los valores booleanos se codifican en un solo byte y pueden deserializarse de forma similar aUInt8.
0esfalse1estrue
Float32, Float64
Números de coma flotante en formato little-endian, codificados en 4 bytes paraFloat32 y en 8 bytes para Float64. Al igual que con los enteros, la mayoría de los lenguajes ofrecen herramientas adecuadas para deserializar estos valores.
BFloat16
BFloat16 (Brain Floating Point) es un formato de punto flotante de 16 bits con el mismo rango que Float32, pero con menor precisión, lo que lo hace útil para cargas de trabajo de aprendizaje automático. El formato de transmisión es, básicamente, los 16 bits más altos de un valor Float32. Si tu lenguaje no lo admite de forma nativa, la forma más sencilla de manejarlo es leerlo y escribirlo como UInt16, convirtiéndolo desde y hacia Float32: Para convertir BFloat16 a Float32 (pseudocódigo):BFloat16:
Decimal32, Decimal64, Decimal128, Decimal256
Los tipos Decimal se representan como enteros little-endian con el ancho en bits correspondiente.Decimal32- 4 bytes, oInt32.Decimal64- 8 bytes, oInt64.Decimal128- 16 bytes, oInt128.Decimal256- 32 bytes, oInt256.
trunc realiza el truncado hacia cero (no la división con redondeo hacia abajo, que da un resultado distinto para valores negativos), y scale es el número de dígitos después del punto decimal. Por ejemplo, para Decimal(10, 2) (equivalente a Decimal32(2)), la escala es 2, y el valor 12345 se representará como (123, 45).
La serialización requiere la operación inversa:
String
Las cadenas de ClickHouse son secuencias arbitrarias de bytes. No es necesario que sean UTF-8 válidas. El prefijo de longitud es la longitud en bytes, no el número de caracteres. Se codifican en dos partes:- Un entero de longitud variable (LEB128) que indica la longitud de la cadena en bytes.
- Los bytes en bruto de la cadena.
foobar se codificará usando siete bytes de la siguiente manera:
FixedString
A diferencia deString, FixedString tiene una longitud fija, definida en el esquema. Se codifica como una secuencia de bytes, rellenada con bytes cero al final si el valor es más corto que N.
Al leer un
FixedString, los bytes cero finales pueden ser relleno o caracteres \0 reales de los datos; no se pueden distinguir en la transmisión. El propio ClickHouse conserva los N bytes tal cual.FixedString(3) vacío contiene únicamente ceros de relleno:
FixedString(3) no vacía que contiene la cadena hi:
FixedString(3) no vacío que contiene la cadena bar:
Date
Se almacena comoUInt16 (dos bytes), que representa el número de días desde 1970-01-01.
Rango de valores admitido: [1970-01-01, 2149-06-06].
Valores subyacentes de ejemplo para Date:
Date32
Se almacena comoInt32 (cuatro bytes), que representa el número de días antes o después de 1970-01-01.
Rango de valores admitido: [0000-01-01, 9999-12-31].
Ejemplos de valores subyacentes para Date32:
DateTime
Se almacena comoUInt32 (cuatro bytes), que representa el número de segundos transcurridos desde 1970-01-01 00:00:00 UTC.
Sintaxis:
DateTime o DateTime('UTC').
El valor binario siempre es un desplazamiento de época en UTC. La zona horaria no cambia la codificación. Sin embargo, la zona horaria sí afecta a cómo se interpretan los valores de cadena al insertar: insertar
'2024-01-15 10:30:00' en una columna DateTime('America/New_York') almacena un valor de época distinto que insertar la misma cadena en una columna DateTime('UTC'), porque la cadena se interpreta como hora local en la zona horaria de la columna. En la transmisión, ambos son simplemente segundos de época UInt32.[1970-01-01 00:00:00, 2106-02-07 06:28:15].
Valores subyacentes de ejemplo para DateTime:
DateTime64
Se almacena comoInt64 (ocho bytes), que representa el número de ticks anteriores o posteriores a 1970-01-01 00:00:00 UTC. La resolución del tick se define mediante el parámetro precision; consulte la sintaxis a continuación:
precision es un entero de 0 a 9. Por lo general, solo se usan los siguientes: 3 (milisegundos), 6 (microsegundos),
9 (nanosegundos).
Ejemplos de definiciones válidas de DateTime64: DateTime64(0), DateTime64(3), DateTime64(6, 'UTC') o DateTime64(9, 'Europe/Amsterdam').
Al igual que con
DateTime, el valor binario siempre es un desplazamiento respecto de la época UTC. La zona horaria afecta a cómo se interpretan los valores de cadena al insertarlos (consulta la nota de DateTime), pero la codificación en sí siempre son ticks Int64 desde la época UTC.Int64 subyacente del tipo DateTime64 puede interpretarse como la cantidad de las siguientes unidades con respecto a la época UNIX, ya sea antes o después de ella:
DateTime64(0)- segundos.DateTime64(3)- milisegundos.DateTime64(6)- microsegundos.DateTime64(9)- nanosegundos.
[0000-01-01 00:00:00, 9999-12-31 23:59:59.999999999] (para precisiones de hasta 7; las precisiones 8 y 9 tienen un rango más limitado; consulta la nota a continuación).
Valores subyacentes de ejemplo para DateTime64:
DateTime64(3): el valor1546300800000representa2019-01-01 00:00:00 UTC.DateTime64(6): el valor1705314600123456representa2024-01-15 10:30:00.123456 UTC.DateTime64(9): el valor1705314600123456789representa2024-01-15 10:30:00.123456789 UTC.
Debido a que el rango de ticks
Int64 subyacente es más limitado a mayor precisión, el valor máximo admitido se reduce: con precisión 8 es 4892-10-07 y con precisión 9 (nanosegundos) es 2262-04-11 23:47:16 en UTC.Time
Se almacena comoInt32 y representa un valor de tiempo en segundos. Los valores negativos son válidos.
Rango de valores admitido: [-999:59:59, 999:59:59] (es decir, [-3599999, 3599999] segundos).
Por el momento, la configuración
enable_time_time64_type debe establecerse en 1 para usar Time o Time64.Time:
Time64
Se almacena internamente como unDecimal64 (que a su vez se almacena como Int64) y representa un valor de tiempo con segundos fraccionarios y precisión configurable. Se permiten valores negativos.
Sintaxis:
precision es un entero entre 0 y 9. Valores comunes: 3 (milisegundos), 6 (microsegundos), 9 (nanosegundos).
Rango de valores admitidos: [-999:59:59.xxxxxxxxx, 999:59:59.xxxxxxxxx].
En este momento, la configuración
enable_time_time64_type debe establecerse en 1 para usar Time o Time64.Int64 representa segundos fraccionarios escalados por 10^precision.
Valores subyacentes de ejemplo para Time64:
Tipos de intervalos
Todos los tipos de intervalos se almacenan comoInt64 (ocho bytes, little-endian). El valor representa la cantidad de la unidad de tiempo correspondiente. Los valores negativos son válidos.
Los tipos de intervalos son: IntervalNanosecond, IntervalMicrosecond, IntervalMillisecond, IntervalSecond, IntervalMinute, IntervalHour, IntervalDay, IntervalWeek, IntervalMonth, IntervalQuarter, IntervalYear.
El nombre del tipo de intervalo (por ejemplo,
IntervalSecond frente a IntervalDay) determina la unidad del valor almacenado. La codificación transmitida es siempre la misma.Enum8, Enum16
Se almacenan como un solo byte (Enum8 == Int8) o dos bytes (Enum16 == Int16) que representan el índice del valor del enum en la definición del enum. Ten en cuenta que el tipo de almacenamiento es con signo: los valores del enum pueden ser negativos (p. ej., Enum8('a' = -128, 'b' = 0)).
Un Enum puede definirse de forma sencilla, así:
enum, como \', y de los símbolos especiales como = que pueden aparecer dentro de cadenas entre comillas.
UUID
Se representa como una secuencia de 16 bytes. El UUID se almacena como dos valoresUInt64 little-endian: los primeros 8 bytes de la representación estándar del UUID se almacenan con el orden de bytes invertido, y los segundos 8 bytes se invierten de forma independiente.
Por ejemplo, dado el UUID 61f0c404-5cb3-11e7-907b-a6006ad3dba0:
- Representación estándar de bytes:
61 f0 c4 04 5c b3 11 e7|90 7b a6 00 6a d3 db a0 - Primera mitad invertida (LE UInt64):
e7 11 b3 5c 04 c4 f0 61 - Segunda mitad invertida (LE UInt64):
a0 db d3 6a 00 a6 7b 90
UUID:
61f0c404-5cb3-11e7-907b-a6006ad3dba0se representa como:
- El UUID predeterminado
00000000-0000-0000-0000-000000000000se representa como 16 bytes en cero:
IPv4
Se almacena en cuatro bytes comoUInt32 en orden de bytes little-endian. Tenga en cuenta que esto difiere del orden de bytes de red tradicional (big-endian), usado habitualmente para las direcciones IP. Valores internos de ejemplo para IPv4:
IPv6
Se almacena en 16 bytes en orden de bytes big-endian / de red (MSB primero). Valores internos de ejemplo paraIPv6:
Nullable
Un tipo de dato Nullable se codifica de la siguiente manera:- Un único byte que indica si el valor es
NULLo no:0x00significa que el valor no esNULL.0x01significa que el valor esNULL.
- Si el valor no es
NULL, el tipo de dato subyacente se codifica como de costumbre. Si el valor esNULL, no se escriben bytes adicionales para el tipo subyacente.
Nullable(UInt32):
LowCardinality
En el formato RowBinary, el marcador de baja cardinalidad no afecta al wire format. Por ejemplo,LowCardinality(String) se codifica de la misma manera que un String normal.
Una columna puede definirse como
LowCardinality(Nullable(T)), pero no es posible definirla como Nullable(LowCardinality(T)): siempre producirá un error del servidor.1 para permitir la mayoría de los tipos de datos dentro de LowCardinality y conseguir una mejor cobertura.
Array
Un array se codifica de la siguiente manera:- Un entero de longitud variable (LEB128) que indica el número de elementos del array.
- Los elementos del array, codificados de la misma manera que el tipo de datos subyacente.
UInt32:
Un array puede contener valores Nullable, pero el array en sí no puede ser Nullable.
Tuple
Una Tuple se codifica como todos sus elementos, uno a continuación del otro, en el wire format correspondiente, sin metainformación ni delimitadores adicionales.Map
UnMap puede verse como un Array(Tuple(K, V)), donde K es el tipo de clave y V es el tipo de valor. El Map se codifica de la siguiente manera:
- Un entero de longitud variable (LEB128) que indica el número de elementos del
Map. - Los elementos del
Mapcomo pares clave-valor, codificados según sus tipos correspondientes.
Map con claves String y valores UInt32:
Es posible tener maps con estructuras muy anidadas, como
Map(String, Map(Int32, Array(Nullable(String)))), que se codificarán de forma similar a lo descrito anteriormente.Variant
Este tipo representa una unión de otros tipos de datos. El tipoVariant(T1, T2, ..., TN) significa que cada fila de este tipo tiene un valor de tipo T1, T2, …, TN o ninguno de ellos (valor NULL).
Considere el siguiente ejemplo:
NULL se codifica con un byte discriminante de 0xFF:
Variant.
Dynamic
El tipoDynamic puede contener valores de cualquier tipo, determinados en tiempo de ejecución. En el formato RowBinary, cada valor se describe a sí mismo: la primera parte es la especificación del tipo en este formato. A continuación va el contenido, con la codificación del valor tal como se describe en este documento. Por lo tanto, para analizar un valor, solo necesitas usar el índice de tipo para determinar el parser adecuado y luego reutilizar la lógica de análisis de RowBinary que ya tengas en otro lugar.
BinaryTypeIndex es un solo byte que identifica el tipo. Consulte la referencia aquí para ver los índices de tipo y los parámetros.
Un valor Dynamic NULL se codifica con BinaryTypeIndex 0x00 (el tipo Nothing), sin bytes adicionales:
JSON
El tipo JSON codifica los datos en dos categorías distintas:- Rutas tipadas - Rutas declaradas con tipos explícitos en el esquema (p. ej.,
JSON(user_id UInt32, name String)) - Rutas dinámicas/rutas de desbordamiento cuando se supera el límite de rutas dinámicas - Rutas detectadas en tiempo de ejecución almacenadas con el tipo
Dynamic. La codificación del valor está precedida por la definición del tipo.
Las rutas se serializan en tres grupos escritos de forma secuencial: rutas tipadas, rutas dinámicas y rutas de datos compartidos (desbordamiento). Las rutas tipadas y dinámicas se escriben en un orden definido por la implementación (determinado por la iteración interna del mapa hash), mientras que las rutas de datos compartidos se escriben en orden alfabético. Los lectores no deben asumir ningún orden específico de rutas. El deserializador resuelve cada ruta por nombre, no por posición.
Cada fila JSON en formato RowBinary se serializa de la siguiente manera:
JSON(user_id UInt32, active Bool)
Fila: {"user_id": 42, "active": true}
Codificación binaria (hex con anotaciones):
JSON(user_id UInt32, active Bool)
Fila: {"user_id": 42, "active": true, "name": "Alice"}
Codificación binaria (hex con anotaciones):
JSON(score Nullable(Int32))
Fila: {"score": null }
Codificación binaria (hex con anotaciones):
JSON(name String)
Fila: {"name": null}
Codificación binaria:
JSON(id UInt64)
Fila: {"id": 100, "metadata": null}
Codificación binaria:
metadata con valor NULL no se incluye porque las rutas dinámicas solo se serializan cuando no son nulas. Esta es una diferencia clave con respecto a las rutas tipadas.
4. Objetos JSON anidados:
Esquema: JSON()
Fila: {"user": {"name": "Bob", "age": 30}}
Codificación binaria (hexadecimal con anotaciones):
user.name en lugar de una estructura anidada).
Alternativa: modo JSON como String
Con la configuración output_format_binary_write_json_as_string=1, las columnas JSON se serializan como una única cadena de texto JSON en lugar de en formato binario estructurado. Existe una configuración correspondiente para escribir en columnas JSON: input_format_binary_read_json_as_string. La elección entre una u otra configuración depende de si desea procesar el JSON en el cliente o en el servidor.
Tipos Geo
Geo es una categoría de tipos de datos que representan datos geográficos. Incluye:Point- comoTuple(Float64, Float64).MultiPoint- comoArray(Point), oArray(Tuple(Float64, Float64)).Ring- comoArray(Point), oArray(Tuple(Float64, Float64)).Polygon- comoArray(Ring), oArray(Array(Tuple(Float64, Float64))).MultiPolygon- comoArray(Polygon), oArray(Array(Array(Tuple(Float64, Float64)))).LineString- comoArray(Point), oArray(Tuple(Float64, Float64)).MultiLineString- comoArray(LineString), oArray(Array(Tuple(Float64, Float64))).
RowBinaryWithNamesAndTypes contendrán los alias de estos tipos, por ejemplo, Point, MultiPoint, Ring, Polygon, MultiPolygon, LineString y MultiLineString.
Geometry
Geometry es un tipo Variant que puede contener cualquiera de los tipos Geo mencionados anteriormente. En el wire format, se codifica exactamente igual que un Variant, con un byte discriminante que indica qué tipo geo viene a continuación.
Los índices discriminantes de Geometry son:
Estructura del wire format:
Point como Geometry:
Ring como Geometry:
Nested
El wire format deNested depende de la configuración flatten_nested.
flatten_nested = 1 (valor predeterminado)
Con la configuración predeterminada, Nested se descompone en arrays independientes. Cada subcolumna se convierte en una columna Array independiente con un nombre separado por puntos:
DESCRIBE TABLE foo muestra las columnas desanidadas:
flatten_nested = 0
Con flatten_nested = 0, Nested se mantiene como una única columna del tipo Array(Tuple(...)). El nombre de la columna no se separa con puntos:
DESCRIBE TABLE foo muestra una sola columna:
Array(Tuple(String, Int32)): un prefijo con la longitud del array, seguido de los campos de la tupla de cada elemento en orden:
SimpleAggregateFunction
SimpleAggregateFunction(func, T) se codifica de forma idéntica a su tipo de dato subyacente T. El nombre de la función de agregación no afecta al wire format.
Por ejemplo, SimpleAggregateFunction(max, UInt32) se codifica de la misma forma que un UInt32 normal:
SimpleAggregateFunction(max, UInt32), pero el valor transmitido es simplemente UInt32:
AggregateFunction
AggregateFunction(func, T) almacena el estado intermedio completo de una función de agregación. A diferencia de SimpleAggregateFunction, que también almacena un estado intermedio pero lo codifica igual que el tipo de dato subyacente, AggregateFunction almacena un blob binario opaco cuyo formato es específico de cada función de agregación.
El formato interno varía según la función. Algunos ejemplos sencillos:
countState — almacena el recuento como un VarUInt (LEB128):
sumState — almacena la suma acumulada en un entero de tamaño fijo. El tamaño depende del tipo del argumento (UInt64 para argumentos enteros):
minState / maxState — almacena un byte de indicador seguido del valor en el tipo subyacente. El indicador es 0x00 para un estado vacío (sin valores registrados) o 0x01 cuando hay un valor presente:
Las funciones más complejas, como
uniq, quantile o groupArray, utilizan formatos específicos de la implementación. Si necesita leer o escribir estos estados, consulte el código fuente de ClickHouse correspondiente a la función en cuestión.aggregate_function_input_format establecido en value o array, una columna AggregateFunction(func, T) se lee como un valor de tipo T (o como una Tuple de los tipos de los argumentos si hay varios), o como un Array de dichos valores, codificado exactamente como se codifica ese tipo en RowBinary. El estado se construye a partir de los valores en el lado del servidor. Por ejemplo, con aggregate_function_input_format = 'array', una celda de AggregateFunction(avg, UInt32) es un Array(UInt32): una longitud LEB128 seguida de los elementos.
QBit
QBit es un tipo de vector para realizar consultas eficientes con distintos niveles de precisión. Internamente, se almacena en un formato transpuesto. A nivel de protocolo, QBit es simplemente un Array del tipo de elemento subyacente (Int8, Float32, Float64 o BFloat16). La optimización de transposición de bits para el almacenamiento se realiza en el servidor, no en el protocolo RowBinary.
Sintaxis:
element_type es Int8, Float32, Float64 o BFloat16, y dimension es la dimensión fija del vector. El stride opcional solo controla cómo se agrupan los planos de bits en flujos de almacenamiento en el servidor; no afecta al wire format de RowBinary, que siempre es el array completo de dimension elementos.
wire format: idéntico a Array(element_type):
QBit(Float32, 4) que contiene [1.0, 2.0, 3.0, 4.0]:
Configuración del formato
Los siguientes ajustes son comunes a todos los formatos de tipoRowBinary.