> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> Документация по формату RowBinary

# RowBinary

| Вход | Выход | Алиас |
| ---- | ----- | ----- |
| ✔    | ✔     |       |

<div id="description">
  ## Описание
</div>

Формат `RowBinary` разбирает данные по строкам в бинарном формате.
Строки и значения идут подряд, без разделителей.
Поскольку данные представлены в бинарном формате, разделитель после `FORMAT RowBinary` должен строго иметь следующий вид:

* Любое количество пробельных символов:
  * `' '` (пробел — код `0x20`)
  * `'\t'` (табуляция — код `0x09`)
  * `'\f'` (перевод страницы — код `0x0C`)
* Затем ровно одна последовательность перевода строки:
  * в стиле Windows `"\r\n"`
  * или в стиле Unix `'\n'`
* Сразу после неё следуют бинарные данные.

<Note>
  Этот формат менее эффективен, чем формат [Native](/docs/ru/reference/formats/Native), поскольку он основан на строках.
</Note>

<div id="data-types-wire-format">
  ## Формат передачи данных для типов данных
</div>

<Tip>
  Большинство запросов из примеров можно выполнить с помощью curl, сохранив вывод в файл.

  ```bash theme={null}
  curl -XPOST "http://localhost:8123?default_format=RowBinary" \
    --data-binary "SELECT 42 :: UInt32"  > out.bin
  ```
</Tip>

Затем данные можно просмотреть в hex-редакторе.

<div id="unsigned-leb128">
  ### Беззнаковый LEB128 (Little Endian Base 128)
</div>

