説明
RowBinary フォーマットは、バイナリ形式のデータを行単位で解析します。
行と値は、区切り文字なしで連続して並びます。
データがバイナリ形式であるため、FORMAT RowBinary の後の区切り文字は以下のとおり厳密に定められています。
- 任意の数の空白文字:
' '(スペース - コード0x20)'\t'(タブ - コード0x09)'\f'(フォームフィード - コード0x0C)
- その後に、ちょうど 1 つの改行シーケンス:
- Windows 形式
"\r\n" - または Unix 形式
'\n'
- Windows 形式
- その直後にバイナリデータ。
このフォーマットは行ベースであるため、Native フォーマットよりも効率が低くなります。
データ型のワイヤ形式
符号なし LEB128 (リトルエンディアン Base 128)
String、Array、Map などの可変サイズのデータ型の長さを符号化するために使われる、符号なしリトルエンディアンの可変長整数エンコーディングです。実装例は LEB128 の Wikipedia ページで確認できます。
(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 の内部値の例:
Decimal32, Decimal64, Decimal128, Decimal256
Decimal32- 4 バイト、つまりInt32Decimal64- 8 バイト、つまりInt64Decimal128- 16 バイト、つまりInt128Decimal256- 32 バイト、つまりInt256
trunc は 0 に向かって切り捨てを行い (負の値では結果が異なる床除算ではなく) 、scale は小数点以下の桁数を表します。たとえば、Decimal(10, 2) (Decimal32(2) と同等) では、scale は 2 で、値 12345 は (123, 45) として表現されます。
シリアライゼーションでは、この逆の操作が必要です:
String
- 文字列の長さをバイト単位で示す可変長整数 (LEB128) 。
- 文字列の生のバイト列。
foobar は次のように 7 バイトでエンコードされます。
FixedString
String とは異なり、FixedString はスキーマで定義される固定長を持ちます。バイト列としてエンコードされ、値が N より短い場合は末尾がゼロバイトで埋められます。
FixedString を読み取る際、末尾のゼロバイトはパディングである場合も、データ中の実際の \0 文字である場合もあり、ワイヤ上では区別できません。ClickHouse 自体は N バイトをすべてそのまま保持します。FixedString(3) にはパディングのゼロだけが含まれます:
hi が入った空でない FixedString(3):
bar を含む、空でない FixedString(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 エポックオフセットです。タイムゾーンによってエンコーディングが変わることはありません。ただし、文字列値が INSERT 時にどのように解釈されるかにはタイムゾーン が 影響します。つまり、
DateTime('America/New_York') カラムに '2024-01-15 10:30:00' を挿入すると、同じ文字列を 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)- ナノ秒。
[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 の内部値の例:
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 の内部値の例:
インターバル型
Int64 (8バイト、リトルエンディアン) として格納されます。値は、それぞれの時間単位の個数を表します。負の値も有効です。
インターバル型は次のとおりです: IntervalNanosecond、IntervalMicrosecond、IntervalMillisecond、IntervalSecond、IntervalMinute、IntervalHour、IntervalDay、IntervalWeek、IntervalMonth、IntervalQuarter、IntervalYear。
インターバルの型名 (例:
IntervalSecond と IntervalDay) によって、格納される値の単位が決まります。ワイヤ形式でのエンコーディングは常に同じです。Enum8、Enum16
Enum8 == Int8) または 2 バイト (Enum16 == Int16) として格納されます。ストレージ型は signed である点に注意してください。つまり、enum 値には負の値も指定できます (例: Enum8('a' = -128, 'b' = 0)) 。
Enum は次のように簡単に定義できます。
\' のような enum 定義内のエスケープされた記号や、引用符で囲まれた文字列内に現れる可能性のある = のような特殊記号を正しく追跡することです。
UUID
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 個のゼロバイトで表されます:
IPv4
UInt32 として、リトルエンディアン のバイト順で格納されます。これは、IP アドレスで一般的に使用される従来のネットワークバイトオーダー (ビッグエンディアン) とは異なる点に注意してください。IPv4 の内部値の例:
IPv6
IPv6 の内部値の例:
Nullable
- 値が
NULLかどうかを示す 1 バイト:0x00は、値がNULLではないことを示します。0x01は、値がNULLであることを示します。
- 値が
NULLでない場合は、基になるデータ型が通常どおりエンコードされます。値がNULLの場合、基になる型については 追加のバイトは一切 書き込まれません。
Nullable(UInt32) 型の値:
LowCardinality
LowCardinality(String) は通常の String と同じようにエンコードされます。
カラムは
LowCardinality(Nullable(T)) として定義できますが、Nullable(LowCardinality(T)) として定義することはできません。常に server から error になります。1 に設定すると、LowCardinality 内でほとんどの data types を許可できます。
Array
- 配列の要素数を示す 可変長整数 (LEB128)
- 配列の各要素。基になるデータ型と同じ方法でエンコードされます。
UInt32 型の値を持つ配列は次のとおりです。
Array には Nullable な値を含めることができますが、Array 自体を Nullable にすることはできません。
Tuple
Map
Array(Tuple(K, V)) とみなすことができ、ここで K はキーの型、V は値の型です。map は次のようにエンコードされます。
- map 内の要素数を示す 可変長整数 (LEB128)。
- map の各要素を、対応する型でエンコードされたキー・バリューのペアとして格納します。
String、値が UInt32 の map は次のとおりです。
Map(String, Map(Int32, Array(Nullable(String)))) のように、深くネストされた構造を持つ Map も可能で、その場合も上記と同様にエンコードされます。Variant
Variant(T1, T2, ..., TN) は、この型の各行が T1、T2、…、TN のいずれかの型、またはそのどれにも該当しない値 (NULL 値) を持つことを意味します。
次の例を見てみましょう。
NULL 値は、判別子バイト 0xFF でエンコードされます:
Variant 型をより網羅的にテストできます。
Dynamic
Dynamic 型は、実行時に決まる任意の型の値を保持できます。RowBinary フォーマットでは、各値は自己記述的です。最初の部分には、このフォーマットで表される型指定が入ります。続いて、このドキュメントで説明している値エンコードに従った内容が続きます。したがって、値をパースするには、型の索引を使って適切なパーサーを判断し、あとはすでに別の場所で使っている RowBinary のパース処理を再利用するだけです。
BinaryTypeIndex は型を識別する 1 バイトの値です。型インデックスとパラメータについては、こちら のリファレンスを参照してください。
NULL の Dynamic 値は、BinaryTypeIndex 0x00 (Nothing 型) としてエンコードされ、追加のバイトはありません:
JSON
- 型付きパス - スキーマ内で型を明示して宣言されたパス (例:
JSON(user_id UInt32, name String)) - 動的パス/動的パスの上限を超えた場合のオーバーフローパス -
Dynamic型として保存される実行時検出パス。値のエンコーディングの前に型定義が付きます。
パスは3つのグループに分けてシリアライズされ、typed paths、dynamic paths、shared data (オーバーフロー) pathsの順に書き込まれます。typed pathsとdynamic pathsは実装定義の順序 (内部ハッシュマップのイテレーションによって決定) で書き込まれ、shared data pathsはアルファベット順で書き込まれます。読み取り側は特定のパス順序に依存しないでください。デシリアライザは各パスを位置ではなく名前によってディスパッチします。
RowBinary フォーマットにおける各 JSON 行は、次のようにシリアライズされます:
JSON(user_id UInt32, active Bool)
行: {"user_id": 42, "active": true}
バイナリエンコーディング (注釈付き16進数) :
JSON(user_id UInt32, active Bool)
行: {"user_id": 42, "active": true, "name": "Alice"}
バイナリエンコーディング (注釈付き16進数) :
JSON(score Nullable(Int32))
行: {"score": null }
バイナリエンコーディング (注釈付き16進数) :
JSON(name String)
行: {"name": null}
バイナリエンコーディング:
JSON(id UInt64)
行: {"id": 100, "metadata": null}
バイナリエンコーディング:
metadata パスは、動的パスが非 null の場合にのみシリアライズされるため、含まれません。これは型付きパスとの重要な相違点です。
4. ネストされたJSONオブジェクト:
スキーマ: JSON()
行: {"user": {"name": "Bob", "age": 30}}
バイナリエンコーディング (注釈付き16進表記) :
user.name) 。
代替: JSONをStringとして扱うモード
設定 output_format_binary_write_json_as_string=1 を有効にすると、JSONカラムは構造化されたバイナリフォーマットではなく、1つのJSONテキスト文字列としてシリアライズされます。JSONカラムへの書き込みには、対応する設定 input_format_binary_read_json_as_string もあります。ここでどちらの設定を使うかは、JSONをクライアント側でパースするか、サーバー側でパースするかによって決まります。
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)))として表されます。
RowBinaryWithNamesAndTypes フォーマットのヘッダーには、これらの型の別名 (たとえば Point、Ring、Polygon、MultiPolygon、LineString、MultiLineString) が含まれます。
Geometry
Geometry は、上記に挙げた任意の Geo 型を保持できる Variant 型です。ワイヤ上では Variant とまったく同じようにエンコードされ、後続する Geo 型を示す 判別子 バイトが付加されます。
Geometry の 判別子 インデックスは次のとおりです。
ワイヤ形式の構造:
Point を Geometry としてエンコードした例:
Ring を Geometry としてエンコードした例:
Nested
Nested のワイヤ形式は、flatten_nested 設定に依存します。
flatten_nested = 1 (デフォルト)
Nested は独立した配列にフラット化されます。各サブカラムは、ドット区切りの名前を持つ個別の Array カラムになります。
DESCRIBE TABLE foo を実行すると、フラット化されたカラムが表示されます。
flatten_nested = 0
flatten_nested = 0 の場合、Nested は Array(Tuple(...)) 型の単一のカラムとして保持されます。カラム名はドット区切りではありません。
DESCRIBE TABLE foo では、1 つのカラムが表示されます。
Array(Tuple(String, Int32)) です。最初に配列長のプレフィックスがあり、続いて各要素のタプルのフィールドが順に並びます。
SimpleAggregateFunction
SimpleAggregateFunction(func, T) は、基になるデータ型 T と同一の形式でエンコードされます。集約関数名はワイヤ形式に影響しません。
たとえば、SimpleAggregateFunction(max, UInt32) は通常の UInt32 と同じ形式でエンコードされます。
SimpleAggregateFunction(max, UInt32) と示されますが、実際にワイヤ上で流れる値は単なる UInt32 です:
AggregateFunction
AggregateFunction(func, T) は、aggregate function の完全な中間状態を格納します。中間状態を格納する点は同じでも、それを基になるデータ型と同じ形式でエンコードする SimpleAggregateFunction とは異なり、AggregateFunction は各 aggregate function 固有のフォーマットを持つ不透明なバイナリブロブを格納します。
内部フォーマットは関数ごとに異なります。簡単な例をいくつか示します。
countState — カウントを VarUInt (LEB128) として格納します。
sumState — 累積された合計を固定サイズの整数に格納します。ビット幅は引数の型に依存します (整数型の引数の場合は UInt64) :
minState / maxState — フラグバイトに続けて、基になる型の値を格納します。フラグは、空の状態 (値が1つもない) の場合は 0x00、値がある場合は 0x01 です:
uniq、quantile、groupArray のようなより複雑な関数では、実装固有のフォーマットが使用されます。これらの状態を読み書きする必要がある場合は、該当する関数の ClickHouse ソースコードを参照してください。QBit
QBit は、異なる精度レベルで効率的にルックアップできるベクトル型です。内部的には転置フォーマットで保存されます。転送時には、QBit は単に基底要素型 (Int8、Float32、Float64、または BFloat16) の Array です。保存時のビット転置の最適化は RowBinary プロトコルではなく、サーバー側で行われます。
構文:
element_type は Int8、Float32、Float64、または BFloat16 で、dimension は固定のベクトル次元です。省略可能な stride は、サーバー側でビットプレーンを保存ストリームにどうグループ化するかだけを制御するもので、RowBinary のワイヤ形式には影響しません。ワイヤ形式は常に dimension 個の要素からなる完全な配列です。
ワイヤ形式: Array(element_type) と同一です。
[1.0, 2.0, 3.0, 4.0] を含む QBit(Float32, 4) のエンコード例:
フォーマット設定
RowBinary 系フォーマットに共通です。