RowBinary
| 入力 | 出力 | エイリアス |
|---|---|---|
| ✔ | ✔ |
説明
RowBinary フォーマットは、バイナリ形式で行ごとにデータをパースします。
行および値は区切り文字なしで連続して並びます。
データがバイナリ形式であるため、FORMAT RowBinary の後に続く区切り文字は次のように厳密に決められています。
- 任意数の空白文字:
' '(スペース - コード0x20)'\t'(タブ - コード0x09)'\f'(フォームフィード - コード0x0C)
- 続いて、正確に 1 つの改行シーケンス:
- Windows スタイルの
"\r\n" - または Unix スタイルの
'\n'
- Windows スタイルの
- その直後にバイナリデータが続きます。
このフォーマットは行ベースであるため、Native フォーマットより効率が劣ります。
データ型のワイヤ形式
例で示されているクエリのほとんどは、curl を使ってファイルに出力する形で実行できます。
その後、データを16進エディタで確認できます。
符号なし LEB128 (リトルエンディアンベース128)
String、Array、Map などの可変長データ型の長さをエンコードするために使用される、符号なしリトルエンディアンの可変長整数エンコーディングです。実装例は LEB128 の wiki ページ にあります。
(U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
すべての整数型は、適切なバイト数のリトルエンディアンでエンコードされます。符号付き型 (Int8 から Int256) は、2 の補数表現を使用します。ほとんどの言語では、組み込みツールまたは広く利用されているライブラリを使って、このような整数をバイト配列から取り出せます。Int128/Int256 および UInt128/UInt256 は、多くの言語のネイティブ整数型のサイズを超えるため、カスタムのデシリアライズが必要になる場合があります。
Bool
ブール値は1バイトでエンコードされ、UInt8 と同様にデシリアライズできます。
0はfalse1はtrue
Float32, Float64
Float32 は 4 バイト、Float64 は 8 バイトでエンコードされるリトルエンディアンの浮動小数点数です。整数と同様に、ほとんどの言語ではこれらの値をデシリアライズするための適切なツールが提供されています。
BFloat16
BFloat16 (Brain Floating Point) は、Float32と同じ範囲を持ちながら精度を抑えた16ビット浮動小数点形式で、機械学習ワークロードに適しています。ワイヤ形式は、基本的にFloat32値の上位16ビットです。使用している言語がこれをネイティブでサポートしていない場合、最も簡単な方法は UInt16 として読み書きし、Float32 との間で変換することです。
BFloat16をFloat32に変換するには (擬似コード) :
Float32 を BFloat16 に変換するには (擬似コード) :
BFloat16 の内部値の例:
Decimal32, Decimal64, Decimal128, Decimal256
Decimal 型は、それぞれのビット幅に対応するリトルエンディアンの整数として表現されます。
Decimal32- 4 バイト、つまりInt32。Decimal64- 8 バイト、つまりInt64。Decimal128- 16 バイト、つまりInt128。Decimal256- 32 バイト、つまりInt256。
Decimal 値をデシリアライズする際、整数部と小数部は次の疑似コードで求められます。
ここで、trunc は 0 方向への切り捨てを行います (負の値では結果が異なるため、床除算ではありません) 。また、scale は小数点以下の桁数です。例えば、Decimal(10, 2) (Decimal32(2) と同等) の場合、scale は 2 で、値 12345 は (123, 45) として表されます。
シリアライズには、この逆の操作が必要です。
詳細は、ClickHouse ドキュメントの Decimal 型を参照してください。
String
ClickHouse の文字列は任意のバイト列です。有効な UTF-8 である必要はありません。長さのプレフィックスは、文字数ではなくバイト長です。
次の 2 つのパートでエンコードされます。
- 文字列の長さをバイト単位で示す可変長整数 (LEB128) 。
- 文字列の生のバイト列。
たとえば、文字列 foobar は次のように 7 バイトでエンコードされます。
FixedString
String とは異なり、FixedString は固定長で、その長さは schema で定義されます。バイト列としてエンコードされ、値が N より短い場合は末尾がゼロバイトで埋められます。
FixedString を読み取る際、末尾のゼロバイトはパディングの場合もあれば、データ中の実際の \0 文字の場合もあり、ワイヤ上では区別できません。ClickHouse 自体は N バイトすべてをそのまま保持します。
空の FixedString(3) には、パディング用のゼロのみが含まれます。
文字列 hi を含む空でない FixedString(3):
文字列 bar を含む空ではない FixedString(3):
最後の例では、3 バイトすべてが使われているため、パディングは不要です。
Date
1970-01-01 からの経過日数 を表す UInt16 (2 バイト) として格納されます。
サポートされる値の範囲: [1970-01-01, 2149-06-06]。
Date の内部値の例:
Date32
1970-01-01 以前または以後の日数を表す Int32 (4 バイト) として格納されます。
サポートされる値の範囲: [1900-01-01, 2299-12-31]。
Date32 の内部値の例:
エポック以前の日付:
DateTime
1970-01-01 00:00:00 UTC からの 経過秒数を表す UInt32 (4 バイト) として格納されます。
構文:
たとえば、DateTime または DateTime('UTC') です。
バイナリ値は常に UTC エポックオフセットです。タイムゾーンによってエンコーディングが変わることはありません。ただし、挿入時に文字列値がどのように解釈されるかにはタイムゾーンが確かに影響します。たとえば、'2024-01-15 10:30:00' を DateTime('America/New_York') カラムに挿入すると、同じ文字列を DateTime('UTC') カラムに挿入した場合とは異なるエポック値が格納されます。これは、その文字列がカラムのタイムゾーンにおけるローカル時刻として解釈されるためです。ワイヤ上では、どちらも単なる UInt32 のエポック秒です。
サポートされる値の範囲: [1970-01-01 00:00:00, 2106-02-07 06:28:15]。
DateTime の基になる値の例:
DateTime64
1970-01-01 00:00:00 UTCを基準として、その前後のティック数を表すInt64 (8バイト) として格納されます。ティックの分解能はprecisionパラメータで定義されます。以下の構文を参照してください。
ここで precision は 0 から 9 までの整数です。通常使用されるのは、3 (ミリ秒) 、6 (マイクロ秒) 、
9 (ナノ秒) のみです。
有効な DateTime64 定義の例: DateTime64(0)、DateTime64(3)、DateTime64(6, 'UTC')、DateTime64(9, 'Europe/Amsterdam')。
DateTime と同様に、バイナリ値は常に UTC エポックからのオフセットです。タイムゾーンは、文字列値が insert 時にどのように解釈されるかに影響します (DateTime の注記を参照) が、エンコーディング自体は常に UTC エポックからの Int64 ティックです。
DateTime64 型の基になる Int64 値は、UNIX エポックの前後における以下の単位数として解釈できます。
DateTime64(0)- 秒。DateTime64(3)- ミリ秒。DateTime64(6)- マイクロ秒。DateTime64(9)- ナノ秒。
サポートされる値の範囲: [1900-01-01 00:00:00, 2299-12-31 23:59:59.99999999]。
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を表します。
最大値の精度は 8 桁です。最大精度の 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 の内部値の例:
Time64
内部的には Decimal64 (Int64 として格納) で保持され、小数秒を含む時刻値を表します。精度は設定可能です。負の値も有効です。
構文:
ここで 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 の内部値の例:
Interval 型
すべての Interval 型は Int64 (8 バイト、リトルエンディアン) として格納されます。値は対応する時間単位の個数を表します。負の値も有効です。
Interval 型は次のとおりです: IntervalNanosecond, IntervalMicrosecond, IntervalMillisecond, IntervalSecond, IntervalMinute, IntervalHour, IntervalDay, IntervalWeek, IntervalMonth, IntervalQuarter, IntervalYear.
Interval 型名 (例: IntervalSecond と IntervalDay) によって、格納される値の単位が決まります。wire エンコーディングは常に同一です。
基になる値の例:
Enum8, Enum16
enum 定義内の列挙値の索引を表す 1 バイト (Enum8 == Int8) または 2 バイト (Enum16 == Int16) の値として格納されます。storage type は符号付きである点に注意してください。つまり、列挙値には負の値を指定できます (例: Enum8('a' = -128, 'b' = 0)) 。
Enum は、次のようにシンプルに定義できます。
上記で定義したEnum8は、クライアントでは以下の値にマップされます:
あるいは、次のように、より複雑な方法で行うこともできます。
上で定義した Enum16 では、Client 側で以下の値にマップされます:
データ型パーサーにおける主な課題は、\' のような enum 定義内のエスケープされた記号や、引用符で囲まれた文字列内に現れる可能性のある = のような特殊記号を追跡することです。
UUID
16 バイトのシーケンスとして表されます。UUID は 2 つのリトルエンディアン 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は次のように表されます:
- デフォルトの UUID
00000000-0000-0000-0000-000000000000は、16個のゼロバイトで表されます:
新しいレコードがinsertされたが、UUID値が指定されていない場合に使用できます。
IPv4
4バイトの UInt32 として、リトルエンディアン のバイト順で格納されます。これは、IPアドレスで一般的に使われる従来のネットワークバイトオーダー (ビッグエンディアン) とは異なる点に注意してください。IPv4 の内部値の例:
IPv6
ビッグエンディアン / ネットワークバイトオーダー (MSB が先頭) の16バイトで格納されます。IPv6 の内部値の例:
Nullable
Nullable データ型は、次のようにエンコードされます。
- 値が
NULLかどうかを示す 1 バイト:0x00は、値がNULLではないことを示します。0x01は、値がNULLであることを示します。
- 値が
NULLではない場合、基になるデータ型は通常どおりエンコードされます。値がNULLの場合、基になる型に対して追加のバイトは一切書き込まれません。
たとえば、Nullable(UInt32) 型の値:
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 値の配列:
少し複雑な例:
配列には Nullable の値を含めることができますが、配列自体を Nullable にすることはできません。
次は有効です。
次のようにエンコードされます:
多次元配列の扱い方の例は、Geoセクションにあります。
Tuple
タプルは、追加のメタ情報や区切り文字を付けずに、タプル内のすべての要素をそれぞれ対応するワイヤ形式で順に並べてエンコードしたものです。
タプルデータ型の文字列表現では、Enum type と同様に、エスケープされた記号や特殊文字の追跡といった課題があります。さらに、Tuple では開き括弧と閉じ括弧についても追跡する必要があります。加えて、より複雑な Tuple には、ネストされた別の Tuple、Array、マップ、さらには enum が含まれる場合もあります。
たとえば、次のテーブルでは、タプルに名前の中にバッククォートと括弧を含む enum が含まれており、適切に処理しないとパースの問題を引き起こす可能性があります:
マップ
マップは Array(Tuple(K, V)) と見なすことができます。ここで、K はキーの型、V は値の型です。マップは次のようにエンコードされます。
- マップ内の要素数を示す 可変長整数 (LEB128) 。
- マップの要素をキーと値のペアとして、それぞれの対応する型でエンコードしたもの。
たとえば、キーが String、値が UInt32 のマップ:
Map(String, Map(Int32, Array(Nullable(String)))) のような深くネストされた構造のマップも使用でき、この場合も上記で説明したのと同様にエンコードされます。
Variant
この型は、他のデータ型の共用体を表します。型 Variant(T1, T2, ..., TN) は、この型の各行が T1、T2、…、TN のいずれかの型の値、またはそれらのいずれにも属さない値 (NULL 値) を持つことを意味します。
エンドユーザーにとっては Variant(T1, T2) と Variant(T2, T1) はまったく同じ意味ですが、ワイヤ形式では定義内の型の順序が重要です。定義内の型は常にアルファベット順に並べ替えられますが、これは重要です。というのも、どのバリアントであるかは "discriminant"、つまり定義内のデータ型の索引によってエンコードされるためです。
次の例を見てください。
NULL 値は、識別バイト 0xFF でエンコードされます:
allow_suspicious_variant_types 設定を使用すると、Variant 型のより網羅的なテストを行えます。
Dynamic
Dynamic 型は、実行時に決まる任意の型の値を保持できます。RowBinary形式では、各値は自己記述的になっています。最初のパートは、この形式による型指定です。その後に内容が続き、値はこのドキュメントで説明されているとおりにエンコードされます。したがって、値を解析するには、型の索引を使って適切なパーサーを特定し、その後はすでに別の箇所で使っているRowBinaryの解析処理を再利用するだけで済みます。
ここで、BinaryTypeIndex は型を識別する 1 バイトの値です。型のインデックスとパラメーターについては、こちらのリファレンスを参照してください。
NULL の Dynamic 値は、追加のバイトを伴わずに BinaryTypeIndex 0x00 (Nothing 型) でエンコードされます:
例:
JSON
JSON typeは、データを2つの異なるカテゴリにエンコードします:
- 型付きパス - スキーマ内で明示的な型を指定して宣言されたパス (例:
JSON(user_id UInt32, name String)) - 動的パスの上限を超えた場合の動的パス/オーバーフローパス - 実行時に検出されたパスは
Dynamic型として保存されます。値のエンコーディングの前に型定義が付加されます。
これら2つのカテゴリでは、ワイヤフォーマットとルールが異なります。
| パスカテゴリ | シリアル化に含まれるか | 値のエンコーディング | Variant/Nullable の使用可否 |
|---|---|---|---|
| 型付きパス | 常に含まれる (NULL の場合も含む) | 型固有のバイナリ形式 | はい |
| 動的パス | NULL でない場合のみ | 動的 | 不可 |
パスは3つのグループに分けてシリアライズされ、順番に書き込まれます。typed paths、dynamic paths、shared data (オーバーフロー) pathsの順です。typed pathsとdynamic pathsは実装定義の順序 (内部ハッシュマップのイテレーションによって決定) で書き込まれ、shared data pathsはアルファベット順で書き込まれます。読み取り側は特定のパスの順序に依存しないでください。デシリアライザは各パスを位置ではなく名前によってディスパッチします。
RowBinary形式の各JSON行は次のようにシリアライズされます:
例:
1. 型付きパスのみを含むシンプルなJSON:
Schema: JSON(user_id UInt32, active Bool)
行: {"user_id": 42, "active": true}
バイナリエンコーディング (アノテーション付き16進数) :
2. 型付きおよび動的パスを持つシンプルなJSON:
Schema: JSON(user_id UInt32, active Bool)
行: {"user_id": 42, "active": true, "name": "Alice"}
バイナリエンコーディング (アノテーション付き16進数) :
3. Nullの処理:
型付きNullableカラムでは、nullが得られます:
Schema: JSON(score Nullable(Int32))
行: {"score": null }
バイナリエンコーディング (アノテーション付き16進数) :
型付きの非Nullableカラムでは、デフォルト値が返されます:
スキーマ: JSON(name String)
行: {"name": null}
Binary encoding:
動的パスの場合、これは無視されます:
Schema: JSON(id UInt64)
行: {"id": 100, "metadata": null}
Binary encoding:
Note: NULL値を持つmetadataパスは含まれません。これは、動的パスがnull以外の場合にのみシリアライズされるためです。型付きパスとの重要な違いです。
4. ネストされたJSON object:
スキーマ: JSON()
行: {"user": {"name": "Bob", "age": 30}}
バイナリエンコーディング (注釈付き16進数) :
注記: ネストされたオブジェクトは、ネスト構造ではなくドット区切りのパス (例: user.name) にフラット化されます。
代替: JSON を文字列として扱うモード
設定 output_format_binary_write_json_as_string=1 を使用すると、JSON カラムは構造化されたバイナリ形式ではなく、単一の JSON テキスト文字列としてシリアライズされます。JSON カラムへの書き込みに対応する設定として、input_format_binary_read_json_as_string もあります。ここでどちらの設定を選ぶかは、JSON をクライアント側で解析するか、サーバー側で解析するかによって決まります。
Geo 型
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) が含まれます。
Geometry
Geometry は、上記に挙げた任意の Geo 型を保持できる Variant 型です。ワイヤ形式では、後続の Geo 型を示す識別子バイトを持つ Variant とまったく同じようにエンコードされます。
Geometry の識別インデックスは次のとおりです。
| Index | Type |
|---|---|
| 0 | LineString |
| 1 | MultiLineString |
| 2 | MultiPolygon |
| 3 | Point |
| 4 | Polygon |
| 5 | Ring |
ワイヤ形式の構造:
Point を Geometry としてエンコードした例:
Ring を Geometry としてエンコードする例:
Nested
Nested のワイヤ形式は、flatten_nested 設定に依存します。
1 つの行内のすべてのコンポーネント配列は、同じ長さでなければなりません。これはサーバー側で強制される制約です。長さが一致しない場合、挿入エラーが発生します。
flatten_nested = 1 (デフォルト)
デフォルト設定では、Nested は個別の配列にフラット化されます。各サブカラムは、ドット区切りの名前を持つ個別の Array 型のカラムになります:
DESCRIBE TABLE foo には、フラット化されたカラムが表示されます:
各配列は、Array セクションで説明されているように、それぞれ独立してシリアル化されます:
flatten_nested = 0
flatten_nested = 0 の場合、Nested は Array(Tuple(...)) 型の単一カラムとして保持されます。カラム名はドット区切りになりません。
DESCRIBE TABLE foo では、1 つのカラムが表示されます:
エンコーディングは Array(Tuple(String, Int32)) です。まず配列長のプレフィックスがあり、その後に各要素のタプルフィールドが順に続きます:
フィールドは、フラット化された表現のようにカラムごと (a₁, a₂, b₁, b₂) にまとめられるのではなく、要素ごとに交互に並んでいる (a₁, b₁, a₂, b₂) ことに注意してください。
SimpleAggregateFunction
SimpleAggregateFunction(func, T) は、基になるデータ型 T と同一の形式でエンコードされます。集約関数名はワイヤ形式に影響しません。
たとえば、SimpleAggregateFunction(max, UInt32) は通常の UInt32 と同じ方法でエンコードされます。
RowBinaryWithNamesAndTypes ヘッダーでは型は SimpleAggregateFunction(max, UInt32) として報告されますが、ワイヤ上の値は単なる UInt32 です。
AggregateFunction
AggregateFunction(func, T) は、集約関数の完全な中間状態を格納します。同じく中間状態を格納するものの、基になるデータ型と同一の形式でエンコードされる SimpleAggregateFunction とは異なり、AggregateFunction は各集約関数に固有の形式を持つ不透明なバイナリblobを格納します。
集約状態には、RowBinary では長さプレフィックスがありません。パーサーは、何バイト読み取るべきかを把握するために、それぞれの集約関数固有の内部シリアライズ形式を理解している必要があります。実際には、ほとんどのクライアントは集約状態を不透明なものとして扱い、シリアライズ処理をサーバーに任せるために *State / *Merge コンビネータを使用します。
内部形式は関数ごとに異なります。簡単な例をいくつか示します。
countState — カウント値を VarUInt (LEB128) として格納します。
sumState — 累積した合計を固定長整数に格納します。ビット幅は引数の型に依存します (整数型の引数では UInt64) :
minState / maxState — 基底の型で、フラグバイトに続けて値を格納します。フラグは、空の状態 (値が一度も現れていない) では 0x00、値が存在する場合は 0x01 です:
空の状態 (集計された行がない場合) :
uniq、quantile、groupArray のような、より複雑な関数では、実装固有の形式が使用されます。これらの状態を読み書きする必要がある場合は、対象の関数に対応する ClickHouse のソースコードを参照してください。
QBit
QBit は、異なる精度レベルで効率的にルックアップを行うためのベクトル型です。内部的には、転置形式で格納されます。転送時には、QBit は単に基になる要素型 (Float32、Float64、または BFloat16) の Array です。格納のためのビット転置最適化はサーバー側で行われ、RowBinary プロトコルでは行われません。
構文:
element_type は Float32、Float64、または BFloat16 で、dimension は固定のベクトル次元です。
ワイヤ形式: Array(element_type) と同一です。
[1.0, 2.0, 3.0, 4.0] を格納した QBit(Float32, 4) のエンコーディング例:
フォーマット設定
The following settings are common to all RowBinary type formats.
| Setting | Description | Default |
|---|---|---|
format_binary_max_string_size | RowBinary フォーマットにおける String の最大許容サイズ。 | 1GiB |
output_format_binary_encode_types_in_binary_format | RowBinaryWithNamesAndTypes 出力フォーマットで、ヘッダー内の型を型名の文字列ではなく、binary encoding を用いたバイナリ表現で書き出すことを許可します。 | false |
input_format_binary_decode_types_in_binary_format | RowBinaryWithNamesAndTypes 入力フォーマットで、ヘッダー内の型を型名の文字列ではなく、binary encoding を用いたバイナリ表現として読み取ることを許可します。 | 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 |