Кодирование беззнакового целого числа переменной длины в формате **little-endian**, используемое для кодирования длины типов данных переменного размера, таких как `String`, `Array` и `Map`. Пример реализации можно найти на [странице LEB128 в Википедии](https://en.wikipedia.org/wiki/LEB128#Decode_unsigned_integer).

<div id="integer-types">
  ### (U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
</div>

Все целочисленные типы кодируются соответствующим количеством байтов в порядке **little-endian**. Знаковые типы (`Int8`–`Int256`) используют представление в **дополнительном коде**. В большинстве языков такие целые числа можно извлекать из массивов байтов либо встроенными средствами, либо с помощью широко известных библиотек. Для `Int128`/`Int256` и `UInt128`/`UInt256`, которые превышают встроенные размеры целых чисел в большинстве языков, может потребоваться собственная десериализация.

<div id="bool">
  ### Bool
</div>

Логические значения кодируются одним байтом и могут быть десериализованы так же, как `UInt8`.

* `0` — `false`
* `1` — `true`

<div id="float32-float64">
  ### Float32, Float64
</div>

**Числа с плавающей запятой в порядке байтов little-endian**, закодированные в 4 байта для `Float32` и в 8 байт для `Float64`. Как и в случае с целыми числами, в большинстве языков есть подходящие инструменты для десериализации этих значений.

<div id="bfloat16">
  ### BFloat16
</div>

[BFloat16](/docs/ru/reference/data-types/float#bfloat16) (Brain Floating Point) — это 16-битный формат чисел с плавающей точкой с диапазоном Float32 и пониженной точностью, что делает его удобным для задач машинного обучения. Формат передачи данных по сути представляет собой старшие 16 бит значения Float32. Если ваш язык не поддерживает его на нативном уровне, проще всего читать и записывать его как UInt16, преобразуя в Float32 и обратно:

Чтобы преобразовать BFloat16 в Float32 (псевдокод):

```text theme={null}
// Читаем 2 байта как UInt16 в формате little-endian
// Сдвигаем влево на 16 бит, чтобы получить биты Float32
bfloat16Bits = readUInt16()
float32Bits = bfloat16Bits << 16
floatValue = reinterpretAsFloat32(float32Bits)
```

Для преобразования Float32 в BFloat16 (псевдокод):

```text theme={null}
// Сдвиг битов Float32 вправо на 16 для усечения до BFloat16
float32Bits = reinterpretAsUInt32(floatValue)
bfloat16Bits = float32Bits >> 16
writeUInt16(bfloat16Bits)
```

Примеры внутренних значений для `BFloat16`:

```sql theme={null}
SELECT CAST(1.25, 'BFloat16')
```

```text theme={null}
0xA0, 0x3F, // 1.25 в формате BFloat16
```

<div id="decimal">
  ### Decimal32, Decimal64, Decimal128, Decimal256
</div>

Типы Decimal представлены в виде целых чисел **little-endian** с соответствующей битовой разрядностью.

* `Decimal32` - 4 байта, или `Int32`.
* `Decimal64` - 8 байт, или `Int64`.
* `Decimal128` - 16 байт, или `Int128`.
* `Decimal256` - 32 байта, или `Int256`.

При десериализации значения Decimal целую и дробную части можно определить с помощью следующего псевдокода:

```text theme={null}
let scale_multiplier = 10 ** scale
let whole_part = trunc(value / scale_multiplier)  // усечение к нулю
let fractional_part = value % scale_multiplier
let result = Decimal(whole_part, fractional_part)
```

Где `trunc` выполняет усечение к нулю (а не деление с округлением вниз, которое для отрицательных значений даёт другой результат), а `scale` — это количество цифр после десятичной точки. Например, для `Decimal(10, 2)` (эквивалент `Decimal32(2)`) `scale` равен `2`, а значение `12345` будет представлено как `(123, 45)`.

Для сериализации требуется выполнить обратную операцию:

```text theme={null}
let scale_multiplier = 10 ** scale
let result = whole_part * scale_multiplier + fractional_part
```

Подробнее см. в [документации ClickHouse по типам Decimal](/docs/ru/reference/data-types/decimal).

<div id="string">
  ### String
</div>

Строки в ClickHouse — это **произвольные последовательности байтов**. Они не обязаны быть корректным UTF-8. Префикс длины — это **длина в байтах**, а не число символов.

Кодирование состоит из двух частей:

1. Целое число переменной длины (LEB128), указывающее длину строки в байтах.
2. Сырые байты строки.

Например, строка `foobar` кодируется *семью* байтами следующим образом:

```text theme={null}
0x06, // LEB128 длина строки (6)
0x66, // 'f'
0x6f, // 'o'
0x6f, // 'o'
0x62, // 'b'
0x61, // 'a'
0x72, // 'r'
```

<div id="fixedstring">
  ### FixedString
</div>

В отличие от `String`, `FixedString` имеет фиксированную длину, заданную в схеме. Он кодируется как последовательность байтов и дополняется нулевыми байтами в конце, если значение короче `N`.

<Note>
  При чтении `FixedString` нулевые байты в конце могут быть как байтами заполнения, так и фактическими символами `\0` в данных; в wire format их невозможно различить. Сам ClickHouse сохраняет все `N` байт без изменений.
</Note>

Пустой `FixedString(3)` содержит только нулевые байты заполнения:

```text theme={null}
0x00, 0x00, 0x00
```

Непустое значение `FixedString(3)`, содержащее строку `hi`:

```text theme={null}
0x68, // 'h'
0x69, // 'i'
0x00, // дополнительный нулевой байт
```

Непустое значение `FixedString(3)`, содержащее строку `bar`:

```text theme={null}
0x62, // 'b'
0x61, // 'a'
0x72, // 'r'
```

В последнем примере дополнение не требуется, поскольку используются все *три* байта.

<div id="date">
  ### Date
</div>

Хранится как `UInt16` (два байта), обозначающий количество дней ***с*** `1970-01-01`.

Поддерживаемый диапазон значений: `[1970-01-01, 2149-06-06]`.

Примеры внутренних значений для `Date`:

```sql theme={null}
SELECT CAST('2024-01-15', 'Date') AS d
```

```text theme={null}
0x19, 0x4D, // 19737 как UInt16 (little-endian) = 19737 дней с 1970-01-01
```

<div id="date32">
  ### Date32
</div>

Хранится как `Int32` (четыре байта), который представляет количество дней ***до или после*** `1970-01-01`.

Поддерживаемый диапазон значений: `[1900-01-01, 2299-12-31]`.

Примеры внутренних значений для `Date32`:

```sql theme={null}
SELECT CAST('2024-01-15', 'Date32') AS d
```

```text theme={null}
0x19, 0x4D, 0x00, 0x00, // 19737 как Int32 (little-endian) = 19737 дней с 1970-01-01
```

Дата до начала эпохи:

```sql theme={null}
SELECT CAST('1900-01-01', 'Date32') AS d
```

```text theme={null}
0x21, 0x9C, 0xFF, 0xFF, // -25567 как Int32 (little-endian) = 25567 дней до 1970-01-01
```

<div id="datetime">
  ### DateTime
</div>

Хранится как `UInt32` (четыре байта), обозначающий количество секунд ***с*** `1970-01-01 00:00:00 UTC`.

Синтаксис:

```text theme={null}
DateTime([timezone])
```

Например, `DateTime` или `DateTime('UTC')`.

<Note>
  Бинарное значение всегда представляет собой смещение относительно эпохи UTC. Часовой пояс не меняет кодирование. Однако часовой пояс **действительно** влияет на то, как строковые значения интерпретируются при вставке: вставка `'2024-01-15 10:30:00'` в столбец `DateTime('America/New_York')` сохраняет другое значение эпохи, чем вставка той же строки в столбец `DateTime('UTC')`, поскольку строка интерпретируется как локальное время в часовом поясе столбца. На уровне протокола оба варианта — это просто секунды эпохи в формате `UInt32`.
</Note>

Поддерживаемый диапазон значений: `[1970-01-01 00:00:00, 2106-02-07 06:28:15]`.

Примеры внутренних значений для `DateTime`:

```sql theme={null}
SELECT CAST('2024-01-15 10:30:00', 'DateTime(\'UTC\')') AS d
```

```text theme={null}
0x28, 0x09, 0xA5, 0x65, // 1705314600 в виде UInt32 (little-endian)
```

<div id="datetime64">
  ### DateTime64
</div>

Хранится как `Int64` (восемь байт), где хранится количество **тиков** ***до или после*** `1970-01-01 00:00:00 UTC`. Разрешение тика задаётся параметром `precision`, см. синтаксис ниже:

```text theme={null}
DateTime64(precision, [timezone])
```

Где `precision` — целое число от `0` до `9`. Обычно используются только следующие значения: `3` (миллисекунды), `6` (микросекунды),
`9` (наносекунды).

Примеры корректных определений `DateTime64`: `DateTime64(0)`, `DateTime64(3)`, `DateTime64(6, 'UTC')` или `DateTime64(9, 'Europe/Amsterdam')`.

<Note>
  Как и в случае с `DateTime`, бинарное значение всегда представляет собой смещение относительно эпохи UTC. Часовой пояс влияет на то, как строковые значения интерпретируются при вставке (см. примечание о [DateTime](#datetime)), но само кодирование всегда представляет собой `Int64`-тики, отсчитываемые от эпохи UTC.
</Note>

Внутреннее значение `Int64` для типа `DateTime64` можно интерпретировать как количество следующих единиц времени до или после эпохи UNIX:

* `DateTime64(0)` - секунды.
* `DateTime64(3)` - миллисекунды.
* `DateTime64(6)` - микросекунды.
* `DateTime64(9)` - наносекунды.

Поддерживаемый диапазон значений: `[0000-01-01 00:00:00, 9999-12-31 23:59:59.999999999]` (для точности до 7; для точности 8 и 9 диапазон уже, см. примечание ниже).

Примеры внутренних значений для `DateTime64`:

* `DateTime64(3)`: значение `1546300800000` соответствует `2019-01-01 00:00:00 UTC`.
* `DateTime64(6)`: значение `1705314600123456` соответствует `2024-01-15 10:30:00.123456 UTC`.
* `DateTime64(9)`: значение `1705314600123456789` соответствует `2024-01-15 10:30:00.123456789 UTC`.

<Note>
  Поскольку диапазон тиков базового `Int64` при более высокой точности уже, максимальное поддерживаемое значение уменьшается: при точности 8 это `4892-10-07`, а при точности 9 (наносекунды) — `2262-04-11 23:47:16` в UTC.
</Note>

<div id="time">
  ### Time
</div>

Хранится как `Int32`, представляющий значение времени в секундах. Допускаются отрицательные значения.

Поддерживаемый диапазон значений: `[-999:59:59, 999:59:59]` (то есть `[-3599999, 3599999]` секунд).

<Note>
  На данный момент для использования `Time` или `Time64` параметр `enable_time_time64_type` должен быть установлен в `1`.
</Note>

Примеры внутренних значений для `Time`:

```sql theme={null}
SET enable_time_time64_type = 1;
SELECT CAST('15:32:16', 'Time') AS t
```

```text theme={null}
0x80, 0xDA, 0x00, 0x00, // 55936 секунд = 15:32:16
```

<div id="time64">
  ### Time64
</div>

Внутренне хранится как `Decimal64` (который, в свою очередь, хранится как `Int64`) и представляет значение времени с дробной частью секунд и настраиваемой точностью. Отрицательные значения допустимы.

Синтаксис:

```text theme={null}
Time64(precision)
```

Где `precision` — целое число от `0` до `9`. Распространённые значения: `3` (миллисекунды), `6` (микросекунды), `9` (наносекунды).

Поддерживаемый диапазон значений: `[-999:59:59.xxxxxxxxx, 999:59:59.xxxxxxxxx]`.

<Note>
  В настоящее время для использования `Time` или `Time64` параметр `enable_time_time64_type` должен быть установлен в `1`.
</Note>

Внутреннее значение `Int64` представляет дробные секунды, масштабированные с коэффициентом `10^precision`.

Примеры внутренних значений для `Time64`:

```sql theme={null}
SET enable_time_time64_type = 1;
SELECT CAST('15:32:16.123456', 'Time64(6)') AS t
```

```text theme={null}
0x40, 0x82, 0x0D, 0x06,
0x0D, 0x00, 0x00, 0x00, // 55936123456 как Int64
// 55936123456 / 10^6 = 55936.123456 seconds = 15:32:16.123456
```

<div id="interval-types">
  ### Типы Interval
</div>

Все типы Interval хранятся как `Int64` (восемь байт, little-endian). Значение представляет собой количество соответствующих единиц времени. Допустимы отрицательные значения.

Типы Interval: `IntervalNanosecond`, `IntervalMicrosecond`, `IntervalMillisecond`, `IntervalSecond`, `IntervalMinute`, `IntervalHour`, `IntervalDay`, `IntervalWeek`, `IntervalMonth`, `IntervalQuarter`, `IntervalYear`.

<Note>
  Имя типа Interval (например, `IntervalSecond` или `IntervalDay`) определяет единицу измерения хранимого значения. Кодирование при передаче всегда одинаковое.
</Note>

Внутреннее значение:

```sql theme={null}
SELECT INTERVAL 5 SECOND   AS a,
     INTERVAL 10 DAY     AS b,
     INTERVAL -7 DAY     AS c,
     INTERVAL 3 YEAR     AS d,
     INTERVAL 500 MICROSECOND AS e
```

```text theme={null}
// IntervalSecond: 5
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// IntervalDay: 10
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// IntervalDay: -7
0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// IntervalYear: 3
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// IntervalMicrosecond: 500
0xF4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
```

<div id="enum8-enum16">
  ### Enum8, Enum16
</div>

Хранится как один байт (`Enum8` == `Int8`) или два байта (`Enum16` == `Int16`), представляя индекс значения enum в его определении. Обратите внимание, что тип хранения — **знаковый**; значения enum могут быть отрицательными (например, `Enum8('a' = -128, 'b' = 0)`).

Enum можно определить простым способом, например так:

```sql theme={null}
SELECT 1 :: Enum8('hello' = 1, 'world' = 2) AS e;
```

```text theme={null}
   ┌─e─────┐
1. │ hello │
   └───────┘
```

Для определённого выше Enum8 на стороне клиента будет использоваться следующее сопоставление значений:

```text theme={null}
Map<Int8, String> {
  1: 'hello',
  2: 'world'
}
```

Или более сложным образом, например так:

```sql theme={null}
SELECT 42 :: Enum16('f\'' = 1, 'x =' = 2, 'b\'\'' = 3, '\'c=4=' = 42, '4' = 1234) AS e;
```

```text theme={null}
   ┌─e─────┐
1. │ 'c=4= │
   └───────┘
```

Для определённого выше Enum16 на стороне клиента значения будут следующими:

```text theme={null}
Map<Int16, String> {
  1:    'f\'',
  2:    'x =',
  3:    'b\'',
  42:   '\'c=4=',
  1234: '4'
}
```

Для парсера типов данных основная сложность — отслеживать экранированные символы в определении `enum`, такие как `\'`, а также специальные символы вроде `=`, которые могут встречаться внутри строк в кавычках.

<div id="uuid">
  ### UUID
</div>

Представлен как последовательность из 16 байт. UUID хранится как **два значения `UInt64` в порядке little-endian**: первые 8 байт стандартного представления UUID записываются в обратном порядке, и вторые 8 байт также независимо записываются в обратном порядке.

Например, для UUID `61f0c404-5cb3-11e7-907b-a6006ad3dba0`:

* Стандартное байтовое представление: `61 f0 c4 04 5c b3 11 e7` | `90 7b a6 00 6a d3 db a0`
* Первая половина в обратном порядке (LE UInt64): `e7 11 b3 5c 04 c4 f0 61`
* Вторая половина в обратном порядке (LE UInt64): `a0 db d3 6a 00 a6 7b 90`

Пример внутреннего значения для `UUID`:

* `61f0c404-5cb3-11e7-907b-a6006ad3dba0` представлен как:

```text theme={null}
0xE7, 0x11, 0xB3, 0x5C, 0x04, 0xC4, 0xF0, 0x61,
0xA0, 0xDB, 0xD3, 0x6A, 0x00, 0xA6, 0x7B, 0x90,
```

* UUID `00000000-0000-0000-0000-000000000000` по умолчанию представляется в виде 16 нулевых байтов:

```text theme={null}
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
```

Его можно использовать, если была вставлена новая запись, но значение UUID не было указано.

<div id="ipv4">
  ### IPv4
</div>

Хранится в четырёх байтах в виде `UInt32` с порядком байтов **little-endian**. Обратите внимание, что это отличается от традиционного сетевого порядка байтов (big-endian), который обычно используется для IP-адресов. Примеры внутренних значений для `IPv4`:

```sql theme={null}
SELECT    
  CAST('0.0.0.0',         'IPv4') AS a,
  CAST('127.0.0.1',       'IPv4') AS b,
  CAST('192.168.0.1',     'IPv4') AS c,
  CAST('255.255.255.255', 'IPv4') AS d,
  CAST('168.212.226.204', 'IPv4') AS e
```

```text theme={null}
0x00, 0x00, 0x00, 0x00, // 0.0.0.0
0x01, 0x00, 0x00, 0x7f, // 127.0.0.1
0x01, 0x00, 0xa8, 0xc0, // 192.168.0.1
0xff, 0xff, 0xff, 0xff, // 255.255.255.255
0xcc, 0xe2, 0xd4, 0xa8, // 168.212.226.204
```

<div id="ipv6">
  ### IPv6
</div>

Хранится в 16 байтах в **порядке байтов big-endian / network** (старший значащий байт первым). Примеры внутренних значений для `IPv6`:

```sql theme={null}
SELECT
    CAST('2a02:aa08:e000:3100::2',        'IPv6') AS a,
    CAST('2001:44c8:129:2632:33:0:252:2', 'IPv6') AS b,
    CAST('2a02:e980:1e::1',               'IPv6') AS c
```

```text theme={null}
// 2a02:aa08:e000:3100::2
0x2A, 0x02, 0xAA, 0x08, 0xE0, 0x00, 0x31, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
// 2001:44c8:129:2632:33:0:252:2
0x20, 0x01, 0x44, 0xC8, 0x01, 0x29, 0x26, 0x32, 
0x00, 0x33, 0x00, 0x00, 0x02, 0x52, 0x00, 0x02,
// 2a02:e980:1e::1
0x2A, 0x02, 0xE9, 0x80, 0x00, 0x1E, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
```

<div id="nullable">
  ### Nullable
</div>

Тип данных Nullable кодируется следующим образом:

1. Один байт, который показывает, является ли значение `NULL`:
   * `0x00` означает, что значение не `NULL`.
   * `0x01` означает, что значение `NULL`.
2. Если значение не `NULL`, базовый тип данных кодируется как обычно. Если значение `NULL`, для базового типа **не записываются дополнительные байты**.

Например, значение `Nullable(UInt32)`:

```sql theme={null}
SELECT    
   CAST(42,   'Nullable(UInt32)') AS a,
   CAST(NULL, 'Nullable(UInt32)') AS b
```

```text theme={null}
0x00,                   // Не NULL — далее следует значение
0x2A, 0x00, 0x00, 0x00, // UInt32(42)
0x01,                   // NULL — значение отсутствует
```

<div id="lowcardinality">
  ### LowCardinality
</div>

В формате RowBinary маркер low-cardinality не влияет на формат передачи данных. Например, `LowCardinality(String)` кодируется так же, как обычный `String`.

<Warning>
  Это относится только к RowBinary. В Native format `LowCardinality` использует другое кодирование на основе словаря.
</Warning>

<Note>
  Столбец можно определить как `LowCardinality(Nullable(T))`, но нельзя определить как `Nullable(LowCardinality(T))` — это всегда будет приводить к ошибке сервера.
</Note>

При тестировании параметр [allow\_suspicious\_low\_cardinality\_types](/docs/ru/reference/settings/session-settings#allow_suspicious_low_cardinality_types) можно установить в `1`, чтобы разрешить использование большинства типов данных внутри `LowCardinality` для более полного покрытия.

<div id="array">
  ### Array
</div>

Массив кодируется следующим образом:

1. [Целое число переменной длины (LEB128)](#unsigned-leb128), указывающее число элементов в массиве.
2. Элементы массива, закодированные так же, как и значения базового типа данных.

Например, массив со значениями `UInt32`:

```sql theme={null}
SELECT CAST(array(1, 2, 3), 'Array(UInt32)') AS arr
```

```text theme={null}
0x03,                   // LEB128 - массив содержит 3 элемента
0x01, 0x00, 0x00, 0x00, // UInt32(1)
0x02, 0x00, 0x00, 0x00, // UInt32(2)
0x03, 0x00, 0x00, 0x00, // UInt32(3)
```

Немного более сложный пример:

```sql theme={null}
SELECT array('foobar', 'qaz') AS arr
```

```text theme={null}
0x02,             // LEB128 - массив содержит 2 элемента
0x06,             // LEB128 - первая строка занимает 6 байт
0x66, 0x6f, 0x6f, 
0x62, 0x61, 0x72, // 'foobar'
0x03,             // LEB128 - вторая строка занимает 3 байта
0x71, 0x61, 0x7a, // 'qaz'
```

<Note>
  Массив может содержать значения типа Nullable, но сам массив не может иметь тип Nullable.
</Note>

Допустим следующий вариант:

```sql theme={null}
SELECT CAST([NULL, 'foo'], 'Array(Nullable(String))') AS arr;
```

```text theme={null}
   ┌─arr──────────┐
1. │ [NULL,'foo'] │
   └──────────────┘
```

И будет закодирован следующим образом:

```text theme={null}
0x02,             // LEB128  - массив содержит 2 элемента
0x01,             // Is NULL - данных для этого элемента нет
0x00,             // Is NOT NULL - далее следуют данные
0x03,             // LEB128  - строка содержит 3 байта
0x66, 0x6f, 0x6f, // 'foo'
```

Пример работы с многомерными массивами можно найти в [разделе Geo](#geo-types).

<div id="tuple">
  ### Tuple
</div>

Кортеж кодируется как все его элементы, следующие друг за другом в соответствующем им формате передачи данных, без какой-либо дополнительной метаинформации или разделителей.

```sql theme={null}
CREATE OR REPLACE TABLE foo
(
    `t` Tuple(
           UInt32,
           String,
           Array(UInt8)
        )
)
ENGINE = Memory;
INSERT INTO foo VALUES ((42, 'foo', array(99, 144)));
```

```text theme={null}
0x2a, 0x00, 0x00, 0x00, // 42 как UInt32
0x03,                   // LEB128 — строка содержит 3 байта
0x66, 0x6f, 0x6f,       // 'foo'
0x02,                   // LEB128 — массив содержит 2 элемента
0x63,                   // 99 как UInt8
0x90,                   // 144 как UInt8
```

Строковое представление типа данных Tuple сопряжено с теми же сложностями, что и [тип Enum](#enum8-enum16), например с отслеживанием экранированных символов и специальных знаков; кроме того, в случае Tuple нужно также отслеживать открывающие и закрывающие круглые скобки. Также обратите внимание, что наиболее сложные Tuple могут содержать другие вложенные Tuple, Arrays, Maps и даже enum.

Например, в следующей таблице tuple содержит enum с апострофом и круглой скобкой в имени, что при неправильной обработке может вызвать проблемы при парсинге:

```sql theme={null}
CREATE OR REPLACE TABLE foo
(
   `t` Tuple(
          Enum8('f\'()' = 0),
          Array(Nullable(Tuple(UInt32, String)))
       )
) ENGINE = Memory;
```

<div id="map">
  ### Map
</div>

Map можно представить как `Array(Tuple(K, V))`, где `K` — тип ключа, а `V` — тип значения. Map кодируется следующим образом:

1. [Целое число переменной длины (LEB128)](#unsigned-leb128), указывающее количество элементов в Map.
2. Элементы Map в виде пар ключ-значение, закодированные в соответствии с их типами.

Например, Map с ключами типа `String` и значениями типа `UInt32`:

```sql theme={null}
SELECT CAST(map('foo', 1, 'bar', 2), 'Map(String, UInt32)') AS m
```

```text theme={null}
0x02,                   // LEB128 - map содержит 2 элемента
0x03,                   // LEB128 - первый ключ занимает 3 байта
0x66, 0x6f, 0x6f,       // 'foo'
0x01, 0x00, 0x00, 0x00, // UInt32(1)
0x03,                   // LEB128 - второй ключ занимает 3 байта
0x62, 0x61, 0x72,       // 'bar'
0x02, 0x00, 0x00, 0x00, // UInt32(2)
```

<Note>
  Возможны значения типа Map с глубоко вложенными структурами, например `Map(String, Map(Int32, Array(Nullable(String))))`, которые кодируются аналогично описанному выше.
</Note>

<div id="variant">
  ### Variant
</div>

Этот тип представляет собой объединение других типов данных. Тип `Variant(T1, T2, ..., TN)` означает, что каждая строка этого типа содержит значение либо типа `T1`, либо `T2`, либо …, либо `TN`, либо не содержит ни одного из них (значение `NULL`).

<Warning>
  Хотя для конечного пользователя `Variant(T1, T2)` означает ровно то же самое, что и `Variant(T2, T1)`, порядок типов в определении важен для формата передачи данных: типы в определении всегда сортируются по алфавиту, и это важно, поскольку конкретный вариант кодируется с помощью "дискриминант" — индекса типа данных в определении.
</Warning>

Рассмотрим следующий пример:

```sql theme={null}
SET allow_experimental_variant_type = 1,
    allow_suspicious_variant_types = 1;
CREATE OR REPLACE TABLE foo
(
  -- Порядок типов во входных данных пользователя не важен;
  -- типы всегда сортируются в алфавитном порядке в формате передачи данных.
  `var` Variant(
           Array(Int16),
           Bool,
           Date,
           FixedString(6),
           Float32, Float64,
           Int128, Int16, Int32, Int64, Int8,
           String,
           UInt128, UInt16, UInt32, UInt64, UInt8
       )
)
ENGINE = MergeTree
ORDER BY ();
INSERT INTO foo VALUES (true), ('foobar' :: FixedString(6)), (100.5 :: Float64), (100 :: Int128), ([1, 2, 3] :: Array(Int16));
SELECT * FROM foo FORMAT RowBinary;
```

```text theme={null}
0x01,                               // индекс типа -> Bool
 0x01,                               // true
 0x03,                               // индекс типа -> FixedString(6)
 0x66, 0x6F, 0x6F, 0x62, 0x61, 0x72, // 'foobar' 
 0x05,                               // индекс типа -> Float64
 0x00, 0x00, 0x00, 0x00, 
 0x00, 0x20, 0x59, 0x40,             // 100.5 как Float64
 0x06,                               // индекс типа -> Int128
 0x64, 0x00, 0x00, 0x00, 
 0x00, 0x00, 0x00, 0x00, 
 0x00, 0x00, 0x00, 0x00, 
 0x00, 0x00, 0x00, 0x00,             // 100 как Int128
 0x00,                               // индекс типа -> Array(Int16)
 0x03,                               // LEB128 - массив содержит 3 элемента
 0x01, 0x00,                         // 1 как Int16
 0x02, 0x00,                         // 2 как Int16
 0x03, 0x00,                         // 3 как Int16
```

Для кодирования значения `NULL` используется байт дискриминанта `0xFF`:

```sql theme={null}
SELECT NULL :: Variant(UInt32, String)
```

```text theme={null}
0xFF, // discriminant = NULL
```

Настройку [allow\_suspicious\_variant\_types](/docs/ru/reference/settings/session-settings#allow_suspicious_variant_types) можно использовать, чтобы разрешить более всестороннее тестирование типа `Variant`.

<div id="dynamic">
  ### Dynamic
</div>

Тип `Dynamic` может содержать значения любого типа, определяемого во время выполнения. В формате RowBinary каждое значение самодостаточно: первая часть — это спецификация типа в [таком формате](/docs/ru/reference/data-types/data-types-binary-encoding). Затем следует содержимое со значением, закодированным так, как описано в этом документе. Поэтому, чтобы разобрать значение, достаточно использовать индекс типа для выбора подходящего парсера, а затем повторно использовать уже имеющийся у вас код разбора RowBinary.

```text theme={null}
[BinaryTypeIndex][type-specific parameters...][value]
```

Где `BinaryTypeIndex` — это один байт, обозначающий тип. Индексы типов и параметры см. в справочнике [здесь](/docs/ru/reference/data-types/data-types-binary-encoding).

Значение Dynamic со значением `NULL` кодируется с `BinaryTypeIndex` `0x00` (тип `Nothing`) без дополнительных байтов:

```sql theme={null}
SELECT NULL::Dynamic
```

```text theme={null}
00                        # BinaryTypeIndex: Nothing (0x00), представляет NULL
```

**Примеры:**

```sql theme={null}
SELECT 42::Dynamic
```

```text theme={null}
0a                        # BinaryTypeIndex: Int64 (0x0A)
2a 00 00 00 00 00 00 00   # Значение Int64: 42
```

```sql theme={null}
SELECT toDateTime64('2024-01-15 10:30:00', 3, 'America/New_York')::Dynamic
```

```text theme={null}
14                        # BinaryTypeIndex: DateTime64WithTimezone (0x14)
03                        # UInt8: точность
10                        # VarUInt: длина имени часового пояса
41 6d 65 72 69 63 61 2f   # "America/"
4e 65 77 5f 59 6f 72 6b   # "New_York"
c0 6c be 0d 8d 01 00 00   # Int64: временные метки
```

<div id="json">
  ### JSON
</div>

Тип JSON кодирует данные в двух различных категориях:

1. **Типизированные пути** - пути, объявленные в схеме с явным указанием типов (например, `JSON(user_id UInt32, name String)`)
2. **Динамические пути/пути переполнения при превышении лимита динамических путей** — пути, обнаруженные во время выполнения и хранящиеся как тип `Dynamic`. Перед кодированием значения указывается определение типа.

Формат передачи данных и правила для этих двух категорий различаются.

| Категория пути            | Включается в сериализацию | Кодирование значений               | Разрешены Variant/Nullable |
| ------------------------- | ------------------------- | ---------------------------------- | -------------------------- |
| **Пути с заданным типом** | Всегда (даже если NULL)   | Бинарный формат, зависящий от типа | Да                         |
| **Динамические пути**     | Только если не NULL       | Динамический                       | Нет                        |

Пути сериализуются в трёх группах, записываемых последовательно: типизированные пути, динамические пути, затем пути общих данных (overflow). Типизированные и динамические пути записываются в порядке, определяемом реализацией (определяется порядком итерации по внутренней хэш-карте), тогда как пути общих данных записываются в алфавитном порядке. Не следует полагаться на какой-либо конкретный порядок путей. Десериализатор обрабатывает каждый путь по имени, а не по позиции.

Каждая JSON-строка в формате RowBinary сериализуется следующим образом:

```text theme={null}
[VarUInt: number_of_paths]
[String: path_1][value_1]
[String: path_2][value_2]
...
```

**Примеры:**

**1. Простой JSON только с типизированными путями:**

Схема: `JSON(user_id UInt32, active Bool)`

Строка: `{"user_id": 42, "active": true}`

Двоичное кодирование (hex с аннотациями):

```text theme={null}
02                              # VarUInt: всего 2 пути

# Типизированный путь "active"
06 61 63 74 69 76 65            # String: "active" (длина 6 + байты)
01                              # Bool/UInt8 значение: true (1)

# Типизированный путь "user_id"
07 75 73 65 72 5F 69 64         # String: "user_id" (длина 7 + байты)
2A 00 00 00                     # UInt32 значение: 42 (little-endian)
```

**2. Простой JSON с типизированными и динамическими путями:**

Схема: `JSON(user_id UInt32, active Bool)`

Строка: `{"user_id": 42, "active": true, "name": "Alice"}`

Двоичное кодирование (hex с аннотациями):

```text theme={null}
03                              # VarUInt: всего 3 пути

# Типизированный путь "active"
06 61 63 74 69 76 65            # String: "active" (длина 6 + байты)
01                              # Значение Bool/UInt8: true (1)

# Динамический путь "name"
04 6E 61 6D 65                  # String: "name" (длина 4 + байты)
15                              # BinaryTypeIndex: String (0x15)
05 41 6C 69 63 65               # Значение String: "Alice" (длина 5 + байты)

# Типизированный путь "user_id"
07 75 73 65 72 5F 69 64         # String: "user_id" (длина 7 + байты)
2A 00 00 00                     # Значение UInt32: 42 (little-endian)
```

**3. Обработка NULL:**

С типизированным столбцом Nullable вы получаете null:

Схема: `JSON(score Nullable(Int32))`

Строка: `{"score": null }`

Двоичное кодирование (hex с аннотациями):

```text theme={null}
01                              # VarUInt: 1 путь всего

# Типизированный путь "score" (Nullable)
05 73 63 6f 72 65               # String: "score" (длина 5 + байты)
01                              # Флаг Nullable: 1 (значение NULL, данные отсутствуют)
```

Для типизированного non-nullable столбца возвращается значение по умолчанию:

Схема: `JSON(name String)`

Строка: `{"name": null}`

Двоичное кодирование:

```text theme={null}
01                              # VarUInt: 1 path (динамические NULL-пути пропускаются!)

04 6e 61 6d 65  # "name"
00              # Длина строки 0 (пустая строка)
```

При динамическом пути это игнорируется:

Схема: `JSON(id UInt64)`

Строка: `{"id": 100, "metadata": null}`

Двоичное кодирование:

```text theme={null}
01                              # VarUInt: 1 путь (динамические NULL-пути пропускаются!)

# Типизированный путь "id"
02 69 64                        # String: "id" (длина 2 + байты)
64 00 00 00 00 00 00 00         # UInt64 значение: 100 (little-endian)
```

Примечание: путь `metadata` со значением NULL **не включается**, так как динамические пути сериализуются только при ненулевых значениях. Это ключевое отличие от типизированных путей.

**4. Вложенные JSON-объекты:**

Схема: `JSON()`

Строка: `{"user": {"name": "Bob", "age": 30}}`

Двоичное кодирование (в шестнадцатеричном виде, с аннотациями):

```text theme={null}
02                              # VarUInt: 2 пути (вложенные объекты разворачиваются)

# Динамический путь "user.age"
08 75 73 65 72 2E 61 67 65      # String: "user.age" (длина 8 + байты)
0A                              # BinaryTypeIndex: Int64 (0x0A)
1E 00 00 00 00 00 00 00         # Значение Int64: 30 (little-endian)

# Динамический путь "user.name"
09 75 73 65 72 2E 6E 61 6D 65   # String: "user.name" (длина 9 + байты)
15                              # BinaryTypeIndex: String (0x15)
03 42 6F 62                     # Значение String: "Bob" (длина 3 + байты)

```

Примечание: вложенные объекты разворачиваются в пути с разделением точками (например, `user.name` вместо вложенной структуры).

**Альтернатива: режим JSON как String**

При использовании настройки `output_format_binary_write_json_as_string=1` JSON-столбцы сериализуются как единая текстовая строка JSON, а не в структурированном бинарном формате. Для записи в JSON-столбцы есть соответствующая настройка — `input_format_binary_read_json_as_string`. Выбор здесь зависит от того, где вы хотите разбирать JSON: на стороне клиента или на стороне сервера.

<div id="geo-types">
  ### Гео-типы
</div>

Geo — это категория типов данных для представления географических данных. Она включает:

* `Point` - в виде `Tuple(Float64, Float64)`.
* `Ring` - в виде `Array(Point)` или `Array(Tuple(Float64, Float64))`.
* `Polygon` - в виде `Array(Ring)` или `Array(Array(Tuple(Float64, Float64)))`.
* `MultiPolygon` - в виде `Array(Polygon)` или `Array(Array(Array(Tuple(Float64, Float64))))`.
* `LineString` - в виде `Array(Point)` или `Array(Tuple(Float64, Float64))`.
* `MultiLineString` - в виде `Array(LineString)` или `Array(Array(Tuple(Float64, Float64)))`.

Формат передачи данных для значений Geo в точности такой же, как у Tuple и Array. Заголовки формата `RowBinaryWithNamesAndTypes` будут содержать псевдонимы этих типов, например `Point`, `Ring`, `Polygon`, `MultiPolygon`, `LineString` и `MultiLineString`.

```sql theme={null}
SELECT    (1.0, 2.0)                                       :: Point           AS point,
    [(3.0, 4.0), (5.0, 6.0)]                         :: Ring            AS ring,
    [[(7.0, 8.0), (9.0, 10.0)], [(11.0, 12.0)]]      :: Polygon         AS polygon,
    [[[(13.0, 14.0), (15.0, 16.0)], [(17.0, 18.0)]]] :: MultiPolygon    AS multi_polygon,
    [(19.0, 20.0), (21.0, 22.0)]                     :: LineString      AS line_string,
    [[(23.0, 24.0), (25.0, 26.0)], [(27.0, 28.0)]]   :: MultiLineString AS multi_line_string
```

```text theme={null}
// Point — или Tuple(Float64, Float64)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, // Point.X
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // Point.Y
// Ring — или Array(Tuple(Float64, Float64))
0x02, // LEB128 — массив "ring" содержит 2 точки
   // Ring — точка №1
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 
   // Ring — точка №2
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, 
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, 
// Polygon — или Array(Array(Tuple(Float64, Float64)))
0x02, // LEB128 — массив "polygon" содержит 2 кольца
   0x02, // LEB128 — первое кольцо содержит 2 точки
      // Polygon — Ring №1 — точка №1
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x40, 
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40,
      // Polygon — Ring №1 — точка №2
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x40, 
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40, 
  0x01, // LEB128 — второе кольцо содержит 1 точку
      // Polygon — Ring №2 — точка №1 (единственная)
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x40, 
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x40, 
// MultiPolygon — или Array(Array(Array(Tuple(Float64, Float64))))
0x01, // LEB128 — массив "multi_polygon" содержит 1 полигон
   0x02, // LEB128 — первый полигон содержит 2 кольца
      0x02, // LEB128 — первое кольцо содержит 2 точки
         // MultiPolygon — Polygon №1 — Ring №1 — точка №1
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x40, 
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x40,
         // MultiPolygon — Polygon №1 — Ring №1 — точка №2
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x40, 
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x40, 
      0x01, // LEB128 — второе кольцо содержит 1 точку
        // MultiPolygon — Polygon №1 — Ring №2 — точка №1 (единственная)
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x40, 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x40, 
 // LineString — или Array(Tuple(Float64, Float64))
 0x02, // LEB128 — LineString содержит 2 точки
    // LineString — точка №1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x40, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40,
    // LineString — точка №2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x40, 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x40, 
 // MultiLineString — или Array(Array(Tuple(Float64, Float64)))
 0x02, // LEB128 — MultiLineString содержит 2 LineString
   0x02, // LEB128 — первый LineString содержит 2 точки
     // MultiLineString — LineString №1 — точка №1
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x40, 
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x40, 
     // MultiLineString — LineString №1 — точка №2
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x40, 
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x40, 
   0x01, // LEB128 — второй LineString содержит 1 точку
     // MultiLineString — LineString №2 — точка №1 (единственная)
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x40, 
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x40,
```

<div id="geometry">
  ### Geometry
</div>

`Geometry` — это тип `Variant`, который может содержать любой из перечисленных выше гео-типов. В формате передачи данных он кодируется точно так же, как `Variant`: байт дискриминант указывает, какой гео-тип идёт следующим.

Индексы дискриминант для Geometry:

| Индекс | Тип             |
| ------ | --------------- |
| 0      | LineString      |
| 1      | MultiLineString |
| 2      | MultiPolygon    |
| 3      | Point           |
| 4      | Polygon         |
| 5      | Ring            |

Структура формата передачи данных:

```text theme={null}
// 1 байт дискриминант (0-5)
// за которым следуют данные соответствующего геотипа
```

Пример кодирования `Point` в виде `Geometry`:

```sql theme={null}
SELECT ((1.0, 2.0)::Point)::Geometry
```

```text theme={null}
0x03,                                           // дискриминант = 3 (Point)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, // Point.X = 1.0 как Float64
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // Point.Y = 2.0 как Float64
```

Пример кодирования `Ring` в виде `Geometry`:

```text theme={null}
0x05,       // дискриминант = 5 (Ring)
0x02,       // LEB128 - массив содержит 2 точки
// Точка #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, // X = 3.0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, // Y = 4.0
// Точка #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, // X = 5.0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, // Y = 6.0
```

<div id="nested">
  ### Nested
</div>

Формат передачи данных для `Nested` зависит от настройки `flatten_nested`.

<Warning>
  Все массивы компонентов в одной строке **должны иметь одинаковую длину**. Это ограничение контролируется сервером. Если длины не совпадают, возникнет ошибка вставки.
</Warning>

<div id="nested-flattened">
  #### `flatten_nested = 1` (по умолчанию)
</div>

При значении по умолчанию `Nested` разворачивается в независимые массивы. Каждый подстолбец становится отдельным столбцом `Array` с именем, части которого разделены точками:

```sql theme={null}
CREATE OR REPLACE TABLE foo
(
    n Nested(a String, b Int32)
) ENGINE = MergeTree ORDER BY ();
-- flatten_nested=1 — значение по умолчанию
INSERT INTO foo VALUES (['foo', 'bar'], [42, 144]);
```

`DESCRIBE TABLE foo` показывает столбцы в развёрнутом виде:

```text theme={null}
   ┌─name─┬─type──────────┐
1. │ n.a  │ Array(String) │
2. │ n.b  │ Array(Int32)  │
   └──────┴───────────────┘
```

Каждый массив сериализуется отдельно, как описано в разделе [Array](#array):

```text theme={null}
0x02,                   // LEB128 - 2 элемента типа String в первом массиве (n.a)
 0x03,                   // LEB128 - первая строка содержит 3 байта
 0x66, 0x6F, 0x6F,       // 'foo'
 0x03,                   // LEB128 - вторая строка содержит 3 байта
 0x62, 0x61, 0x72,       // 'bar'
0x02,                   // LEB128 - 2 элемента типа Int32 во втором массиве (n.b)
 0x2A, 0x00, 0x00, 0x00, // 42 как Int32
 0x90, 0x00, 0x00, 0x00, // 144 как Int32
```

<div id="nested-unflattened">
  #### `flatten_nested = 0`
</div>

При `flatten_nested = 0` `Nested` сохраняется как один столбец типа `Array(Tuple(...))`. Имя столбца не содержит точек:

```sql theme={null}
SET flatten_nested = 0;
CREATE OR REPLACE TABLE foo
(
    n Nested(a String, b Int32)
) ENGINE = MergeTree ORDER BY ();
INSERT INTO foo VALUES ([('foo', 42), ('bar', 144)]);
```

`DESCRIBE TABLE foo` возвращает один столбец:

```text theme={null}
   ┌─name─┬─type───────────────────────┐
1. │ n    │ Nested(a String, b Int32)  │
   └──────┴────────────────────────────┘
```

Кодировка имеет вид `Array(Tuple(String, Int32))`: сначала префикс длины массива, затем поля кортежа каждого элемента по порядку:

```text theme={null}
0x02,                   // LEB128 - 2 элемента в массиве
 0x03,                   // LEB128 - первый кортеж, поле a: 3 байта
 0x66, 0x6F, 0x6F,       // 'foo'
 0x2A, 0x00, 0x00, 0x00, // первый кортеж, поле b: 42 в виде Int32
 0x03,                   // LEB128 - второй кортеж, поле a: 3 байта
 0x62, 0x61, 0x72,       // 'bar'
 0x90, 0x00, 0x00, 0x00, // второй кортеж, поле b: 144 в виде Int32
```

Обратите внимание, что поля чередуются внутри каждого элемента (a₁, b₁, a₂, b₂), а не группируются по столбцам (a₁, a₂, b₁, b₂), как в сглаженном представлении.

<div id="simpleaggregatefunction">
  ### SimpleAggregateFunction
</div>

`SimpleAggregateFunction(func, T)` кодируется так же, как базовый тип данных `T`. Имя агрегатной функции не влияет на формат передачи данных.

Например, `SimpleAggregateFunction(max, UInt32)` кодируется так же, как обычный `UInt32`:

```sql theme={null}
CREATE TABLE test_saf
(
    key UInt32,
    val SimpleAggregateFunction(max, UInt32)
) ENGINE = AggregatingMergeTree ORDER BY key;

INSERT INTO test_saf VALUES (1, 42);
SELECT val FROM test_saf;
```

В заголовке RowBinaryWithNamesAndTypes тип указан как `SimpleAggregateFunction(max, UInt32)`, но фактическое значение в формате передачи данных — просто `UInt32`:

```text theme={null}
0x2A, 0x00, 0x00, 0x00, // 42 в виде UInt32
```

<div id="aggregatefunction">
  ### AggregateFunction
</div>

`AggregateFunction(func, T)` хранит полное промежуточное состояние агрегатной функции. В отличие от `SimpleAggregateFunction`, которая тоже хранит промежуточное состояние, но кодирует его так же, как и базовый тип данных, `AggregateFunction` хранит непрозрачный бинарный blob, формат которого зависит от конкретной агрегатной функции.

<Warning>
  Состояния агрегатных функций в RowBinary **не имеют префикса длины**. Парсер должен понимать внутренний формат сериализации каждой конкретной агрегатной функции, чтобы определить, сколько байтов нужно прочитать. На практике большинство клиентов рассматривают состояния агрегатных функций как непрозрачные и используют комбинаторы `*State` / `*Merge`, чтобы сервер сам выполнял сериализацию.
</Warning>

Внутренний формат зависит от функции. Несколько простых примеров:

**`countState`** — хранит счётчик как VarUInt (LEB128):

```sql theme={null}
SELECT countState(number) FROM numbers(5)
```

```text theme={null}
0x05, // VarUInt: 5
```

**`sumState`** — хранит накопленную сумму в целочисленном значении фиксированной разрядности. Разрядность зависит от типа аргумента (`UInt64` для целочисленных аргументов):

```sql theme={null}
SELECT sumState(toUInt32(number)) FROM numbers(5) -- сумма = 0+1+2+3+4 = 10
```

```text theme={null}
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 как UInt64
```

**`minState` / `maxState`** — хранит байт флага, за которым следует значение соответствующего базового типа. Флаг равен `0x00` для пустого состояния (значения не встречались) или `0x01`, если значение присутствует:

```sql theme={null}
SELECT maxState(toUInt32(number)) FROM numbers(5) -- max = 4
```

```text theme={null}
0x01,                   // флаг: значение присутствует
0x04, 0x00, 0x00, 0x00, // 4 как UInt32
```

Пустое состояние (не агрегирована ни одна строка):

```sql theme={null}
SELECT minState(toUInt32(number)) FROM numbers(0)
```

```text theme={null}
0x00, // флаг: значение отсутствует
```

<Note>
  Более сложные функции, такие как `uniq`, `quantile` или `groupArray`, используют форматы, специфичные для конкретной реализации. Если вам нужно читать или записывать эти состояния, обратитесь к исходному коду ClickHouse для соответствующей функции.
</Note>

<div id="qbit">
  ### QBit
</div>

`QBit` — это векторный тип для эффективного поиска с различными уровнями точности. Внутренне он хранится в транспонированном виде. При передаче QBit представляет собой просто `Array` базового типа элемента (`Int8`, `Float32`, `Float64` или `BFloat16`). Оптимизация хранения за счёт транспонирования битов выполняется на стороне сервера, а не в протоколе RowBinary.

Синтаксис:

```text theme={null}
QBit(element_type, dimension[, stride])
```

Где `element_type` — `Int8`, `Float32`, `Float64` или `BFloat16`, а `dimension` — фиксированная размерность вектора. Необязательный параметр `stride` управляет только тем, как битовые плоскости группируются в потоки хранения на стороне сервера; он не влияет на формат передачи данных RowBinary, который всегда представляет собой полный массив из `dimension` элементов.

Формат передачи данных: идентичен `Array(element_type)`:

```text theme={null}
// LEB128 length
// followed by `length` elements of `element_type`
```

Пример кодирования `QBit(Float32, 4)` для `[1.0, 2.0, 3.0, 4.0]`:

```sql theme={null}
SELECT [1.0, 2.0, 3.0, 4.0]::QBit(Float32, 4)
```

```text theme={null}
0x04,                   // LEB128 - array has 4 elements
0x00, 0x00, 0x80, 0x3F, // 1.0 as Float32
0x00, 0x00, 0x00, 0x40, // 2.0 as Float32
0x00, 0x00, 0x40, 0x40, // 3.0 as Float32
0x00, 0x00, 0x80, 0x40, // 4.0 as Float32
```

<div id="format-settings">
  ## Настройки формата
</div>

Следующие настройки общие для всех форматов типа `RowBinary`.

| Настройка                                                                                                                                | Описание                                                                                                                                                                                                                                                                          | По умолчанию |
| ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| [`format_binary_max_string_size`](/docs/ru/reference/settings/formats#format_binary_max_string_size)                                          | Максимально допустимый размер значения String в формате RowBinary.                                                                                                                                                                                                                | `1GiB`       |
| [`output_format_binary_encode_types_in_binary_format`](/docs/ru/reference/settings/formats#input_format_binary_decode_types_in_binary_format) | Позволяет записывать типы в заголовке с использованием [`двоичного кодирования`](/docs/ru/reference/data-types/data-types-binary-encoding) вместо строк с именами типов в выходном формате [`RowBinaryWithNamesAndTypes`](/docs/ru/reference/formats/RowBinary/RowBinaryWithNamesAndTypes). | `false`      |
| [`input_format_binary_decode_types_in_binary_format`](/docs/ru/reference/settings/formats#input_format_binary_decode_types_in_binary_format)  | Позволяет читать типы в заголовке с использованием [`двоичного кодирования`](/docs/ru/reference/data-types/data-types-binary-encoding) вместо строк с именами типов во входном формате [`RowBinaryWithNamesAndTypes`](/docs/ru/reference/formats/RowBinary/RowBinaryWithNamesAndTypes).     | `false`      |
| [`output_format_binary_write_json_as_string`](/docs/ru/reference/settings/formats#output_format_binary_write_json_as_string)                  | Позволяет записывать значения типа данных [`JSON`](/docs/ru/reference/data-types/newjson) как значения `JSON` типа [String](/docs/ru/reference/data-types/string) в выходном формате [`RowBinary`](/docs/ru/reference/formats/RowBinary/RowBinary).                                              | `false`      |
| [`input_format_binary_read_json_as_string`](/docs/ru/reference/settings/formats#input_format_binary_read_json_as_string)                      | Позволяет читать значения типа данных [`JSON`](/docs/ru/reference/data-types/newjson) как значения `JSON` типа [String](/docs/ru/reference/data-types/string) во входном формате [`RowBinary`](/docs/ru/reference/formats/RowBinary/RowBinary).                                                  | `false`      |
