@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
package/dist/index.js
CHANGED
|
@@ -771,7 +771,7 @@ function rowImage(row, prefix, columns) {
|
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
// src/internal/ducklake-connection-manager.ts
|
|
774
|
-
import { LakeStorageError as
|
|
774
|
+
import { LakeStorageError as LakeStorageError11 } from "@sixb/core/lake-storage";
|
|
775
775
|
|
|
776
776
|
// src/internal/catalog-key.ts
|
|
777
777
|
import { resolve } from "path";
|
|
@@ -791,9 +791,14 @@ function localCatalogCoordinationKey(options) {
|
|
|
791
791
|
}
|
|
792
792
|
|
|
793
793
|
// src/internal/duckdb-runtime.ts
|
|
794
|
+
import { mkdir, mkdtemp, rm } from "fs/promises";
|
|
795
|
+
import { join, resolve as resolve2 } from "path";
|
|
794
796
|
import {
|
|
795
797
|
DuckDBInstance
|
|
796
798
|
} from "@duckdb/node-api";
|
|
799
|
+
import { LakeStorageError as LakeStorageError10 } from "@sixb/core/lake-storage";
|
|
800
|
+
|
|
801
|
+
// src/internal/duckdb-reader.ts
|
|
797
802
|
import { LakeStorageError as LakeStorageError9 } from "@sixb/core/lake-storage";
|
|
798
803
|
|
|
799
804
|
// src/internal/duckdb-value-converter.ts
|
|
@@ -816,16 +821,147 @@ var sixbDuckDbValueConverter = (value, type, converter) => {
|
|
|
816
821
|
return JSDuckDBValueConverter(value, type, converter);
|
|
817
822
|
};
|
|
818
823
|
|
|
824
|
+
// src/internal/duckdb-reader.ts
|
|
825
|
+
class NodeDuckDbReader {
|
|
826
|
+
connection;
|
|
827
|
+
options;
|
|
828
|
+
resultPromise;
|
|
829
|
+
pending;
|
|
830
|
+
closePromise;
|
|
831
|
+
finished = false;
|
|
832
|
+
consumed = false;
|
|
833
|
+
onAbort = () => {
|
|
834
|
+
this.close().catch(() => {});
|
|
835
|
+
};
|
|
836
|
+
constructor(connection, options) {
|
|
837
|
+
this.connection = connection;
|
|
838
|
+
this.options = options;
|
|
839
|
+
}
|
|
840
|
+
async start(sql) {
|
|
841
|
+
this.options.signal?.throwIfAborted();
|
|
842
|
+
this.options.signal?.addEventListener("abort", this.onAbort, { once: true });
|
|
843
|
+
await this.track(this.connection.run("SET preserve_insertion_order = true"));
|
|
844
|
+
this.assertOpen();
|
|
845
|
+
const prepared = await this.track(this.connection.prepare(sql));
|
|
846
|
+
try {
|
|
847
|
+
this.assertOpen();
|
|
848
|
+
this.resultPromise = this.track(prepared.startStream().getResult()).finally(() => {
|
|
849
|
+
prepared.destroySync();
|
|
850
|
+
});
|
|
851
|
+
this.resultPromise.catch(() => {});
|
|
852
|
+
} catch (error) {
|
|
853
|
+
prepared.destroySync();
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async* rows() {
|
|
858
|
+
if (this.consumed)
|
|
859
|
+
throw new LakeStorageError9("[SixbDuckLake] Reader can only be consumed once.");
|
|
860
|
+
this.consumed = true;
|
|
861
|
+
try {
|
|
862
|
+
this.assertOpen();
|
|
863
|
+
const result = await this.resultPromise;
|
|
864
|
+
this.assertOpen();
|
|
865
|
+
const batches = result.yieldConvertedRowObjects(sixbDuckDbValueConverter)[Symbol.asyncIterator]();
|
|
866
|
+
while (true) {
|
|
867
|
+
this.assertOpen();
|
|
868
|
+
const batch = await this.track(batches.next());
|
|
869
|
+
this.assertOpen();
|
|
870
|
+
if (batch.done) {
|
|
871
|
+
this.finished = true;
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
for (const row of batch.value) {
|
|
875
|
+
this.assertOpen();
|
|
876
|
+
yield row;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} finally {
|
|
880
|
+
await this.close();
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
close() {
|
|
884
|
+
if (!this.closePromise) {
|
|
885
|
+
this.options.signal?.removeEventListener("abort", this.onAbort);
|
|
886
|
+
if (this.pending)
|
|
887
|
+
this.connection.interrupt();
|
|
888
|
+
this.closePromise = this.finish();
|
|
889
|
+
}
|
|
890
|
+
return this.closePromise;
|
|
891
|
+
}
|
|
892
|
+
async finish() {
|
|
893
|
+
try {
|
|
894
|
+
await this.pending?.catch(() => {});
|
|
895
|
+
if (!this.finished) {
|
|
896
|
+
await this.connection.run("SELECT 1");
|
|
897
|
+
}
|
|
898
|
+
} finally {
|
|
899
|
+
this.resultPromise = undefined;
|
|
900
|
+
try {
|
|
901
|
+
this.connection.closeSync();
|
|
902
|
+
} finally {
|
|
903
|
+
await this.options.onClose?.();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async track(operation) {
|
|
908
|
+
this.pending = operation;
|
|
909
|
+
try {
|
|
910
|
+
return await operation;
|
|
911
|
+
} catch (error) {
|
|
912
|
+
this.assertOpen();
|
|
913
|
+
throw error;
|
|
914
|
+
} finally {
|
|
915
|
+
if (this.pending === operation)
|
|
916
|
+
this.pending = undefined;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
assertOpen() {
|
|
920
|
+
this.options.signal?.throwIfAborted();
|
|
921
|
+
if (this.closePromise)
|
|
922
|
+
throw new LakeStorageError9("[SixbDuckLake] Reader is closed.");
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
819
926
|
// src/internal/duckdb-runtime.ts
|
|
820
927
|
class NodeDuckDbRuntime {
|
|
821
928
|
instance;
|
|
822
929
|
connection;
|
|
930
|
+
temporaryDirectory;
|
|
823
931
|
closing = false;
|
|
824
932
|
closed = false;
|
|
825
933
|
operations = Promise.resolve();
|
|
826
|
-
|
|
934
|
+
readers = new Set;
|
|
935
|
+
closePromise;
|
|
936
|
+
constructor(instance, connection, temporaryDirectory) {
|
|
827
937
|
this.instance = instance;
|
|
828
938
|
this.connection = connection;
|
|
939
|
+
this.temporaryDirectory = temporaryDirectory;
|
|
940
|
+
}
|
|
941
|
+
async openReader(sql, options = {}) {
|
|
942
|
+
return this.enqueue(async () => {
|
|
943
|
+
this.assertAcceptingOperations();
|
|
944
|
+
const connection = await this.instance.connect();
|
|
945
|
+
if (this.closing) {
|
|
946
|
+
connection.closeSync();
|
|
947
|
+
this.assertAcceptingOperations();
|
|
948
|
+
}
|
|
949
|
+
const reader = new NodeDuckDbReader(connection, {
|
|
950
|
+
...options,
|
|
951
|
+
onClose: async () => {
|
|
952
|
+
this.readers.delete(reader);
|
|
953
|
+
await options.onClose?.();
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
this.readers.add(reader);
|
|
957
|
+
try {
|
|
958
|
+
await reader.start(sql);
|
|
959
|
+
return reader;
|
|
960
|
+
} catch (error) {
|
|
961
|
+
await reader.close();
|
|
962
|
+
throw error;
|
|
963
|
+
}
|
|
964
|
+
});
|
|
829
965
|
}
|
|
830
966
|
async run(sql, values) {
|
|
831
967
|
await this.enqueue(async () => {
|
|
@@ -845,28 +981,6 @@ class NodeDuckDbRuntime {
|
|
|
845
981
|
return reader.convertRowObjects(sixbDuckDbValueConverter);
|
|
846
982
|
});
|
|
847
983
|
}
|
|
848
|
-
async* streamRows(sql, values) {
|
|
849
|
-
this.assertAcceptingOperations();
|
|
850
|
-
let releaseOperation;
|
|
851
|
-
const operationFinished = new Promise((resolve) => {
|
|
852
|
-
releaseOperation = resolve;
|
|
853
|
-
});
|
|
854
|
-
const waitForTurn = this.operations.then(() => {
|
|
855
|
-
this.assertNotClosed();
|
|
856
|
-
});
|
|
857
|
-
this.operations = waitForTurn.then(() => operationFinished).then(() => {}, () => {});
|
|
858
|
-
try {
|
|
859
|
-
await waitForTurn;
|
|
860
|
-
const result = await this.connection.stream(sql, values === undefined ? undefined : [...values]);
|
|
861
|
-
for await (const batch of result.yieldConvertedRowObjects(sixbDuckDbValueConverter)) {
|
|
862
|
-
for (const row of batch) {
|
|
863
|
-
yield row;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
} finally {
|
|
867
|
-
releaseOperation?.();
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
984
|
async withAppender(tableName, useAppender) {
|
|
871
985
|
return this.enqueue(async () => {
|
|
872
986
|
const appender = await this.connection.createAppender(tableName);
|
|
@@ -882,15 +996,28 @@ class NodeDuckDbRuntime {
|
|
|
882
996
|
return useRuntime(new NodeDuckDbExclusiveRuntime(this.connection));
|
|
883
997
|
});
|
|
884
998
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
999
|
+
close() {
|
|
1000
|
+
this.closePromise ??= this.finish();
|
|
1001
|
+
return this.closePromise;
|
|
1002
|
+
}
|
|
1003
|
+
async stopReaders() {
|
|
1004
|
+
const results = await Promise.allSettled([...this.readers].map((reader) => reader.close()));
|
|
1005
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
1006
|
+
if (failed?.status === "rejected")
|
|
1007
|
+
throw failed.reason;
|
|
1008
|
+
}
|
|
1009
|
+
async finish() {
|
|
889
1010
|
this.closing = true;
|
|
1011
|
+
const results = await Promise.allSettled([...this.readers].map((reader) => reader.close()));
|
|
890
1012
|
await this.operations.catch(() => {});
|
|
891
1013
|
this.closed = true;
|
|
892
1014
|
this.connection.closeSync();
|
|
893
1015
|
this.instance.closeSync();
|
|
1016
|
+
if (this.temporaryDirectory)
|
|
1017
|
+
await rm(this.temporaryDirectory, { recursive: true, force: true });
|
|
1018
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
1019
|
+
if (failed?.status === "rejected")
|
|
1020
|
+
throw failed.reason;
|
|
894
1021
|
}
|
|
895
1022
|
enqueue(operation) {
|
|
896
1023
|
this.assertAcceptingOperations();
|
|
@@ -903,12 +1030,12 @@ class NodeDuckDbRuntime {
|
|
|
903
1030
|
}
|
|
904
1031
|
assertAcceptingOperations() {
|
|
905
1032
|
if (this.closing || this.closed) {
|
|
906
|
-
throw new
|
|
1033
|
+
throw new LakeStorageError10("[SixbDuckLake] DuckDB runtime is closed.");
|
|
907
1034
|
}
|
|
908
1035
|
}
|
|
909
1036
|
assertNotClosed() {
|
|
910
1037
|
if (this.closed) {
|
|
911
|
-
throw new
|
|
1038
|
+
throw new LakeStorageError10("[SixbDuckLake] DuckDB runtime is closed.");
|
|
912
1039
|
}
|
|
913
1040
|
}
|
|
914
1041
|
}
|
|
@@ -968,8 +1095,27 @@ class NodeDuckDbAppenderAdapter {
|
|
|
968
1095
|
}
|
|
969
1096
|
}
|
|
970
1097
|
async function createDuckDbRuntime(options = {}) {
|
|
971
|
-
const
|
|
972
|
-
|
|
1098
|
+
const path = options.path ?? ":memory:";
|
|
1099
|
+
const base = options.config?.temp_directory ?? (path === ":memory:" ? ".tmp" : `${path}.tmp`);
|
|
1100
|
+
let temporaryDirectory;
|
|
1101
|
+
if (base !== "") {
|
|
1102
|
+
const directory = resolve2(base);
|
|
1103
|
+
await mkdir(directory, { recursive: true });
|
|
1104
|
+
temporaryDirectory = await mkdtemp(join(directory, "sixb-"));
|
|
1105
|
+
}
|
|
1106
|
+
let instance;
|
|
1107
|
+
try {
|
|
1108
|
+
instance = await DuckDBInstance.create(path, {
|
|
1109
|
+
...options.config,
|
|
1110
|
+
...temporaryDirectory ? { temp_directory: temporaryDirectory } : {}
|
|
1111
|
+
});
|
|
1112
|
+
return new NodeDuckDbRuntime(instance, await instance.connect(), temporaryDirectory);
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
instance?.closeSync();
|
|
1115
|
+
if (temporaryDirectory)
|
|
1116
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
1117
|
+
throw error;
|
|
1118
|
+
}
|
|
973
1119
|
}
|
|
974
1120
|
async function installDuckLakeExtensions(runtime, options) {
|
|
975
1121
|
await runtime.run("INSTALL ducklake");
|
|
@@ -1002,6 +1148,62 @@ async function setupDuckLake(runtime, options, setupOptions = {}) {
|
|
|
1002
1148
|
}
|
|
1003
1149
|
}
|
|
1004
1150
|
|
|
1151
|
+
// src/internal/ducklake-read-gate.ts
|
|
1152
|
+
class DuckLakeReadGate {
|
|
1153
|
+
active = 0;
|
|
1154
|
+
maintenance = false;
|
|
1155
|
+
waiters = new Set;
|
|
1156
|
+
async acquire(signal) {
|
|
1157
|
+
while (true) {
|
|
1158
|
+
signal.throwIfAborted();
|
|
1159
|
+
if (!this.maintenance) {
|
|
1160
|
+
this.active++;
|
|
1161
|
+
let released = false;
|
|
1162
|
+
return () => {
|
|
1163
|
+
if (released)
|
|
1164
|
+
return;
|
|
1165
|
+
released = true;
|
|
1166
|
+
this.active--;
|
|
1167
|
+
this.wake();
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
await this.wait(signal);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async withMaintenance(signal, run) {
|
|
1174
|
+
while (this.active > 0 || this.maintenance)
|
|
1175
|
+
await this.wait(signal);
|
|
1176
|
+
signal.throwIfAborted();
|
|
1177
|
+
this.maintenance = true;
|
|
1178
|
+
try {
|
|
1179
|
+
return await run();
|
|
1180
|
+
} finally {
|
|
1181
|
+
this.maintenance = false;
|
|
1182
|
+
this.wake();
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
wait(signal) {
|
|
1186
|
+
return new Promise((resolve, reject) => {
|
|
1187
|
+
const wake = () => {
|
|
1188
|
+
signal.removeEventListener("abort", abort);
|
|
1189
|
+
this.waiters.delete(wake);
|
|
1190
|
+
resolve();
|
|
1191
|
+
};
|
|
1192
|
+
const abort = () => {
|
|
1193
|
+
this.waiters.delete(wake);
|
|
1194
|
+
reject(signal.reason);
|
|
1195
|
+
};
|
|
1196
|
+
signal.throwIfAborted();
|
|
1197
|
+
this.waiters.add(wake);
|
|
1198
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
wake() {
|
|
1202
|
+
for (const wake of this.waiters)
|
|
1203
|
+
wake();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1005
1207
|
// src/internal/ducklake-connection-manager.ts
|
|
1006
1208
|
class DuckLakeConnectionManager {
|
|
1007
1209
|
options;
|
|
@@ -1009,18 +1211,77 @@ class DuckLakeConnectionManager {
|
|
|
1009
1211
|
installExtensionsPromise;
|
|
1010
1212
|
attachPromise;
|
|
1011
1213
|
refreshPromise;
|
|
1012
|
-
|
|
1214
|
+
attachmentLock = Promise.resolve();
|
|
1013
1215
|
observedLocalCatalogGeneration;
|
|
1014
1216
|
attached = false;
|
|
1015
1217
|
closed = false;
|
|
1218
|
+
closePromise;
|
|
1016
1219
|
runtimePoisoned = false;
|
|
1220
|
+
pinnedReaders = 0;
|
|
1221
|
+
refreshRequested = false;
|
|
1222
|
+
shutdown = new AbortController;
|
|
1223
|
+
reads = new DuckLakeReadGate;
|
|
1224
|
+
maxStreamingReads;
|
|
1017
1225
|
constructor(options) {
|
|
1018
1226
|
this.options = options;
|
|
1019
1227
|
this.observedLocalCatalogGeneration = currentLocalCatalogGeneration(options);
|
|
1228
|
+
this.maxStreamingReads = options.maxStreamingReads ?? 4;
|
|
1229
|
+
if (!Number.isSafeInteger(this.maxStreamingReads) || this.maxStreamingReads < 1) {
|
|
1230
|
+
throw new LakeStorageError11("[SixbDuckLake] maxStreamingReads must be a positive integer.");
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
async acquireRead(signal) {
|
|
1234
|
+
this.assertOpen();
|
|
1235
|
+
const combined = signal ? AbortSignal.any([signal, this.shutdown.signal]) : this.shutdown.signal;
|
|
1236
|
+
return { signal: combined, release: await this.reads.acquire(combined) };
|
|
1237
|
+
}
|
|
1238
|
+
async nativeReaderLimit(runtime) {
|
|
1239
|
+
if (this.options.catalog.type === "duckdb")
|
|
1240
|
+
return this.maxStreamingReads;
|
|
1241
|
+
if (this.options.catalog.type !== "postgres")
|
|
1242
|
+
return 0;
|
|
1243
|
+
const [setting] = await runtime.query("SELECT current_setting('pg_pool_max_connections') AS value");
|
|
1244
|
+
const budget = Number(setting?.value);
|
|
1245
|
+
return Number.isFinite(budget) ? Math.min(this.maxStreamingReads, Math.max(0, budget - 4)) : 0;
|
|
1246
|
+
}
|
|
1247
|
+
async openReader(runtime, sql, signal, releaseRead) {
|
|
1248
|
+
const limit = await this.nativeReaderLimit(runtime);
|
|
1249
|
+
signal.throwIfAborted();
|
|
1250
|
+
if (this.pinnedReaders >= limit)
|
|
1251
|
+
return null;
|
|
1252
|
+
this.pinnedReaders++;
|
|
1253
|
+
let starting = true;
|
|
1254
|
+
let released = false;
|
|
1255
|
+
const onClose = async () => {
|
|
1256
|
+
if (released)
|
|
1257
|
+
return;
|
|
1258
|
+
released = true;
|
|
1259
|
+
this.pinnedReaders--;
|
|
1260
|
+
if (signal.aborted || this.closed || this.runtimePoisoned)
|
|
1261
|
+
releaseRead();
|
|
1262
|
+
if (starting || this.closed || this.runtimePoisoned)
|
|
1263
|
+
return;
|
|
1264
|
+
await this.withAttachmentLock(async () => {
|
|
1265
|
+
if (this.pinnedReaders === 0 && this.refreshRequested)
|
|
1266
|
+
await this.resetRuntimeUnlocked();
|
|
1267
|
+
await this.detachDuckDbCatalogAfterLease();
|
|
1268
|
+
});
|
|
1269
|
+
};
|
|
1270
|
+
try {
|
|
1271
|
+
return await runtime.openReader(sql, { signal, onClose });
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
await onClose();
|
|
1274
|
+
throw error;
|
|
1275
|
+
} finally {
|
|
1276
|
+
starting = false;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async withMaintenanceRuntime(run) {
|
|
1280
|
+
return this.reads.withMaintenance(this.shutdown.signal, () => this.withExclusiveAttached(run));
|
|
1020
1281
|
}
|
|
1021
1282
|
assertOpen() {
|
|
1022
1283
|
if (this.closed) {
|
|
1023
|
-
throw new
|
|
1284
|
+
throw new LakeStorageError11("[SixbDuckLake] DuckLakeStorage is closed.");
|
|
1024
1285
|
}
|
|
1025
1286
|
}
|
|
1026
1287
|
async runtime() {
|
|
@@ -1051,7 +1312,7 @@ class DuckLakeConnectionManager {
|
|
|
1051
1312
|
}
|
|
1052
1313
|
async acquireAttachedRuntime() {
|
|
1053
1314
|
this.assertOpen();
|
|
1054
|
-
const releaseLock = await this.
|
|
1315
|
+
const releaseLock = await this.acquireAttachmentLock();
|
|
1055
1316
|
let released = false;
|
|
1056
1317
|
const release = async () => {
|
|
1057
1318
|
if (released) {
|
|
@@ -1060,8 +1321,8 @@ class DuckLakeConnectionManager {
|
|
|
1060
1321
|
released = true;
|
|
1061
1322
|
try {
|
|
1062
1323
|
if (this.runtimePoisoned) {
|
|
1063
|
-
this.runtimePoisoned = false;
|
|
1064
1324
|
await this.discardRuntimeUnlocked();
|
|
1325
|
+
this.runtimePoisoned = false;
|
|
1065
1326
|
} else {
|
|
1066
1327
|
await this.detachDuckDbCatalogAfterLease();
|
|
1067
1328
|
}
|
|
@@ -1110,7 +1371,7 @@ class DuckLakeConnectionManager {
|
|
|
1110
1371
|
}
|
|
1111
1372
|
}
|
|
1112
1373
|
async resetRuntime() {
|
|
1113
|
-
const releaseLock = await this.
|
|
1374
|
+
const releaseLock = await this.acquireAttachmentLock();
|
|
1114
1375
|
try {
|
|
1115
1376
|
await this.resetRuntimeUnlocked();
|
|
1116
1377
|
} finally {
|
|
@@ -1118,6 +1379,11 @@ class DuckLakeConnectionManager {
|
|
|
1118
1379
|
}
|
|
1119
1380
|
}
|
|
1120
1381
|
async resetRuntimeUnlocked() {
|
|
1382
|
+
if (this.pinnedReaders > 0) {
|
|
1383
|
+
this.refreshRequested = true;
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
this.refreshRequested = false;
|
|
1121
1387
|
const runtimePromise = this.runtimePromise;
|
|
1122
1388
|
if (runtimePromise === undefined) {
|
|
1123
1389
|
return;
|
|
@@ -1158,7 +1424,7 @@ class DuckLakeConnectionManager {
|
|
|
1158
1424
|
}
|
|
1159
1425
|
if (this.refreshPromise === undefined) {
|
|
1160
1426
|
let refreshPromise;
|
|
1161
|
-
refreshPromise = this.
|
|
1427
|
+
refreshPromise = this.withAttachmentLock(() => this.refreshForExternalChangesUnlocked()).finally(() => {
|
|
1162
1428
|
if (this.refreshPromise === refreshPromise) {
|
|
1163
1429
|
this.refreshPromise = undefined;
|
|
1164
1430
|
}
|
|
@@ -1178,8 +1444,10 @@ class DuckLakeConnectionManager {
|
|
|
1178
1444
|
return;
|
|
1179
1445
|
}
|
|
1180
1446
|
try {
|
|
1181
|
-
|
|
1182
|
-
|
|
1447
|
+
if (this.pinnedReaders === 0) {
|
|
1448
|
+
await runtime.run(`DETACH ${quoteIdentifier(duckLakeAlias(this.options))}`);
|
|
1449
|
+
this.attached = false;
|
|
1450
|
+
}
|
|
1183
1451
|
} catch {} finally {
|
|
1184
1452
|
this.markLocalCatalogChanged();
|
|
1185
1453
|
}
|
|
@@ -1195,11 +1463,13 @@ class DuckLakeConnectionManager {
|
|
|
1195
1463
|
needsLocalCatalogRefresh() {
|
|
1196
1464
|
return this.observedLocalCatalogGeneration !== currentLocalCatalogGeneration(this.options);
|
|
1197
1465
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1466
|
+
close() {
|
|
1467
|
+
this.closePromise ??= this.finishClose();
|
|
1468
|
+
return this.closePromise;
|
|
1469
|
+
}
|
|
1470
|
+
async finishClose() {
|
|
1202
1471
|
this.closed = true;
|
|
1472
|
+
this.shutdown.abort(new LakeStorageError11("[SixbDuckLake] DuckLakeStorage is closed."));
|
|
1203
1473
|
const runtimePromise = this.runtimePromise;
|
|
1204
1474
|
this.runtimePromise = undefined;
|
|
1205
1475
|
await this.closeRuntimePromise(runtimePromise, {
|
|
@@ -1258,13 +1528,13 @@ class DuckLakeConnectionManager {
|
|
|
1258
1528
|
]);
|
|
1259
1529
|
}
|
|
1260
1530
|
async detachRuntime() {
|
|
1261
|
-
await this.
|
|
1531
|
+
await this.withAttachmentLock(() => this.detachRuntimeUnlocked());
|
|
1262
1532
|
}
|
|
1263
1533
|
poisonRuntime() {
|
|
1264
1534
|
this.runtimePoisoned = true;
|
|
1265
1535
|
}
|
|
1266
1536
|
async detachDuckDbCatalogAfterLease() {
|
|
1267
|
-
if (this.options.catalog.type !== "duckdb" || !this.attached) {
|
|
1537
|
+
if (this.options.catalog.type !== "duckdb" || !this.attached || this.pinnedReaders > 0) {
|
|
1268
1538
|
return;
|
|
1269
1539
|
}
|
|
1270
1540
|
try {
|
|
@@ -1280,6 +1550,8 @@ class DuckLakeConnectionManager {
|
|
|
1280
1550
|
await this.closeRuntimePromise(runtimePromise, { detach: false }).catch(() => {});
|
|
1281
1551
|
}
|
|
1282
1552
|
async detachRuntimeUnlocked() {
|
|
1553
|
+
if (this.pinnedReaders > 0)
|
|
1554
|
+
return;
|
|
1283
1555
|
if (this.attachPromise !== undefined) {
|
|
1284
1556
|
await this.attachPromise;
|
|
1285
1557
|
}
|
|
@@ -1290,24 +1562,21 @@ class DuckLakeConnectionManager {
|
|
|
1290
1562
|
await runtime.run(`DETACH ${quoteIdentifier(duckLakeAlias(this.options))}`);
|
|
1291
1563
|
this.attached = false;
|
|
1292
1564
|
}
|
|
1293
|
-
async
|
|
1294
|
-
const releaseLock = await this.
|
|
1565
|
+
async withAttachmentLock(run) {
|
|
1566
|
+
const releaseLock = await this.acquireAttachmentLock();
|
|
1295
1567
|
try {
|
|
1296
1568
|
return await run();
|
|
1297
1569
|
} finally {
|
|
1298
1570
|
releaseLock();
|
|
1299
1571
|
}
|
|
1300
1572
|
}
|
|
1301
|
-
async
|
|
1302
|
-
|
|
1303
|
-
return () => {};
|
|
1304
|
-
}
|
|
1305
|
-
const previous = this.localAttachmentLock.catch(() => {});
|
|
1573
|
+
async acquireAttachmentLock() {
|
|
1574
|
+
const previous = this.attachmentLock.catch(() => {});
|
|
1306
1575
|
let release;
|
|
1307
1576
|
const current = new Promise((resolve) => {
|
|
1308
1577
|
release = resolve;
|
|
1309
1578
|
});
|
|
1310
|
-
this.
|
|
1579
|
+
this.attachmentLock = previous.then(() => current);
|
|
1311
1580
|
await previous;
|
|
1312
1581
|
let released = false;
|
|
1313
1582
|
return () => {
|
|
@@ -1331,12 +1600,16 @@ class DuckLakeConnectionManager {
|
|
|
1331
1600
|
await this.closeRuntime(runtime, options);
|
|
1332
1601
|
}
|
|
1333
1602
|
async closeRuntime(runtime, options) {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1603
|
+
try {
|
|
1604
|
+
await runtime.stopReaders();
|
|
1605
|
+
if (options.detach) {
|
|
1606
|
+
try {
|
|
1607
|
+
await runtime.run(`DETACH ${quoteIdentifier(duckLakeAlias(this.options))}`);
|
|
1608
|
+
} catch {}
|
|
1609
|
+
}
|
|
1610
|
+
} finally {
|
|
1611
|
+
await runtime.close();
|
|
1338
1612
|
}
|
|
1339
|
-
await runtime.close();
|
|
1340
1613
|
}
|
|
1341
1614
|
}
|
|
1342
1615
|
var localCatalogGenerations = new Map;
|
|
@@ -1358,12 +1631,12 @@ function incrementLocalCatalogGeneration(options) {
|
|
|
1358
1631
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1359
1632
|
import {
|
|
1360
1633
|
assertDatasetDefinition,
|
|
1361
|
-
LakeStorageError as
|
|
1634
|
+
LakeStorageError as LakeStorageError14,
|
|
1362
1635
|
planDatasetDefinitionUpdate
|
|
1363
1636
|
} from "@sixb/core/lake-storage";
|
|
1364
1637
|
|
|
1365
1638
|
// src/internal/ducklake-catalog-schema.ts
|
|
1366
|
-
import { LakeStorageError as
|
|
1639
|
+
import { LakeStorageError as LakeStorageError12 } from "@sixb/core/lake-storage";
|
|
1367
1640
|
var SIXB_PRIMARY_KEY_COMMENT_NAMESPACE = "sixb:primary-key:";
|
|
1368
1641
|
var DATASET_SEQUENCE_COLUMN_COMMENT = "sixb.sequenceBy.v1";
|
|
1369
1642
|
var SIXB_PRIMARY_KEY_COMMENT_PREFIX = `${SIXB_PRIMARY_KEY_COMMENT_NAMESPACE}v1:`;
|
|
@@ -1414,14 +1687,14 @@ function duckLakeCatalogColumnsToDatasetPrimaryKey(tableName, rows) {
|
|
|
1414
1687
|
throwInvalidPrimaryKeyMetadata(tableName, column, comment);
|
|
1415
1688
|
}
|
|
1416
1689
|
if (seenOrdinals.has(ordinal)) {
|
|
1417
|
-
throw new
|
|
1690
|
+
throw new LakeStorageError12(`[SixbDuckLake] Dataset table '${tableName}' has duplicate primary-key ordinal '${ordinal}'.`);
|
|
1418
1691
|
}
|
|
1419
1692
|
seenOrdinals.add(ordinal);
|
|
1420
1693
|
if (duckDbTypeToDatasetColumnType(column.columnType) !== "string") {
|
|
1421
|
-
throw new
|
|
1694
|
+
throw new LakeStorageError12(`[SixbDuckLake] Dataset table '${tableName}' primary-key column '${column.columnName}' must map to type 'string'.`);
|
|
1422
1695
|
}
|
|
1423
1696
|
if (column.nullsAllowed) {
|
|
1424
|
-
throw new
|
|
1697
|
+
throw new LakeStorageError12(`[SixbDuckLake] Dataset table '${tableName}' primary-key column '${column.columnName}' must not be nullable.`);
|
|
1425
1698
|
}
|
|
1426
1699
|
keyedColumns.push({ column, ordinal });
|
|
1427
1700
|
}
|
|
@@ -1431,7 +1704,7 @@ function duckLakeCatalogColumnsToDatasetPrimaryKey(tableName, rows) {
|
|
|
1431
1704
|
keyedColumns.sort((left, right) => left.ordinal - right.ordinal);
|
|
1432
1705
|
for (const [expectedOrdinal, keyedColumn] of keyedColumns.entries()) {
|
|
1433
1706
|
if (keyedColumn.ordinal !== expectedOrdinal) {
|
|
1434
|
-
throw new
|
|
1707
|
+
throw new LakeStorageError12(`[SixbDuckLake] Dataset table '${tableName}' primary-key ordinals must be contiguous from 0.`);
|
|
1435
1708
|
}
|
|
1436
1709
|
}
|
|
1437
1710
|
const columnNames = keyedColumns.map(({ column }) => column.columnName);
|
|
@@ -1441,7 +1714,7 @@ function duckLakeCatalogColumnsToDatasetPrimaryKey(tableName, rows) {
|
|
|
1441
1714
|
return columnNames;
|
|
1442
1715
|
}
|
|
1443
1716
|
function throwInvalidPrimaryKeyMetadata(tableName, column, comment) {
|
|
1444
|
-
throw new
|
|
1717
|
+
throw new LakeStorageError12(`[SixbDuckLake] Dataset table '${tableName}' has malformed primary-key metadata '${comment}' on column '${column.columnName}'.`);
|
|
1445
1718
|
}
|
|
1446
1719
|
function duckLakeCatalogColumnToDefinition(tableName, column, childrenByParentId) {
|
|
1447
1720
|
const children = [...childrenByParentId.get(column.columnId) ?? []].sort(compareDuckLakeCatalogColumnOrder);
|
|
@@ -1458,7 +1731,7 @@ function duckLakeCatalogColumnTypeToDatasetType(tableName, column, children) {
|
|
|
1458
1731
|
if (isFileRefStruct(children)) {
|
|
1459
1732
|
return "fileRef";
|
|
1460
1733
|
}
|
|
1461
|
-
throw new
|
|
1734
|
+
throw new LakeStorageError12(`[SixbDuckLake] DuckDB column type '${formatDuckLakeCatalogColumnType(column, children)}' in dataset table '${tableName}' cannot be mapped to a Sixb dataset column type.`);
|
|
1462
1735
|
}
|
|
1463
1736
|
function compareDuckLakeCatalogColumnOrder(left, right) {
|
|
1464
1737
|
return Number(left.columnOrder - right.columnOrder);
|
|
@@ -1480,7 +1753,7 @@ function formatDuckLakeCatalogColumnType(column, children) {
|
|
|
1480
1753
|
}
|
|
1481
1754
|
|
|
1482
1755
|
// src/internal/ducklake-current-dataset-definitions.ts
|
|
1483
|
-
import { LakeStorageError as
|
|
1756
|
+
import { LakeStorageError as LakeStorageError13 } from "@sixb/core/lake-storage";
|
|
1484
1757
|
async function readCurrentDatasetDefinitions(input) {
|
|
1485
1758
|
if (input.datasetIds?.length === 0) {
|
|
1486
1759
|
return new Map;
|
|
@@ -1607,7 +1880,7 @@ async function readCurrentDatasetPartitions(input, tableNames) {
|
|
|
1607
1880
|
transform: getString(row, "transform")
|
|
1608
1881
|
};
|
|
1609
1882
|
if (partition.transform !== "identity") {
|
|
1610
|
-
throw new
|
|
1883
|
+
throw new LakeStorageError13(`[SixbDuckLake] Dataset table '${partition.tableName}' uses unsupported DuckLake partition transform '${partition.transform}'.`);
|
|
1611
1884
|
}
|
|
1612
1885
|
return partition;
|
|
1613
1886
|
});
|
|
@@ -1709,7 +1982,7 @@ class DuckLakeDatasetCatalog {
|
|
|
1709
1982
|
if (failures.length > 0) {
|
|
1710
1983
|
const details = failures.join(`
|
|
1711
1984
|
`);
|
|
1712
|
-
throw new
|
|
1985
|
+
throw new LakeStorageError14(`[SixbLake] Lake dataset definition check failed for ${failures.length} dataset(s).
|
|
1713
1986
|
${details}`);
|
|
1714
1987
|
}
|
|
1715
1988
|
}
|
|
@@ -1745,12 +2018,12 @@ ${details}`);
|
|
|
1745
2018
|
assertDuckLakeSnapshotId(snapshotId);
|
|
1746
2019
|
const columns = await this.readDatasetColumnsAtSnapshot(runtime, tableName, tableId, snapshotId);
|
|
1747
2020
|
if (columns.length === 0) {
|
|
1748
|
-
throw new
|
|
2021
|
+
throw new LakeStorageError14(`[SixbDuckLake] Could not reconstruct schema for dataset '${datasetId}' at DuckLake snapshot '${snapshotId}'.`);
|
|
1749
2022
|
}
|
|
1750
2023
|
return duckLakeCatalogColumnsToDatasetSchema(tableName, columns);
|
|
1751
2024
|
}
|
|
1752
2025
|
assertSchema(definition) {
|
|
1753
|
-
assertDatasetDefinition(definition, (message) => new
|
|
2026
|
+
assertDatasetDefinition(definition, (message) => new LakeStorageError14(`[SixbDuckLake] ${message}`));
|
|
1754
2027
|
}
|
|
1755
2028
|
async applyExistingDatasetUpdate(plan) {
|
|
1756
2029
|
const tableName = encodeDatasetTableName(plan.definition.id);
|
|
@@ -1797,7 +2070,7 @@ ${details}`);
|
|
|
1797
2070
|
await runtime.run(`ALTER TABLE ${qualifiedName} SET PARTITIONED BY (${definition.partitionBy.map((columnName) => quoteIdentifier(columnName)).join(", ")})`);
|
|
1798
2071
|
} catch (error) {
|
|
1799
2072
|
const message = error instanceof Error ? error.message : String(error);
|
|
1800
|
-
throw new
|
|
2073
|
+
throw new LakeStorageError14(`[SixbDuckLake] Dataset '${definition.id}' cannot apply partitionBy because DuckLake rejected ALTER TABLE SET PARTITIONED BY: ${message}`);
|
|
1801
2074
|
}
|
|
1802
2075
|
}
|
|
1803
2076
|
async applyPrimaryKey(runtime, qualifiedName, definition) {
|
|
@@ -1856,12 +2129,12 @@ ${details}`);
|
|
|
1856
2129
|
}
|
|
1857
2130
|
function assertDuckLakeSnapshotId(snapshotId) {
|
|
1858
2131
|
if (!/^\d+$/.test(snapshotId)) {
|
|
1859
|
-
throw new
|
|
2132
|
+
throw new LakeStorageError14(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
|
|
1860
2133
|
}
|
|
1861
2134
|
}
|
|
1862
2135
|
|
|
1863
2136
|
// src/internal/ducklake-maintenance.ts
|
|
1864
|
-
import { LakeStorageError as
|
|
2137
|
+
import { LakeStorageError as LakeStorageError15 } from "@sixb/core/lake-storage";
|
|
1865
2138
|
var DEFAULT_RETENTION = "7 days";
|
|
1866
2139
|
|
|
1867
2140
|
class DuckLakeMaintenance {
|
|
@@ -1877,9 +2150,9 @@ class DuckLakeMaintenance {
|
|
|
1877
2150
|
const deleteOlderThan = options.deleteOlderThan ?? expireOlderThan;
|
|
1878
2151
|
const dryRun = options.dryRun ?? false;
|
|
1879
2152
|
if (this.options.readOnly && !dryRun) {
|
|
1880
|
-
throw new
|
|
2153
|
+
throw new LakeStorageError15("[SixbDuckLake] DuckLake maintenance cannot run in read-only mode.");
|
|
1881
2154
|
}
|
|
1882
|
-
return this.connections.
|
|
2155
|
+
return this.connections.withMaintenanceRuntime(async (runtime) => {
|
|
1883
2156
|
const counts = await runMaintenanceProcedures(runtime, this.options, {
|
|
1884
2157
|
expireOlderThan,
|
|
1885
2158
|
deleteOlderThan
|
|
@@ -1914,7 +2187,7 @@ function maintenanceProcedureSql(procedure, aliasSql, olderThan, dryRun) {
|
|
|
1914
2187
|
}
|
|
1915
2188
|
|
|
1916
2189
|
// src/internal/ducklake-row-reader.ts
|
|
1917
|
-
import { LakeStorageError as
|
|
2190
|
+
import { LakeStorageError as LakeStorageError16 } from "@sixb/core/lake-storage";
|
|
1918
2191
|
var READ_PAGE_ROWS = 5000;
|
|
1919
2192
|
var PHYSICAL_ROW_ID_ALIAS = "__sixb_physical_row_id";
|
|
1920
2193
|
|
|
@@ -1928,58 +2201,68 @@ class DuckLakeRowReader {
|
|
|
1928
2201
|
this.snapshots = snapshots;
|
|
1929
2202
|
}
|
|
1930
2203
|
async* readRows(input) {
|
|
1931
|
-
this.connections.
|
|
1932
|
-
|
|
2204
|
+
const read = await this.connections.acquireRead(input.signal);
|
|
2205
|
+
let reader = null;
|
|
1933
2206
|
try {
|
|
1934
|
-
const
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2207
|
+
const prepared = await this.connections.withAttachedRuntime(async (runtime) => {
|
|
2208
|
+
read.signal.throwIfAborted();
|
|
2209
|
+
const tableRef = await resolveDatasetTableRef(this.options, runtime, input.datasetId);
|
|
2210
|
+
if (!tableRef) {
|
|
2211
|
+
throw new LakeStorageError16(`[SixbDuckLake] Unknown dataset '${input.datasetId}'.`);
|
|
2212
|
+
}
|
|
2213
|
+
const version = await this.resolveVersion(runtime, tableRef, input.versionId);
|
|
2214
|
+
const columns = this.resolveReadColumns(tableRef.datasetId, version.schema, input.columns);
|
|
2215
|
+
const names = new Set(version.schema.columns.map((column) => column.name.toLowerCase()));
|
|
2216
|
+
const physicalOrder = !names.has("rowid") && !names.has(PHYSICAL_ROW_ID_ALIAS);
|
|
2217
|
+
const relation = `${qualifiedTableName(this.options, tableRef.tableName)} AT (VERSION => ${parseVersionId(version.versionId)})`;
|
|
2218
|
+
const columnsSql = columns.map((column) => quoteIdentifier(column.name)).join(", ");
|
|
2219
|
+
const offset = Math.max(0, Math.trunc(input.offset ?? 0));
|
|
2220
|
+
const limit = input.limit === undefined ? undefined : Math.max(0, Math.trunc(input.limit));
|
|
2221
|
+
const sql = `SELECT ${columnsSql} FROM ${relation}${physicalOrder ? " ORDER BY rowid" : ""}${limit === undefined ? "" : ` LIMIT ${limit}`}${offset > 0 ? ` OFFSET ${offset}` : ""}`;
|
|
2222
|
+
const cursor = limit === undefined || limit > READ_PAGE_ROWS ? await this.connections.openReader(runtime, sql, read.signal, read.release) : null;
|
|
2223
|
+
const query = { columnsSql, relation, physicalOrder };
|
|
2224
|
+
const firstPage = cursor || limit === 0 ? [] : await runtime.query(pageSql(query, Math.min(limit ?? READ_PAGE_ROWS, READ_PAGE_ROWS), offset, null));
|
|
2225
|
+
return { columns, ...query, offset, limit, cursor, firstPage };
|
|
2226
|
+
});
|
|
2227
|
+
reader = prepared.cursor;
|
|
2228
|
+
if (!reader)
|
|
2229
|
+
read.signal.addEventListener("abort", read.release, { once: true });
|
|
2230
|
+
read.signal.throwIfAborted();
|
|
2231
|
+
if (reader) {
|
|
2232
|
+
yield* this.normalizeRows(reader.rows(), prepared.columns, read.signal);
|
|
1951
2233
|
return;
|
|
1952
|
-
|
|
1953
|
-
let
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
offset += rows.length;
|
|
1966
|
-
if (remaining !== undefined) {
|
|
2234
|
+
}
|
|
2235
|
+
let offset = prepared.offset;
|
|
2236
|
+
let remaining = prepared.limit;
|
|
2237
|
+
let cursor = null;
|
|
2238
|
+
let page = prepared.firstPage;
|
|
2239
|
+
while (remaining !== 0) {
|
|
2240
|
+
read.signal.throwIfAborted();
|
|
2241
|
+
const pageSize = Math.min(remaining ?? READ_PAGE_ROWS, READ_PAGE_ROWS);
|
|
2242
|
+
const rows = page ?? await this.connections.withAttachedRuntime((runtime) => runtime.query(pageSql(prepared, pageSize, offset, cursor)));
|
|
2243
|
+
page = undefined;
|
|
2244
|
+
read.signal.throwIfAborted();
|
|
2245
|
+
yield* this.normalizeRows(rows, prepared.columns, read.signal);
|
|
2246
|
+
if (remaining !== undefined)
|
|
1967
2247
|
remaining -= rows.length;
|
|
1968
|
-
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
if (canUsePhysicalRowId) {
|
|
1972
|
-
physicalCursor = getBigIntLike(rows[rows.length - 1], PHYSICAL_ROW_ID_ALIAS);
|
|
1973
|
-
}
|
|
1974
|
-
if (rows.length < pageRows)
|
|
2248
|
+
if (rows.length < pageSize)
|
|
1975
2249
|
return;
|
|
2250
|
+
if (prepared.physicalOrder)
|
|
2251
|
+
cursor = getBigIntLike(rows[rows.length - 1], PHYSICAL_ROW_ID_ALIAS);
|
|
2252
|
+
offset += rows.length;
|
|
1976
2253
|
}
|
|
1977
2254
|
} finally {
|
|
1978
|
-
|
|
2255
|
+
read.signal.removeEventListener("abort", read.release);
|
|
2256
|
+
try {
|
|
2257
|
+
await reader?.close();
|
|
2258
|
+
} finally {
|
|
2259
|
+
read.release();
|
|
2260
|
+
}
|
|
1979
2261
|
}
|
|
1980
2262
|
}
|
|
1981
|
-
async* normalizeRows(rows, selectedColumns) {
|
|
2263
|
+
async* normalizeRows(rows, selectedColumns, signal) {
|
|
1982
2264
|
for await (const row of rows) {
|
|
2265
|
+
signal.throwIfAborted();
|
|
1983
2266
|
const output = {};
|
|
1984
2267
|
for (const column of selectedColumns) {
|
|
1985
2268
|
output[column.name] = normalizeReadValue(row[column.name], column);
|
|
@@ -2010,18 +2293,23 @@ class DuckLakeRowReader {
|
|
|
2010
2293
|
return columns.map((columnName) => {
|
|
2011
2294
|
const column = columnsByName.get(columnName);
|
|
2012
2295
|
if (!column) {
|
|
2013
|
-
throw new
|
|
2296
|
+
throw new LakeStorageError16(`[SixbDuckLake] Dataset '${datasetId}' does not have column '${columnName}' at the requested version.`);
|
|
2014
2297
|
}
|
|
2015
2298
|
return column;
|
|
2016
2299
|
});
|
|
2017
2300
|
}
|
|
2018
2301
|
throwNoCommittedVersion(datasetId) {
|
|
2019
|
-
throw new
|
|
2302
|
+
throw new LakeStorageError16(`[SixbDuckLake] No committed version found for dataset '${datasetId}'.`);
|
|
2020
2303
|
}
|
|
2021
2304
|
}
|
|
2305
|
+
function pageSql(read, limit, offset, cursor) {
|
|
2306
|
+
return read.physicalOrder ? `SELECT rowid AS ${quoteIdentifier(PHYSICAL_ROW_ID_ALIAS)}, ${read.columnsSql}
|
|
2307
|
+
FROM ${read.relation} ${cursor === null ? "" : `WHERE rowid > ${cursor}`}
|
|
2308
|
+
ORDER BY rowid LIMIT ${limit} ${cursor === null && offset > 0 ? `OFFSET ${offset}` : ""}` : `SELECT ${read.columnsSql} FROM ${read.relation} LIMIT ${limit} OFFSET ${offset}`;
|
|
2309
|
+
}
|
|
2022
2310
|
|
|
2023
2311
|
// src/internal/ducklake-snapshot-reader.ts
|
|
2024
|
-
import { LakeStorageError as
|
|
2312
|
+
import { LakeStorageError as LakeStorageError17 } from "@sixb/core/lake-storage";
|
|
2025
2313
|
var SNAPSHOT_ROW_BATCH_SIZE = 128;
|
|
2026
2314
|
var CATALOG_SNAPSHOT_SCAN_LIMIT = 512;
|
|
2027
2315
|
|
|
@@ -2234,7 +2522,7 @@ class DuckLakeSnapshotReader {
|
|
|
2234
2522
|
}
|
|
2235
2523
|
assertNoMetadataConflict(snapshotId, datasetId, metadata) {
|
|
2236
2524
|
if (metadata !== undefined && metadata.datasetId !== datasetId) {
|
|
2237
|
-
throw new
|
|
2525
|
+
throw new LakeStorageError17(`[SixbDuckLake] DuckLake snapshot '${snapshotId}' changed dataset '${datasetId}' but Sixb commit metadata references dataset '${metadata.datasetId}'.`);
|
|
2238
2526
|
}
|
|
2239
2527
|
}
|
|
2240
2528
|
async getLatestVersionForDefinition(runtime, dataset) {
|
|
@@ -2379,7 +2667,10 @@ class DuckLakeSnapshotReader {
|
|
|
2379
2667
|
if (input.limit !== undefined && input.limit <= 0) {
|
|
2380
2668
|
return [];
|
|
2381
2669
|
}
|
|
2382
|
-
const
|
|
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
|
+
];
|
|
2383
2674
|
if (input.exactSnapshotId !== undefined) {
|
|
2384
2675
|
assertDuckLakeSnapshotId2(input.exactSnapshotId);
|
|
2385
2676
|
where.push(`snapshot.snapshot_id = ${input.exactSnapshotId}`);
|
|
@@ -2388,7 +2679,7 @@ class DuckLakeSnapshotReader {
|
|
|
2388
2679
|
assertDuckLakeSnapshotId2(input.beforeSnapshotId);
|
|
2389
2680
|
where.push(`snapshot.snapshot_id < ${input.beforeSnapshotId}`);
|
|
2390
2681
|
}
|
|
2391
|
-
const whereSql =
|
|
2682
|
+
const whereSql = `WHERE ${where.join(" AND ")}`;
|
|
2392
2683
|
const limitSql = input.limit === undefined ? "" : `LIMIT ${Math.max(0, Math.trunc(input.limit))}`;
|
|
2393
2684
|
const ducklakeSnapshot = duckLakeMetadataTableName(this.options, "ducklake_snapshot");
|
|
2394
2685
|
const ducklakeSnapshotChanges = duckLakeMetadataTableName(this.options, "ducklake_snapshot_changes");
|
|
@@ -2514,7 +2805,7 @@ class DuckLakeSnapshotReader {
|
|
|
2514
2805
|
}
|
|
2515
2806
|
function assertDuckLakeSnapshotId2(snapshotId) {
|
|
2516
2807
|
if (!/^\d+$/.test(snapshotId)) {
|
|
2517
|
-
throw new
|
|
2808
|
+
throw new LakeStorageError17(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
|
|
2518
2809
|
}
|
|
2519
2810
|
}
|
|
2520
2811
|
function fileChangeKey(snapshotId, tableId) {
|
|
@@ -2524,17 +2815,17 @@ function fileChangeKey(snapshotId, tableId) {
|
|
|
2524
2815
|
// src/internal/ducklake-sql-executor.ts
|
|
2525
2816
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
2526
2817
|
import {
|
|
2527
|
-
LakeStorageError as
|
|
2818
|
+
LakeStorageError as LakeStorageError20
|
|
2528
2819
|
} from "@sixb/core/lake-storage";
|
|
2529
2820
|
|
|
2530
2821
|
// src/internal/dataset-row-commit.ts
|
|
2531
2822
|
import {
|
|
2532
2823
|
getDatasetPrimaryKeyColumns,
|
|
2533
|
-
LakeStorageError as
|
|
2824
|
+
LakeStorageError as LakeStorageError18
|
|
2534
2825
|
} from "@sixb/core/lake-storage";
|
|
2535
2826
|
function assertDatasetWriteMode(mode, operation) {
|
|
2536
2827
|
if (mode !== "snapshot" && mode !== "append") {
|
|
2537
|
-
throw new
|
|
2828
|
+
throw new LakeStorageError18(`[SixbDuckLake] Invalid ${operation} mode '${String(mode)}'. Expected 'snapshot' or 'append'.`);
|
|
2538
2829
|
}
|
|
2539
2830
|
}
|
|
2540
2831
|
async function applyDatasetRowsFromRelation(input) {
|
|
@@ -2594,7 +2885,7 @@ async function applyDatasetRowsFromRelation(input) {
|
|
|
2594
2885
|
async function inspectUniqueKeyedRelation(input) {
|
|
2595
2886
|
const primaryKeyColumns = getDatasetPrimaryKeyColumns(input.dataset);
|
|
2596
2887
|
if (primaryKeyColumns === null) {
|
|
2597
|
-
throw new
|
|
2888
|
+
throw new LakeStorageError18(`[SixbDuckLake] Dataset '${input.dataset.id}' must define a primaryKey before rows can be keyed.`);
|
|
2598
2889
|
}
|
|
2599
2890
|
const keysSql = primaryKeyColumns.map((column) => quoteIdentifier(column)).join(", ");
|
|
2600
2891
|
const [row] = await input.runtime.query(`
|
|
@@ -2609,7 +2900,7 @@ async function inspectUniqueKeyedRelation(input) {
|
|
|
2609
2900
|
const rowCount = row === undefined ? 0 : Number(getBigIntLike(row, "row_count"));
|
|
2610
2901
|
const maxKeyCount = row === undefined ? 0 : Number(getBigIntLike(row, "max_key_count"));
|
|
2611
2902
|
if (maxKeyCount > 1) {
|
|
2612
|
-
throw new
|
|
2903
|
+
throw new LakeStorageError18(`[SixbDuckLake] Dataset '${input.dataset.id}' ${input.context} contains duplicate primary key.`);
|
|
2613
2904
|
}
|
|
2614
2905
|
return rowCount;
|
|
2615
2906
|
}
|
|
@@ -2626,7 +2917,7 @@ async function assertNoPrimaryKeyOverlap(input) {
|
|
|
2626
2917
|
LIMIT 1
|
|
2627
2918
|
`);
|
|
2628
2919
|
if (collision !== undefined) {
|
|
2629
|
-
throw new
|
|
2920
|
+
throw new LakeStorageError18(`[SixbDuckLake] Dataset '${input.dataset.id}' ${input.context} contains duplicate primary key.`);
|
|
2630
2921
|
}
|
|
2631
2922
|
}
|
|
2632
2923
|
async function fingerprintRelation(runtime, relationSql, columnsSql) {
|
|
@@ -2660,7 +2951,7 @@ async function countRows(runtime, relationSql) {
|
|
|
2660
2951
|
// src/internal/sql-transform-relations.ts
|
|
2661
2952
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2662
2953
|
import {
|
|
2663
|
-
LakeStorageError as
|
|
2954
|
+
LakeStorageError as LakeStorageError19
|
|
2664
2955
|
} from "@sixb/core/lake-storage";
|
|
2665
2956
|
var RELATION_PLACEHOLDER_PREFIX = "__sixb_sql_transform_relation_";
|
|
2666
2957
|
var RELATION_PLACEHOLDER_PATTERN = /__sixb_sql_transform_relation_[0-9a-f]{32}_[0-9]+__/g;
|
|
@@ -2678,7 +2969,7 @@ function renderDuckLakeSqlTransformSql(input) {
|
|
|
2678
2969
|
const context = createDuckLakeSqlTransformRelationContext(input);
|
|
2679
2970
|
const sql = input.sql(context.relations);
|
|
2680
2971
|
if (typeof sql !== "string") {
|
|
2681
|
-
throw new
|
|
2972
|
+
throw new LakeStorageError19("[SixbDuckLake] SQL transform body must return a SQL string.");
|
|
2682
2973
|
}
|
|
2683
2974
|
return context.render(sql);
|
|
2684
2975
|
}
|
|
@@ -2716,7 +3007,7 @@ function renderDuckLakeSourceRelation(options, source) {
|
|
|
2716
3007
|
return `(SELECT * FROM ${qualifiedTableName(options, tableName)} AT (VERSION => ${snapshotId}))`;
|
|
2717
3008
|
}
|
|
2718
3009
|
function throwUnresolvedPlaceholder() {
|
|
2719
|
-
throw new
|
|
3010
|
+
throw new LakeStorageError19("[SixbDuckLake] SQL transform contains an unresolved relation placeholder.");
|
|
2720
3011
|
}
|
|
2721
3012
|
|
|
2722
3013
|
// src/internal/ducklake-sql-executor.ts
|
|
@@ -2810,14 +3101,14 @@ class DuckLakeSqlExecutor {
|
|
|
2810
3101
|
async resolveSourceDataset(runtime, dataset) {
|
|
2811
3102
|
const definition = await this.datasets.getDatasetOnRuntime(runtime, dataset.id);
|
|
2812
3103
|
if (!definition) {
|
|
2813
|
-
throw new
|
|
3104
|
+
throw new LakeStorageError20(`[SixbDuckLake] Unknown SQL transform source dataset '${dataset.id}'.`);
|
|
2814
3105
|
}
|
|
2815
3106
|
return definition;
|
|
2816
3107
|
}
|
|
2817
3108
|
async resolveTargetDataset(runtime, dataset) {
|
|
2818
3109
|
const definition = await this.datasets.getDatasetOnRuntime(runtime, dataset.id);
|
|
2819
3110
|
if (!definition) {
|
|
2820
|
-
throw new
|
|
3111
|
+
throw new LakeStorageError20(`[SixbDuckLake] Unknown SQL transform target dataset '${dataset.id}'.`);
|
|
2821
3112
|
}
|
|
2822
3113
|
this.datasets.assertSchema(definition);
|
|
2823
3114
|
return definition;
|
|
@@ -2938,10 +3229,10 @@ function normalizePreviewValue(value) {
|
|
|
2938
3229
|
return value;
|
|
2939
3230
|
}
|
|
2940
3231
|
function throwNoCommittedSourceVersion(datasetId) {
|
|
2941
|
-
throw new
|
|
3232
|
+
throw new LakeStorageError20(`[SixbDuckLake] No committed version found for SQL transform source dataset '${datasetId}'.`);
|
|
2942
3233
|
}
|
|
2943
3234
|
function throwResultSchemaMismatch(datasetId, detail) {
|
|
2944
|
-
throw new
|
|
3235
|
+
throw new LakeStorageError20(`[SixbDuckLake] SQL transform result schema does not match target dataset '${datasetId}': ${detail}.`);
|
|
2945
3236
|
}
|
|
2946
3237
|
|
|
2947
3238
|
// src/internal/ducklake-write-coordinator.ts
|
|
@@ -2951,20 +3242,20 @@ import {
|
|
|
2951
3242
|
getDatasetPrimaryKeyColumns as getDatasetPrimaryKeyColumns4,
|
|
2952
3243
|
hasDatasetInputChanges,
|
|
2953
3244
|
LakeConcurrencyError,
|
|
2954
|
-
LakeStorageError as
|
|
3245
|
+
LakeStorageError as LakeStorageError24,
|
|
2955
3246
|
retryDatasetMergeCommit
|
|
2956
3247
|
} from "@sixb/core/lake-storage";
|
|
2957
3248
|
|
|
2958
3249
|
// src/internal/dataset-row-merge.ts
|
|
2959
3250
|
import {
|
|
2960
3251
|
getDatasetPrimaryKeyColumns as getDatasetPrimaryKeyColumns2,
|
|
2961
|
-
LakeStorageError as
|
|
3252
|
+
LakeStorageError as LakeStorageError21,
|
|
2962
3253
|
reconcileDatasetSequences
|
|
2963
3254
|
} from "@sixb/core/lake-storage";
|
|
2964
3255
|
async function applyDatasetMergeFromRelation(input) {
|
|
2965
3256
|
const primaryKeyColumns = getDatasetPrimaryKeyColumns2(input.dataset);
|
|
2966
3257
|
if (primaryKeyColumns === null) {
|
|
2967
|
-
throw new
|
|
3258
|
+
throw new LakeStorageError21(`[SixbDuckLake] Dataset '${input.dataset.id}' must define a primaryKey before it can be merged.`);
|
|
2968
3259
|
}
|
|
2969
3260
|
const stagingTable = quoteIdentifier(input.stagingTableName);
|
|
2970
3261
|
const targetTable = qualifiedTableName(input.options, encodeDatasetTableName(input.dataset.id));
|
|
@@ -3125,7 +3416,7 @@ function finalChangesSelectSql(input, primaryKeyColumns) {
|
|
|
3125
3416
|
async function countMergeEffects(input, targetTable, finalChanges, primaryKeyColumns) {
|
|
3126
3417
|
const matchColumn = primaryKeyColumns[0];
|
|
3127
3418
|
if (matchColumn === undefined) {
|
|
3128
|
-
throw new
|
|
3419
|
+
throw new LakeStorageError21(`[SixbDuckLake] Dataset '${input.dataset.id}' primaryKey must contain at least one column.`);
|
|
3129
3420
|
}
|
|
3130
3421
|
const keyMatchSql = primaryKeyMatchSql(primaryKeyColumns, "target", "source");
|
|
3131
3422
|
const targetMatch = `target.${quoteIdentifier(matchColumn)} IS NOT NULL`;
|
|
@@ -3179,7 +3470,7 @@ import {
|
|
|
3179
3470
|
cloneDatasetMergeChange,
|
|
3180
3471
|
datasetSequenceChange,
|
|
3181
3472
|
getDatasetMergeChangeValidationError,
|
|
3182
|
-
LakeStorageError as
|
|
3473
|
+
LakeStorageError as LakeStorageError22
|
|
3183
3474
|
} from "@sixb/core/lake-storage";
|
|
3184
3475
|
|
|
3185
3476
|
// src/internal/row-appender.ts
|
|
@@ -3280,7 +3571,7 @@ class DuckLakeMergeSession {
|
|
|
3280
3571
|
for await (const change of changes) {
|
|
3281
3572
|
const validationError = getDatasetMergeChangeValidationError(change, this.input.dataset);
|
|
3282
3573
|
if (validationError) {
|
|
3283
|
-
throw new
|
|
3574
|
+
throw new LakeStorageError22(`[SixbDuckLake] ${validationError}`);
|
|
3284
3575
|
}
|
|
3285
3576
|
batch.push({
|
|
3286
3577
|
sequence: this.nextSequence,
|
|
@@ -3343,7 +3634,7 @@ class DuckLakeMergeSession {
|
|
|
3343
3634
|
}
|
|
3344
3635
|
assertOpen() {
|
|
3345
3636
|
if (this.closed) {
|
|
3346
|
-
throw new
|
|
3637
|
+
throw new LakeStorageError22("[SixbDuckLake] Merge session is already closed.");
|
|
3347
3638
|
}
|
|
3348
3639
|
}
|
|
3349
3640
|
async cleanup() {
|
|
@@ -3363,7 +3654,7 @@ import { getDatasetRowValidationError } from "@sixb/core";
|
|
|
3363
3654
|
import {
|
|
3364
3655
|
encodeDatasetPrimaryKey,
|
|
3365
3656
|
getDatasetPrimaryKeyColumns as getDatasetPrimaryKeyColumns3,
|
|
3366
|
-
LakeStorageError as
|
|
3657
|
+
LakeStorageError as LakeStorageError23
|
|
3367
3658
|
} from "@sixb/core/lake-storage";
|
|
3368
3659
|
var WRITE_ROW_BATCH_SIZE = 1000;
|
|
3369
3660
|
async function createDuckLakeWriteSession(input) {
|
|
@@ -3394,13 +3685,13 @@ class DuckLakeWriteSession {
|
|
|
3394
3685
|
for await (const row of rows) {
|
|
3395
3686
|
const validationError = getDatasetRowValidationError(row, this.input.dataset);
|
|
3396
3687
|
if (validationError) {
|
|
3397
|
-
throw new
|
|
3688
|
+
throw new LakeStorageError23(`[SixbDuckLake] ${validationError}`);
|
|
3398
3689
|
}
|
|
3399
3690
|
const clonedRow = structuredClone(row);
|
|
3400
3691
|
if (getDatasetPrimaryKeyColumns3(this.input.dataset) !== null) {
|
|
3401
3692
|
const primaryKey = encodeDatasetPrimaryKey(this.input.dataset, clonedRow);
|
|
3402
3693
|
if (this.stagedPrimaryKeys.has(primaryKey) || batchPrimaryKeys.has(primaryKey)) {
|
|
3403
|
-
throw new
|
|
3694
|
+
throw new LakeStorageError23(`[SixbDuckLake] Dataset '${this.input.dataset.id}' ${this.input.mode ?? "snapshot"} source contains duplicate primary key.`);
|
|
3404
3695
|
}
|
|
3405
3696
|
batchPrimaryKeys.add(primaryKey);
|
|
3406
3697
|
}
|
|
@@ -3457,7 +3748,7 @@ class DuckLakeWriteSession {
|
|
|
3457
3748
|
}
|
|
3458
3749
|
assertOpen() {
|
|
3459
3750
|
if (this.closed) {
|
|
3460
|
-
throw new
|
|
3751
|
+
throw new LakeStorageError23("[SixbDuckLake] Write session is already closed.");
|
|
3461
3752
|
}
|
|
3462
3753
|
}
|
|
3463
3754
|
async cleanup() {
|
|
@@ -3465,6 +3756,7 @@ class DuckLakeWriteSession {
|
|
|
3465
3756
|
return;
|
|
3466
3757
|
}
|
|
3467
3758
|
this.cleanedUp = true;
|
|
3759
|
+
this.stagedPrimaryKeys.clear();
|
|
3468
3760
|
await this.dropStagingTable();
|
|
3469
3761
|
}
|
|
3470
3762
|
async dropStagingTable() {
|
|
@@ -3490,7 +3782,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3490
3782
|
this.connections.assertOpen();
|
|
3491
3783
|
const definition = await this.datasets.getDataset(input.dataset.id);
|
|
3492
3784
|
if (!definition) {
|
|
3493
|
-
throw new
|
|
3785
|
+
throw new LakeStorageError24(`[SixbDuckLake] Unknown dataset '${input.dataset.id}'.`);
|
|
3494
3786
|
}
|
|
3495
3787
|
this.datasets.assertSchema(definition);
|
|
3496
3788
|
assertUnsequencedDatasetWrite(definition);
|
|
@@ -3511,10 +3803,10 @@ class DuckLakeWriteCoordinator {
|
|
|
3511
3803
|
this.connections.assertOpen();
|
|
3512
3804
|
const definition = await this.datasets.getDataset(input.dataset.id);
|
|
3513
3805
|
if (!definition) {
|
|
3514
|
-
throw new
|
|
3806
|
+
throw new LakeStorageError24(`[SixbDuckLake] Unknown dataset '${input.dataset.id}'.`);
|
|
3515
3807
|
}
|
|
3516
3808
|
if (getDatasetPrimaryKeyColumns4(definition) === null) {
|
|
3517
|
-
throw new
|
|
3809
|
+
throw new LakeStorageError24(`[SixbDuckLake] Dataset '${definition.id}' must define a primaryKey before it can be merged.`);
|
|
3518
3810
|
}
|
|
3519
3811
|
this.datasets.assertSchema(definition);
|
|
3520
3812
|
const latestVersion = await this.connections.withAttachedRuntime((runtime) => this.snapshots.getLatestVersionForDefinition(runtime, definition));
|
|
@@ -3532,7 +3824,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3532
3824
|
async commitWrite(input) {
|
|
3533
3825
|
const definition = await this.datasets.getDataset(input.write.dataset.id);
|
|
3534
3826
|
if (!definition) {
|
|
3535
|
-
throw new
|
|
3827
|
+
throw new LakeStorageError24(`[SixbDuckLake] Unknown dataset '${input.write.dataset.id}'.`);
|
|
3536
3828
|
}
|
|
3537
3829
|
const mode = input.write.mode ?? "snapshot";
|
|
3538
3830
|
assertDatasetWriteMode(mode, "write");
|
|
@@ -3553,10 +3845,10 @@ class DuckLakeWriteCoordinator {
|
|
|
3553
3845
|
async commitMerge(input) {
|
|
3554
3846
|
const definition = await this.datasets.getDataset(input.merge.dataset.id);
|
|
3555
3847
|
if (!definition) {
|
|
3556
|
-
throw new
|
|
3848
|
+
throw new LakeStorageError24(`[SixbDuckLake] Unknown dataset '${input.merge.dataset.id}'.`);
|
|
3557
3849
|
}
|
|
3558
3850
|
if (getDatasetPrimaryKeyColumns4(definition) === null) {
|
|
3559
|
-
throw new
|
|
3851
|
+
throw new LakeStorageError24(`[SixbDuckLake] Dataset '${definition.id}' must define a primaryKey before it can be merged.`);
|
|
3560
3852
|
}
|
|
3561
3853
|
return retryDatasetMergeCommit(input.merge, input.commit, (rebase) => this.withCommitRuntime(async (runtime) => {
|
|
3562
3854
|
input.commit?.signal?.throwIfAborted();
|
|
@@ -3584,7 +3876,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3584
3876
|
validatedPrimaryKeyColumns: context.validatedPrimaryKeyColumns
|
|
3585
3877
|
});
|
|
3586
3878
|
if (result.sourceRowCount !== input.changesWritten) {
|
|
3587
|
-
throw new
|
|
3879
|
+
throw new LakeStorageError24(`[SixbDuckLake] Staged merge for dataset '${definition.id}' accepted ${input.changesWritten} change(s), but DuckLake saw ${result.sourceRowCount} source change(s) at commit time.`);
|
|
3588
3880
|
}
|
|
3589
3881
|
return result;
|
|
3590
3882
|
}
|
|
@@ -3606,7 +3898,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3606
3898
|
disableRetries: input.mode === "append" && getDatasetPrimaryKeyColumns4(input.dataset) !== null
|
|
3607
3899
|
});
|
|
3608
3900
|
if (result.version === null) {
|
|
3609
|
-
throw new
|
|
3901
|
+
throw new LakeStorageError24(`[SixbDuckLake] No DuckLake changes were committed for dataset '${input.dataset.id}', and no previous version exists.`);
|
|
3610
3902
|
}
|
|
3611
3903
|
return { ...result.version, outcome: result.outcome };
|
|
3612
3904
|
}
|
|
@@ -3637,10 +3929,9 @@ class DuckLakeWriteCoordinator {
|
|
|
3637
3929
|
}
|
|
3638
3930
|
async commitDatasetVersionUnlocked(input) {
|
|
3639
3931
|
const previousWriteSnapshotId = await this.lastCommittedSnapshotId(input.runtime);
|
|
3640
|
-
await input.runtime.run("BEGIN TRANSACTION");
|
|
3641
3932
|
let committed = false;
|
|
3642
3933
|
try {
|
|
3643
|
-
const latestVersion = await this.
|
|
3934
|
+
const latestVersion = await this.beginVersionTransaction(input);
|
|
3644
3935
|
if (input.expectedLatestVersionId !== undefined) {
|
|
3645
3936
|
this.assertExpectedLatestVersion({
|
|
3646
3937
|
datasetId: input.dataset.id,
|
|
@@ -3688,7 +3979,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3688
3979
|
if (version) {
|
|
3689
3980
|
return { outcome: "created", version };
|
|
3690
3981
|
}
|
|
3691
|
-
throw new
|
|
3982
|
+
throw new LakeStorageError24(`[SixbDuckLake] DuckLake committed snapshot '${ownSnapshotId}' for dataset '${input.dataset.id}', but Sixb could not hydrate it as a dataset version.`);
|
|
3692
3983
|
}
|
|
3693
3984
|
if (!input.changeResult.dataChangeExpected) {
|
|
3694
3985
|
const version = await this.versionForNoOpCommit(input.runtime, input.dataset, input.allowInitialNoOp ?? false);
|
|
@@ -3706,9 +3997,9 @@ class DuckLakeWriteCoordinator {
|
|
|
3706
3997
|
};
|
|
3707
3998
|
}
|
|
3708
3999
|
if (input.committedWriteSnapshotId === input.previousWriteSnapshotId) {
|
|
3709
|
-
throw new
|
|
4000
|
+
throw new LakeStorageError24(`[SixbDuckLake] DuckLake commit for dataset '${input.dataset.id}' completed without producing a snapshot for this non-empty write.`);
|
|
3710
4001
|
}
|
|
3711
|
-
throw new
|
|
4002
|
+
throw new LakeStorageError24(`[SixbDuckLake] DuckLake commit for dataset '${input.dataset.id}' completed, but Sixb could not find a matching snapshot for this write.`);
|
|
3712
4003
|
}
|
|
3713
4004
|
async applyStagedRows(input) {
|
|
3714
4005
|
const mode = input.write.mode ?? "snapshot";
|
|
@@ -3721,7 +4012,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3721
4012
|
previousRowCount: input.previousRowCount
|
|
3722
4013
|
});
|
|
3723
4014
|
if (result.sourceRowCount !== input.rowsWritten) {
|
|
3724
|
-
throw new
|
|
4015
|
+
throw new LakeStorageError24(`[SixbDuckLake] Staged write for dataset '${input.write.dataset.id}' accepted ${input.rowsWritten} row(s), but DuckLake saw ${result.sourceRowCount} source row(s) at commit time.`);
|
|
3725
4016
|
}
|
|
3726
4017
|
return result;
|
|
3727
4018
|
}
|
|
@@ -3743,7 +4034,7 @@ class DuckLakeWriteCoordinator {
|
|
|
3743
4034
|
if (changeResult.dataChangeExpected) {
|
|
3744
4035
|
return;
|
|
3745
4036
|
}
|
|
3746
|
-
const latestVersion = knownLatestVersion
|
|
4037
|
+
const latestVersion = knownLatestVersion === undefined ? await this.snapshots.getLatestVersionSummaryForDefinition(input.runtime, input.dataset) : knownLatestVersion;
|
|
3747
4038
|
if (latestVersion ? input.mode === "merge" || !hasDatasetInputChanges(latestVersion.inputs, input.inputs) : input.allowInitialNoOp) {
|
|
3748
4039
|
return;
|
|
3749
4040
|
}
|
|
@@ -3759,18 +4050,39 @@ class DuckLakeWriteCoordinator {
|
|
|
3759
4050
|
if (allowInitialNoOp) {
|
|
3760
4051
|
return null;
|
|
3761
4052
|
}
|
|
3762
|
-
throw new
|
|
4053
|
+
throw new LakeStorageError24(`[SixbDuckLake] No DuckLake changes were committed for dataset '${definition.id}', and no previous version exists.`);
|
|
3763
4054
|
}
|
|
3764
4055
|
async rollbackTransaction(runtime) {
|
|
3765
4056
|
try {
|
|
3766
4057
|
await runtime.run("ROLLBACK");
|
|
3767
4058
|
} catch {}
|
|
3768
4059
|
}
|
|
3769
|
-
async
|
|
3770
|
-
if (
|
|
3771
|
-
|
|
4060
|
+
async beginVersionTransaction(input) {
|
|
4061
|
+
if (!isSqliteCatalog(this.options.catalog)) {
|
|
4062
|
+
await input.runtime.run("BEGIN TRANSACTION");
|
|
4063
|
+
if (input.expectedLatestVersionId === undefined && input.mode !== "append") {
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
return this.snapshots.getLatestVersionSummaryForDefinition(input.runtime, input.dataset);
|
|
3772
4067
|
}
|
|
3773
|
-
|
|
4068
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
4069
|
+
input.signal?.throwIfAborted();
|
|
4070
|
+
const snapshotId = await this.currentSnapshotId(input.runtime);
|
|
4071
|
+
const latestVersion = await this.snapshots.getLatestVersionSummaryForDefinition(input.runtime, input.dataset);
|
|
4072
|
+
await input.runtime.run("BEGIN TRANSACTION");
|
|
4073
|
+
if (await this.currentSnapshotId(input.runtime) === snapshotId) {
|
|
4074
|
+
return latestVersion;
|
|
4075
|
+
}
|
|
4076
|
+
await input.runtime.run("ROLLBACK");
|
|
4077
|
+
}
|
|
4078
|
+
throw new LakeConcurrencyError(`[SixbDuckLake] Catalog changed during three write preparations for dataset '${input.dataset.id}'. Retry the write.`);
|
|
4079
|
+
}
|
|
4080
|
+
async currentSnapshotId(runtime) {
|
|
4081
|
+
const [row] = await runtime.query(`SELECT id FROM ${quoteIdentifier(duckLakeAlias(this.options))}.current_snapshot()`);
|
|
4082
|
+
if (row === undefined) {
|
|
4083
|
+
throw new LakeStorageError24("[SixbDuckLake] DuckLake did not return a current snapshot.");
|
|
4084
|
+
}
|
|
4085
|
+
return String(getBigIntLike(row, "id"));
|
|
3774
4086
|
}
|
|
3775
4087
|
async lastCommittedSnapshotId(runtime) {
|
|
3776
4088
|
const [row] = await runtime.query(`SELECT id FROM ${quoteIdentifier(duckLakeAlias(this.options))}.last_committed_snapshot()`);
|
|
@@ -3819,6 +4131,9 @@ class DuckLakeWriteCoordinator {
|
|
|
3819
4131
|
}
|
|
3820
4132
|
}
|
|
3821
4133
|
}
|
|
4134
|
+
function isSqliteCatalog(catalog) {
|
|
4135
|
+
return catalog.type === "sqlite" || catalog.type === "custom" && catalog.uri.toLowerCase().startsWith("sqlite:");
|
|
4136
|
+
}
|
|
3822
4137
|
function commitRowCount(input, changeResult, latestVersion) {
|
|
3823
4138
|
if (input.mode === "append" && input.expectedLatestVersionId === undefined) {
|
|
3824
4139
|
return { kind: "unknown" };
|
|
@@ -3833,7 +4148,7 @@ function commitRowCount(input, changeResult, latestVersion) {
|
|
|
3833
4148
|
}
|
|
3834
4149
|
function assertDuckLakeSnapshotId3(snapshotId) {
|
|
3835
4150
|
if (!/^\d+$/.test(snapshotId)) {
|
|
3836
|
-
throw new
|
|
4151
|
+
throw new LakeStorageError24(`[SixbDuckLake] Invalid DuckLake snapshot id '${snapshotId}'.`);
|
|
3837
4152
|
}
|
|
3838
4153
|
}
|
|
3839
4154
|
var localCatalogCommitLocks = new Map;
|
|
@@ -3939,4 +4254,4 @@ export {
|
|
|
3939
4254
|
DuckLakeStorage
|
|
3940
4255
|
};
|
|
3941
4256
|
|
|
3942
|
-
//# debugId=
|
|
4257
|
+
//# debugId=35ABBEC5A94F36A964756E2164756E21
|