@sixb/ducklake 0.1.7 → 0.1.8
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 +1 -1
- package/dist/ducklake-storage.d.ts +2 -1
- package/dist/ducklake-storage.d.ts.map +1 -1
- package/dist/index.js +253 -76
- package/dist/index.js.map +5 -5
- package/dist/internal/ducklake-snapshot-reader.d.ts +26 -1
- package/dist/internal/ducklake-snapshot-reader.d.ts.map +1 -1
- package/dist/internal/sql.d.ts +11 -2
- package/dist/internal/sql.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ducklake-storage.ts +8 -0
- package/src/internal/ducklake-snapshot-reader.ts +331 -84
- package/src/internal/sql.ts +35 -6
package/README.md
CHANGED
|
@@ -534,7 +534,7 @@ Local DuckLake e2e tests:
|
|
|
534
534
|
bun --filter @sixb/ducklake test:e2e:local
|
|
535
535
|
```
|
|
536
536
|
|
|
537
|
-
Docker-backed PostgreSQL and
|
|
537
|
+
Docker-backed PostgreSQL and SeaweedFS (S3-compatible) e2e tests:
|
|
538
538
|
|
|
539
539
|
```bash
|
|
540
540
|
bun --filter @sixb/ducklake test:e2e:remote
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DatasetDefinition, DatasetRow, LakeStorageWithSql } from "@sixb/core";
|
|
2
|
-
import type { BeginDatasetMergeInput, BeginDatasetWriteInput, DatasetCatalogState, DatasetChanges, DatasetVersion, LakeMergeSession, LakeWriteSession, ReadDatasetChangesInput, ReadDatasetRowsInput } from "@sixb/core/lake-storage";
|
|
2
|
+
import type { BeginDatasetMergeInput, BeginDatasetWriteInput, DatasetCatalogState, DatasetChanges, DatasetVersion, LakeMergeSession, LakeWriteSession, LatestVersionsSince, ListLatestVersionsSinceInput, ReadDatasetChangesInput, ReadDatasetRowsInput } from "@sixb/core/lake-storage";
|
|
3
3
|
import { DuckLakeSqlExecutor } from "./internal/ducklake-sql-executor";
|
|
4
4
|
import type { DuckLakeMaintenanceOptions, DuckLakeMaintenanceReport, DuckLakeStorageOptions } from "./types";
|
|
5
5
|
/**
|
|
@@ -33,6 +33,7 @@ export declare class DuckLakeStorage implements LakeStorageWithSql<"duckdb"> {
|
|
|
33
33
|
beginMerge(input: BeginDatasetMergeInput): Promise<LakeMergeSession>;
|
|
34
34
|
getLatestVersion(datasetId: string): Promise<DatasetVersion | null>;
|
|
35
35
|
getVersion(datasetId: string, versionId: string): Promise<DatasetVersion | null>;
|
|
36
|
+
listLatestVersionsSince(input: ListLatestVersionsSinceInput): Promise<LatestVersionsSince | null>;
|
|
36
37
|
readRows(input: ReadDatasetRowsInput): AsyncIterable<DatasetRow>;
|
|
37
38
|
readChanges(input: ReadDatasetChangesInput): Promise<DatasetChanges | null>;
|
|
38
39
|
runMaintenance(options?: DuckLakeMaintenanceOptions): Promise<DuckLakeMaintenanceReport>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ducklake-storage.d.ts","sourceRoot":"","sources":["../src/ducklake-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,yBAAyB,CAAA;AAOhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAEtE,OAAO,KAAK,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,SAAS,CAAA;AAEhB;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,kBAAkB,CAAC,QAAQ,CAAC;IAClE,QAAQ,CAAC,QAAQ;;;MAA8C;IAC/D,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAA;IAEjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;gBAErC,OAAO,EAAE,sBAAsB;IAwCrC,aAAa,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIxE,kCAAkC,CACtC,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAAC,IAAI,CAAC;IAIV,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAIhE,YAAY,IAAI,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC;IAIrD,uBAAuB,CAC3B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC;IAIpC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC;IAInF,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIpE,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIpE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAInE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ducklake-storage.d.ts","sourceRoot":"","sources":["../src/ducklake-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,4BAA4B,EAC5B,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,yBAAyB,CAAA;AAOhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAEtE,OAAO,KAAK,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,SAAS,CAAA;AAEhB;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,kBAAkB,CAAC,QAAQ,CAAC;IAClE,QAAQ,CAAC,QAAQ;;;MAA8C;IAC/D,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAA;IAEjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;gBAErC,OAAO,EAAE,sBAAsB;IAwCrC,aAAa,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIxE,kCAAkC,CACtC,WAAW,EAAE,SAAS,iBAAiB,EAAE,GACxC,OAAO,CAAC,IAAI,CAAC;IAIV,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAIhE,YAAY,IAAI,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC;IAIrD,uBAAuB,CAC3B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC;IAIpC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC;IAInF,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIpE,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIpE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAInE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAIhF,uBAAuB,CAC3B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAItC,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,aAAa,CAAC,UAAU,CAAC;IAIhE,WAAW,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAIrE,cAAc,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAI9F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
package/dist/index.js
CHANGED
|
@@ -158,8 +158,20 @@ function buildSetPostgresPoolSql(options) {
|
|
|
158
158
|
return postgresPoolParameters(options.postgresPool).map(({ name, value }) => `SET pg_pool_${name} = ${value}`);
|
|
159
159
|
}
|
|
160
160
|
function duckLakeMetadataTableName(options, tableName) {
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
return `${quoteIdentifier(duckLakeMetadataCatalog(options))}.${quoteIdentifier(duckLakeMetadataSchema(options))}.${quoteIdentifier(tableName)}`;
|
|
162
|
+
}
|
|
163
|
+
function buildDuckLakeMetadataQuery(options, build) {
|
|
164
|
+
if (options.catalog.type !== "postgres") {
|
|
165
|
+
return build((tableName) => duckLakeMetadataTableName(options, tableName));
|
|
166
|
+
}
|
|
167
|
+
const schema = options.catalog.metadataSchema;
|
|
168
|
+
const sql = build((tableName) => schema === undefined ? quoteIdentifier(tableName) : `${quoteIdentifier(schema)}.${quoteIdentifier(tableName)}`);
|
|
169
|
+
return `SELECT * FROM postgres_query(${quoteSqlString(duckLakeMetadataCatalog(options))}, ${quoteSqlString(sql)})`;
|
|
170
|
+
}
|
|
171
|
+
function duckLakeMetadataSchema(options) {
|
|
172
|
+
if (options.catalog.type !== "postgres")
|
|
173
|
+
return "main";
|
|
174
|
+
return options.catalog.metadataSchema ?? "public";
|
|
163
175
|
}
|
|
164
176
|
function quoteIdentifier(identifier) {
|
|
165
177
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
@@ -2311,6 +2323,7 @@ function pageSql(read, limit, offset, cursor) {
|
|
|
2311
2323
|
// src/internal/ducklake-snapshot-reader.ts
|
|
2312
2324
|
import { LakeStorageError as LakeStorageError17 } from "@sixb/core/lake-storage";
|
|
2313
2325
|
var SNAPSHOT_ROW_BATCH_SIZE = 128;
|
|
2326
|
+
var FEED_PAGE_SIZE = 512;
|
|
2314
2327
|
var CATALOG_SNAPSHOT_SCAN_LIMIT = 512;
|
|
2315
2328
|
|
|
2316
2329
|
class DuckLakeSnapshotReader {
|
|
@@ -2376,6 +2389,135 @@ class DuckLakeSnapshotReader {
|
|
|
2376
2389
|
};
|
|
2377
2390
|
});
|
|
2378
2391
|
}
|
|
2392
|
+
async listLatestVersionsSince(input) {
|
|
2393
|
+
this.connections.assertOpen();
|
|
2394
|
+
return this.connections.withAttachedRuntime(async (runtime) => {
|
|
2395
|
+
if (input.cursor === null) {
|
|
2396
|
+
return { cursor: await this.queryHeadSnapshotId(runtime), versions: [] };
|
|
2397
|
+
}
|
|
2398
|
+
if (!/^\d+$/.test(input.cursor)) {
|
|
2399
|
+
return null;
|
|
2400
|
+
}
|
|
2401
|
+
const snapshots = await this.querySnapshotsFrom(runtime, input.cursor);
|
|
2402
|
+
if (snapshots[0]?.snapshotId !== input.cursor) {
|
|
2403
|
+
return null;
|
|
2404
|
+
}
|
|
2405
|
+
const newSnapshots = snapshots.slice(1);
|
|
2406
|
+
if (newSnapshots.length === 0) {
|
|
2407
|
+
return { cursor: input.cursor, versions: [] };
|
|
2408
|
+
}
|
|
2409
|
+
const tables = await this.resolveFeedTables(runtime, input.datasetIds);
|
|
2410
|
+
const latest = await this.collectFeedPage(runtime, tables, newSnapshots);
|
|
2411
|
+
return {
|
|
2412
|
+
cursor: newSnapshots[newSnapshots.length - 1]?.snapshotId ?? input.cursor,
|
|
2413
|
+
versions: [...latest].map(([datasetId, row]) => this.snapshotRowToSummary(datasetId, row))
|
|
2414
|
+
};
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
async queryHeadSnapshotId(runtime) {
|
|
2418
|
+
const [row] = await runtime.query(buildDuckLakeMetadataQuery(this.options, (table) => `SELECT max(snapshot_id) AS snapshot_id FROM ${table("ducklake_snapshot")}`));
|
|
2419
|
+
if (row?.snapshot_id === null || row?.snapshot_id === undefined) {
|
|
2420
|
+
throw new LakeStorageError17("[SixbDuckLake] DuckLake catalog has no snapshots.");
|
|
2421
|
+
}
|
|
2422
|
+
return String(getBigIntLike(row, "snapshot_id"));
|
|
2423
|
+
}
|
|
2424
|
+
async querySnapshotsFrom(runtime, cursor) {
|
|
2425
|
+
const rows = await runtime.query(buildDuckLakeMetadataQuery(this.options, (table) => `
|
|
2426
|
+
SELECT snapshot_id, snapshot_time
|
|
2427
|
+
FROM ${table("ducklake_snapshot")}
|
|
2428
|
+
WHERE snapshot_id >= ${cursor}
|
|
2429
|
+
ORDER BY snapshot_id
|
|
2430
|
+
LIMIT ${FEED_PAGE_SIZE + 1}
|
|
2431
|
+
`));
|
|
2432
|
+
return rows.map((row) => ({
|
|
2433
|
+
snapshotId: String(getBigIntLike(row, "snapshot_id")),
|
|
2434
|
+
createdAt: getDate(row, "snapshot_time")
|
|
2435
|
+
}));
|
|
2436
|
+
}
|
|
2437
|
+
async resolveFeedTables(runtime, datasetIds) {
|
|
2438
|
+
const requested = datasetIds === undefined ? null : new Set(datasetIds);
|
|
2439
|
+
const rows = await runtime.query(`
|
|
2440
|
+
SELECT table_id, table_name, begin_snapshot, end_snapshot
|
|
2441
|
+
FROM ${duckLakeMetadataTableName(this.options, "ducklake_table")}
|
|
2442
|
+
`);
|
|
2443
|
+
const startByTableId = new Map;
|
|
2444
|
+
const current = [];
|
|
2445
|
+
for (const row of rows) {
|
|
2446
|
+
const tableId = getBigIntLike(row, "table_id");
|
|
2447
|
+
const begin = getBigIntLike(row, "begin_snapshot");
|
|
2448
|
+
const start = startByTableId.get(tableId);
|
|
2449
|
+
if (start === undefined || begin < start) {
|
|
2450
|
+
startByTableId.set(tableId, begin);
|
|
2451
|
+
}
|
|
2452
|
+
if (row.end_snapshot !== null && row.end_snapshot !== undefined) {
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
const datasetId = decodeDatasetTableName(getString(row, "table_name"));
|
|
2456
|
+
if (datasetId !== null && (requested === null || requested.has(datasetId))) {
|
|
2457
|
+
current.push({ datasetId, tableId });
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
return current.map((table) => ({
|
|
2461
|
+
...table,
|
|
2462
|
+
startSnapshotId: startByTableId.get(table.tableId) ?? 0n
|
|
2463
|
+
}));
|
|
2464
|
+
}
|
|
2465
|
+
async collectFeedPage(runtime, tables, snapshots) {
|
|
2466
|
+
const latest = new Map;
|
|
2467
|
+
const first = snapshots[0]?.snapshotId;
|
|
2468
|
+
const last = snapshots[snapshots.length - 1]?.snapshotId;
|
|
2469
|
+
if (first === undefined || last === undefined || tables.length === 0) {
|
|
2470
|
+
return latest;
|
|
2471
|
+
}
|
|
2472
|
+
const changeRows = await runtime.query(buildDuckLakeMetadataQuery(this.options, (table) => `
|
|
2473
|
+
SELECT snapshot_id, changes_made, commit_extra_info
|
|
2474
|
+
FROM ${table("ducklake_snapshot_changes")}
|
|
2475
|
+
WHERE snapshot_id >= ${first} AND snapshot_id <= ${last}
|
|
2476
|
+
`));
|
|
2477
|
+
const fileFlags = await this.queryFileChangeFlags(runtime, snapshots.map((snapshot) => snapshot.snapshotId), tables.map((table) => table.tableId));
|
|
2478
|
+
const changesBySnapshotId = new Map(changeRows.map((row) => [String(getBigIntLike(row, "snapshot_id")), row]));
|
|
2479
|
+
const tablesByTableId = new Map(tables.map((table) => [table.tableId.toString(), table]));
|
|
2480
|
+
const tablesByDatasetId = new Map(tables.map((table) => [table.datasetId, table]));
|
|
2481
|
+
for (const snapshot of snapshots) {
|
|
2482
|
+
const changes = changesBySnapshotId.get(snapshot.snapshotId);
|
|
2483
|
+
if (changes === undefined) {
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
const changesMade = getString(changes, "changes_made");
|
|
2487
|
+
const metadata = parseCommitMetadata(changes.commit_extra_info);
|
|
2488
|
+
const touched = new Set;
|
|
2489
|
+
for (const change of changesMade.split(",")) {
|
|
2490
|
+
const table = tablesByTableId.get(change.split(":")[1] ?? "");
|
|
2491
|
+
if (table)
|
|
2492
|
+
touched.add(table);
|
|
2493
|
+
}
|
|
2494
|
+
for (const table of tables) {
|
|
2495
|
+
if (fileFlags.has(fileChangeKey(snapshot.snapshotId, table.tableId)))
|
|
2496
|
+
touched.add(table);
|
|
2497
|
+
}
|
|
2498
|
+
const tagged = metadata === undefined ? undefined : tablesByDatasetId.get(metadata.datasetId);
|
|
2499
|
+
if (tagged)
|
|
2500
|
+
touched.add(tagged);
|
|
2501
|
+
for (const table of touched) {
|
|
2502
|
+
if (BigInt(snapshot.snapshotId) < table.startSnapshotId) {
|
|
2503
|
+
continue;
|
|
2504
|
+
}
|
|
2505
|
+
const flags = fileFlags.get(fileChangeKey(snapshot.snapshotId, table.tableId));
|
|
2506
|
+
const row = this.candidateToSnapshotRow(table.datasetId, table.tableId, {
|
|
2507
|
+
snapshotId: snapshot.snapshotId,
|
|
2508
|
+
createdAt: snapshot.createdAt,
|
|
2509
|
+
changesMade,
|
|
2510
|
+
hasFileChange: flags?.hasFileChange ?? false,
|
|
2511
|
+
hasFileDeleteChange: flags?.hasFileDeleteChange ?? false,
|
|
2512
|
+
...metadata !== undefined ? { metadata } : {}
|
|
2513
|
+
});
|
|
2514
|
+
if (row && row.mode !== "schema") {
|
|
2515
|
+
latest.set(table.datasetId, row);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
return latest;
|
|
2520
|
+
}
|
|
2379
2521
|
snapshotRowToSummary(datasetId, row) {
|
|
2380
2522
|
return {
|
|
2381
2523
|
datasetId,
|
|
@@ -2639,6 +2781,7 @@ class DuckLakeSnapshotReader {
|
|
|
2639
2781
|
let beforeSnapshotId = input.beforeSnapshotId;
|
|
2640
2782
|
while (input.visibleRowLimit === undefined || snapshots.length < input.visibleRowLimit) {
|
|
2641
2783
|
const candidates = await this.querySnapshotCandidates(runtime, {
|
|
2784
|
+
datasetId: input.datasetId,
|
|
2642
2785
|
tableId: input.tableId,
|
|
2643
2786
|
exactSnapshotId: input.exactSnapshotId,
|
|
2644
2787
|
beforeSnapshotId,
|
|
@@ -2648,6 +2791,9 @@ class DuckLakeSnapshotReader {
|
|
|
2648
2791
|
break;
|
|
2649
2792
|
}
|
|
2650
2793
|
for (const candidate of candidates) {
|
|
2794
|
+
if ("missing" in candidate) {
|
|
2795
|
+
continue;
|
|
2796
|
+
}
|
|
2651
2797
|
const snapshot = this.candidateToSnapshotRow(input.datasetId, input.tableId, candidate);
|
|
2652
2798
|
if (snapshot) {
|
|
2653
2799
|
snapshots.push(snapshot);
|
|
@@ -2664,90 +2810,118 @@ class DuckLakeSnapshotReader {
|
|
|
2664
2810
|
return snapshots;
|
|
2665
2811
|
}
|
|
2666
2812
|
async querySnapshotCandidates(runtime, input) {
|
|
2667
|
-
|
|
2813
|
+
const tableId = input.tableId.toString();
|
|
2814
|
+
const limit = Math.max(0, Math.trunc(input.limit));
|
|
2815
|
+
if (limit === 0) {
|
|
2668
2816
|
return [];
|
|
2669
2817
|
}
|
|
2670
|
-
const ducklakeTable = duckLakeMetadataTableName(this.options, "ducklake_table");
|
|
2671
|
-
const where = [
|
|
2672
|
-
`snapshot.snapshot_id >= (SELECT min(begin_snapshot) FROM ${ducklakeTable} WHERE table_id = ${input.tableId})`
|
|
2673
|
-
];
|
|
2674
2818
|
if (input.exactSnapshotId !== undefined) {
|
|
2675
2819
|
assertDuckLakeSnapshotId2(input.exactSnapshotId);
|
|
2676
|
-
where.push(`snapshot.snapshot_id = ${input.exactSnapshotId}`);
|
|
2677
2820
|
}
|
|
2678
2821
|
if (input.beforeSnapshotId !== undefined) {
|
|
2679
2822
|
assertDuckLakeSnapshotId2(input.beforeSnapshotId);
|
|
2680
|
-
where.push(`snapshot.snapshot_id < ${input.beforeSnapshotId}`);
|
|
2681
2823
|
}
|
|
2682
|
-
const
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2824
|
+
const rows = await runtime.query(buildDuckLakeMetadataQuery(this.options, (table) => {
|
|
2825
|
+
const tableStart = `(SELECT min(begin_snapshot) FROM ${table("ducklake_table")} WHERE table_id = ${tableId})`;
|
|
2826
|
+
const candidateIds = input.exactSnapshotId !== undefined ? `SELECT CAST(${input.exactSnapshotId} AS BIGINT) AS snapshot_id
|
|
2827
|
+
WHERE ${input.exactSnapshotId} >= ${tableStart}` : `SELECT DISTINCT snapshot_id
|
|
2828
|
+
FROM (
|
|
2829
|
+
SELECT begin_snapshot AS snapshot_id
|
|
2830
|
+
FROM ${table("ducklake_data_file")} WHERE table_id = ${tableId}
|
|
2831
|
+
UNION ALL
|
|
2832
|
+
SELECT end_snapshot
|
|
2833
|
+
FROM ${table("ducklake_data_file")}
|
|
2834
|
+
WHERE table_id = ${tableId} AND end_snapshot IS NOT NULL
|
|
2835
|
+
UNION ALL
|
|
2836
|
+
SELECT begin_snapshot
|
|
2837
|
+
FROM ${table("ducklake_delete_file")} WHERE table_id = ${tableId}
|
|
2838
|
+
UNION ALL
|
|
2839
|
+
SELECT end_snapshot
|
|
2840
|
+
FROM ${table("ducklake_delete_file")}
|
|
2841
|
+
WHERE table_id = ${tableId} AND end_snapshot IS NOT NULL
|
|
2842
|
+
UNION ALL
|
|
2843
|
+
SELECT snapshot_id
|
|
2844
|
+
FROM ${table("ducklake_snapshot_changes")}
|
|
2845
|
+
WHERE strpos(',' || changes_made || ',', ${quoteSqlString(`:${tableId},`)}) > 0
|
|
2846
|
+
OR strpos(
|
|
2847
|
+
commit_extra_info,
|
|
2848
|
+
${quoteSqlString(`"datasetId":${JSON.stringify(input.datasetId)}`)}
|
|
2849
|
+
) > 0
|
|
2850
|
+
) touched
|
|
2851
|
+
WHERE snapshot_id >= ${tableStart}
|
|
2852
|
+
${input.beforeSnapshotId === undefined ? "" : `AND snapshot_id < ${input.beforeSnapshotId}`}
|
|
2853
|
+
ORDER BY snapshot_id DESC
|
|
2854
|
+
LIMIT ${limit}`;
|
|
2855
|
+
return `
|
|
2856
|
+
WITH candidate_ids AS (
|
|
2857
|
+
${candidateIds}
|
|
2858
|
+
),
|
|
2859
|
+
candidate_snapshots AS (
|
|
2860
|
+
SELECT
|
|
2861
|
+
snapshot.snapshot_id,
|
|
2862
|
+
snapshot.snapshot_time,
|
|
2863
|
+
changes.changes_made,
|
|
2864
|
+
changes.commit_extra_info
|
|
2865
|
+
FROM ${table("ducklake_snapshot")} snapshot
|
|
2866
|
+
JOIN ${table("ducklake_snapshot_changes")} changes
|
|
2867
|
+
ON changes.snapshot_id = snapshot.snapshot_id
|
|
2868
|
+
WHERE snapshot.snapshot_id IN (SELECT snapshot_id FROM candidate_ids)
|
|
2869
|
+
),
|
|
2870
|
+
file_changes AS (
|
|
2871
|
+
-- File metadata tells whether this table changed in each candidate snapshot.
|
|
2872
|
+
SELECT begin_snapshot AS snapshot_id, false AS is_delete_change
|
|
2873
|
+
FROM ${table("ducklake_data_file")}
|
|
2874
|
+
WHERE table_id = ${tableId}
|
|
2875
|
+
AND begin_snapshot IN (SELECT snapshot_id FROM candidate_ids)
|
|
2876
|
+
UNION ALL
|
|
2877
|
+
SELECT end_snapshot AS snapshot_id, true AS is_delete_change
|
|
2878
|
+
FROM ${table("ducklake_data_file")}
|
|
2879
|
+
WHERE table_id = ${tableId}
|
|
2880
|
+
AND end_snapshot IN (SELECT snapshot_id FROM candidate_ids)
|
|
2881
|
+
UNION ALL
|
|
2882
|
+
SELECT begin_snapshot AS snapshot_id, true AS is_delete_change
|
|
2883
|
+
FROM ${table("ducklake_delete_file")}
|
|
2884
|
+
WHERE table_id = ${tableId}
|
|
2885
|
+
AND begin_snapshot IN (SELECT snapshot_id FROM candidate_ids)
|
|
2886
|
+
UNION ALL
|
|
2887
|
+
SELECT end_snapshot AS snapshot_id, true AS is_delete_change
|
|
2888
|
+
FROM ${table("ducklake_delete_file")}
|
|
2889
|
+
WHERE table_id = ${tableId}
|
|
2890
|
+
AND end_snapshot IN (SELECT snapshot_id FROM candidate_ids)
|
|
2891
|
+
),
|
|
2892
|
+
file_changes_by_snapshot AS (
|
|
2893
|
+
-- Collapse file-level changes into one row per snapshot.
|
|
2894
|
+
SELECT
|
|
2895
|
+
snapshot_id,
|
|
2896
|
+
count(*) > 0 AS has_file_change,
|
|
2897
|
+
count(*) FILTER (WHERE is_delete_change) > 0 AS has_file_delete_change
|
|
2898
|
+
FROM file_changes
|
|
2899
|
+
GROUP BY snapshot_id
|
|
2900
|
+
)
|
|
2901
|
+
SELECT
|
|
2902
|
+
-- Keep metadata-only candidates; Sixb filters them with commit_extra_info.
|
|
2903
|
+
candidate_ids.snapshot_id,
|
|
2904
|
+
candidate.snapshot_time,
|
|
2905
|
+
candidate.changes_made,
|
|
2906
|
+
candidate.commit_extra_info,
|
|
2907
|
+
coalesce(file_changes.has_file_change, false) AS has_file_change,
|
|
2908
|
+
coalesce(file_changes.has_file_delete_change, false) AS has_file_delete_change
|
|
2909
|
+
FROM candidate_ids
|
|
2910
|
+
LEFT JOIN candidate_snapshots candidate
|
|
2911
|
+
ON candidate.snapshot_id = candidate_ids.snapshot_id
|
|
2912
|
+
LEFT JOIN file_changes_by_snapshot file_changes
|
|
2913
|
+
ON file_changes.snapshot_id = candidate_ids.snapshot_id
|
|
2914
|
+
ORDER BY candidate_ids.snapshot_id DESC
|
|
2915
|
+
`;
|
|
2916
|
+
}));
|
|
2747
2917
|
return rows.map((row) => {
|
|
2918
|
+
const snapshotId = String(getBigIntLike(row, "snapshot_id"));
|
|
2919
|
+
if (row.snapshot_time === null || row.snapshot_time === undefined) {
|
|
2920
|
+
return { snapshotId, missing: true };
|
|
2921
|
+
}
|
|
2748
2922
|
const metadata = parseCommitMetadata(row.commit_extra_info);
|
|
2749
2923
|
return {
|
|
2750
|
-
snapshotId
|
|
2924
|
+
snapshotId,
|
|
2751
2925
|
createdAt: getDate(row, "snapshot_time"),
|
|
2752
2926
|
changesMade: getString(row, "changes_made"),
|
|
2753
2927
|
hasFileChange: getBoolean(row, "has_file_change"),
|
|
@@ -4237,6 +4411,9 @@ class DuckLakeStorage {
|
|
|
4237
4411
|
async getVersion(datasetId, versionId) {
|
|
4238
4412
|
return this.snapshotReader.getVersion(datasetId, versionId);
|
|
4239
4413
|
}
|
|
4414
|
+
async listLatestVersionsSince(input) {
|
|
4415
|
+
return this.snapshotReader.listLatestVersionsSince(input);
|
|
4416
|
+
}
|
|
4240
4417
|
readRows(input) {
|
|
4241
4418
|
return this.rows.readRows(input);
|
|
4242
4419
|
}
|
|
@@ -4254,4 +4431,4 @@ export {
|
|
|
4254
4431
|
DuckLakeStorage
|
|
4255
4432
|
};
|
|
4256
4433
|
|
|
4257
|
-
//# debugId=
|
|
4434
|
+
//# debugId=99E61447F4C74C9E64756E2164756E21
|