バージョン
バージョン ID、version.json の項目、固めたカタログ、dataset.json、ほかの DuckLake 形式のカタログ。
バージョンは versions/<version ID>/ の下のファイルです。一度書いたら変えてはいけません。例外は次のとおりです。
- 保持期間を過ぎたとき、
expired.jsonを足し、カタログを消す(データファイル) - 取り下げのとき、
"reason": "withdrawn"のexpired.jsonを足してカタログを消し、version.json自体に取り下げるデータがあればそれも消す(データファイル) catalogs/<format>.duckdbはあとから足してよい。既にあるファイルは変えないversion.json.sigは、バージョンを作ったあとに署名する書き手が足してよい
バージョン ID
- 小文字・ハイフン区切りの UUIDv7。たとえば
019a3c4e-7b21-7f3a-9c55-2f0d8e1a4b6c。作った順に並び、書き手どうしが調整しなくても重ならない - 中身のハッシュではない。同じカタログでも別のバイト列で書かれることがあり、ハッシュは安定しない。中身は
catalog.sha256で確かめる - 人に見せる番号は
seq。ブランチをまたぐと重なるので ID にはしない
version.json
{
"fdl": "0.1",
"id": "019a3c4e-7b21-7f3a-9c55-2f0d8e1a4b6c",
"seq": 12,
"parents": ["019a2f10-0c4d-7e88-a1b2-55c0d9e3f7aa"],
"created_at": "2026-10-30T09:00:00Z",
"author": { "name": "example" },
"message": "Add the final 2025 figures",
"catalog": {
"path": "ducklake.duckdb",
"storage": "duckdb-v1.5.0",
"bytes": 4194304,
"sha256": "9f2c…"
},
"dataset": {
"path": "dataset.json",
"bytes": 2048,
"sha256": "4c1d…"
},
"ducklake": {
"format": "1.0",
"snapshot_id": 345,
"snapshots": [301, 345]
},
"files": {
"path": "files.json",
"bytes": 1536,
"sha256": "7a0e…"
},
"changes": [
{ "table": "main.population", "kind": "add_column", "column": "note", "breaking": false }
],
"inputs": [
{
"source_id": "example:population-2025",
"sha256": "1b7e…",
"retrieved_at": "2026-10-29T03:12:00Z",
"url": "https://example.org/population-2025.csv"
}
],
"annotations": {}
}
| 項目 | 必須 | 意味 |
|---|---|---|
fdl |
must | 書いたときの仕様のバージョン |
id |
must | ディレクトリと同じ ID。違っていたら読み手は読んではいけない |
seq |
must | 人に見せる番号。最初は 1、そのあとは最初の親の seq に 1 を足す |
parents |
must | 親のバージョン ID。最初のバージョンでは空、そのあとは 1 つ |
created_at |
must | UTC、ISO 8601 |
author |
must | 作った人。name は必須、email などは任意 |
message |
must | 何を変えたか |
catalog |
must | カタログのファイル名、大きさ、sha256、保存形式(storage) |
dataset |
must | カタログの隣の dataset.json のファイル名、大きさ、sha256。必須になる前に書かれたバージョンには無い |
ducklake |
must | DuckLake の形式、固めたスナップショット、親から足したスナップショットの範囲(最初と最後) |
files |
may | カタログの隣の files.json を書いたときの、そのファイル名、大きさ、sha256(files.json) |
changes |
may | 親との列の違い。breaking は、親に向けて書いた SQL が失敗しうるか(列の削除・改名・作り直し) |
inputs |
may | 使った元のファイル。source_id の付け方は書き手が決める |
annotations |
may | 形式が解釈しない情報。読む側(ホストやツール)の名前をキーにする |
読み手は、知らない項目を無視しなければなりません。
FDL 0.1 では、書き手が書く親は多くても 1 つです。parents をリストにしているのは、仕様の将来のバージョンで、項目を変えずにマージを表せるようにするためです。読み手は、どんな長さのリストも受け付け、履歴をたどるときは最初の要素を使わなければなりません。
スナップショットごとの説明と SQL は、DuckLake の commit_message と commit_extra_info としてカタログの中に残し、version.json には写しません。
形式番号
| 項目 | 指すもの | 変わるとき |
|---|---|---|
fdl |
この仕様 | この仕様が変わったとき |
ducklake.format |
DuckLake の管理用の表の形 | DuckLake の仕様が変わったとき |
catalog.storage |
カタログファイルの保存形式 | DuckDB の保存形式が変わったとき |
読み手は、version.json だけで、そのバージョンを開けるか判断できます。
files.json
files.json は、ducklake.snapshot_id の時点の表ごとのデータファイルの索引です。カタログを開けない読み手のためのものです。書き手はこれをカタログから作り、カタログと食い違ったときはカタログが正です。置くかどうかは任意で、読み手はこれが無くても動かなければなりません。Git の到達可能性ビットマップ(pack の隣の .bitmap)に当たります。あるコミットからたどれるオブジェクトの一覧をオブジェクトから作ったもので、読むのを速くし、無くても読めます。
{
"tables": {
"main.population": {
"files": [
{
"path": "main/population/ducklake-019a3c4e-….parquet",
"rows": 1741,
"bytes": 52311,
"footer_bytes": 2048,
"deletes": { "path": "main/population/ducklake-019a3c4f-…-delete.parquet", "rows": 3, "bytes": 812 }
}
]
}
}
}
tablesには、そのスナップショットにあるすべての表をschema.tableをキーにして入れます。行の無い表のfilesは空ですpathはリポジトリのdata_dirからの相対パスです。ファイルはカタログの順に並べますrowsとbytesは、カタログが記録している行数と大きさです。footer_bytesは Parquet のフッターの大きさで、記録があるときだけ書きますdeletesは、そのデータファイルから行を消したときの削除ファイルですrenamed_columnsは、その表の列の名前を変えたことがあるときにtrueにします。名前を変える前に書いたデータファイルは、古い名前のままです
DuckLake を使わずに Parquet ファイルを読む読み手は、deletes を持つファイルがある表や、renamed_columns が true の表を読んではいけません。DuckLake と同じように削除と改名を当てはめる場合は読んでかまいません。当てはめずに読むと、消した行が返ったり、名前を変えた列が欠けたりします。
保持期間から外すとき、files.json はカタログと一緒に消します。
固めたカタログ
ducklake.duckdb は、DuckDB のファイル形式の DuckLake カタログです。
- 管理用の行を
ducklake.snapshot_idまで持ち、それより後は持たない - 行をカタログの中に持たない。データはすべてデータファイルにある
- 参照するデータファイルはすべて
data_dirの下にあり、カタログのデータパスからの相対で記録されている。書き手はこれを確かめなければならず、ホストも確かめてよい data_pathは、書き手が公開したデータフォルダの絶対 URL(ルートとdata_dir)。読み手はこれを上書きする(読むを参照)- DuckLake がスナップショットにタグを付けられるようになるまで、書き手は作業カタログで、バージョンを固めたスナップショットを消すような
ducklake_expire_snapshotsを流してはいけない
dataset.json
バージョンの中身を言葉で説明するファイルです。データセット、その表、表の列を説明します。バージョンの一部としてバージョンと一緒に一度だけ書くので、読み手はカタログを開かなくても、何がどんな条件で入っているかが分かり、古いバージョンも公開したときの説明を持ち続けます。
書き手が説明を書き、書き手のツールがそれを固めたカタログと照らし合わせ、カタログから分かることを書き足します。
{
"name": "population",
"title": "Population by town",
"description": "Census population of each town, by age and sex",
"keywords": ["population", "census"],
"language": "en",
"homepage": "https://example.org/population",
"issued": "2026-10-30",
"update_frequency": "yearly",
"licenses": [{ "id": "CC-BY-4.0", "spdx": "CC-BY-4.0", "url": "https://creativecommons.org/licenses/by/4.0/" }],
"sources": [{ "title": "2025 census", "path": "https://example.org/census-2025" }],
"contributors": [{ "title": "Example City", "roles": ["publisher"] }],
"temporal_coverage": { "start": "2025", "end": "2025" },
"spatial_coverage": ["JP"],
"schemas": [{ "name": "main", "title": "Tables" }],
"tables": [
{
"schema": "main",
"name": "population",
"title": "Population",
"description": "One row per town, age group and sex",
"primary_key": ["town_code", "age_group", "sex"],
"depends_on": [],
"sql": "SELECT …",
"rows": 120000,
"fields": [
{ "name": "town_code", "title": "Town code", "description": "…", "type": "varchar", "nullable": false }
]
}
],
"annotations": {}
}
- 必須なのは
nameとtitleだけで、ほかは省いてよい - 書き手は説明を YAML(
dataset.yml)で持ってもよく、テーブルごとに別のファイル(<name>.table.yml、tablesの 1 項目)に分けてもよい。バージョンには常に説明全体が 1 つの JSON で入る licensesの各要素は SPDX の識別子か、idに書き手が足したいもの(spdx・url・title)を添えたオブジェクトtablesには、カタログの最後のスナップショットにある表をすべて並べる。書き手が説明しなかった表は、書き手のツールがカタログから分かることで足す。カタログに無い表や列を説明していたら断る- カタログのビューは表のあとに
"kind": "view"を付けて並べ、sqlはカタログのものを入れる。カタログはビューの列を記録しないので、ビューのfieldsは書き手が書いたままで、type・nullable・rowsを持たない。表にはkindを付けない - カタログから取るもの: 表ごとの
rows、列ごとのtype(DuckLake の型名)とnullable。書き手がdescriptionを書かなかったところには、カタログのコメント(COMMENT ON)を使う - この仕様が決めていないキーは断る。形式が解釈しない情報は、最上位・表・列それぞれの
annotationsに、読む側の名前をキーにして置く。書き手は書かれたまま残す annotationsという名前になる前のバージョンは、同じものをextensionsに持つ。読み手はextensionsをannotationsとして読む- ライセンスや出典に書いたことは書き手の主張で、ホストは公開する前に確かめてよい
ほかの形式のカタログ
1 つのバージョンが DuckLake の形式ごとにカタログを持ってよく、読み手は DuckDB を替えずに、開けるファイルを選べます。
versions/<ID>/
ducklake.duckdb 作ったときのカタログ(version.json が説明するもの)
catalogs/1.0.duckdb 形式 1.0 で書いたもの
catalogs/1.1.duckdb 形式 1.1 で書いたもの
catalogs/<format>.duckdbはすべて、同じデータファイルを参照し、同じ行を返さなければならない- 読み手は、開ける形式を新しい順に試し、どれも無ければ
ducklake.duckdbを開く - 宣言した形式をあるバージョンで作れない書き手は、そのファイルを置かない。どのファイルも開けない読み手は、そのバージョンを読めない
- 古い形式で表せないものを使ったバージョンには、その形式のカタログを置かない
fdl.jsonのuntilを過ぎた形式は、新しいバージョンでは書かない。過去のバージョンはそのまま残してよい- データファイルは、
fdl.jsonで宣言した一番古い形式が読めるように書かなければならない catalog.storageは、宣言した読み手が開ける一番古い DuckDB の保存形式にするのがよい
DuckDB 2.0 で古い形式を書く方法は DuckLake の形式にあります。