@sixb/ducklake 0.2.0 → 0.2.2
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.
- package/README.md +20 -2
- package/dist/index.js +160 -18
- package/dist/index.js.map +10 -9
- package/dist/internal/duckdb-extensions.d.ts +15 -0
- package/dist/internal/duckdb-extensions.d.ts.map +1 -0
- package/dist/internal/duckdb-runtime.d.ts.map +1 -1
- package/dist/internal/ducklake-change-reader.d.ts.map +1 -1
- package/dist/internal/ducklake-snapshot-reader.d.ts +11 -0
- package/dist/internal/ducklake-snapshot-reader.d.ts.map +1 -1
- package/dist/internal/ducklake-write-coordinator.d.ts +7 -6
- package/dist/internal/ducklake-write-coordinator.d.ts.map +1 -1
- package/dist/internal/versions.d.ts +4 -0
- package/dist/internal/versions.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/internal/dataset-row-merge.ts +9 -2
- package/src/internal/duckdb-extensions.ts +102 -0
- package/src/internal/duckdb-runtime.ts +5 -1
- package/src/internal/ducklake-change-reader.ts +30 -0
- package/src/internal/ducklake-snapshot-reader.ts +75 -0
- package/src/internal/ducklake-write-coordinator.ts +27 -13
- package/src/internal/versions.ts +7 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DuckDbQueryRuntime } from "./duckdb-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Install missing core extensions from `https://extensions.duckdb.org`.
|
|
4
|
+
*
|
|
5
|
+
* DuckDB's `INSTALL` downloads from `http://extensions.duckdb.org`, and fetching over HTTPS needs
|
|
6
|
+
* `httpfs`, which DuckDB itself fetches over HTTP. A host that only allows HTTPS by name, such as
|
|
7
|
+
* a Vercel sandbox whose firewall filters on TLS SNI, can reach neither. So the file is fetched
|
|
8
|
+
* here and handed to `INSTALL '<file>'`, which puts it where DuckDB looks; the plain `INSTALL`
|
|
9
|
+
* that follows is then a no-op, and `LOAD` still checks the extension's signature.
|
|
10
|
+
*
|
|
11
|
+
* Best effort: a failure is logged and leaves the extension to DuckDB's own `INSTALL`. A project
|
|
12
|
+
* that set `custom_extension_repository` keeps its own repository.
|
|
13
|
+
*/
|
|
14
|
+
export declare function prefetchDuckDbExtensions(runtime: DuckDbQueryRuntime, extensions: readonly string[]): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=duckdb-extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duckdb-extensions.d.ts","sourceRoot":"","sources":["../../src/internal/duckdb-extensions.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAO1D;;;;;;;;;;;GAWG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,kBAAkB,EAC3B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duckdb-runtime.d.ts","sourceRoot":"","sources":["../../src/internal/duckdb-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"duckdb-runtime.d.ts","sourceRoot":"","sources":["../../src/internal/duckdb-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAE5E,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAoB,MAAM,iBAAiB,CAAA;AAW/F;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,aAAa,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;IAChG,YAAY,CAAC,CAAC,EACZ,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACxD,OAAO,CAAC,CAAC,CAAC,CAAA;CACd;AAED,MAAM,WAAW,aAAc,SAAQ,kBAAkB;IACvD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC7E,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACzF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAA;AAEvD,MAAM,WAAW,cAAc;IAC7B,UAAU,IAAI,IAAI,CAAA;IAClB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACnC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,IAAI,CAAA;IAChE,MAAM,IAAI,IAAI,CAAA;CACf;AAED,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAsND;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CAuBxB;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,sBAAsB,EAC/B,YAAY,GAAE,oBAAyB,GACtC,OAAO,CAAC,IAAI,CAAC,CA8Bf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ducklake-change-reader.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-change-reader.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,uBAAuB,EAE7B,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAGtD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAE9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAOxE,sGAAsG;AACtG,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,SAAS,EAAE,sBAAsB;IAG9C,WAAW,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ducklake-change-reader.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-change-reader.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,uBAAuB,EAE7B,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAGtD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAE9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAOxE,sGAAsG;AACtG,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,SAAS,EAAE,sBAAsB;IAG9C,WAAW,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAwGlF"}
|
|
@@ -12,6 +12,8 @@ export interface DatasetSnapshotRow {
|
|
|
12
12
|
readonly tableId: bigint;
|
|
13
13
|
readonly createdAt: Date;
|
|
14
14
|
readonly mode: DatasetVersionMode;
|
|
15
|
+
readonly rowsChanged: boolean;
|
|
16
|
+
readonly lineageChanged: boolean;
|
|
15
17
|
readonly parentSnapshotId?: string;
|
|
16
18
|
readonly metadata?: SixbCommitMetadata;
|
|
17
19
|
}
|
|
@@ -56,6 +58,15 @@ export declare class DuckLakeSnapshotReader {
|
|
|
56
58
|
listVersions(datasetId: string, limit?: number): Promise<readonly DatasetVersion[]>;
|
|
57
59
|
getLatestVersion(datasetId: string): Promise<DatasetVersion | null>;
|
|
58
60
|
getVersion(datasetId: string, versionId: string): Promise<DatasetVersion | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Whether a version holds exactly the rows of an earlier version of the dataset: every version
|
|
63
|
+
* committed after that one, up to this one, changed no rows.
|
|
64
|
+
*
|
|
65
|
+
* Reads the snapshots that name the dataset's tables between the two, from metadata only. More
|
|
66
|
+
* than `UNCHANGED_ROWS_WALK_LIMIT` of them, or one that is no Sixb version, such as a change
|
|
67
|
+
* made outside Sixb, reads as false: the caller compares the rows instead.
|
|
68
|
+
*/
|
|
69
|
+
holdsRowsOf(datasetId: string, versionId: string, earlierVersionId: string): Promise<boolean>;
|
|
59
70
|
/**
|
|
60
71
|
* Bulk catalog-summary read used by the dataset list view.
|
|
61
72
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ducklake-snapshot-reader.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-snapshot-reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,yBAAyB,CAAA;AAEhC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AAQ7E,OAAO,EAKL,KAAK,kBAAkB,EAExB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CACvC;
|
|
1
|
+
{"version":3,"file":"ducklake-snapshot-reader.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-snapshot-reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,18 +64,19 @@ export declare class DuckLakeWriteCoordinator {
|
|
|
64
64
|
/** A commit that changes no rows still creates a version for a first version or new provenance. */
|
|
65
65
|
private noOpCreatesVersion;
|
|
66
66
|
/**
|
|
67
|
-
* The latest version a write that began without reading it compared its rows with:
|
|
68
|
-
* the lake snapshot its first read fixed. The version metadata also shows versions
|
|
69
|
-
* since. One of this dataset means the
|
|
70
|
-
* reruns.
|
|
67
|
+
* The latest version a write that began without reading it replaces and compared its rows with:
|
|
68
|
+
* the one at the lake snapshot its first read fixed. The version metadata also shows versions
|
|
69
|
+
* committed since. One of this dataset means the write was prepared on an older version, so it
|
|
70
|
+
* reruns: a snapshot that changed rows would conflict with that version at commit anyway.
|
|
71
71
|
*/
|
|
72
72
|
private latestVersionAtReadSnapshot;
|
|
73
73
|
private versionForNoOpCommit;
|
|
74
74
|
private rollbackTransaction;
|
|
75
75
|
/**
|
|
76
76
|
* Every version but a snapshot keeps its parent's rows, and the lineage it records merges the
|
|
77
|
-
* parent's. A snapshot replaces its parent: it reads it only to guard on it, or on SQLite,
|
|
78
|
-
* later reads inside the transaction would block its commit.
|
|
77
|
+
* parent's. A snapshot replaces its parent: it reads it first only to guard on it, or on SQLite,
|
|
78
|
+
* whose later reads inside the transaction would block its commit. Otherwise it reads it once
|
|
79
|
+
* its rows are written (see `latestVersionAtReadSnapshot`).
|
|
79
80
|
*/
|
|
80
81
|
private beginVersionTransaction;
|
|
81
82
|
private lastCommittedSnapshotId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ducklake-write-coordinator.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-write-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EAKtB,wBAAwB,EACxB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"ducklake-write-coordinator.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-write-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,10 @@ export interface SixbCommitMetadata {
|
|
|
14
14
|
readonly schemaChange?: SixbSchemaChangeMetadata;
|
|
15
15
|
/** As `encodeLineage` stored it: decoded only for the versions a read returns. */
|
|
16
16
|
readonly lineage?: unknown;
|
|
17
|
+
/** As `DatasetVersion.rowsChanged`. Absent on versions written before Sixb recorded it. */
|
|
18
|
+
readonly rowsChanged?: boolean;
|
|
19
|
+
/** As `DatasetVersion.lineageChanged`. Absent on versions written before Sixb recorded it. */
|
|
20
|
+
readonly lineageChanged?: boolean;
|
|
17
21
|
}
|
|
18
22
|
export declare function parseVersionId(versionId: string): string;
|
|
19
23
|
export declare function toVersionId(snapshotId: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../src/internal/versions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAG9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvD,QAAQ,CAAC,YAAY,CAAC,EAAE,wBAAwB,CAAA;IAChD,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../../src/internal/versions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAG9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvD,QAAQ,CAAC,YAAY,CAAC,EAAE,wBAAwB,CAAA;IAChD,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,8FAA8F;IAC9F,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAClC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWxD;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAgDlF;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAG5F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd;IAAE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAsBxE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sixb/ducklake",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "DuckDB/DuckLake lake storage provider for Sixb",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sixb",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"typecheck": "tsc --noEmit",
|
|
37
37
|
"test": "bun test",
|
|
38
38
|
"test:e2e": "bun run test:e2e:local && bun run test:e2e:remote",
|
|
39
|
-
"test:e2e:local": "bun test --preload ./tests/extensions.ts ./tests/ducklake-reader.e2e.ts ./tests/ducklake-changes.e2e.ts ./tests/ducklake-contract.e2e.ts ./tests/ducklake-catalog-state.e2e.ts ./tests/ducklake-catalogs.e2e.ts ./tests/ducklake-concurrency.e2e.ts ./tests/ducklake-datasets.e2e.ts ./tests/ducklake-driver.e2e.ts ./tests/ducklake-maintenance.e2e.ts ./tests/ducklake-merges.e2e.ts ./tests/ducklake-sql-transforms.e2e.ts ./tests/ducklake-versions.e2e.ts ./tests/ducklake-long-history.e2e.ts ./tests/ducklake-writes.e2e.ts ./tests/ducklake-sqlite-writes.e2e.ts",
|
|
39
|
+
"test:e2e:local": "bun test --preload ./tests/extensions.ts ./tests/duckdb-extensions.e2e.ts ./tests/ducklake-reader.e2e.ts ./tests/ducklake-changes.e2e.ts ./tests/ducklake-contract.e2e.ts ./tests/ducklake-catalog-state.e2e.ts ./tests/ducklake-catalogs.e2e.ts ./tests/ducklake-concurrency.e2e.ts ./tests/ducklake-datasets.e2e.ts ./tests/ducklake-driver.e2e.ts ./tests/ducklake-maintenance.e2e.ts ./tests/ducklake-merges.e2e.ts ./tests/ducklake-sql-transforms.e2e.ts ./tests/ducklake-versions.e2e.ts ./tests/ducklake-long-history.e2e.ts ./tests/ducklake-writes.e2e.ts ./tests/ducklake-sqlite-writes.e2e.ts",
|
|
40
40
|
"test:e2e:remote": "bun test --preload ./tests/extensions.ts --preload ./tests/setup.ts ./tests/ducklake-remote.e2e.ts",
|
|
41
41
|
"prepack": "bun run build"
|
|
42
42
|
},
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"@duckdb/node-api": "1.5.2-r.2"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@sixb/core": "^0.2.
|
|
47
|
+
"@sixb/core": "^0.2.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@sixb/core": "0.2.
|
|
50
|
+
"@sixb/core": "0.2.2",
|
|
51
51
|
"@types/bun": "latest",
|
|
52
52
|
"typescript": "^5.7.0"
|
|
53
53
|
},
|
|
@@ -313,19 +313,26 @@ function primaryKeyMatchSql(
|
|
|
313
313
|
.join(" AND ")
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Whether a staged upsert changes the stored row of its key. A row that differs only in its
|
|
318
|
+
* `sequenceBy` column is the stored row delivered again (`isSequenceOnlyChange`): it stays as is,
|
|
319
|
+
* while the companion table still advances the key's ordering.
|
|
320
|
+
*/
|
|
316
321
|
function datasetRowDifferenceSql(
|
|
317
322
|
dataset: DatasetDefinition,
|
|
318
323
|
leftAlias: string,
|
|
319
324
|
rightAlias: string
|
|
320
325
|
): string {
|
|
321
|
-
|
|
326
|
+
const differences = dataset.schema.columns
|
|
327
|
+
.filter((column) => column.name !== dataset.sequenceBy)
|
|
322
328
|
.map(
|
|
323
329
|
(column) =>
|
|
324
330
|
`${leftAlias}.${quoteIdentifier(column.name)} IS DISTINCT FROM ${rightAlias}.${quoteIdentifier(
|
|
325
331
|
column.name
|
|
326
332
|
)}`
|
|
327
333
|
)
|
|
328
|
-
|
|
334
|
+
|
|
335
|
+
return differences.length > 0 ? differences.join(" OR ") : "FALSE"
|
|
329
336
|
}
|
|
330
337
|
|
|
331
338
|
async function countRows(runtime: DuckDbQueryRuntime, relationSql: string): Promise<number> {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises"
|
|
2
|
+
import { tmpdir } from "node:os"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
import { promisify } from "node:util"
|
|
5
|
+
import { gunzip } from "node:zlib"
|
|
6
|
+
import type { DuckDbQueryRuntime } from "./duckdb-runtime"
|
|
7
|
+
import { quoteSqlString, quoteSqlStringList } from "./sql"
|
|
8
|
+
|
|
9
|
+
const extensionsUrl = "https://extensions.duckdb.org"
|
|
10
|
+
const downloadTimeoutMillis = 60_000
|
|
11
|
+
const gunzipAsync = promisify(gunzip)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Install missing core extensions from `https://extensions.duckdb.org`.
|
|
15
|
+
*
|
|
16
|
+
* DuckDB's `INSTALL` downloads from `http://extensions.duckdb.org`, and fetching over HTTPS needs
|
|
17
|
+
* `httpfs`, which DuckDB itself fetches over HTTP. A host that only allows HTTPS by name, such as
|
|
18
|
+
* a Vercel sandbox whose firewall filters on TLS SNI, can reach neither. So the file is fetched
|
|
19
|
+
* here and handed to `INSTALL '<file>'`, which puts it where DuckDB looks; the plain `INSTALL`
|
|
20
|
+
* that follows is then a no-op, and `LOAD` still checks the extension's signature.
|
|
21
|
+
*
|
|
22
|
+
* Best effort: a failure is logged and leaves the extension to DuckDB's own `INSTALL`. A project
|
|
23
|
+
* that set `custom_extension_repository` keeps its own repository.
|
|
24
|
+
*/
|
|
25
|
+
export async function prefetchDuckDbExtensions(
|
|
26
|
+
runtime: DuckDbQueryRuntime,
|
|
27
|
+
extensions: readonly string[]
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
await prefetch(runtime, extensions).catch((error: unknown) =>
|
|
30
|
+
warnFallback(extensions.join("', '"), error)
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function prefetch(runtime: DuckDbQueryRuntime, extensions: readonly string[]) {
|
|
35
|
+
const [target] = await runtime.query(`
|
|
36
|
+
SELECT
|
|
37
|
+
version() AS version,
|
|
38
|
+
(SELECT platform FROM pragma_platform()) AS platform,
|
|
39
|
+
current_setting('custom_extension_repository') AS repository
|
|
40
|
+
`)
|
|
41
|
+
if (target?.repository !== "") return
|
|
42
|
+
|
|
43
|
+
const missing = await missingExtensions(runtime, extensions)
|
|
44
|
+
if (missing.length === 0) return
|
|
45
|
+
|
|
46
|
+
const directory = await mkdtemp(join(tmpdir(), "sixb-duckdb-extensions-"))
|
|
47
|
+
try {
|
|
48
|
+
const downloads = await Promise.all(
|
|
49
|
+
missing.map(async (name) => ({
|
|
50
|
+
name,
|
|
51
|
+
file: await download(`${target.version}/${target.platform}/${name}`, directory, name).catch(
|
|
52
|
+
(error: unknown) => warnFallback(name, error)
|
|
53
|
+
),
|
|
54
|
+
}))
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// One at a time: the runtime runs statements in order anyway, and this keeps failures apart.
|
|
58
|
+
for (const { name, file } of downloads) {
|
|
59
|
+
if (file === undefined) continue
|
|
60
|
+
await runtime
|
|
61
|
+
.run(`INSTALL ${quoteSqlString(file)}`)
|
|
62
|
+
.catch((error: unknown) => warnFallback(name, error))
|
|
63
|
+
}
|
|
64
|
+
} finally {
|
|
65
|
+
await rm(directory, { recursive: true, force: true })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function warnFallback(name: string, error: unknown): undefined {
|
|
70
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
71
|
+
console.warn(
|
|
72
|
+
`[SixbDuckLake] Could not install DuckDB extension '${name}' over HTTPS (${reason}); falling back to DuckDB's INSTALL.`
|
|
73
|
+
)
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Canonical names of the requested extensions not installed yet: `sqlite` is `sqlite_scanner`. */
|
|
78
|
+
async function missingExtensions(
|
|
79
|
+
runtime: DuckDbQueryRuntime,
|
|
80
|
+
extensions: readonly string[]
|
|
81
|
+
): Promise<string[]> {
|
|
82
|
+
const requested = `[${quoteSqlStringList(extensions.map((extension) => extension.toLowerCase()))}]`
|
|
83
|
+
const rows = await runtime.query(`
|
|
84
|
+
SELECT extension_name
|
|
85
|
+
FROM duckdb_extensions()
|
|
86
|
+
WHERE NOT installed
|
|
87
|
+
AND (list_contains(${requested}, extension_name) OR list_has_any(${requested}, aliases))
|
|
88
|
+
`)
|
|
89
|
+
return rows.map((row) => String(row.extension_name))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** `INSTALL '<file>'` names the extension after the file, so it is written as `<name>.duckdb_extension`. */
|
|
93
|
+
async function download(path: string, directory: string, name: string): Promise<string> {
|
|
94
|
+
const response = await fetch(`${extensionsUrl}/${path}.duckdb_extension.gz`, {
|
|
95
|
+
signal: AbortSignal.timeout(downloadTimeoutMillis),
|
|
96
|
+
})
|
|
97
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
98
|
+
|
|
99
|
+
const file = join(directory, `${name}.duckdb_extension`)
|
|
100
|
+
await writeFile(file, await gunzipAsync(new Uint8Array(await response.arrayBuffer())))
|
|
101
|
+
return file
|
|
102
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@duckdb/node-api"
|
|
9
9
|
import { LakeStorageError } from "@sixb/core/lake-storage"
|
|
10
10
|
import type { DuckDbRuntimeOptions, DuckLakeStorageOptions } from "../types"
|
|
11
|
+
import { prefetchDuckDbExtensions } from "./duckdb-extensions"
|
|
11
12
|
import { type DuckDbReader, type DuckDbReaderOptions, NodeDuckDbReader } from "./duckdb-reader"
|
|
12
13
|
import { sixbDuckDbValueConverter } from "./duckdb-value-converter"
|
|
13
14
|
import {
|
|
@@ -323,9 +324,12 @@ export async function installDuckLakeExtensions(
|
|
|
323
324
|
runtime: DuckDbRuntime,
|
|
324
325
|
options: DuckLakeStorageOptions
|
|
325
326
|
): Promise<void> {
|
|
327
|
+
const extensions = requiredExtensions(options)
|
|
328
|
+
await prefetchDuckDbExtensions(runtime, ["ducklake", ...extensions])
|
|
329
|
+
|
|
326
330
|
await runtime.run("INSTALL ducklake")
|
|
327
331
|
|
|
328
|
-
for (const extension of
|
|
332
|
+
for (const extension of extensions) {
|
|
329
333
|
await runtime.run(`INSTALL ${quoteIdentifier(extension)}`)
|
|
330
334
|
}
|
|
331
335
|
}
|
|
@@ -33,6 +33,13 @@ export class DuckLakeChangeReader {
|
|
|
33
33
|
const after = await this.snapshots.getVersion(input.datasetId, input.toVersionId)
|
|
34
34
|
const columns = resolveDatasetChangeColumns(input, before, after)
|
|
35
35
|
if (!columns) return null
|
|
36
|
+
|
|
37
|
+
// Versions that only record new provenance or source ordering hold their parent's rows: their
|
|
38
|
+
// change set is empty, whatever the keys, so it needs neither the key checks nor the join.
|
|
39
|
+
const unchanged =
|
|
40
|
+
(input.toVersionId === input.fromVersionId || !after!.rowsChanged) &&
|
|
41
|
+
(await this.snapshots.holdsRowsOf(input.datasetId, input.toVersionId, input.fromVersionId))
|
|
42
|
+
|
|
36
43
|
const lease = await this.connections.acquireAttachedRuntime()
|
|
37
44
|
const runtime = lease.runtime
|
|
38
45
|
const temporary = quoteIdentifier(`sixb_delta_${randomUUID().replaceAll("-", "")}`)
|
|
@@ -44,6 +51,12 @@ export class DuckLakeChangeReader {
|
|
|
44
51
|
const table = qualifiedTableName(this.options, ref.tableName)
|
|
45
52
|
const oldRelation = `${table} AT (VERSION => ${parseVersionId(input.fromVersionId)})`
|
|
46
53
|
const newRelation = `${table} AT (VERSION => ${parseVersionId(input.toVersionId)})`
|
|
54
|
+
if (unchanged) {
|
|
55
|
+
return noChanges(
|
|
56
|
+
after!.rowCount ?? before!.rowCount ?? (await countRows(runtime, newRelation))
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
const previousCount = await uniqueRowCount(runtime, oldRelation, input.keyColumns)
|
|
48
61
|
const nextCount = await uniqueRowCount(runtime, newRelation, input.keyColumns)
|
|
49
62
|
if (previousCount === null || nextCount === null) return null
|
|
@@ -120,6 +133,23 @@ export class DuckLakeChangeReader {
|
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
|
|
136
|
+
function noChanges(rowCount: number): DatasetChanges {
|
|
137
|
+
return {
|
|
138
|
+
fromRowCount: rowCount,
|
|
139
|
+
toRowCount: rowCount,
|
|
140
|
+
changeCount: 0,
|
|
141
|
+
changes: (async function* () {
|
|
142
|
+
yield* []
|
|
143
|
+
})(),
|
|
144
|
+
async close() {},
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function countRows(runtime: DuckDbQueryRuntime, relation: string): Promise<number> {
|
|
149
|
+
const [row] = await runtime.query(`SELECT count(*) AS total FROM ${relation}`)
|
|
150
|
+
return safeCount(row!, "total")
|
|
151
|
+
}
|
|
152
|
+
|
|
123
153
|
async function uniqueRowCount(
|
|
124
154
|
runtime: DuckDbQueryRuntime,
|
|
125
155
|
relation: string,
|
|
@@ -44,6 +44,8 @@ export interface DatasetSnapshotRow {
|
|
|
44
44
|
readonly tableId: bigint
|
|
45
45
|
readonly createdAt: Date
|
|
46
46
|
readonly mode: DatasetVersionMode
|
|
47
|
+
readonly rowsChanged: boolean
|
|
48
|
+
readonly lineageChanged: boolean
|
|
47
49
|
readonly parentSnapshotId?: string
|
|
48
50
|
readonly metadata?: SixbCommitMetadata
|
|
49
51
|
}
|
|
@@ -66,6 +68,8 @@ interface SnapshotCandidateQueryInput {
|
|
|
66
68
|
readonly table: DatasetTableRef
|
|
67
69
|
readonly exactSnapshotId?: string
|
|
68
70
|
readonly beforeSnapshotId?: string
|
|
71
|
+
/** Inclusive lower bound. */
|
|
72
|
+
readonly fromSnapshotId?: string
|
|
69
73
|
readonly limit: number
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -110,6 +114,10 @@ const FEED_PAGE_SIZE = 512
|
|
|
110
114
|
// metadata of its version, whose lineage may be large.
|
|
111
115
|
const SHORT_READ_BATCH_SIZE = 8
|
|
112
116
|
|
|
117
|
+
// A change read between two versions of identical rows skips comparing them. Consumers read changes
|
|
118
|
+
// for every version they apply, so few versions usually stand between the two.
|
|
119
|
+
const UNCHANGED_ROWS_WALK_LIMIT = 32
|
|
120
|
+
|
|
113
121
|
// The bulk catalog scan shares one descending walk of recent snapshots across
|
|
114
122
|
// all requested datasets, so its cost is bounded by this window rather than by
|
|
115
123
|
// the dataset count. A dataset whose latest version is older than the window
|
|
@@ -181,6 +189,60 @@ export class DuckLakeSnapshotReader {
|
|
|
181
189
|
})
|
|
182
190
|
}
|
|
183
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Whether a version holds exactly the rows of an earlier version of the dataset: every version
|
|
194
|
+
* committed after that one, up to this one, changed no rows.
|
|
195
|
+
*
|
|
196
|
+
* Reads the snapshots that name the dataset's tables between the two, from metadata only. More
|
|
197
|
+
* than `UNCHANGED_ROWS_WALK_LIMIT` of them, or one that is no Sixb version, such as a change
|
|
198
|
+
* made outside Sixb, reads as false: the caller compares the rows instead.
|
|
199
|
+
*/
|
|
200
|
+
async holdsRowsOf(
|
|
201
|
+
datasetId: string,
|
|
202
|
+
versionId: string,
|
|
203
|
+
earlierVersionId: string
|
|
204
|
+
): Promise<boolean> {
|
|
205
|
+
const snapshotId = parseVersionId(versionId)
|
|
206
|
+
const earlierSnapshotId = parseVersionId(earlierVersionId)
|
|
207
|
+
if (snapshotId === earlierSnapshotId) {
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
if (BigInt(snapshotId) < BigInt(earlierSnapshotId)) {
|
|
211
|
+
return false
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.connections.assertOpen()
|
|
215
|
+
|
|
216
|
+
return this.connections.withAttachedRuntime(async (runtime) => {
|
|
217
|
+
const tableRef = await resolveDatasetTableRef(this.options, runtime, datasetId)
|
|
218
|
+
if (!tableRef) {
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Newest first: this version, the snapshots between, then the earlier version itself.
|
|
223
|
+
const candidates = await this.querySnapshotCandidates(runtime, {
|
|
224
|
+
table: tableRef,
|
|
225
|
+
beforeSnapshotId: String(BigInt(snapshotId) + 1n),
|
|
226
|
+
fromSnapshotId: earlierSnapshotId,
|
|
227
|
+
limit: UNCHANGED_ROWS_WALK_LIMIT + 1,
|
|
228
|
+
})
|
|
229
|
+
if (
|
|
230
|
+
candidates[0]?.snapshotId !== snapshotId ||
|
|
231
|
+
candidates[candidates.length - 1]?.snapshotId !== earlierSnapshotId
|
|
232
|
+
) {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return candidates
|
|
237
|
+
.slice(0, -1)
|
|
238
|
+
.every(
|
|
239
|
+
(candidate) =>
|
|
240
|
+
this.candidateToSnapshotRow(datasetId, tableRef.tableId, candidate)?.rowsChanged ===
|
|
241
|
+
false
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
184
246
|
/**
|
|
185
247
|
* Bulk catalog-summary read used by the dataset list view.
|
|
186
248
|
*
|
|
@@ -905,6 +967,9 @@ export class DuckLakeSnapshotReader {
|
|
|
905
967
|
if (input.beforeSnapshotId !== undefined) {
|
|
906
968
|
assertDuckLakeSnapshotId(input.beforeSnapshotId)
|
|
907
969
|
}
|
|
970
|
+
if (input.fromSnapshotId !== undefined) {
|
|
971
|
+
assertDuckLakeSnapshotId(input.fromSnapshotId)
|
|
972
|
+
}
|
|
908
973
|
|
|
909
974
|
const rows = await runtime.query(
|
|
910
975
|
buildDuckLakeMetadataQuery(this.options, (table) => {
|
|
@@ -924,6 +989,9 @@ export class DuckLakeSnapshotReader {
|
|
|
924
989
|
if (input.beforeSnapshotId !== undefined) {
|
|
925
990
|
bounds.push(`changes.snapshot_id < ${input.beforeSnapshotId}`)
|
|
926
991
|
}
|
|
992
|
+
if (input.fromSnapshotId !== undefined) {
|
|
993
|
+
bounds.push(`changes.snapshot_id >= ${input.fromSnapshotId}`)
|
|
994
|
+
}
|
|
927
995
|
|
|
928
996
|
const namesDatasetTable = datasetTableIds(input.table)
|
|
929
997
|
.map(
|
|
@@ -1022,6 +1090,8 @@ export class DuckLakeSnapshotReader {
|
|
|
1022
1090
|
tableId,
|
|
1023
1091
|
createdAt: candidate.createdAt,
|
|
1024
1092
|
mode: metadata.mode ?? "schema",
|
|
1093
|
+
rowsChanged: metadata.rowsChanged ?? true,
|
|
1094
|
+
lineageChanged: metadata.lineageChanged ?? true,
|
|
1025
1095
|
metadata,
|
|
1026
1096
|
}
|
|
1027
1097
|
}
|
|
@@ -1031,11 +1101,14 @@ export class DuckLakeSnapshotReader {
|
|
|
1031
1101
|
// points elsewhere, fail loudly rather than hydrating the wrong lineage.
|
|
1032
1102
|
this.assertNoMetadataConflict(candidate.snapshotId, datasetId, candidate.metadata)
|
|
1033
1103
|
|
|
1104
|
+
// DuckLake recorded a change to the table's rows, whatever the metadata says.
|
|
1034
1105
|
return {
|
|
1035
1106
|
snapshotId: candidate.snapshotId,
|
|
1036
1107
|
tableId,
|
|
1037
1108
|
createdAt: candidate.createdAt,
|
|
1038
1109
|
mode: candidate.metadata?.mode ?? (hasDeleteChange ? "snapshot" : "append"),
|
|
1110
|
+
rowsChanged: true,
|
|
1111
|
+
lineageChanged: candidate.metadata?.lineageChanged ?? true,
|
|
1039
1112
|
...(candidate.metadata !== undefined ? { metadata: candidate.metadata } : {}),
|
|
1040
1113
|
}
|
|
1041
1114
|
}
|
|
@@ -1089,6 +1162,8 @@ function snapshotToVersion(
|
|
|
1089
1162
|
producer: row.metadata?.producer,
|
|
1090
1163
|
inputs: row.metadata?.inputs,
|
|
1091
1164
|
...storedLineage(tableRef.datasetId, row),
|
|
1165
|
+
rowsChanged: row.rowsChanged,
|
|
1166
|
+
lineageChanged: row.lineageChanged,
|
|
1092
1167
|
...(row.metadata?.rowCount !== undefined ? { rowCount: row.metadata.rowCount } : {}),
|
|
1093
1168
|
}
|
|
1094
1169
|
}
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
import {
|
|
16
16
|
assertUnsequencedDatasetWrite,
|
|
17
17
|
getDatasetPrimaryKeyColumns,
|
|
18
|
+
hasDatasetLineageChanges,
|
|
18
19
|
hasDatasetProvenanceChanges,
|
|
19
20
|
LakeConcurrencyError,
|
|
20
21
|
LakeStorageError,
|
|
@@ -321,12 +322,12 @@ export class DuckLakeWriteCoordinator {
|
|
|
321
322
|
})
|
|
322
323
|
input.signal?.throwIfAborted()
|
|
323
324
|
|
|
325
|
+
// The version this one replaces or builds on: what it records having changed compares with.
|
|
326
|
+
const parent =
|
|
327
|
+
latestVersion === undefined ? await this.latestVersionAtReadSnapshot(input) : latestVersion
|
|
328
|
+
|
|
324
329
|
// A commit that changes no rows is decided against the latest version it compared with.
|
|
325
|
-
const noOpBase = changeResult.dataChangeExpected
|
|
326
|
-
? undefined
|
|
327
|
-
: latestVersion === undefined
|
|
328
|
-
? await this.latestVersionAtReadSnapshot(input)
|
|
329
|
-
: latestVersion
|
|
330
|
+
const noOpBase = changeResult.dataChangeExpected ? undefined : parent
|
|
330
331
|
|
|
331
332
|
const createsVersion =
|
|
332
333
|
noOpBase === undefined || this.noOpCreatesVersion(input, noOpBase, lineage)
|
|
@@ -339,7 +340,16 @@ export class DuckLakeWriteCoordinator {
|
|
|
339
340
|
input,
|
|
340
341
|
commitId,
|
|
341
342
|
commitRowCount(input, changeResult, latestVersion),
|
|
342
|
-
lineage
|
|
343
|
+
lineage,
|
|
344
|
+
{
|
|
345
|
+
rowsChanged: parent === null || changeResult.changedTableRows,
|
|
346
|
+
lineageChanged: hasDatasetLineageChanges(parent, {
|
|
347
|
+
datasetId: input.dataset.id,
|
|
348
|
+
schema: input.dataset.schema,
|
|
349
|
+
producer: input.producer,
|
|
350
|
+
lineage,
|
|
351
|
+
}),
|
|
352
|
+
}
|
|
343
353
|
)
|
|
344
354
|
|
|
345
355
|
await commitVersionTransaction(
|
|
@@ -477,7 +487,8 @@ export class DuckLakeWriteCoordinator {
|
|
|
477
487
|
input: DuckLakeCommitVersionOutcomeRuntimeInput,
|
|
478
488
|
commitId: string,
|
|
479
489
|
rowCount: CommitRowCount,
|
|
480
|
-
lineage: DatasetVersionLineage | undefined
|
|
490
|
+
lineage: DatasetVersionLineage | undefined,
|
|
491
|
+
changed: Pick<DatasetVersion, "rowsChanged" | "lineageChanged">
|
|
481
492
|
): Promise<void> {
|
|
482
493
|
// DuckLake stores this JSON on the snapshot it creates for the current
|
|
483
494
|
// transaction. Version id, timestamp, and data-change detection still come
|
|
@@ -498,6 +509,8 @@ export class DuckLakeWriteCoordinator {
|
|
|
498
509
|
? { validatedPrimaryKeyColumns: [...validatedPrimaryKeyColumns] }
|
|
499
510
|
: {}),
|
|
500
511
|
...(lineage === undefined ? {} : { lineage: encodeLineage(lineage) }),
|
|
512
|
+
rowsChanged: changed.rowsChanged,
|
|
513
|
+
lineageChanged: changed.lineageChanged,
|
|
501
514
|
}
|
|
502
515
|
|
|
503
516
|
await input.runtime.run(
|
|
@@ -552,10 +565,10 @@ export class DuckLakeWriteCoordinator {
|
|
|
552
565
|
}
|
|
553
566
|
|
|
554
567
|
/**
|
|
555
|
-
* The latest version a write that began without reading it compared its rows with:
|
|
556
|
-
* the lake snapshot its first read fixed. The version metadata also shows versions
|
|
557
|
-
* since. One of this dataset means the
|
|
558
|
-
* reruns.
|
|
568
|
+
* The latest version a write that began without reading it replaces and compared its rows with:
|
|
569
|
+
* the one at the lake snapshot its first read fixed. The version metadata also shows versions
|
|
570
|
+
* committed since. One of this dataset means the write was prepared on an older version, so it
|
|
571
|
+
* reruns: a snapshot that changed rows would conflict with that version at commit anyway.
|
|
559
572
|
*/
|
|
560
573
|
private async latestVersionAtReadSnapshot(
|
|
561
574
|
input: DuckLakeCommitVersionOutcomeRuntimeInput
|
|
@@ -602,8 +615,9 @@ export class DuckLakeWriteCoordinator {
|
|
|
602
615
|
|
|
603
616
|
/**
|
|
604
617
|
* Every version but a snapshot keeps its parent's rows, and the lineage it records merges the
|
|
605
|
-
* parent's. A snapshot replaces its parent: it reads it only to guard on it, or on SQLite,
|
|
606
|
-
* later reads inside the transaction would block its commit.
|
|
618
|
+
* parent's. A snapshot replaces its parent: it reads it first only to guard on it, or on SQLite,
|
|
619
|
+
* whose later reads inside the transaction would block its commit. Otherwise it reads it once
|
|
620
|
+
* its rows are written (see `latestVersionAtReadSnapshot`).
|
|
607
621
|
*/
|
|
608
622
|
private async beginVersionTransaction(
|
|
609
623
|
input: DuckLakeCommitVersionOutcomeRuntimeInput
|
package/src/internal/versions.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface SixbCommitMetadata {
|
|
|
17
17
|
readonly schemaChange?: SixbSchemaChangeMetadata
|
|
18
18
|
/** As `encodeLineage` stored it: decoded only for the versions a read returns. */
|
|
19
19
|
readonly lineage?: unknown
|
|
20
|
+
/** As `DatasetVersion.rowsChanged`. Absent on versions written before Sixb recorded it. */
|
|
21
|
+
readonly rowsChanged?: boolean
|
|
22
|
+
/** As `DatasetVersion.lineageChanged`. Absent on versions written before Sixb recorded it. */
|
|
23
|
+
readonly lineageChanged?: boolean
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function parseVersionId(versionId: string): string {
|
|
@@ -62,6 +66,7 @@ export function parseCommitMetadata(value: unknown): SixbCommitMetadata | undefi
|
|
|
62
66
|
const producer = parseDatasetProducer(parsed.sixb.producer)
|
|
63
67
|
const rowCount = parsed.sixb.rowCount
|
|
64
68
|
const validatedPrimaryKeyColumns = parsed.sixb.validatedPrimaryKeyColumns
|
|
69
|
+
const { rowsChanged, lineageChanged } = parsed.sixb
|
|
65
70
|
|
|
66
71
|
return {
|
|
67
72
|
kind: "datasetVersion",
|
|
@@ -80,6 +85,8 @@ export function parseCommitMetadata(value: unknown): SixbCommitMetadata | undefi
|
|
|
80
85
|
? { schemaChange: parsed.sixb.schemaChange }
|
|
81
86
|
: {}),
|
|
82
87
|
...(parsed.sixb.lineage === undefined ? {} : { lineage: parsed.sixb.lineage }),
|
|
88
|
+
...(typeof rowsChanged === "boolean" ? { rowsChanged } : {}),
|
|
89
|
+
...(typeof lineageChanged === "boolean" ? { lineageChanged } : {}),
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
|