@sixb/ducklake 0.1.6 → 0.1.7
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 +24 -26
- package/dist/index.js +489 -174
- package/dist/index.js.map +12 -10
- package/dist/internal/duckdb-reader.d.ts +28 -0
- package/dist/internal/duckdb-reader.d.ts.map +1 -0
- package/dist/internal/duckdb-runtime.d.ts +5 -3
- package/dist/internal/duckdb-runtime.d.ts.map +1 -1
- package/dist/internal/ducklake-connection-manager.d.ts +19 -3
- package/dist/internal/ducklake-connection-manager.d.ts.map +1 -1
- package/dist/internal/ducklake-read-gate.d.ts +11 -0
- package/dist/internal/ducklake-read-gate.d.ts.map +1 -0
- package/dist/internal/ducklake-row-reader.d.ts.map +1 -1
- package/dist/internal/ducklake-snapshot-reader.d.ts.map +1 -1
- package/dist/internal/ducklake-write-coordinator.d.ts +2 -1
- package/dist/internal/ducklake-write-coordinator.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/internal/duckdb-reader.ts +129 -0
- package/src/internal/duckdb-runtime.ts +75 -51
- package/src/internal/ducklake-connection-manager.ts +117 -33
- package/src/internal/ducklake-maintenance.ts +1 -1
- package/src/internal/ducklake-read-gate.ts +58 -0
- package/src/internal/ducklake-row-reader.ts +84 -63
- package/src/internal/ducklake-snapshot-reader.ts +7 -2
- package/src/internal/ducklake-write-coordinator.ts +56 -15
- package/src/internal/ducklake-write-session.ts +1 -0
- package/src/types.ts +2 -0
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
LakeStorageError,
|
|
19
19
|
retryDatasetMergeCommit,
|
|
20
20
|
} from "@sixb/core/lake-storage"
|
|
21
|
-
import type { DuckLakeStorageOptions } from "../types"
|
|
21
|
+
import type { DuckLakeCatalogOptions, DuckLakeStorageOptions } from "../types"
|
|
22
22
|
import { localCatalogCoordinationKey } from "./catalog-key"
|
|
23
23
|
import {
|
|
24
24
|
type ApplyDatasetRowsResult,
|
|
@@ -330,13 +330,12 @@ export class DuckLakeWriteCoordinator {
|
|
|
330
330
|
// which snapshot belongs to this transaction.
|
|
331
331
|
const previousWriteSnapshotId = await this.lastCommittedSnapshotId(input.runtime)
|
|
332
332
|
|
|
333
|
-
await input.runtime.run("BEGIN TRANSACTION")
|
|
334
333
|
let committed = false
|
|
335
334
|
try {
|
|
336
335
|
// Guarded Sixb commits need compare-and-swap semantics. DuckLake can
|
|
337
336
|
// automatically retry non-conflicting commits, so guarded transactions
|
|
338
|
-
// disable retries and
|
|
339
|
-
const latestVersion = await this.
|
|
337
|
+
// disable retries and validate a head belonging to the native write snapshot.
|
|
338
|
+
const latestVersion = await this.beginVersionTransaction(input)
|
|
340
339
|
if (input.expectedLatestVersionId !== undefined) {
|
|
341
340
|
this.assertExpectedLatestVersion({
|
|
342
341
|
datasetId: input.dataset.id,
|
|
@@ -527,15 +526,16 @@ export class DuckLakeWriteCoordinator {
|
|
|
527
526
|
private async ensureNoOpHasSnapshot(
|
|
528
527
|
input: DuckLakeCommitVersionOutcomeRuntimeInput,
|
|
529
528
|
changeResult: ApplyDatasetRowsResult,
|
|
530
|
-
knownLatestVersion: DuckLakeVersionSummary | null
|
|
529
|
+
knownLatestVersion: DuckLakeVersionSummary | null | undefined
|
|
531
530
|
): Promise<void> {
|
|
532
531
|
if (changeResult.dataChangeExpected) {
|
|
533
532
|
return
|
|
534
533
|
}
|
|
535
534
|
|
|
536
535
|
const latestVersion =
|
|
537
|
-
knownLatestVersion
|
|
538
|
-
|
|
536
|
+
knownLatestVersion === undefined
|
|
537
|
+
? await this.snapshots.getLatestVersionSummaryForDefinition(input.runtime, input.dataset)
|
|
538
|
+
: knownLatestVersion
|
|
539
539
|
if (
|
|
540
540
|
latestVersion
|
|
541
541
|
? input.mode === "merge" || !hasDatasetInputChanges(latestVersion.inputs, input.inputs)
|
|
@@ -582,14 +582,49 @@ export class DuckLakeWriteCoordinator {
|
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
-
private async
|
|
585
|
+
private async beginVersionTransaction(
|
|
586
586
|
input: DuckLakeCommitVersionOutcomeRuntimeInput
|
|
587
|
-
): Promise<DuckLakeVersionSummary | null> {
|
|
588
|
-
if (
|
|
589
|
-
|
|
587
|
+
): Promise<DuckLakeVersionSummary | null | undefined> {
|
|
588
|
+
if (!isSqliteCatalog(this.options.catalog)) {
|
|
589
|
+
await input.runtime.run("BEGIN TRANSACTION")
|
|
590
|
+
if (input.expectedLatestVersionId === undefined && input.mode !== "append") {
|
|
591
|
+
return undefined
|
|
592
|
+
}
|
|
593
|
+
return this.snapshots.getLatestVersionSummaryForDefinition(input.runtime, input.dataset)
|
|
590
594
|
}
|
|
591
595
|
|
|
592
|
-
|
|
596
|
+
// Direct metadata reads enlist a second SQLite transaction on the outer DuckDB connection.
|
|
597
|
+
// Its read lock blocks DuckLake's own metadata connection at COMMIT. Read in autocommit,
|
|
598
|
+
// then pin the native DuckLake snapshot and verify nothing changed in between. The summary
|
|
599
|
+
// is now valid for this write transaction, including null (no previous version).
|
|
600
|
+
// Only preparation can repeat: staged mutations and COMMIT are never replayed here.
|
|
601
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
602
|
+
input.signal?.throwIfAborted()
|
|
603
|
+
const snapshotId = await this.currentSnapshotId(input.runtime)
|
|
604
|
+
const latestVersion = await this.snapshots.getLatestVersionSummaryForDefinition(
|
|
605
|
+
input.runtime,
|
|
606
|
+
input.dataset
|
|
607
|
+
)
|
|
608
|
+
await input.runtime.run("BEGIN TRANSACTION")
|
|
609
|
+
if ((await this.currentSnapshotId(input.runtime)) === snapshotId) {
|
|
610
|
+
return latestVersion
|
|
611
|
+
}
|
|
612
|
+
await input.runtime.run("ROLLBACK")
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
throw new LakeConcurrencyError(
|
|
616
|
+
`[SixbDuckLake] Catalog changed during three write preparations for dataset '${input.dataset.id}'. Retry the write.`
|
|
617
|
+
)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private async currentSnapshotId(runtime: DuckDbQueryRuntime): Promise<string> {
|
|
621
|
+
const [row] = await runtime.query(
|
|
622
|
+
`SELECT id FROM ${quoteIdentifier(duckLakeAlias(this.options))}.current_snapshot()`
|
|
623
|
+
)
|
|
624
|
+
if (row === undefined) {
|
|
625
|
+
throw new LakeStorageError("[SixbDuckLake] DuckLake did not return a current snapshot.")
|
|
626
|
+
}
|
|
627
|
+
return String(getBigIntLike(row, "id"))
|
|
593
628
|
}
|
|
594
629
|
|
|
595
630
|
private async lastCommittedSnapshotId(runtime: DuckDbQueryRuntime): Promise<string | null> {
|
|
@@ -681,15 +716,21 @@ export class DuckLakeWriteCoordinator {
|
|
|
681
716
|
}
|
|
682
717
|
}
|
|
683
718
|
|
|
719
|
+
function isSqliteCatalog(catalog: DuckLakeCatalogOptions): boolean {
|
|
720
|
+
return (
|
|
721
|
+
catalog.type === "sqlite" ||
|
|
722
|
+
(catalog.type === "custom" && catalog.uri.toLowerCase().startsWith("sqlite:"))
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
|
|
684
726
|
function commitRowCount(
|
|
685
727
|
input: Pick<DuckLakeCommitVersionOutcomeInput, "expectedLatestVersionId" | "mode">,
|
|
686
728
|
changeResult: ApplyDatasetRowsResult,
|
|
687
|
-
latestVersion: DuckLakeVersionSummary | null
|
|
729
|
+
latestVersion: DuckLakeVersionSummary | null | undefined
|
|
688
730
|
): CommitRowCount {
|
|
689
731
|
// Unguarded append commits can be retried by DuckLake after this transaction
|
|
690
732
|
// read the previous latest version. In that case previousRowCount +
|
|
691
|
-
// sourceRowCount can underreport the committed snapshot, so
|
|
692
|
-
// and let hydration count the exact committed snapshot instead.
|
|
733
|
+
// sourceRowCount can underreport the committed snapshot, so leave the count unknown.
|
|
693
734
|
if (input.mode === "append" && input.expectedLatestVersionId === undefined) {
|
|
694
735
|
return { kind: "unknown" }
|
|
695
736
|
}
|
package/src/types.ts
CHANGED
|
@@ -151,6 +151,8 @@ export interface DuckLakeStorageOptions {
|
|
|
151
151
|
readonly alias?: string
|
|
152
152
|
readonly duckdb?: DuckDbRuntimeOptions
|
|
153
153
|
readonly postgresPool?: DuckLakePostgresPoolOptions
|
|
154
|
+
/** Maximum native streaming readers per provider; extra reads use pages. Default: 4. */
|
|
155
|
+
readonly maxStreamingReads?: number
|
|
154
156
|
readonly secrets?: readonly DuckDbSecretOptions[]
|
|
155
157
|
/** Runs after DuckLake/catalog extensions load and before ATTACH. */
|
|
156
158
|
readonly setupSql?: readonly string[]
|