RowBinary 형식
RowBinary 형식
RowBinary 형식은 데이터를 행 단위로 바이너리 형식으로 파싱해요. 행과 값이 구분자 없이 연속으로 나열됩니다. Native 형식보다 덜 효율적이지만(행 기반이므로) 널리 사용되는 이진 형식이에요. 각 데이터 타입의 와이어 인코딩을 이해하면 다양한 언더클라이언트에서 파싱할 수 있습니다.
출처: 문서
본문
| Input | Output | Alias |
|---|---|---|
| ✔ | ✔ |
설명 (Description)
RowBinary 형식은 데이터를 행 단위로 바이너리 형식으로 파싱합니다. 행과 값이 구분자 없이 연속으로 나열됩니다.
데이터가 바이너리 형식이므로 FORMAT RowBinary 뒤의 구분자는 다음과 같이 엄격하게 지정됩니다:
- 임의의 수의 공백:
' '(스페이스 - 코드0x20)'\t'(탭 - 코드0x09)'\f'(폼 피드 - 코드0x0C)
- 그 뒤에 정확히 하나의 새 줄 시퀀스:
- Windows 스타일
"\r\n" - 또는 Unix 스타일
'\n'
- Windows 스타일
- 그리고 즉시 바이너리 데이터.
이 형식은 행 기반이므로 Native 형식보다 덜 효율적입니다.
데이터 타입 와이어 형식 (Data types wire format)
예시에 있는 대부분의 쿼리는 파일 출력과 함께 curl로 실행할 수 있습니다.
curl -XPOST "http://localhost:8123?default_format=RowBinary" \
--data-binary "SELECT 42 :: UInt32" > out.bin
그런 다음 데이터를 hex 편집기로 검사할 수 있어요.
부호 없는 LEB128 (Unsigned LEB128, Little Endian Base 128)
String, Array, Map과 같은 가변 크기 데이터 타입의 길이를 인코딩하는 데 사용되는 부호 없는 리틀엔디언 가변 폭 정수 인코딩입니다. 샘플 구현은 LEB128 위키 페이지에서 찾을 수 있어요.
(U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
모든 정수 타입은 리틀엔디언으로 적절한 바이트 수로 인코딩됩니다. 부호 있는 타입(Int8~Int256)은 2의 보수 표현을 사용합니다. 대부분의 언어는 내장 도구나 잘 알려진 라이브러리를 사용하여 바이트 배열에서 이러한 정수를 추출하는 것을 지원합니다. 대부분 언어의 네이티브 정수 크기를 초과하는 Int128/Int256, UInt128/UInt256의 경우 사용자 정의 역직렬화가 필요할 수 있습니다.
Bool
불리언 값은 단일 바이트로 인코딩되며 UInt8과 유사하게 역직렬화할 수 있습니다.
0은false1은true
Float32, Float64
Float32는 4바이트, Float64는 8바이트로 인코딩된 리틀엔디언 부동 소수점 숫자입니다. 정수와 마찬가지로 대부분의 언어는 이러한 값을 역직렬화할 적절한 도구를 제공합니다.
BFloat16
BFloat16(Brain Floating Point)은 Float32의 범위와 줄어든 정밀도를 가진 16비트 부동 소수점 형식으로, 머신러닝 워크로드에 유용합니다. 와이어 형식은 기본적으로 Float32 값의 상위 16비트입니다. 언어가 이를 네이티브로 지원하지 않으면 UInt16으로 읽고 쓰며 Float32로 변환하고 다시 변환하는 것이 가장 쉬운 방법입니다.
BFloat16을 Float32로 변환(의사코드):
// Read 2 bytes as little-endian UInt16
// Left-shift by 16 bits to get Float32 bits
bfloat16Bits = readUInt16()
float32Bits = bfloat16Bits << 16
floatValue = reinterpretAsFloat32(float32Bits)
Float32를 BFloat16으로 변환(의사코드):
// Right-shift Float32 bits by 16 to truncate to BFloat16
float32Bits = reinterpretAsUInt32(floatValue)
bfloat16Bits = float32Bits >> 16
writeUInt16(bfloat16Bits)
BFloat16의 샘플 기본 값:
SELECT CAST(1.25, 'BFloat16')
0xA0, 0x3F, // 1.25 as BFloat16
Decimal32, Decimal64, Decimal128, Decimal256
Decimal 타입은 각각의 비트 폭의 리틀엔디언 정수로 표현됩니다.
Decimal32- 4바이트, 또는Int32.Decimal64- 8바이트, 또는Int64.Decimal128- 16바이트, 또는Int128.Decimal256- 32바이트, 또는Int256.
Decimal 값을 역직렬화할 때 정수 부분과 소수 부분은 다음 의사코드로 파생할 수 있습니다:
let scale_multiplier = 10 ** scale
let whole_part = trunc(value / scale_multiplier) // truncate toward zero
let fractional_part = value % scale_multiplier
let result = Decimal(whole_part, fractional_part)
여기서 trunc는 0을 향한 잘림(음수 값에 대해 다른 floor 나눗셈이 아님)을 수행하고, scale은 소수점 이하 자릿수입니다. 예를 들어 Decimal(10, 2)(Decimal32(2)에 해당)에서 scale은 2이고, 값 12345는 (123, 45)로 표현됩니다.
직렬화에는 역 연산이 필요합니다:
let scale_multiplier = 10 ** scale
let result = whole_part * scale_multiplier + fractional_part
자세한 내용은 ClickHouse 문서의 Decimal 타입을 참조하세요.
String
ClickHouse 문자열은 임의의 바이트 시퀀스입니다. 유효한 UTF-8일 필요는 없습니다. 길이 접두사는 문자 수가 아닌 바이트 길이입니다.
두 부분으로 인코딩됩니다:
- 문자열의 바이트 길이를 나타내는 가변 길이 정수(LEB128).
- 문자열의 원시 바이트.
예를 들어 foobar 문자열은 다음과 같이 7바이트로 인코딩됩니다:
0x06, // LEB128 length of the string (6)
0x66, // 'f'
0x6f, // 'o'
0x6f, // 'o'
0x62, // 'b'
0x61, // 'a'
0x72, // 'r'
FixedString
String과 달리 FixedString은 스키마에 정의된 고정 길이를 갖습니다. 바이트 시퀀스로 인코딩되며, 값이 N보다 짧으면 뒤에 0바이트로 패딩됩니다.
FixedString을 읽을 때 뒤따르는 0바이트는 패딩이거나 데이터의 실제 \0 문자일 수 있으며, 와이어에서 구분할 수 없습니다. ClickHouse 자체는 모든 N 바이트를 그대로 보존합니다.
빈 FixedString(3)은 패딩 0만 포함합니다:
0x00, 0x00, 0x00
hi 문자열을 포함하는 비어 있지 않은 FixedString(3):
0x68, // 'h'
0x69, // 'i'
0x00, // padding zero
bar 문자열을 포함하는 비어 있지 않은 FixedString(3):
0x62, // 'b'
0x61, // 'a'
0x72, // 'r'
마지막 예시에서는 세 바이트를 모두 사용하므로 패딩이 필요하지 않습니다.
Date
1970-01-01 이후의 일 수를 나타내는 UInt16(두 바이트)로 저장됩니다.
지원 범위: [1970-01-01, 2149-06-06].
Date의 샘플 기본 값:
SELECT CAST('2024-01-15', 'Date') AS d
0x19, 0x4D, // 19737 as UInt16 (little-endian) = 19737 days since 1970-01-01
Date32
1970-01-01 이전 또는 이후의 일 수를 나타내는 Int32(네 바이트)로 저장됩니다.
지원 범위: [0000-01-01, 9999-12-31].
Date32의 샘플 기본 값:
SELECT CAST('2024-01-15', 'Date32') AS d
0x19, 0x4D, 0x00, 0x00, // 19737 as Int32 (little-endian) = 19737 days since 1970-01-01
epoch 이전의 날짜:
SELECT CAST('1900-01-01', 'Date32') AS d
0x21, 0x9C, 0xFF, 0xFF, // -25567 as Int32 (little-endian) = 25567 days before 1970-01-01
DateTime
1970-01-01 00:00:00 UTC 이후의 초 수를 나타내는 UInt32(네 바이트)로 저장됩니다.
구문:
DateTime([timezone])
예: DateTime 또는 DateTime('UTC').
바이너리 값은 항상 UTC epoch 오프셋입니다. 시간대는 인코딩을 변경하지 않습니다. 그러나 시간대는 삽입 시 문자열 값이 어떻게 해석되는지에 영향을 줍니다. '2024-01-15 10:30:00'을 DateTime('America/New_York') 컬럼에 삽입하면 같은 문자열을 DateTime('UTC') 컬럼에 삽입한 것과 다른 epoch 값을 저장합니다. 문자열이 컬럼의 시간대에서 로컬 시간으로 해석되기 때문입니다. 와이어에서는 둘 다 UInt32 epoch 초일 뿐입니다.
지원 범위: [1970-01-01 00:00:00, 2106-02-07 06:28:15].
DateTime의 샘플 기본 값:
SELECT CAST('2024-01-15 10:30:00', 'DateTime(\'UTC\')') AS d
0x28, 0x09, 0xA5, 0x65, // 1705314600 as UInt32 (little-endian)
DateTime64
1970-01-01 00:00:00 UTC 이전 또는 이후의 틱 수를 나타내는 Int64(여덟 바이트)로 저장됩니다. 틱 해상도는 아래 구문을 참조하여 precision 매개변수로 정의됩니다:
DateTime64(precision, [timezone])
precision은 0에서 9 사이의 정수입니다. 일반적으로 3(밀리초), 6(마이크로초), 9(나노초)만 사용됩니다.
유효한 DateTime64 정의의 예: DateTime64(0), DateTime64(3), DateTime64(6, 'UTC'), 또는 DateTime64(9, 'Europe/Amsterdam').
DateTime과 마찬가지로 바이너리 값은 항상 UTC epoch 오프셋입니다. 시간대는 삽입 시 문자열 값이 어떻게 해석되는지에 영향을 주지만(DateTime 참고 참조), 인코딩 자체는 항상 UTC epoch 이후의 Int64 틱입니다.
DateTime64 타입의 기본 Int64 값은 UNIX epoch 이전 또는 이후의 다음 단위 수로 해석될 수 있습니다:
DateTime64(0)- 초.DateTime64(3)- 밀리초.DateTime64(6)- 마이크로초.DateTime64(9)- 나노초.
지원 범위: [0000-01-01 00:00:00, 9999-12-31 23:59:59.999999999] (precision 7까지; precision 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를 나타냅니다.
기본 Int64 틱 범위가 더 높은 정밀도에서 더 좁기 때문에 최대 지원 값이 줄어듭니다. precision 8에서는 4892-10-07이고 precision 9(나노초)에서는 UTC로 2262-04-11 23:47:16입니다.
Time
초 단위의 시간 값을 나타내는 Int32로 저장됩니다. 음수 값도 유효합니다.
지원 범위: [-999:59:59, 999:59:59] (즉 [-3599999, 3599999] 초).
현재 Time 또는 Time64를 사용하려면 enable_time_time64_type 설정을 1로 설정해야 합니다.
Time의 샘플 기본 값:
SET enable_time_time64_type = 1;
SELECT CAST('15:32:16', 'Time') AS t
0x80, 0xDA, 0x00, 0x00, // 55936 seconds = 15:32:16
Time64
소수 초를 가진 시간 값을 나타내는 Decimal64(Int64로 저장)로 내부 저장됩니다. 정밀도를 구성할 수 있으며 음수 값도 유효합니다.
구문:
Time64(precision)
precision은 0에서 9 사이의 정수입니다. 일반적인 값: 3(밀리초), 6(마이크로초), 9(나노초).
지원 범위: [-999:59:59.xxxxxxxxx, 999:59:59.xxxxxxxxx].
현재 Time 또는 Time64를 사용하려면 enable_time_time64_type 설정을 1로 설정해야 합니다.
기본 Int64 값은 10^precision으로 스케일된 소수 초를 나타냅니다.
Time64의 샘플 기본 값:
SET enable_time_time64_type = 1;
SELECT CAST('15:32:16.123456', 'Time64(6)') AS t
0x40, 0x82, 0x0D, 0x06,
0x0D, 0x00, 0x00, 0x00, // 55936123456 as Int64
// 55936123456 / 10^6 = 55936.123456 seconds = 15:32:16.123456
Interval 타입 (Interval types)
모든 interval 타입은 Int64(여덟 바이트, 리틀엔디언)로 저장됩니다. 값은 해당 시간 단위의 수를 나타냅니다. 음수 값도 유효합니다.
Interval 타입: IntervalNanosecond, IntervalMicrosecond, IntervalMillisecond, IntervalSecond, IntervalMinute, IntervalHour, IntervalDay, IntervalWeek, IntervalMonth, IntervalQuarter, IntervalYear.
Interval 타입 이름(예: IntervalSecond vs IntervalDay)은 저장된 값의 단위를 결정합니다. 와이어 인코딩은 항상 동일합니다.
샘플 기본 값:
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
// 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,
Enum8, Enum16
enum 정의에서 enum 값의 인덱스를 나타내는 단일 바이트(Enum8 == Int8) 또는 두 바이트(Enum16 == Int16)로 저장됩니다. 저장 타입은 부호가 있다는 점에 유의하세요 — enum 값은 음수일 수 있습니다(예: Enum8('a' = -128, 'b' = 0)).
Enum은 다음과 같이 간단하게 정의할 수 있습니다:
SELECT 1 :: Enum8('hello' = 1, 'world' = 2) AS e;
┌─e─────┐
1. │ hello │
└───────┘
위에 정의된 Enum8은 클라이언트에서 다음 값 맵을 갖습니다:
Map<Int8, String> {
1: 'hello',
2: 'world'
}
또는 다음과 같이 더 복잡하게:
SELECT 42 :: Enum16('f\'' = 1, 'x =' = 2, 'b\'\'' = 3, '\'c=4=' = 42, '4' = 1234) AS e;
┌─e─────┐
1. │ 'c=4= │
└───────┘
위에 정의된 Enum16은 클라이언트에서 다음 값 맵을 갖습니다:
Map<Int16, String> {
1: 'f\'',
2: 'x =',
3: 'b\'',
42: '\'c=4=',
1234: '4'
}
데이터 타입 파서의 경우 주요 과제는 ' 같은 enum 정의의 이스케이프된 기호와 따옴표 문자열 안에 나타날 수 있는 = 같은 특수 기호를 추적하는 것입니다.
UUID
16바이트 시퀀스로 표현됩니다. UUID는 두 개의 little-endian UInt64 값으로 저장됩니다. 표준 UUID 표현의 처음 8바이트는 바이트 반전되고, 두 번째 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은 다음과 같이 표현됩니다:
0xE7, 0x11, 0xB3, 0x5C, 0x04, 0xC4, 0xF0, 0x61,
0xA0, 0xDB, 0xD3, 0x6A, 0x00, 0xA6, 0x7B, 0x90,
- 기본 UUID
00000000-0000-0000-0000-000000000000은 16개의 0바이트로 표현됩니다:
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
새 레코드가 삽입되었지만 UUID 값이 지정되지 않은 경우 사용할 수 있습니다.
IPv4
UInt32로 리틀엔디언 바이트 순서로 4바이트에 저장됩니다. IP 주소에 일반적으로 사용되는 전통적인 네트워크 바이트 순서(big-endian)와 다르다는 점에 유의하세요.
IPv4의 샘플 기본 값:
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
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
IPv6
16바이트에 big-endian / 네트워크 바이트 순서(MSB 먼저)로 저장됩니다.
IPv6의 샘플 기본 값:
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
// 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,
Nullable
nullable 데이터 타입은 다음과 같이 인코딩됩니다:
- 값이
NULL인지 여부를 나타내는 단일 바이트:0x00은 값이NULL이 아님을 의미합니다.0x01은 값이NULL임을 의미합니다.
- 값이
NULL이 아니면 기본 데이터 타입이 평소처럼 인코딩됩니다. 값이NULL이면 기본 타입에 대해 추가 바이트가 기록되지 않습니다.
예를 들어 Nullable(UInt32) 값:
SELECT
CAST(42, 'Nullable(UInt32)') AS a,
CAST(NULL, 'Nullable(UInt32)') AS b
0x00, // Not NULL - the value follows
0x2A, 0x00, 0x00, 0x00, // UInt32(42)
0x01, // NULL - nothing follows
LowCardinality
RowBinary 형식에서 low-cardinality 마커는 와이어 형식에 영향을 주지 않습니다. 예를 들어 LowCardinality(String)은 일반 String과 동일하게 인코딩됩니다.
이것은 RowBinary에만 적용됩니다. Native 형식에서 LowCardinality는 다른 딕셔너리 기반 인코딩을 사용합니다.
컬럼은 LowCardinality(Nullable(T))로 정의할 수 있지만 Nullable(LowCardinality(T))로 정의할 수는 없습니다. 항상 서버에서 오류가 발생합니다.
테스트 중에는 allow_suspicious_low_cardinality_types를 1로 설정하여 LowCardinality 내부의 대부분의 데이터 타입을 더 나은 커버리지로 허용할 수 있습니다.
Array
배열은 다음과 같이 인코딩됩니다:
- 배열의 요소 수를 나타내는 가변 길이 정수(LEB128).
- 기본 데이터 타입과 동일한 방식으로 인코딩된 배열의 요소.
예를 들어 UInt32 값이 있는 배열:
SELECT CAST(array(1, 2, 3), 'Array(UInt32)') AS arr
0x03, // LEB128 - the array has 3 elements
0x01, 0x00, 0x00, 0x00, // UInt32(1)
0x02, 0x00, 0x00, 0x00, // UInt32(2)
0x03, 0x00, 0x00, 0x00, // UInt32(3)
약간 더 복잡한 예:
SELECT array('foobar', 'qaz') AS arr
0x02, // LEB128 - the array has 2 elements
0x06, // LEB128 - the first string has 6 bytes
0x66, 0x6f, 0x6f,
0x62, 0x61, 0x72, // 'foobar'
0x03, // LEB128 - the second string has 3 bytes
0x71, 0x61, 0x7a, // 'qaz'
배열은 nullable 값을 포함할 수 있지만 배열 자체는 nullable일 수 없습니다.
다음은 유효합니다:
SELECT CAST([NULL, 'foo'], 'Array(Nullable(String))') AS arr;
┌─arr──────────┐
1. │ [NULL,'foo'] │
└──────────────┘
그리고 다음과 같이 인코딩됩니다:
0x02, // LEB128 - the array has 2 elements
0x01, // Is NULL - nothing follows for this element
0x00, // Is NOT NULL - the data follows
0x03, // LEB128 - the string has 3 bytes
0x66, 0x6f, 0x6f, // 'foo'
다차원 배열을 다루는 예시는 Geo 섹션에서 볼 수 있습니다.
Tuple
튜플은 튜플의 모든 요소가 추가 메타 정보나 구분자 없이 해당 와이어 형식으로 차례로 이어지도록 인코딩됩니다.
CREATE OR REPLACE TABLE foo
(
`t` Tuple(
UInt32,
String,
Array(UInt8)
)
)
ENGINE = Memory;
INSERT INTO foo VALUES ((42, 'foo', array(99, 144)));
0x2a, 0x00, 0x00, 0x00, // 42 as UInt32
0x03, // LEB128 - the string has 3 bytes
0x66, 0x6f, 0x6f, // 'foo'
0x02, // LEB128 - the array has 2 elements
0x63, // 99 as UInt8
0x90, // 144 as UInt8
튜플 데이터 타입의 문자열 인코딩은 Enum 타입과 유사한 과제를 제시합니다. 이스케이프된 기호와 특수 문자 추적과 같은 것을요. 이제 Tuple에서는 열고 닫는 괄호를 추적하는 것도 필요합니다. 또한 가장 복잡한 튜플은 다른 중첩된 Tuple, Array, Map, 심지어 enum을 포함할 수 있다는 점에 유의하세요.
예를 들어 다음 테이블에서 튜플은 이름에 따옴표와 괄호가 있는 enum을 포함하는데, 제대로 처리하지 않으면 파싱 문제가 발생할 수 있습니다:
CREATE OR REPLACE TABLE foo
(
`t` Tuple(
Enum8('f\'()' = 0),
Array(Nullable(Tuple(UInt32, String)))
)
) ENGINE = Memory;
Map
Map은 Array(Tuple(K, V))로 볼 수 있으며, K는 키 타입, V는 값 타입입니다. Map은 다음과 같이 인코딩됩니다:
- 맵의 요소 수를 나타내는 가변 길이 정수(LEB128).
- 해당 타입으로 인코딩된 키-값 쌍으로서의 맵의 요소.
예를 들어 String 키와 UInt32 값이 있는 맵:
SELECT CAST(map('foo', 1, 'bar', 2), 'Map(String, UInt32)') AS m
0x02, // LEB128 - the map has 2 elements
0x03, // LEB128 - the first key has 3 bytes
0x66, 0x6f, 0x6f, // 'foo'
0x01, 0x00, 0x00, 0x00, // UInt32(1)
0x03, // LEB128 - the second key has 3 bytes
0x62, 0x61, 0x72, // 'bar'
0x02, 0x00, 0x00, 0x00, // UInt32(2)
Map(String, Map(Int32, Array(Nullable(String))))과 같은 깊게 중첩된 구조의 맵도 가능하며, 위에서 설명한 것과 유사하게 인코딩됩니다.
Variant
이 타입은 다른 데이터 타입들의 합집합(union)을 나타냅니다. Variant(T1, T2, ..., TN) 타입은 이 타입의 각 행이 T1, T2, … 또는 TN 중 하나이거나 그 중 어느 것도 아닌 값(NULL 값)을 가진다는 뜻입니다.
최종 사용자에게 Variant(T1, T2)는 Variant(T2, T1)와 정확히 동일하게 의미하지만, 정의의 타입 순서는 와이어 형식에 중요합니다. 정의의 타입은 항상 알파벳순으로 정렬되며, 정확한 variant가 "판별자(discriminant)"(정의의 데이터 타입 인덱스)로 인코딩되므로 이것이 중요합니다.
다음 예시를 고려해 보세요:
SET allow_experimental_variant_type = 1,
allow_suspicious_variant_types = 1;
CREATE OR REPLACE TABLE foo
(
-- It does not matter what is the order of types in the user input;
-- the types are always sorted alphabetically in the wire format.
`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;
0x01, // type index -> Bool
0x01, // true
0x03, // type index -> FixedString(6)
0x66, 0x6F, 0x6F, 0x62, 0x61, 0x72, // 'foobar'
0x05, // type index -> Float64
0x00, 0x00, 0x00, 0x00,
0x00, 0x20, 0x59, 0x40, // 100.5 as Float64
0x06, // type index -> Int128
0x64, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // 100 as Int128
0x00, // type index -> Array(Int16)
0x03, // LEB128 - the array has 3 elements
0x01, 0x00, // 1 as Int16
0x02, 0x00, // 2 as Int16
0x03, 0x00, // 3 as Int16
NULL 값은 판별자 바이트 0xFF로 인코딩됩니다:
SELECT NULL :: Variant(UInt32, String)
0xFF, // discriminant = NULL
allow_suspicious_variant_types 설정을 사용하여 Variant 타입의 더 철저한 테스트를 허용할 수 있습니다.
Dynamic
Dynamic 타입은 런타임에 결정된 어떤 타입의 값도 보유할 수 있습니다. RowBinary 형식에서 각 값은 자체 기술(self-describing)입니다. 첫 번째 부분은 이 형식의 타입 사양입니다. 그 다음에 이 문서에서 설명한 값 인코딩이 따르며 내용이 옵니다. 따라서 값을 파싱하려면 타입 인덱스를 사용하여 올바른 파서를 결정하고, 이미 있는 RowBinary 파싱을 재사용하면 됩니다.
[BinaryTypeIndex][type-specific parameters...][value]
BinaryTypeIndex는 타입을 식별하는 단일 바이트입니다. 타입 인덱스와 매개변수에 대한 참조는 여기를 참조하세요.
NULL Dynamic 값은 추가 바이트 없이 BinaryTypeIndex 0x00(Nothing 타입)으로 인코딩됩니다:
SELECT NULL::Dynamic
00 # BinaryTypeIndex: Nothing (0x00), represents NULL
예시:
SELECT 42::Dynamic
0a # BinaryTypeIndex: Int64 (0x0A)
2a 00 00 00 00 00 00 00 # Int64 value: 42
SELECT toDateTime64('2024-01-15 10:30:00', 3, 'America/New_York')::Dynamic
14 # BinaryTypeIndex: DateTime64WithTimezone (0x14)
03 # UInt8: precision
10 # VarUInt: timezone name length
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: timestamps
JSON
JSON 타입은 데이터를 두 가지 범주로 인코딩합니다:
- Typed Paths(타입 있는 경로) - 스키마에 명시적 타입으로 선언된 경로(예:
JSON(user_id UInt32, name String)) - Dynamic Paths(동적 경로)/Overflow paths(동적 경로 한도 초과 시) -
Dynamic타입으로 저장된 런타임 발견 경로. 값 인코딩 앞에 타입 정의가 옵니다.
이 두 범주에 대한 와이어 형식과 규칙이 다릅니다.
| Path Category | Included in Serialization | Value Encoding | Variant/Nullable allowed |
|---|---|---|---|
| Typed paths | 항상 (NULL이어도) | 타입별 바이너리 형식 | Yes |
| Dynamic paths | non-null일 때만 | Dynamic | No |
경로는 세 그룹으로 직렬화되어 순차적으로 기록됩니다: typed 경로, dynamic 경로, 그 다음 shared data(오버플로우) 경로. Typed와 dynamic 경로는 구현 정의 순서(내부 해시맵 반복에 의해 결정)로 기록되는 반면, shared data 경로는 알파벳순으로 기록됩니다. 리더는 특정 경로 순서에 의존해서는 안 됩니다. 역직렬화기는 각 경로를 위치가 아닌 이름으로 디스패치합니다.
RowBinary 형식의 각 JSON 행은 다음과 같이 직렬화됩니다:
[VarUInt: number_of_paths]
[String: path_1][value_1]
[String: path_2][value_2]
...
예시:
- Typed 경로만 있는 단순 JSON:
스키마: JSON(user_id UInt32, active Bool) 행: {"user_id": 42, "active": true} 바이너리 인코딩(주석이 있는 hex):
02 # VarUInt: 2 paths total
# Typed path "active"
06 61 63 74 69 76 65 # String: "active" (length 6 + bytes)
01 # Bool/UInt8 value: true (1)
# Typed path "user_id"
07 75 73 65 72 5F 69 64 # String: "user_id" (length 7 + bytes)
2A 00 00 00 # UInt32 value: 42 (little-endian)
- Typed와 dynamic 경로가 있는 단순 JSON:
스키마: JSON(user_id UInt32, active Bool) 행: {"user_id": 42, "active": true, "name": "Alice"} 바이너리 인코딩(주석이 있는 hex):
03 # VarUInt: 3 paths total
# Typed path "active"
06 61 63 74 69 76 65 # String: "active" (length 6 + bytes)
01 # Bool/UInt8 value: true (1)
# Dynamic path "name"
04 6E 61 6D 65 # String: "name" (length 4 + bytes)
15 # BinaryTypeIndex: String (0x15)
05 41 6C 69 63 65 # String value: "Alice" (length 5 + bytes)
# Typed path "user_id"
07 75 73 65 72 5F 69 64 # String: "user_id" (length 7 + bytes)
2A 00 00 00 # UInt32 value: 42 (little-endian)
- Null 처리:
Typed nullable 컬럼으로 null을 얻습니다:
스키마: JSON(score Nullable(Int32)) 행: {"score": null } 바이너리 인코딩(주석이 있는 hex):
01 # VarUInt: 1 path total
# Typed path "score" (Nullable)
05 73 63 6f 72 65 # String: "score" (length 5 + bytes)
01 # Nullable flag: 1 (is NULL, no value follows)
Typed non-nullable 컬럼으로 기본값을 얻습니다:
스키마: JSON(name String) 행: {"name": null} 바이너리 인코딩:
01 # VarUInt: 1 path (dynamic NULL paths are skipped!)
04 6e 61 6d 65 # "name"
00 # String length 0 (empty string)
Dynamic 경로는 무시됩니다:
스키마: JSON(id UInt64) 행: {"id": 100, "metadata": null} 바이너리 인코딩:
01 # VarUInt: 1 path (dynamic NULL paths are skipped!)
# Typed path "id"
02 69 64 # String: "id" (length 2 + bytes)
64 00 00 00 00 00 00 00 # UInt64 value: 100 (little-endian)
참고: NULL 값의 metadata 경로는 dynamic 경로가 non-null일 때만 직렬화되므로 포함되지 않습니다. 이것은 typed 경로와의 핵심 차이입니다.
- 중첩 JSON 객체:
스키마: JSON() 행: {"user": {"name": "Bob", "age": 30}} 바이너리 인코딩(주석이 있는 hex):
02 # VarUInt: 2 paths (nested objects are flattened)
# Dynamic path "user.age"
08 75 73 65 72 2E 61 67 65 # String: "user.age" (length 8 + bytes)
0A # BinaryTypeIndex: Int64 (0x0A)
1E 00 00 00 00 00 00 00 # Int64 value: 30 (little-endian)
# Dynamic path "user.name"
09 75 73 65 72 2E 6E 61 6D 65 # String: "user.name" (length 9 + bytes)
15 # BinaryTypeIndex: String (0x15)
03 42 6F 62 # String value: "Bob" (length 3 + bytes)
참고: 중첩 객체는 점으로 구분된 경로로 평탄화됩니다(중첩 구조 대신 user.name처럼).
대안: JSON을 String 모드로
output_format_binary_write_json_as_string=1 설정으로 JSON 컬럼은 구조화된 바이너리 형식 대신 단일 JSON 텍스트 문자열로 직렬화됩니다. JSON 컬럼에 쓰는 것에도 해당 설정 input_format_binary_read_json_as_string이 있습니다. 여기서 설정을 선택하는 것은 클라이언트에서 파싱할지 서버에서 파싱할지에 달려 있습니다.
Geo 타입 (Geo types)
Geo는 지리 데이터를 나타내는 데이터 타입 범주입니다. 다음을 포함합니다:
Point-Tuple(Float64, Float64)로.MultiPoint-Array(Point)또는Array(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, MultiPoint, Ring, Polygon, MultiPolygon, LineString, MultiLineString과 같은 이러한 타입의 별칭이 포함됩니다.
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
// Point - or Tuple(Float64, Float64)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, // Point.X
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // Point.Y
// Ring - or Array(Tuple(Float64, Float64))
0x02, // LEB128 - the "ring" array has 2 points
// Ring - Point #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40,
// Ring - Point #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40,
// Polygon - or Array(Array(Tuple(Float64, Float64)))
0x02, // LEB128 - the "polygon" array has 2 rings
0x02, // LEB128 - the first ring has 2 points
// Polygon - Ring #1 - Point #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40,
// Polygon - Ring #1 - Point #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40,
0x01, // LEB128 - the second ring has 1 point
// Polygon - Ring #2 - Point #1 (the only one)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x40,
// MultiPolygon - or Array(Array(Array(Tuple(Float64, Float64))))
0x01, // LEB128 - the "multi_polygon" array has 1 polygon
0x02, // LEB128 - the first polygon has 2 rings
0x02, // LEB128 - the first ring has 2 points
// MultiPolygon - Polygon #1 - Ring #1 - Point #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x40,
// MultiPolygon - Polygon #1 - Ring #1 - Point #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x40,
0x01, // LEB128 - the second ring has 1 point
// MultiPolygon - Polygon #1 - Ring #2 - Point #1 (the only one)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x40,
// LineString - or Array(Tuple(Float64, Float64))
0x02, // LEB128 - the line string has 2 points
// LineString - Point #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x40,
// LineString - Point #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x40,
// MultiLineString - or Array(Array(Tuple(Float64, Float64)))
0x02, // LEB128 - the multi line string has 2 line strings
0x02, // LEB128 - the first line string has 2 points
// MultiLineString - LineString #1 - Point #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x40,
// MultiLineString - LineString #1 - Point #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x40,
0x01, // LEB128 - the second line string has 1 point
// MultiLineString - LineString #2 - Point #1 (the only one)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x40,
Geometry
Geometry는 위에 나열된 Geo 타입 중 어떤 것이든 보유할 수 있는 Variant 타입입니다. 와이어에서는 Variant와 정확히 동일하게 인코딩되며, 어떤 geo 타입이 뒤에 오는지 나타내는 판별자 바이트가 있습니다.
Geometry의 판별자 인덱스:
| Index | Type |
|---|---|
| 0 | LineString |
| 1 | MultiLineString |
| 2 | MultiPolygon |
| 3 | Point |
| 4 | Polygon |
| 5 | Ring |
| 6 | MultiPoint |
와이어 형식 구조:
// 1 byte discriminant (0-6)
// followed by the corresponding geo type data
Point를 Geometry로 인코딩한 샘플:
SELECT ((1.0, 2.0)::Point)::Geometry
0x03, // discriminant = 3 (Point)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, // Point.X = 1.0 as Float64
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, // Point.Y = 2.0 as Float64
Ring을 Geometry로 인코딩한 샘플:
0x05, // discriminant = 5 (Ring)
0x02, // LEB128 - array has 2 points
// Point #1
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, // X = 3.0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, // Y = 4.0
// Point #2
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x40, // X = 5.0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, // Y = 6.0
Nested
Nested의 와이어 형식은 flatten_nested 설정에 따라 다릅니다. 단일 행의 모든 구성요소 배열은 같은 길이를 가져야 합니다. 이는 서버 강제 제약입니다. 길이가 일치하지 않으면 삽입 오류가 발생합니다.
flatten_nested = 1 (기본값)
기본 설정에서 Nested는 독립 배열로 평탄화됩니다. 각 하위 컬럼은 점으로 구분된 이름을 가진 별도의 Array 컬럼이 됩니다:
CREATE OR REPLACE TABLE foo
(
n Nested(a String, b Int32)
) ENGINE = MergeTree ORDER BY ();
-- flatten_nested=1 is the default
INSERT INTO foo VALUES (['foo', 'bar'], [42, 144]);
DESCRIBE TABLE foo는 평탄화된 컬럼을 보여줍니다:
┌─name─┬─type──────────┐
1. │ n.a │ Array(String) │
2. │ n.b │ Array(Int32) │
└──────┴───────────────┘
각 배열은 Array 섹션에서 설명한 대로 독립적으로 직렬화됩니다:
0x02, // LEB128 - 2 String elements in the first array (n.a)
0x03, // LEB128 - the first string has 3 bytes
0x66, 0x6F, 0x6F, // 'foo'
0x03, // LEB128 - the second string has 3 bytes
0x62, 0x61, 0x72, // 'bar'
0x02, // LEB128 - 2 Int32 elements in the second array (n.b)
0x2A, 0x00, 0x00, 0x00, // 42 as Int32
0x90, 0x00, 0x00, 0x00, // 144 as Int32
flatten_nested = 0
flatten_nested = 0을 사용하면 Nested는 Array(Tuple(...)) 타입의 단일 컬럼으로 보존됩니다. 컬럼 이름은 점으로 구분되지 않습니다:
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는 단일 컬럼을 보여줍니다:
┌─name─┬─type───────────────────────┐
1. │ n │ Nested(a String, b Int32) │
└──────┴────────────────────────────┘
인코딩은 Array(Tuple(String, Int32))입니다: 배열 길이 접두사, 그 다음 각 요소의 튜플 필드가 순서대로:
0x02, // LEB128 - 2 elements in the array
0x03, // LEB128 - first tuple, field a: 3 bytes
0x66, 0x6F, 0x6F, // 'foo'
0x2A, 0x00, 0x00, 0x00, // first tuple, field b: 42 as Int32
0x03, // LEB128 - second tuple, field a: 3 bytes
0x62, 0x61, 0x72, // 'bar'
0x90, 0x00, 0x00, 0x00, // second tuple, field b: 144 as Int32
필드가 요소별로 인터리브되어 있다는 점에 유의하세요(a₁, b₁, a₂, b₂). 평탄화 표현에서처럼 컬럼별로 그룹화되지 않은 것(a₁, a₂, b₁, b₂)이에요.
SimpleAggregateFunction
SimpleAggregateFunction(func, T)는 기본 데이터 타입 T와 동일하게 인코딩됩니다. 집계 함수 이름은 와이어 형식에 영향을 주지 않습니다.
예를 들어 SimpleAggregateFunction(max, UInt32)는 일반 UInt32와 동일하게 인코딩됩니다:
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입니다:
0x2A, 0x00, 0x00, 0x00, // 42 as UInt32
AggregateFunction
AggregateFunction(func, T)는 집계 함수의 전체 중간 상태를 저장합니다. SimpleAggregateFunction도 중간 상태를 저장하지만 기본 데이터 타입과 동일하게 인코딩하는 반면, AggregateFunction은 각 집계 함수에 특정된 형식의 불투명한 바이너리 블롭을 저장합니다.
집계 상태는 RowBinary에서 길이 접두사가 없습니다. 파서는 각 특정 집계 함수의 내부 직렬화 형식을 이해해야 몇 바이트를 소비할지 알 수 있습니다. 실제로 대부분의 클라이언트는 집계 상태를 불투명하게 취급하고 *State / *Merge 결합자를 사용하여 서버가 직렬화를 처리하게 합니다.
내부 형식은 함수에 따라 다양합니다. 몇 가지 간단한 예:
countState — 개수를 VarUInt(LEB128)로 저장합니다:
SELECT countState(number) FROM numbers(5)
0x05, // VarUInt: 5
sumState — 누적 합을 고정 크기 정수로 저장합니다. 너비는 인자 타입에 따라 다릅니다(정수 인자의 경우 UInt64):
SELECT sumState(toUInt32(number)) FROM numbers(5) -- sum = 0+1+2+3+4 = 10
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 as UInt64
minState / maxState — 플래그 바이트 다음에 기본 타입의 값을 저장합니다. 플래그는 빈 상태(값을 본 적 없음)의 경우 0x00 또는 값이 있을 때 0x01입니다:
SELECT maxState(toUInt32(number)) FROM numbers(5) -- max = 4
0x01, // flag: has value
0x04, 0x00, 0x00, 0x00, // 4 as UInt32
빈 상태(집계된 행 없음):
SELECT minState(toUInt32(number)) FROM numbers(0)
0x00, // flag: no value
uniq, quantile, groupArray와 같은 더 복잡한 함수는 구현별 형식을 사용합니다. 이러한 상태를 읽거나 써야 한다면 특정 함수에 대한 ClickHouse 소스 코드를 참조하세요.
aggregate_function_input_format 설정을 value 또는 array로 설정하면 AggregateFunction(func, T) 컬럼이 T 값(여러 개면 인자 타입들의 Tuple) 또는 그러한 값들의 Array로 읽히며, 정확히 RowBinary에서 해당 타입이 인코딩되는 방식으로 인코딩됩니다. 상태는 서버 측에서 값으로부터 구축됩니다. 예를 들어 aggregate_function_input_format = 'array'로 설정하면 AggregateFunction(avg, UInt32) 셀은 Array(UInt32)입니다: LEB128 길이 다음에 요소들이 옵니다.
QBit
QBit은 다양한 정밀도 수준으로 효율적인 조회를 위한 벡터 타입입니다. 내부적으로 전치(transposed) 형식으로 저장됩니다. 와이어에서 QBit은 기본 요소 타입(Int8, Float32, Float64, 또는 BFloat16)의 단순 Array입니다. 비트 전치 최적화는 RowBinary 프로토콜이 아닌 서버 측 스토리지에서 발생합니다.
구문:
QBit(element_type, dimension[, stride])
element_type은 Int8, Float32, Float64 또는 BFloat16이고, dimension은 고정 벡터 차원입니다. 선택적 stride는 비트 평면이 서버 측 스토리지 스트림으로 그룹화되는 방식을 제어할 뿐이며, 항상 dimension 요소의 전체 배열인 RowBinary 와이어 형식에는 영향을 주지 않습니다.
와이어 형식: Array(element_type)와 동일:
// LEB128 length
// followed by `length` elements of `element_type`
[1.0, 2.0, 3.0, 4.0]을 포함하는 QBit(Float32, 4)의 샘플 인코딩:
SELECT [1.0, 2.0, 3.0, 4.0]::QBit(Float32, 4)
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
형식 설정 (Format settings)
다음 설정은 모든 RowBinary 타입 형식에 공통입니다.
| Setting | Description | Default |
|---|---|---|
| format_binary_max_string_size | RowBinary 형식에서 String의 허용 최대 크기. | 1GiB |
| output_format_binary_encode_types_in_binary_format | RowBinaryWithNamesAndTypes 출력 형식에서 타입 이름 문자열 대신 바이너리 인코딩으로 헤더의 타입 기록 허용. | false |
| input_format_binary_decode_types_in_binary_format | RowBinaryWithNamesAndTypes 입력 형식에서 타입 이름 문자열 대신 바이너리 인코딩으로 헤더의 타입 읽기 허용. | false |
| output_format_binary_write_json_as_string | RowBinary 출력 형식에서 JSON 데이터 타입 값을 JSON String 값으로 기록 허용. | false |
| input_format_binary_read_json_as_string | RowBinary 입력 형식에서 JSON 데이터 타입 값을 JSON String 값으로 읽기 허용. | false |