fdl-cli 0.13.0__tar.gz → 0.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/.github/workflows/docs.yml +13 -5
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/.gitignore +5 -0
- fdl_cli-0.14.0/.node-version +1 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/CHANGELOG.md +63 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/CLAUDE.md +12 -1
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/PKG-INFO +6 -3
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/README.md +4 -2
- fdl_cli-0.14.0/blume.config.ts +28 -0
- fdl_cli-0.14.0/docs/concepts/meta.ts +7 -0
- fdl_cli-0.14.0/docs/concepts/repositories.md +47 -0
- fdl_cli-0.14.0/docs/concepts/versions-and-refs.md +86 -0
- fdl_cli-0.14.0/docs/concepts/working-catalogs.md +48 -0
- fdl_cli-0.14.0/docs/getting-started.md +139 -0
- fdl_cli-0.14.0/docs/guides/building-a-host.md +53 -0
- fdl_cli-0.14.0/docs/guides/meta.ts +7 -0
- fdl_cli-0.14.0/docs/guides/mirrors.md +52 -0
- fdl_cli-0.14.0/docs/guides/public-and-private.md +38 -0
- fdl_cli-0.14.0/docs/guides/serving-over-http.md +48 -0
- fdl_cli-0.14.0/docs/guides/stores.md +52 -0
- fdl_cli-0.14.0/docs/index.md +48 -0
- fdl_cli-0.14.0/docs/internals/ducklake-formats.md +63 -0
- fdl_cli-0.14.0/docs/internals/inside-a-repository.md +127 -0
- fdl_cli-0.14.0/docs/internals/meta.ts +7 -0
- fdl_cli-0.14.0/docs/ja/concepts/meta.ts +7 -0
- fdl_cli-0.14.0/docs/ja/concepts/repositories.md +47 -0
- fdl_cli-0.14.0/docs/ja/concepts/versions-and-refs.md +86 -0
- fdl_cli-0.14.0/docs/ja/concepts/working-catalogs.md +48 -0
- fdl_cli-0.14.0/docs/ja/getting-started.md +139 -0
- fdl_cli-0.14.0/docs/ja/guides/building-a-host.md +53 -0
- fdl_cli-0.14.0/docs/ja/guides/meta.ts +7 -0
- fdl_cli-0.14.0/docs/ja/guides/mirrors.md +52 -0
- fdl_cli-0.14.0/docs/ja/guides/public-and-private.md +38 -0
- fdl_cli-0.14.0/docs/ja/guides/serving-over-http.md +48 -0
- fdl_cli-0.14.0/docs/ja/guides/stores.md +52 -0
- fdl_cli-0.14.0/docs/ja/index.md +48 -0
- fdl_cli-0.14.0/docs/ja/internals/ducklake-formats.md +63 -0
- fdl_cli-0.14.0/docs/ja/internals/inside-a-repository.md +127 -0
- fdl_cli-0.14.0/docs/ja/internals/meta.ts +7 -0
- fdl_cli-0.14.0/docs/ja/reference/cli.md +76 -0
- fdl_cli-0.14.0/docs/ja/reference/meta.ts +7 -0
- fdl_cli-0.14.0/docs/ja/reference/python.md +91 -0
- fdl_cli-0.14.0/docs/ja/spec/data-files.md +58 -0
- fdl_cli-0.14.0/docs/ja/spec/http.md +32 -0
- fdl_cli-0.14.0/docs/ja/spec/index.md +46 -0
- fdl_cli-0.14.0/docs/ja/spec/layout.md +78 -0
- fdl_cli-0.14.0/docs/ja/spec/meta.ts +7 -0
- fdl_cli-0.14.0/docs/ja/spec/reading.md +48 -0
- fdl_cli-0.14.0/docs/ja/spec/refs.md +59 -0
- fdl_cli-0.14.0/docs/ja/spec/versions.md +171 -0
- fdl_cli-0.14.0/docs/ja/spec/writing.md +49 -0
- fdl_cli-0.14.0/docs/reference/cli.md +76 -0
- fdl_cli-0.14.0/docs/reference/meta.ts +7 -0
- fdl_cli-0.14.0/docs/reference/python.md +91 -0
- fdl_cli-0.14.0/docs/spec/data-files.md +58 -0
- fdl_cli-0.14.0/docs/spec/http.md +32 -0
- fdl_cli-0.14.0/docs/spec/index.md +46 -0
- fdl_cli-0.14.0/docs/spec/layout.md +78 -0
- fdl_cli-0.14.0/docs/spec/meta.ts +7 -0
- fdl_cli-0.14.0/docs/spec/reading.md +48 -0
- fdl_cli-0.14.0/docs/spec/refs.md +59 -0
- fdl_cli-0.14.0/docs/spec/versions.md +171 -0
- fdl_cli-0.14.0/docs/spec/writing.md +49 -0
- fdl_cli-0.14.0/package-lock.json +11997 -0
- fdl_cli-0.14.0/package.json +16 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/pyproject.toml +2 -3
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/__init__.py +1 -1
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/catalog.py +52 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/cli.py +37 -8
- fdl_cli-0.14.0/src/fdl/dataset.py +188 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/layout.py +2 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/repo.py +69 -5
- fdl_cli-0.14.0/src/fdl/stores.py +357 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/version.py +28 -0
- fdl_cli-0.14.0/tests/http_repo.py +107 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/tests/test_cli.py +10 -0
- fdl_cli-0.14.0/tests/test_dataset.py +70 -0
- fdl_cli-0.14.0/tests/test_http.py +110 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/tests/test_stores.py +3 -2
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/uv.lock +3 -253
- fdl_cli-0.13.0/docs/cli.md +0 -55
- fdl_cli-0.13.0/docs/index.md +0 -12
- fdl_cli-0.13.0/docs/spec.md +0 -287
- fdl_cli-0.13.0/src/fdl/stores.py +0 -177
- fdl_cli-0.13.0/zensical.toml +0 -61
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/.github/workflows/publish.yml +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/.github/workflows/release.yml +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/.python-version +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/CONTRIBUTING.md +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/LICENSE +0 -0
- {fdl_cli-0.13.0/docs → fdl_cli-0.14.0/public}/CNAME +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/py.typed +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/refs.py +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/src/fdl/store.py +0 -0
- {fdl_cli-0.13.0 → fdl_cli-0.14.0}/tests/test_repo.py +0 -0
|
@@ -5,8 +5,10 @@ on:
|
|
|
5
5
|
branches: [main]
|
|
6
6
|
paths:
|
|
7
7
|
- "docs/**"
|
|
8
|
-
- "
|
|
9
|
-
- "
|
|
8
|
+
- "public/**"
|
|
9
|
+
- "blume.config.ts"
|
|
10
|
+
- "package.json"
|
|
11
|
+
- "package-lock.json"
|
|
10
12
|
- ".github/workflows/docs.yml"
|
|
11
13
|
workflow_dispatch:
|
|
12
14
|
|
|
@@ -24,11 +26,17 @@ jobs:
|
|
|
24
26
|
runs-on: ubuntu-latest
|
|
25
27
|
steps:
|
|
26
28
|
- uses: actions/checkout@v5
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
with:
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
- uses: actions/setup-node@v5
|
|
32
|
+
with:
|
|
33
|
+
node-version-file: .node-version
|
|
34
|
+
cache: npm
|
|
35
|
+
- run: npm ci
|
|
36
|
+
- run: npm run check
|
|
29
37
|
- uses: actions/upload-pages-artifact@v4
|
|
30
38
|
with:
|
|
31
|
-
path:
|
|
39
|
+
path: dist
|
|
32
40
|
|
|
33
41
|
deploy:
|
|
34
42
|
needs: build
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22
|
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.14.0 (2026-11-01)
|
|
5
|
+
|
|
6
|
+
### Documentation
|
|
7
|
+
|
|
8
|
+
- Add a Japanese translation of every page
|
|
9
|
+
([`0d7bb97`](https://github.com/queria-io/fdl/commit/0d7bb9707d51b83fe029fef8f875a4007f4669ee))
|
|
10
|
+
|
|
11
|
+
English stays the source. The Japanese pages live under docs/ja/ and are served at /ja/.
|
|
12
|
+
|
|
13
|
+
- Bring in dataset.json and pushing over HTTP from the implementation
|
|
14
|
+
([`0e7817b`](https://github.com/queria-io/fdl/commit/0e7817b9c120b7f683dcf00f23663480793e91f7))
|
|
15
|
+
|
|
16
|
+
Port the specification and CLI text of fdl#23 into the new pages: dataset.json in each version, the
|
|
17
|
+
HTTP push requests, fdl login and commit --dataset.
|
|
18
|
+
|
|
19
|
+
- Fix review findings in the spec and guides
|
|
20
|
+
([`53e8cc4`](https://github.com/queria-io/fdl/commit/53e8cc4d20d4cfdc8dced8e529d899432aa1687f))
|
|
21
|
+
|
|
22
|
+
Name the HTTP repository as the exception to where new data files go, say a host answers HEAD
|
|
23
|
+
requests, list withdrawal among the changes a version allows, add dataset.json to the second
|
|
24
|
+
commit example, pass DATA_PATH in the README reader, and say versions have one parent.
|
|
25
|
+
|
|
26
|
+
- Rewrite the documentation with Blume and split the specification into pages
|
|
27
|
+
([`8bfdb79`](https://github.com/queria-io/fdl/commit/8bfdb79039a63ee82f260d4e5b5689b701d47e48))
|
|
28
|
+
|
|
29
|
+
Guide pages (getting started, concepts, how it works, guides) explain the format the way Git's
|
|
30
|
+
documentation does; docs/spec/ is the normative specification, with the data_path and DuckLake
|
|
31
|
+
format rules written in. GitHub Pages now builds the site with Blume.
|
|
32
|
+
|
|
33
|
+
- **spec**: Settle the open questions
|
|
34
|
+
([`b98a88f`](https://github.com/queria-io/fdl/commit/b98a88f4928a54deaa1c8298cd671c91d6b636a8))
|
|
35
|
+
|
|
36
|
+
Add refs.json, ASCII-only ref names, one parent per version in 0.1, optional SSH signatures
|
|
37
|
+
(version.json.sig and signers in fdl.json), withdrawal, and leave retention defaults to hosts.
|
|
38
|
+
Remove the open questions page.
|
|
39
|
+
|
|
40
|
+
### Features
|
|
41
|
+
|
|
42
|
+
- Describe every version in dataset.json
|
|
43
|
+
([`4608dec`](https://github.com/queria-io/fdl/commit/4608dec8631d12fafac2169ba5948f79988b202d))
|
|
44
|
+
|
|
45
|
+
dataset.json is now part of a version, listed in version.json with its hash. fdl commit reads it,
|
|
46
|
+
refuses tables, columns and keys the format does not know, and fills in types, nulls, row counts
|
|
47
|
+
and comments from the catalog. --attach is gone.
|
|
48
|
+
|
|
49
|
+
- Push to a repository over HTTP
|
|
50
|
+
([`659399b`](https://github.com/queria-io/fdl/commit/659399b12f06e50aa229b8dee9d42d725f6aa427))
|
|
51
|
+
|
|
52
|
+
A host can take pushes over plain HTTP: GET, HEAD and conditional PUT, a listing, and uploads in
|
|
53
|
+
parts for large files. The writer needs a token (fdl login, or FDL_TOKEN) instead of keys to the
|
|
54
|
+
storage.
|
|
55
|
+
|
|
56
|
+
DuckDB cannot write data files over HTTP, so a working catalog of such a repository writes new files
|
|
57
|
+
to a folder beside it and reads the published ones where they are; commit makes both relative
|
|
58
|
+
again and push uploads the new ones. commit --attach keeps a file beside the version.
|
|
59
|
+
|
|
60
|
+
- Read the description from dataset.yml too
|
|
61
|
+
([`c04b6e8`](https://github.com/queria-io/fdl/commit/c04b6e891428b82749b231ab69d77f783d3b69ea))
|
|
62
|
+
|
|
63
|
+
fdl commit looks for dataset.json, then dataset.yml, in the current directory. Licenses may be
|
|
64
|
+
written as bare SPDX identifiers.
|
|
65
|
+
|
|
66
|
+
|
|
4
67
|
## v0.13.0 (2026-11-01)
|
|
5
68
|
|
|
6
69
|
### Features
|
|
@@ -6,9 +6,20 @@
|
|
|
6
6
|
uv sync
|
|
7
7
|
uv run fdl --help
|
|
8
8
|
uv run pytest
|
|
9
|
-
uv run zensical serve
|
|
10
9
|
```
|
|
11
10
|
|
|
11
|
+
Docs (Blume, served at https://fdl.flo8s.com):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm ci
|
|
15
|
+
npm run dev
|
|
16
|
+
npm run check # blume build --strict && blume validate --strict
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The pages under `docs/spec/` are the specification. Change the spec first, then the code.
|
|
20
|
+
|
|
21
|
+
English is the source; `docs/ja/` mirrors every page in Japanese. Change both in the same PR (plain です・ます, 「バージョン」 not 「版」).
|
|
22
|
+
|
|
12
23
|
## Code style
|
|
13
24
|
|
|
14
25
|
- Python 3.10+ — `X | Y` unions are fine; nothing newer (no `typing.Self`, `except*`, `datetime.UTC`)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fdl-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Versions of a DuckLake catalog on object storage, kept the way Git keeps commits
|
|
5
5
|
Project-URL: Homepage, https://fdl.flo8s.com/
|
|
6
6
|
Project-URL: Documentation, https://fdl.flo8s.com/
|
|
@@ -11,6 +11,7 @@ License-Expression: MIT
|
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Requires-Dist: duckdb<2.0,>=1.5.2
|
|
14
|
+
Requires-Dist: pyyaml>=6.0
|
|
14
15
|
Requires-Dist: typer>=0.15
|
|
15
16
|
Provides-Extra: s3
|
|
16
17
|
Requires-Dist: boto3>=1.36; extra == 's3'
|
|
@@ -37,7 +38,7 @@ FDL is a storage layout and a tool for it. Each version is a frozen catalog with
|
|
|
37
38
|
data/main/towns/ducklake-….parquet
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
The
|
|
41
|
+
The documentation and the specification are at [fdl.flo8s.com](https://fdl.flo8s.com).
|
|
41
42
|
|
|
42
43
|
## Quick start
|
|
43
44
|
|
|
@@ -76,7 +77,9 @@ fdl url s3://my-bucket/towns/
|
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
```sql
|
|
79
|
-
ATTACH 'ducklake:s3://my-bucket/towns/versions/019a3c4e-…/ducklake.duckdb' AS towns (
|
|
80
|
+
ATTACH 'ducklake:s3://my-bucket/towns/versions/019a3c4e-…/ducklake.duckdb' AS towns (
|
|
81
|
+
DATA_PATH 's3://my-bucket/towns/data/', OVERRIDE_DATA_PATH true, READ_ONLY
|
|
82
|
+
);
|
|
80
83
|
```
|
|
81
84
|
|
|
82
85
|
Credentials and the endpoint come from boto3's usual places (`AWS_*` environment variables, `AWS_ENDPOINT_URL` for R2 and other S3-compatible stores). A folder works as a repository too: pass its path instead of an `s3://` URL.
|
|
@@ -19,7 +19,7 @@ FDL is a storage layout and a tool for it. Each version is a frozen catalog with
|
|
|
19
19
|
data/main/towns/ducklake-….parquet
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
The
|
|
22
|
+
The documentation and the specification are at [fdl.flo8s.com](https://fdl.flo8s.com).
|
|
23
23
|
|
|
24
24
|
## Quick start
|
|
25
25
|
|
|
@@ -58,7 +58,9 @@ fdl url s3://my-bucket/towns/
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
```sql
|
|
61
|
-
ATTACH 'ducklake:s3://my-bucket/towns/versions/019a3c4e-…/ducklake.duckdb' AS towns (
|
|
61
|
+
ATTACH 'ducklake:s3://my-bucket/towns/versions/019a3c4e-…/ducklake.duckdb' AS towns (
|
|
62
|
+
DATA_PATH 's3://my-bucket/towns/data/', OVERRIDE_DATA_PATH true, READ_ONLY
|
|
63
|
+
);
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
Credentials and the endpoint come from boto3's usual places (`AWS_*` environment variables, `AWS_ENDPOINT_URL` for R2 and other S3-compatible stores). A folder works as a repository too: pass its path instead of an `s3://` URL.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineConfig } from "blume";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
title: "FDL",
|
|
5
|
+
description: "Versions of a DuckLake catalog on object storage, kept the way Git keeps commits",
|
|
6
|
+
theme: {
|
|
7
|
+
accent: "#673ab7",
|
|
8
|
+
},
|
|
9
|
+
deployment: {
|
|
10
|
+
site: "https://fdl.flo8s.com",
|
|
11
|
+
},
|
|
12
|
+
content: {
|
|
13
|
+
root: "docs",
|
|
14
|
+
},
|
|
15
|
+
i18n: {
|
|
16
|
+
defaultLocale: "en",
|
|
17
|
+
locales: [
|
|
18
|
+
{ code: "en", label: "English" },
|
|
19
|
+
{ code: "ja", label: "日本語" },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
seo: {
|
|
23
|
+
og: {
|
|
24
|
+
fonts: ["Noto Sans JP"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
redirects: [{ from: "/cli", to: "/reference/cli" }],
|
|
28
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Repositories
|
|
3
|
+
description: What a repository is, what it needs from the store it lives in, and what it leaves to the host.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
A repository is everything under one prefix of a store: `s3://bucket/lake/` on S3, R2 or another S3-compatible store, or a folder on disk. It is what a bare Git repository is on a server. There is no index file and no process that owns it. Every reader and writer works on the same files, under the rules in the [specification](/spec).
|
|
7
|
+
|
|
8
|
+
## What a store has to do
|
|
9
|
+
|
|
10
|
+
| Operation | Used by | For |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| read an object | readers and writers | refs, `version.json`, catalogs, data files |
|
|
13
|
+
| write an object | writers | data files, catalogs, `version.json` |
|
|
14
|
+
| write on condition | writers | refs: create only if absent, replace only if unchanged |
|
|
15
|
+
| list a prefix | maintenance | finding data files that no version names |
|
|
16
|
+
| delete | maintenance | catalogs of versions past retention, and unused data files |
|
|
17
|
+
|
|
18
|
+
Conditional writes are what make two writers safe. A ref is created with `If-None-Match: *` and moved with `If-Match: <the ETag that was read>`. If someone else moved it in between, the store refuses the write and nothing changes. S3 and R2 support this. The folder store checks the condition without a lock, so it suits one writer at a time (see [Stores](/guides/stores)). A store without conditional writes can still be read from, but must not be pushed to: the last writer would silently replace someone else's version.
|
|
19
|
+
|
|
20
|
+
Readers only read. A static HTTP server in front of the repository is enough for them.
|
|
21
|
+
|
|
22
|
+
## What is in a repository
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
<root>/
|
|
26
|
+
fdl.json settings: format version, ID, where data files are
|
|
27
|
+
HEAD the default branch
|
|
28
|
+
refs/heads/<name> branches
|
|
29
|
+
refs/tags/<name> tags
|
|
30
|
+
versions/<ID>/ one folder per version
|
|
31
|
+
data/ Parquet files, shared by all versions
|
|
32
|
+
sources/<sha256> the files a version was built from (optional)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The repository holds no catalog outside a version folder, and never a working catalog. Either would hand readers a state that was never published.
|
|
36
|
+
|
|
37
|
+
`fdl.json` carries an `id`, a UUID written once by `fdl init`. Copies of the repository on other hosts keep the same `id`, so a tool can tell that two URLs hold the same repository. A service that hosts repositories may write its own ID for the dataset there.
|
|
38
|
+
|
|
39
|
+
## What FDL leaves to the host
|
|
40
|
+
|
|
41
|
+
FDL describes files. It does not say who may read them, who may move `main`, or what a URL looks like. Those belong to whoever hosts the repository:
|
|
42
|
+
|
|
43
|
+
- access control. Everything in one repository is readable by the same people. To publish part of a dataset, make a second repository (see [Public and private data](/guides/public-and-private))
|
|
44
|
+
- approval. A host may keep writers from moving refs and move them itself after a review (see [Building a host](/guides/building-a-host))
|
|
45
|
+
- authentication, URL shapes and how long versions are kept
|
|
46
|
+
|
|
47
|
+
Nothing in the format names a host or a service. A repository can move from one host to another by copying its files.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Versions and refs
|
|
3
|
+
description: A version is a frozen catalog with a record; a ref is a name that points at one. How they relate to DuckLake snapshots and to Git commits.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Versions
|
|
7
|
+
|
|
8
|
+
A version is one frozen DuckLake catalog and a record of how it came to be. It lives in its own folder and is never changed once written.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
versions/019a3c4e-7b21-7f3a-9c55-2f0d8e1a4b6c/
|
|
12
|
+
version.json the record: parents, author, message, checksums
|
|
13
|
+
dataset.json what it holds: tables, columns, license
|
|
14
|
+
ducklake.duckdb the frozen catalog
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The record says:
|
|
18
|
+
|
|
19
|
+
- `id`: the version ID, a UUIDv7. IDs sort by creation time, and two writers never make the same one
|
|
20
|
+
- `seq`: the number shown to people, the parent's `seq` plus one
|
|
21
|
+
- `parents`: the version it was built on. Empty for the first
|
|
22
|
+
- `author`, `message` and `created_at`
|
|
23
|
+
- `catalog`: the size and sha256 of the catalog file, so a reader can check what it fetched
|
|
24
|
+
- `ducklake`: the DuckLake format of the catalog and the snapshot it was frozen at
|
|
25
|
+
|
|
26
|
+
The ID is not a hash of the content, as a Git commit's is. The same catalog can be written as different bytes by two DuckDB releases, so a content hash would not be stable. The sha256 in `catalog` checks the content instead.
|
|
27
|
+
|
|
28
|
+
The full list of fields is in [Versions](/spec/versions) in the specification.
|
|
29
|
+
|
|
30
|
+
## Snapshots and versions
|
|
31
|
+
|
|
32
|
+
DuckLake makes a snapshot on every write: each `INSERT`, `CREATE TABLE` or `ALTER` in its own transaction. A day of work can make hundreds. A version is made when you decide the work is ready, and it takes the last of those snapshots.
|
|
33
|
+
|
|
34
|
+
A snapshot is to a version what a commit on a work branch is to the squashed commit that lands on `main`. The snapshots stay inside the catalog, with their `commit_message` and `commit_extra_info`, so the detail is not lost. `version.json` records the range of snapshots the version adds after its parent, but does not copy them.
|
|
35
|
+
|
|
36
|
+
## Refs
|
|
37
|
+
|
|
38
|
+
A ref is a file that holds a version ID and a newline. It is how a reader finds a version without knowing its ID.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
refs/heads/main 019a3c4e-7b21-7f3a-9c55-2f0d8e1a4b6c
|
|
42
|
+
refs/tags/2025-final 019a2f10-0c4d-7e88-a1b2-55c0d9e3f7aa
|
|
43
|
+
HEAD ref: refs/heads/main
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Kind | Path | Moves |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| branch | `refs/heads/<name>` | yes, forward |
|
|
49
|
+
| tag | `refs/tags/<name>` | never |
|
|
50
|
+
| request | `refs/requests/<number>` | as the host decides |
|
|
51
|
+
| HEAD | `HEAD` | rarely |
|
|
52
|
+
|
|
53
|
+
`main` is the branch readers get unless they ask for another. `HEAD` names it, as in Git. A tag pins a version that readers can rely on, such as the final figures of a year. `refs/requests/` is for hosts: a version waiting for approval can be held there without being on `main`.
|
|
54
|
+
|
|
55
|
+
Refs are the only files in a repository that change. Everything else is written once.
|
|
56
|
+
|
|
57
|
+
## Moving a branch
|
|
58
|
+
|
|
59
|
+
A branch moves forward only. A new version can become `main` when its `parents` include the version `main` holds now. This is a fast-forward in Git's terms.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
main → A
|
|
63
|
+
Alice checks out A, commits B (parent A), pushes: main → B
|
|
64
|
+
Bob checked out A too, commits C (parent A), pushes: refused
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Bob's push is refused because C is not built on B. If it were accepted, B would vanish from the history of `main` with no one deciding that. Bob checks out B and makes his change again.
|
|
68
|
+
|
|
69
|
+
FDL does not merge rows. Merging two DuckLake catalogs would mean deciding which row wins when both sides changed it, and DuckLake gives tables no identity across catalogs that would make that safe. Redoing the change on the new version is usually a matter of running the same SQL again.
|
|
70
|
+
|
|
71
|
+
Moving a branch back, to undo a version, is allowed only when asked for explicitly. The version that was undone stays in the repository and in the history.
|
|
72
|
+
|
|
73
|
+
## History
|
|
74
|
+
|
|
75
|
+
`fdl log` follows the first parent from the version a ref holds back to the first one:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
3 019a4b… 2026-11-01T09:00:00Z alice
|
|
79
|
+
Correct the 2026 figures
|
|
80
|
+
2 019a3c… 2026-10-15T09:00:00Z bob
|
|
81
|
+
Add 2026
|
|
82
|
+
1 019a2f… 2026-10-01T09:00:00Z alice
|
|
83
|
+
First towns
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Versions that were taken out of retention stay in the history. Their record is kept, only their catalog is gone. See [Retention](/internals/inside-a-repository#taking-versions-out-of-retention).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Working catalogs
|
|
3
|
+
description: "Where writing happens: a DuckLake catalog in SQLite, checked out from a version and frozen into the next one."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
A working catalog is a DuckLake catalog in a SQLite file, on the writer's own disk or in a container. It is the only place anyone writes to. It is to FDL what a working tree is to Git: made from a version, changed freely, and turned into the next version when the change is ready.
|
|
7
|
+
|
|
8
|
+
## The cycle
|
|
9
|
+
|
|
10
|
+
| Step | Command | What happens |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| check out | `fdl checkout` | Copy the catalog of the branch's version into SQLite, and remember that version |
|
|
13
|
+
| write | any DuckDB client | DuckLake writes Parquet into the repository and bookkeeping into SQLite |
|
|
14
|
+
| commit | `fdl commit` | Freeze the last snapshot into a new version, upload it, move the branch |
|
|
15
|
+
|
|
16
|
+
Checking out copies DuckLake's bookkeeping tables from the frozen DuckDB file into SQLite as they are. Freezing does the reverse. Neither rewrites a Parquet file, so both are fast even for a large dataset: the catalog is small and the data stays where it is.
|
|
17
|
+
|
|
18
|
+
## Why SQLite
|
|
19
|
+
|
|
20
|
+
A working catalog has to be written by DuckDB and kept between sessions. SQLite does both without a database server, works the same on a laptop and in a container, and copies in and out of DuckDB's format without loss.
|
|
21
|
+
|
|
22
|
+
There is one kind of working catalog. A host that writes on behalf of its users (a scheduled import, a web form) checks out, writes and commits the same way a person does on a laptop. When two of them commit at once, the conditional write on the ref settles it: one wins, and the other checks out again.
|
|
23
|
+
|
|
24
|
+
## Attaching
|
|
25
|
+
|
|
26
|
+
`fdl checkout` prints the statement to attach the working catalog with:
|
|
27
|
+
|
|
28
|
+
```sql
|
|
29
|
+
ATTACH 'ducklake:work/lake.sqlite' AS lake (
|
|
30
|
+
DATA_PATH 's3://my-bucket/towns/data/', OVERRIDE_DATA_PATH true,
|
|
31
|
+
META_TYPE 'sqlite', DATA_INLINING_ROW_LIMIT 0,
|
|
32
|
+
META_JOURNAL_MODE 'WAL', BUSY_TIMEOUT 5000
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `DATA_PATH` is the repository's data folder, so new Parquet files land where readers will look for them. For a repository over HTTP it is a folder beside the working catalog instead, and `fdl commit` uploads the new files (see [Pushing over HTTP](/spec/http#writing-to-it))
|
|
37
|
+
- `DATA_INLINING_ROW_LIMIT 0` keeps every row out of the catalog. A version must not carry inlined rows, and `fdl commit` refuses a working catalog that holds some
|
|
38
|
+
- `META_TYPE 'sqlite'` has to be stated. Left to guess from the file, DuckDB passes an option to SQLite that SQLite rejects
|
|
39
|
+
|
|
40
|
+
## Data files written before a commit
|
|
41
|
+
|
|
42
|
+
DuckLake writes Parquet as you go, so data files reach the repository before any version names them. Readers never see them: a reader only opens catalogs of versions, and no version names those files yet.
|
|
43
|
+
|
|
44
|
+
A file that stays unnamed for longer than the grace period (7 days by default) may be deleted by maintenance. Commit within that time. If a commit is refused because a file it names is missing, rebuild the table in the working catalog and commit again.
|
|
45
|
+
|
|
46
|
+
## Snapshots in the working catalog
|
|
47
|
+
|
|
48
|
+
Do not run `ducklake_expire_snapshots` or `ducklake_cleanup_old_files` on a working catalog. They only see the working catalog, not the repository, and would delete files that other versions still name. Removing old versions and files is done on the repository as a whole (see [Retention](/internals/inside-a-repository#taking-versions-out-of-retention)).
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting started
|
|
3
|
+
description: Make a repository, write a first version with DuckDB, and read it back.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This page makes a repository in a folder, writes a table into it and reads the result as a reader would. The same commands work on S3 and R2: pass `s3://bucket/prefix/` instead of a folder.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install 'fdl-cli[s3]'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The package installs the `fdl` command and the `fdl` Python package. Python 3.10 or later is needed. Leave out `[s3]` if you only use folders.
|
|
15
|
+
|
|
16
|
+
## Make a repository
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
fdl init ./towns/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`init` writes two files and nothing else:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
towns/
|
|
26
|
+
fdl.json {"fdl": "0.1", "id": "5f0c…", "data_dir": "data/"}
|
|
27
|
+
HEAD ref: refs/heads/main
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`HEAD` names the default branch, `main`. The branch itself does not exist yet: there is no version for it to point at.
|
|
31
|
+
|
|
32
|
+
## Check out a working catalog
|
|
33
|
+
|
|
34
|
+
You never write to the repository directly. You write to a working catalog, a DuckLake catalog in a SQLite file on your own disk, and turn it into a version when you are done.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
fdl checkout ./towns/ work/lake.sqlite
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Before the first version, `checkout` makes an empty catalog. Later it copies the catalog of the version `main` holds. It prints the statement to attach the working catalog with:
|
|
41
|
+
|
|
42
|
+
```sql
|
|
43
|
+
ATTACH 'ducklake:work/lake.sqlite' AS lake (
|
|
44
|
+
DATA_PATH '/abs/path/towns/data/', OVERRIDE_DATA_PATH true,
|
|
45
|
+
META_TYPE 'sqlite', DATA_INLINING_ROW_LIMIT 0,
|
|
46
|
+
META_JOURNAL_MODE 'WAL', BUSY_TIMEOUT 5000
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Beside the working catalog, `work/lake.sqlite.fdl.json` remembers the repository, the branch and the version the checkout was made from.
|
|
51
|
+
|
|
52
|
+
## Write
|
|
53
|
+
|
|
54
|
+
Use any DuckDB client. The Parquet files go straight into the repository's `data/` folder.
|
|
55
|
+
|
|
56
|
+
```sql
|
|
57
|
+
CREATE TABLE lake.main.towns AS
|
|
58
|
+
SELECT * FROM 'towns.csv';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`DATA_INLINING_ROW_LIMIT 0` makes DuckLake write every row to Parquet. Left at its default, DuckLake keeps small tables inside the catalog, and a version has to carry its rows in data files that a reader can fetch.
|
|
62
|
+
|
|
63
|
+
## Describe
|
|
64
|
+
|
|
65
|
+
Every version carries a `dataset.json` that says what it holds. Write one next to where you run `fdl commit`:
|
|
66
|
+
|
|
67
|
+
```json dataset.json
|
|
68
|
+
{
|
|
69
|
+
"name": "towns",
|
|
70
|
+
"title": "Towns",
|
|
71
|
+
"tables": [{ "schema": "main", "name": "towns", "title": "Towns and their codes" }]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Only `name` and `title` are required. `fdl commit` checks the file against the catalog and fills in each table's row count and each column's type.
|
|
76
|
+
|
|
77
|
+
## Commit
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
fdl commit work/lake.sqlite -m "First towns" --author alice
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`commit` freezes the working catalog into a version, uploads it and moves `main` to it. It prints `refs/heads/main -> 019a3c4e-… (version 1)`. The repository now holds:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
towns/
|
|
87
|
+
fdl.json
|
|
88
|
+
HEAD
|
|
89
|
+
refs/heads/main 019a3c4e-…
|
|
90
|
+
versions/019a3c4e-…/
|
|
91
|
+
ducklake.duckdb
|
|
92
|
+
dataset.json
|
|
93
|
+
version.json
|
|
94
|
+
data/main/towns/ducklake-….parquet
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
fdl log ./towns/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
1 019a3c4e-… 2026-11-01T09:00:00Z alice
|
|
103
|
+
First towns
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Read
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
fdl url ./towns/
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
prints the catalog of the version `main` holds. Attach it read-only, and tell DuckDB where the data files are:
|
|
113
|
+
|
|
114
|
+
```sql
|
|
115
|
+
ATTACH 'ducklake:towns/versions/019a3c4e-…/ducklake.duckdb' AS towns (
|
|
116
|
+
DATA_PATH 'towns/data/', OVERRIDE_DATA_PATH true, READ_ONLY
|
|
117
|
+
);
|
|
118
|
+
SELECT count(*) FROM towns.main.towns;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A version's catalog never changes, so a reader may cache it forever. Only `HEAD` and the files under `refs/` change.
|
|
122
|
+
|
|
123
|
+
## Make a second version
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
fdl checkout ./towns/ work/lake.sqlite
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```sql
|
|
130
|
+
INSERT INTO lake.main.towns SELECT * FROM 'towns-2026.csv';
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
fdl commit work/lake.sqlite -m "Add 2026" --author alice
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The new version lists the first as its parent. Both catalogs name the first Parquet file; only the second names the new one.
|
|
138
|
+
|
|
139
|
+
If someone else committed in between, the commit is refused, because the new version would not be built on what `main` holds now. Check out again and redo the change. [Versions and refs](/concepts/versions-and-refs) explains why.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Building a host
|
|
3
|
+
description: Using the fdl Python package in a service that stores repositories for others, approves changes and moves refs itself.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
A host is a service that keeps repositories for other people: it decides who can read and write them, and often reviews a change before readers see it. The `fdl` package is written for this. A host calls it the way a code hosting service calls Git. Nothing in the package knows about any host.
|
|
7
|
+
|
|
8
|
+
## Writers that cannot move refs
|
|
9
|
+
|
|
10
|
+
A host that reviews changes keeps writers from writing refs. The writer uploads a version and stops:
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from fdl import repo
|
|
14
|
+
from fdl.stores import open_store
|
|
15
|
+
|
|
16
|
+
store = open_store("s3://lake/towns/")
|
|
17
|
+
version = repo.commit(working, out, parent=base, data_path=repo.data_url(store),
|
|
18
|
+
author={"name": "alice"}, message="Add 2026")
|
|
19
|
+
repo.push(store, out / version.id, version, move_ref=False)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The version is in the repository but no ref points at it, so readers do not see it. The host can hold it under `refs/requests/<number>` while it waits for approval. A request ref is an ordinary ref: it holds a version ID and a newline.
|
|
23
|
+
|
|
24
|
+
## Approving
|
|
25
|
+
|
|
26
|
+
On approval, the host moves `main` itself, on the same condition a writer would:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from fdl import refs
|
|
30
|
+
|
|
31
|
+
current = refs.read(store, "refs/heads/main")
|
|
32
|
+
refs.write(store, "refs/heads/main", version.id, expected=current)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The host checks that the version is built on what `main` holds now (`repo.is_ancestor`). If `main` moved since the request was made, the request is out of date and the writer builds again.
|
|
36
|
+
|
|
37
|
+
## Writing on behalf of users
|
|
38
|
+
|
|
39
|
+
A scheduled import or a form in a web app writes the same way a person does: check out into a SQLite file in a container, write with DuckDB, commit. Two jobs that commit at once are settled by the conditional write on the ref. The loser checks out again and reruns its SQL.
|
|
40
|
+
|
|
41
|
+
## Keeping the host out of the format
|
|
42
|
+
|
|
43
|
+
What a host adds goes outside the fields FDL defines:
|
|
44
|
+
|
|
45
|
+
- extra files in a version folder (a preview), which readers ignore
|
|
46
|
+
- `extensions` in `version.json` and `dataset.json`, keyed by the host's name
|
|
47
|
+
- its own ID for the dataset in `fdl.json`'s `id`, written at `init` and never changed
|
|
48
|
+
|
|
49
|
+
Access rules, approvals and billing stay in the host's own database. A repository copied off the host still reads correctly, with nothing in it that only the host understands.
|
|
50
|
+
|
|
51
|
+
## Retention
|
|
52
|
+
|
|
53
|
+
`repo.prune` takes versions out of retention and `repo.expire` takes out one. Deleting data files that no live version names is left to the host, which can list the whole repository and read every live catalog. See [Data files](/spec/data-files) for when a file may be deleted.
|