@minnowdb/core 0.6.1 → 0.6.3
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/dist/engine/artifact-cache.d.ts +5 -0
- package/dist/engine/artifact-cache.js +64 -13
- package/dist/engine/client.d.ts +2 -2
- package/dist/engine/client.js +6 -2
- package/dist/engine/database.d.ts +11 -1
- package/dist/engine/database.js +291 -9
- package/dist/engine/point-read.d.ts +57 -0
- package/dist/engine/point-read.js +189 -0
- package/dist/engine/sql-driver.d.ts +7 -2
- package/dist/engine/worker-host.js +17 -2
- package/dist/storage/toolkit/record-core.js +104 -75
- package/dist/storage/types.js +34 -6
- package/dist/transactions/index.js +9 -1
- package/package.json +1 -1
- package/sql-feature-matrix.json +52 -2
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateTableForeignKey, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
|
|
2
2
|
import { dateIsoString } from "../../date-value.js";
|
|
3
3
|
import { assertWellFormedString, crc32, MAX_STORED_BLOCK_BYTE_LENGTH, } from "../../block-format/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* A deep copy for the plain record shapes this core stores — objects, arrays, and primitives,
|
|
6
|
+
* bigint included. Every read and every job advance copies a record across this boundary, and
|
|
7
|
+
* `structuredClone` was a sixth of a settle phase's CPU; a value outside the plain shape
|
|
8
|
+
* (a typed array, a Date, a Map) falls back to `structuredClone` for that value, so the copy
|
|
9
|
+
* stays exact whatever the record carries.
|
|
10
|
+
*/
|
|
11
|
+
function cloneRecord(value) {
|
|
12
|
+
return cloneRecordValue(value);
|
|
13
|
+
}
|
|
14
|
+
function cloneRecordValue(value) {
|
|
15
|
+
if (typeof value !== "object" || value === null) {
|
|
16
|
+
// Preserve structuredClone's rejection of uncloneable leaves instead of silently sharing.
|
|
17
|
+
return typeof value === "function" ? structuredClone(value) : value;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
const copy = new Array(value.length);
|
|
21
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
22
|
+
copy[index] = cloneRecordValue(value[index]);
|
|
23
|
+
}
|
|
24
|
+
return copy;
|
|
25
|
+
}
|
|
26
|
+
const prototype = Object.getPrototypeOf(value);
|
|
27
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
28
|
+
return structuredClone(value);
|
|
29
|
+
const copy = {};
|
|
30
|
+
for (const key of Object.keys(value)) {
|
|
31
|
+
copy[key] = cloneRecordValue(value[key]);
|
|
32
|
+
}
|
|
33
|
+
return copy;
|
|
34
|
+
}
|
|
4
35
|
const postingTextEncoder = new TextEncoder();
|
|
5
36
|
const RESOURCE_EXPIRY_SWEEP_ITEMS = 64;
|
|
6
37
|
/** Leaves ample canonical-wire overhead below the 4 MiB metadata frame ceiling. */
|
|
@@ -1153,12 +1184,12 @@ export class RecordCore {
|
|
|
1153
1184
|
if (this.#tempOwners.size >= MAX_ACTIVE_TEMP_OWNERS) {
|
|
1154
1185
|
throw new StorageResourceLimitError("temp owner", this.#tempOwners.size + 1, MAX_ACTIVE_TEMP_OWNERS);
|
|
1155
1186
|
}
|
|
1156
|
-
this.#tempOwners.set(record.ownerId,
|
|
1187
|
+
this.#tempOwners.set(record.ownerId, cloneRecord(record));
|
|
1157
1188
|
}
|
|
1158
1189
|
getTempOwner(ownerId) {
|
|
1159
1190
|
validateId(ownerId);
|
|
1160
1191
|
const record = this.#tempOwners.get(ownerId);
|
|
1161
|
-
return record === undefined ? undefined :
|
|
1192
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
1162
1193
|
}
|
|
1163
1194
|
renewTempOwner(input) {
|
|
1164
1195
|
validateId(input.ownerId);
|
|
@@ -1176,7 +1207,7 @@ export class RecordCore {
|
|
|
1176
1207
|
revision: safeWholeIncrement(record.revision, "Temp owner revision"),
|
|
1177
1208
|
};
|
|
1178
1209
|
this.#tempOwners.set(input.ownerId, renewed);
|
|
1179
|
-
return
|
|
1210
|
+
return cloneRecord(renewed);
|
|
1180
1211
|
}
|
|
1181
1212
|
listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit) {
|
|
1182
1213
|
const page = boundedExpiryPage(this.#tempOwners, expiresAtCutoff, afterCursor, limit, (record) => record.ownerId, "Temp owner page");
|
|
@@ -1278,7 +1309,7 @@ export class RecordCore {
|
|
|
1278
1309
|
}
|
|
1279
1310
|
/** Applies a table whose complete admission checks already passed. */
|
|
1280
1311
|
#installTableRecord(record) {
|
|
1281
|
-
this.#setTable(
|
|
1312
|
+
this.#setTable(cloneRecord(record));
|
|
1282
1313
|
this.#tableIdsByName.set(record.name, record.id);
|
|
1283
1314
|
if (record.uniqueKeyColumnId !== undefined)
|
|
1284
1315
|
this.#uniqueKeys.set(record.id, new OrderedStringSet());
|
|
@@ -1305,7 +1336,7 @@ export class RecordCore {
|
|
|
1305
1336
|
}
|
|
1306
1337
|
getTable(id) {
|
|
1307
1338
|
const record = this.#tables.get(id);
|
|
1308
|
-
return record === undefined ? undefined :
|
|
1339
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
1309
1340
|
}
|
|
1310
1341
|
updateTable(id, expectedRevision, update) {
|
|
1311
1342
|
const record = this.#tables.get(id);
|
|
@@ -1368,18 +1399,16 @@ export class RecordCore {
|
|
|
1368
1399
|
validateTableView(nextView ?? undefined);
|
|
1369
1400
|
const updated = {
|
|
1370
1401
|
...base,
|
|
1371
|
-
columns: update.columns === undefined ? record.columns :
|
|
1372
|
-
...(nextFts === null || nextFts === undefined
|
|
1373
|
-
? {}
|
|
1374
|
-
: { ftsColumns: structuredClone(nextFts) }),
|
|
1402
|
+
columns: update.columns === undefined ? record.columns : cloneRecord(update.columns),
|
|
1403
|
+
...(nextFts === null || nextFts === undefined ? {} : { ftsColumns: cloneRecord(nextFts) }),
|
|
1375
1404
|
...(nextSecondary === null || nextSecondary === undefined
|
|
1376
1405
|
? {}
|
|
1377
|
-
: { secondaryIndexes:
|
|
1406
|
+
: { secondaryIndexes: cloneRecord(nextSecondary) }),
|
|
1378
1407
|
...(nextTriggers === null || nextTriggers === undefined
|
|
1379
1408
|
? {}
|
|
1380
|
-
: { triggers:
|
|
1381
|
-
...(nextForeignKeys === undefined ? {} : { foreignKeys:
|
|
1382
|
-
...(nextView === null || nextView === undefined ? {} : { view:
|
|
1409
|
+
: { triggers: cloneRecord(nextTriggers) }),
|
|
1410
|
+
...(nextForeignKeys === undefined ? {} : { foreignKeys: cloneRecord(nextForeignKeys) }),
|
|
1411
|
+
...(nextView === null || nextView === undefined ? {} : { view: cloneRecord(nextView) }),
|
|
1383
1412
|
revision: safeWholeIncrement(expectedRevision, "Table revision"),
|
|
1384
1413
|
};
|
|
1385
1414
|
validateSecondaryIndexes(updated);
|
|
@@ -1440,7 +1469,7 @@ export class RecordCore {
|
|
|
1440
1469
|
this.#setTable(updated);
|
|
1441
1470
|
this.#catalogEpoch = nextCatalogEpoch;
|
|
1442
1471
|
this.#schemaEpoch = nextSchemaEpoch;
|
|
1443
|
-
return
|
|
1472
|
+
return cloneRecord(updated);
|
|
1444
1473
|
}
|
|
1445
1474
|
removeTable(id, expectedRevision, options = {}) {
|
|
1446
1475
|
const record = this.#tables.get(id);
|
|
@@ -1526,7 +1555,7 @@ export class RecordCore {
|
|
|
1526
1555
|
this.#deleteTableRecords(table);
|
|
1527
1556
|
this.#catalogEpoch = nextCatalogEpoch;
|
|
1528
1557
|
this.#schemaEpoch = nextSchemaEpoch;
|
|
1529
|
-
return
|
|
1558
|
+
return cloneRecord(manifest);
|
|
1530
1559
|
}
|
|
1531
1560
|
dropTableColumn(input) {
|
|
1532
1561
|
validateId(input.tableId);
|
|
@@ -1603,7 +1632,7 @@ export class RecordCore {
|
|
|
1603
1632
|
this.#deleteFtsColumn(input.tableId, input.columnId);
|
|
1604
1633
|
this.#catalogEpoch = nextCatalogEpoch;
|
|
1605
1634
|
this.#schemaEpoch = nextSchemaEpoch;
|
|
1606
|
-
return
|
|
1635
|
+
return cloneRecord(manifest);
|
|
1607
1636
|
}
|
|
1608
1637
|
#assertTableNotInUse(id) {
|
|
1609
1638
|
const target = this.#tables.get(id);
|
|
@@ -1675,7 +1704,7 @@ export class RecordCore {
|
|
|
1675
1704
|
}
|
|
1676
1705
|
validateFtsBaseInput(input, "Full-text base");
|
|
1677
1706
|
const key = `${tableId}/${columnId}`;
|
|
1678
|
-
this.#ftsBases.set(key,
|
|
1707
|
+
this.#ftsBases.set(key, cloneRecord(input));
|
|
1679
1708
|
const deltas = this.#ftsDeltas.get(key);
|
|
1680
1709
|
if (deltas !== undefined) {
|
|
1681
1710
|
for (const version of [...deltas.keys()]) {
|
|
@@ -1777,7 +1806,7 @@ export class RecordCore {
|
|
|
1777
1806
|
continue;
|
|
1778
1807
|
}
|
|
1779
1808
|
deltas.set(version, {
|
|
1780
|
-
postings:
|
|
1809
|
+
postings: cloneRecord(column.postings),
|
|
1781
1810
|
totalTokens: column.totalTokens,
|
|
1782
1811
|
});
|
|
1783
1812
|
this.#ftsDeltas.set(key, deltas);
|
|
@@ -1798,7 +1827,7 @@ export class RecordCore {
|
|
|
1798
1827
|
continue;
|
|
1799
1828
|
deltaChunkCount += 1;
|
|
1800
1829
|
deltaTokens += delta.totalTokens;
|
|
1801
|
-
chunkLists.push(
|
|
1830
|
+
chunkLists.push(cloneRecord(delta.postings));
|
|
1802
1831
|
}
|
|
1803
1832
|
return { chunkLists, deltaChunkCount, deltaTokens };
|
|
1804
1833
|
}
|
|
@@ -1940,7 +1969,7 @@ export class RecordCore {
|
|
|
1940
1969
|
this.#manifestBlocks.delete(id);
|
|
1941
1970
|
for (const id of reclaimedTransactionIds)
|
|
1942
1971
|
this.#transactions.delete(id);
|
|
1943
|
-
this.#garbageCollectionJobs.set(updated.id,
|
|
1972
|
+
this.#garbageCollectionJobs.set(updated.id, cloneRecord(updated));
|
|
1944
1973
|
}
|
|
1945
1974
|
getTableByName(name) {
|
|
1946
1975
|
const id = this.#tableIdsByName.get(name);
|
|
@@ -1953,7 +1982,7 @@ export class RecordCore {
|
|
|
1953
1982
|
listTables() {
|
|
1954
1983
|
return [...this.#tables.values()]
|
|
1955
1984
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
1956
|
-
.map((record) =>
|
|
1985
|
+
.map((record) => cloneRecord(record));
|
|
1957
1986
|
}
|
|
1958
1987
|
getSegment(id) {
|
|
1959
1988
|
const record = this.#segments.get(id);
|
|
@@ -2138,7 +2167,7 @@ export class RecordCore {
|
|
|
2138
2167
|
this.#replaceSegments([desired, ...rebasedOldOwnerSegments]);
|
|
2139
2168
|
this.#setTransaction(updatedOldOwner);
|
|
2140
2169
|
this.#setTransaction(updatedReplacement);
|
|
2141
|
-
return
|
|
2170
|
+
return cloneRecord(updatedReplacement);
|
|
2142
2171
|
}
|
|
2143
2172
|
reserveRowIds(tableId, count) {
|
|
2144
2173
|
validateId(tableId);
|
|
@@ -2253,7 +2282,7 @@ export class RecordCore {
|
|
|
2253
2282
|
updatedAt: input.createdAt,
|
|
2254
2283
|
};
|
|
2255
2284
|
if (deepRecordEqual(current.record, expected))
|
|
2256
|
-
return
|
|
2285
|
+
return cloneRecord(current.record);
|
|
2257
2286
|
throw new UniqueKeyBuildConflictError(input.buildId, "another live owner exists");
|
|
2258
2287
|
}
|
|
2259
2288
|
if (current?.record.state === "completed") {
|
|
@@ -2282,12 +2311,12 @@ export class RecordCore {
|
|
|
2282
2311
|
updatedAt: input.createdAt,
|
|
2283
2312
|
};
|
|
2284
2313
|
this.#setUniqueKeyBuild({ record, chunks: [], tokens: new Set(), completedInput: null }, input.buildId);
|
|
2285
|
-
return
|
|
2314
|
+
return cloneRecord(record);
|
|
2286
2315
|
}
|
|
2287
2316
|
getUniqueKeyBuild(buildId) {
|
|
2288
2317
|
validateId(buildId);
|
|
2289
2318
|
const state = this.#uniqueKeyBuilds.get(buildId);
|
|
2290
|
-
return state === undefined ? undefined :
|
|
2319
|
+
return state === undefined ? undefined : cloneRecord(state.record);
|
|
2291
2320
|
}
|
|
2292
2321
|
renewUniqueKeyBuild(input) {
|
|
2293
2322
|
validateId(input.buildId);
|
|
@@ -2301,13 +2330,13 @@ export class RecordCore {
|
|
|
2301
2330
|
throw new UniqueKeyBuildConflictError(input.buildId, "ownership expired or changed");
|
|
2302
2331
|
}
|
|
2303
2332
|
if (input.expiresAt <= state.record.expiresAt)
|
|
2304
|
-
return
|
|
2333
|
+
return cloneRecord(state.record);
|
|
2305
2334
|
const next = {
|
|
2306
2335
|
...state,
|
|
2307
2336
|
record: { ...state.record, expiresAt: input.expiresAt, updatedAt: input.updatedAt },
|
|
2308
2337
|
};
|
|
2309
2338
|
this.#setUniqueKeyBuild(next, input.buildId);
|
|
2310
|
-
return
|
|
2339
|
+
return cloneRecord(next.record);
|
|
2311
2340
|
}
|
|
2312
2341
|
appendUniqueKeyBuildChunk(input) {
|
|
2313
2342
|
validateId(input.buildId);
|
|
@@ -2326,7 +2355,7 @@ export class RecordCore {
|
|
|
2326
2355
|
}
|
|
2327
2356
|
if (input.ordinal < state.record.nextOrdinal) {
|
|
2328
2357
|
if (deepRecordEqual(state.chunks[input.ordinal], input.keyTokens)) {
|
|
2329
|
-
return
|
|
2358
|
+
return cloneRecord(state.record);
|
|
2330
2359
|
}
|
|
2331
2360
|
throw new UniqueKeyBuildConflictError(input.buildId, "chunk replay changed");
|
|
2332
2361
|
}
|
|
@@ -2357,7 +2386,7 @@ export class RecordCore {
|
|
|
2357
2386
|
completedInput: null,
|
|
2358
2387
|
};
|
|
2359
2388
|
this.#setUniqueKeyBuild(next, input.buildId);
|
|
2360
|
-
return
|
|
2389
|
+
return cloneRecord(next.record);
|
|
2361
2390
|
}
|
|
2362
2391
|
finishUniqueKeyBuild(input) {
|
|
2363
2392
|
validateId(input.buildId);
|
|
@@ -2372,7 +2401,7 @@ export class RecordCore {
|
|
|
2372
2401
|
const table = this.#tables.get(state.record.tableId);
|
|
2373
2402
|
if (table === undefined)
|
|
2374
2403
|
throw new UniqueKeyBuildConflictError(input.buildId, "table was removed");
|
|
2375
|
-
return
|
|
2404
|
+
return cloneRecord(table);
|
|
2376
2405
|
}
|
|
2377
2406
|
if (state?.record.state !== "active" ||
|
|
2378
2407
|
state.record.ownerId !== input.ownerId ||
|
|
@@ -2421,11 +2450,11 @@ export class RecordCore {
|
|
|
2421
2450
|
record: completedRecord,
|
|
2422
2451
|
chunks: [],
|
|
2423
2452
|
tokens: state.tokens,
|
|
2424
|
-
completedInput:
|
|
2453
|
+
completedInput: cloneRecord(input),
|
|
2425
2454
|
}, input.buildId);
|
|
2426
2455
|
this.#catalogEpoch = nextCatalogEpoch;
|
|
2427
2456
|
this.#schemaEpoch = nextSchemaEpoch;
|
|
2428
|
-
return
|
|
2457
|
+
return cloneRecord(updated);
|
|
2429
2458
|
}
|
|
2430
2459
|
abortUniqueKeyBuild(input) {
|
|
2431
2460
|
validateId(input.buildId);
|
|
@@ -2550,15 +2579,15 @@ export class RecordCore {
|
|
|
2550
2579
|
}
|
|
2551
2580
|
getCurrentManifest() {
|
|
2552
2581
|
const manifest = this.#currentVersion === null ? undefined : this.#manifests.get(this.#currentVersion);
|
|
2553
|
-
return manifest === undefined ? undefined :
|
|
2582
|
+
return manifest === undefined ? undefined : cloneRecord(manifest);
|
|
2554
2583
|
}
|
|
2555
2584
|
getManifest(version) {
|
|
2556
2585
|
const manifest = this.#manifests.get(version);
|
|
2557
|
-
return manifest === undefined ? undefined :
|
|
2586
|
+
return manifest === undefined ? undefined : cloneRecord(manifest);
|
|
2558
2587
|
}
|
|
2559
2588
|
listManifestPage(afterVersion, limit) {
|
|
2560
2589
|
validatePageLimit(limit);
|
|
2561
|
-
const records = boundedRecordPage(this.#manifests, afterVersion, limit).map((manifest) =>
|
|
2590
|
+
const records = boundedRecordPage(this.#manifests, afterVersion, limit).map((manifest) => cloneRecord(manifest));
|
|
2562
2591
|
return {
|
|
2563
2592
|
records,
|
|
2564
2593
|
nextCursor: records.length === limit ? (records.at(-1)?.version ?? null) : null,
|
|
@@ -2621,13 +2650,13 @@ export class RecordCore {
|
|
|
2621
2650
|
this.#assertTableForeignKeys(pending.record);
|
|
2622
2651
|
}
|
|
2623
2652
|
const record = {
|
|
2624
|
-
...
|
|
2653
|
+
...cloneRecord(input.record),
|
|
2625
2654
|
snapshotVersion: this.#currentVersion,
|
|
2626
2655
|
schemaEpochGuard: this.#schemaEpoch,
|
|
2627
2656
|
...(pending === undefined
|
|
2628
2657
|
? {}
|
|
2629
2658
|
: {
|
|
2630
|
-
pendingTable:
|
|
2659
|
+
pendingTable: cloneRecord(pending.record),
|
|
2631
2660
|
pendingTableNextRowId: pending.nextRowId,
|
|
2632
2661
|
catalogEpochGuard: pending.expectedCatalogEpoch,
|
|
2633
2662
|
}),
|
|
@@ -2680,14 +2709,14 @@ export class RecordCore {
|
|
|
2680
2709
|
this.#nextAutoIncrement.set(autoCounterKey, autoIncrementValues.endExclusive);
|
|
2681
2710
|
}
|
|
2682
2711
|
return {
|
|
2683
|
-
record:
|
|
2712
|
+
record: cloneRecord(record),
|
|
2684
2713
|
...(rowIds === undefined ? {} : { rowIds }),
|
|
2685
2714
|
...(autoIncrementValues === undefined ? {} : { autoIncrementValues }),
|
|
2686
2715
|
};
|
|
2687
2716
|
}
|
|
2688
2717
|
createTransaction(record) {
|
|
2689
2718
|
const normalized = {
|
|
2690
|
-
...
|
|
2719
|
+
...cloneRecord(record),
|
|
2691
2720
|
...(record.status === "active" && record.schemaEpochGuard === undefined
|
|
2692
2721
|
? { schemaEpochGuard: this.#schemaEpoch }
|
|
2693
2722
|
: {}),
|
|
@@ -2728,7 +2757,7 @@ export class RecordCore {
|
|
|
2728
2757
|
}
|
|
2729
2758
|
getTransaction(id) {
|
|
2730
2759
|
const record = this.#transactions.get(id);
|
|
2731
|
-
return record === undefined ? undefined :
|
|
2760
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
2732
2761
|
}
|
|
2733
2762
|
getTransactions(ids) {
|
|
2734
2763
|
assertStorageBulkReadItems(ids, "Transaction read");
|
|
@@ -2736,12 +2765,12 @@ export class RecordCore {
|
|
|
2736
2765
|
validateId(id);
|
|
2737
2766
|
return ids.map((id) => {
|
|
2738
2767
|
const record = this.#transactions.get(id);
|
|
2739
|
-
return record === undefined ? undefined :
|
|
2768
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
2740
2769
|
});
|
|
2741
2770
|
}
|
|
2742
2771
|
listTransactionPage(afterId, limit) {
|
|
2743
2772
|
validatePageLimit(limit);
|
|
2744
|
-
const records = boundedRecordPage(this.#transactions, afterId, limit).map((record) =>
|
|
2773
|
+
const records = boundedRecordPage(this.#transactions, afterId, limit).map((record) => cloneRecord(record));
|
|
2745
2774
|
return { records, nextCursor: records.length === limit ? (records.at(-1)?.id ?? null) : null };
|
|
2746
2775
|
}
|
|
2747
2776
|
updateTransaction(id, expectedRevision, update) {
|
|
@@ -2756,7 +2785,7 @@ export class RecordCore {
|
|
|
2756
2785
|
}
|
|
2757
2786
|
this.#assertPendingArtifactsAvailable(updated, update.pendingBlockIds !== undefined, update.pendingSegmentIds !== undefined);
|
|
2758
2787
|
this.#setTransaction(updated);
|
|
2759
|
-
return
|
|
2788
|
+
return cloneRecord(updated);
|
|
2760
2789
|
}
|
|
2761
2790
|
renewTransaction(input) {
|
|
2762
2791
|
validateId(input.transactionId);
|
|
@@ -2790,7 +2819,7 @@ export class RecordCore {
|
|
|
2790
2819
|
updatedAt: input.updatedAt,
|
|
2791
2820
|
});
|
|
2792
2821
|
this.#setTransaction(updated);
|
|
2793
|
-
return
|
|
2822
|
+
return cloneRecord(updated);
|
|
2794
2823
|
}
|
|
2795
2824
|
/**
|
|
2796
2825
|
* Validates and applies the record half of staging: the journal update and the segment
|
|
@@ -2806,7 +2835,7 @@ export class RecordCore {
|
|
|
2806
2835
|
this.#assertTransactionResourceTransition(plan.updated, blockByteLengths);
|
|
2807
2836
|
this.#replaceSegments(plan.segments);
|
|
2808
2837
|
this.#setTransaction(plan.updated, blockByteLengths);
|
|
2809
|
-
return
|
|
2838
|
+
return cloneRecord(plan.updated);
|
|
2810
2839
|
}
|
|
2811
2840
|
/**
|
|
2812
2841
|
* Runs the complete staging preflight without changing record state. Persistent adapters use
|
|
@@ -2991,7 +3020,7 @@ export class RecordCore {
|
|
|
2991
3020
|
const updated = updateTransactionRecord(current, update);
|
|
2992
3021
|
this.#replaceSegments([], input.removeSegmentIds);
|
|
2993
3022
|
this.#setTransaction(updated);
|
|
2994
|
-
return
|
|
3023
|
+
return cloneRecord(updated);
|
|
2995
3024
|
}
|
|
2996
3025
|
#validateStagedSegment(segment, transactionId, journalBlockIds) {
|
|
2997
3026
|
if (segment.transactionId !== transactionId) {
|
|
@@ -3085,7 +3114,7 @@ export class RecordCore {
|
|
|
3085
3114
|
throw new WriteConflictError(input.expectedManifestVersion, this.#currentVersion);
|
|
3086
3115
|
}
|
|
3087
3116
|
base = {
|
|
3088
|
-
...
|
|
3117
|
+
...cloneRecord(input.transaction.record),
|
|
3089
3118
|
snapshotVersion: input.expectedManifestVersion,
|
|
3090
3119
|
};
|
|
3091
3120
|
validateTransactionRuntimeRecord(base, "Transaction");
|
|
@@ -3532,7 +3561,7 @@ export class RecordCore {
|
|
|
3532
3561
|
columnId: column.columnId,
|
|
3533
3562
|
count: this.#ftsDeltas.get(`${changes.tableId}/${column.columnId}`)?.size ?? 0,
|
|
3534
3563
|
})));
|
|
3535
|
-
return
|
|
3564
|
+
return cloneRecord({
|
|
3536
3565
|
...manifest,
|
|
3537
3566
|
...(ftsDeltaCounts.length === 0 ? {} : { ftsDeltaCounts }),
|
|
3538
3567
|
});
|
|
@@ -3549,21 +3578,21 @@ export class RecordCore {
|
|
|
3549
3578
|
if (this.#leases.size >= MAX_ACTIVE_LEASES) {
|
|
3550
3579
|
throw new StorageResourceLimitError("lease", this.#leases.size + 1, MAX_ACTIVE_LEASES);
|
|
3551
3580
|
}
|
|
3552
|
-
this.#leases.set(record.id,
|
|
3581
|
+
this.#leases.set(record.id, cloneRecord(record));
|
|
3553
3582
|
}
|
|
3554
3583
|
getLease(id) {
|
|
3555
3584
|
const record = this.#leases.get(id);
|
|
3556
|
-
return record === undefined ? undefined :
|
|
3585
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
3557
3586
|
}
|
|
3558
3587
|
listLeases() {
|
|
3559
3588
|
return [...this.#leases.values()]
|
|
3560
3589
|
.sort((left, right) => left.id.localeCompare(right.id))
|
|
3561
|
-
.map((record) =>
|
|
3590
|
+
.map((record) => cloneRecord(record));
|
|
3562
3591
|
}
|
|
3563
3592
|
listExpiredLeasePage(expiresAtCutoff, afterCursor, limit) {
|
|
3564
3593
|
const page = boundedExpiryPage(this.#leases, expiresAtCutoff, afterCursor, limit, (record) => record.id, "Lease page");
|
|
3565
3594
|
return {
|
|
3566
|
-
records: page.records.map((record) =>
|
|
3595
|
+
records: page.records.map((record) => cloneRecord(record)),
|
|
3567
3596
|
nextCursor: page.nextCursor,
|
|
3568
3597
|
};
|
|
3569
3598
|
}
|
|
@@ -3583,7 +3612,7 @@ export class RecordCore {
|
|
|
3583
3612
|
revision: safeWholeIncrement(record.revision, "Lease revision"),
|
|
3584
3613
|
};
|
|
3585
3614
|
this.#leases.set(input.id, renewed);
|
|
3586
|
-
return
|
|
3615
|
+
return cloneRecord(renewed);
|
|
3587
3616
|
}
|
|
3588
3617
|
moveLease(input) {
|
|
3589
3618
|
const cutoff = validateBoundedExpiration(input.expiresAtCutoff, input.expiresAt, "Lease", MAX_LEASE_TTL_MS);
|
|
@@ -3603,7 +3632,7 @@ export class RecordCore {
|
|
|
3603
3632
|
revision: safeWholeIncrement(record.revision, "Lease revision"),
|
|
3604
3633
|
};
|
|
3605
3634
|
this.#leases.set(input.id, moved);
|
|
3606
|
-
return
|
|
3635
|
+
return cloneRecord(moved);
|
|
3607
3636
|
}
|
|
3608
3637
|
removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff) {
|
|
3609
3638
|
const cutoff = Date.parse(expiresAtCutoff);
|
|
@@ -3654,17 +3683,17 @@ export class RecordCore {
|
|
|
3654
3683
|
}
|
|
3655
3684
|
getCompactionJob(id) {
|
|
3656
3685
|
const record = this.#compactionJobs.get(id);
|
|
3657
|
-
return record === undefined ? undefined :
|
|
3686
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
3658
3687
|
}
|
|
3659
3688
|
listCompactionJobs(tableId) {
|
|
3660
3689
|
return [...this.#compactionJobs.values()]
|
|
3661
3690
|
.filter((record) => tableId === undefined || record.tableId === tableId)
|
|
3662
3691
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
|
|
3663
|
-
.map((record) =>
|
|
3692
|
+
.map((record) => cloneRecord(record));
|
|
3664
3693
|
}
|
|
3665
3694
|
listCompactionJobPage(afterId, limit) {
|
|
3666
3695
|
validatePageLimit(limit);
|
|
3667
|
-
const records = boundedRecordPage(this.#compactionJobs, afterId, limit).map((record) =>
|
|
3696
|
+
const records = boundedRecordPage(this.#compactionJobs, afterId, limit).map((record) => cloneRecord(record));
|
|
3668
3697
|
return { records, nextCursor: records.length === limit ? (records.at(-1)?.id ?? null) : null };
|
|
3669
3698
|
}
|
|
3670
3699
|
updateCompactionJob(id, expectedRevision, update) {
|
|
@@ -3679,7 +3708,7 @@ export class RecordCore {
|
|
|
3679
3708
|
this.#assertCompactionJobReferences(updated);
|
|
3680
3709
|
this.#assertTerminalCompactionJobTransition(current, updated);
|
|
3681
3710
|
this.#compactionJobs.set(id, updated);
|
|
3682
|
-
return
|
|
3711
|
+
return cloneRecord(updated);
|
|
3683
3712
|
}
|
|
3684
3713
|
#assertCompactionJobReferences(job) {
|
|
3685
3714
|
// Cancelled/aborted jobs are retained diagnostics rather than live physical roots.
|
|
@@ -3746,7 +3775,7 @@ export class RecordCore {
|
|
|
3746
3775
|
throw new CompactionJobConflictError(id, expectedRevision, current?.revision ?? null);
|
|
3747
3776
|
}
|
|
3748
3777
|
if (isTerminalCompactionJob(current)) {
|
|
3749
|
-
return
|
|
3778
|
+
return cloneRecord(current);
|
|
3750
3779
|
}
|
|
3751
3780
|
const transaction = current.transactionId === null ? undefined : this.#transactions.get(current.transactionId);
|
|
3752
3781
|
if (transaction?.status === "committed") {
|
|
@@ -3761,7 +3790,7 @@ export class RecordCore {
|
|
|
3761
3790
|
});
|
|
3762
3791
|
this.#assertTerminalCompactionJobTransition(current, published);
|
|
3763
3792
|
this.#compactionJobs.set(id, published);
|
|
3764
|
-
return
|
|
3793
|
+
return cloneRecord(published);
|
|
3765
3794
|
}
|
|
3766
3795
|
const cancelled = updateCompactionJobRecord(current, {
|
|
3767
3796
|
state: "cancelled",
|
|
@@ -3780,7 +3809,7 @@ export class RecordCore {
|
|
|
3780
3809
|
this.#setTransaction(abortedTransaction);
|
|
3781
3810
|
}
|
|
3782
3811
|
this.#compactionJobs.set(id, cancelled);
|
|
3783
|
-
return
|
|
3812
|
+
return cloneRecord(cancelled);
|
|
3784
3813
|
}
|
|
3785
3814
|
removeCompactionJob(id) {
|
|
3786
3815
|
if (!this.canRemoveCompactionJob(id))
|
|
@@ -3850,7 +3879,7 @@ export class RecordCore {
|
|
|
3850
3879
|
assertGarbageCollectionCandidateProvenance(record, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
|
|
3851
3880
|
this.#pruneOneCompletedGarbageCollectionJob();
|
|
3852
3881
|
this.#garbageCollectionJobs.set(record.id, record);
|
|
3853
|
-
return
|
|
3882
|
+
return cloneRecord(record);
|
|
3854
3883
|
}
|
|
3855
3884
|
updateGarbageCollectionPlanning(input) {
|
|
3856
3885
|
const current = this.#garbageCollectionJobs.get(input.jobId);
|
|
@@ -3860,20 +3889,20 @@ export class RecordCore {
|
|
|
3860
3889
|
const updated = updateGarbageCollectionPlanningRecord(current, input);
|
|
3861
3890
|
assertGarbageCollectionCandidateProvenance(updated, this.#manifests, this.#manifestBlocks, this.#segments, this.#transactions, this.#roots);
|
|
3862
3891
|
this.#garbageCollectionJobs.set(updated.id, updated);
|
|
3863
|
-
return
|
|
3892
|
+
return cloneRecord(updated);
|
|
3864
3893
|
}
|
|
3865
3894
|
getGarbageCollectionJob(id) {
|
|
3866
3895
|
const record = this.#garbageCollectionJobs.get(id);
|
|
3867
|
-
return record === undefined ? undefined :
|
|
3896
|
+
return record === undefined ? undefined : cloneRecord(record);
|
|
3868
3897
|
}
|
|
3869
3898
|
listGarbageCollectionJobs() {
|
|
3870
3899
|
return [...this.#garbageCollectionJobs.values()]
|
|
3871
3900
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
|
|
3872
|
-
.map((record) =>
|
|
3901
|
+
.map((record) => cloneRecord(record));
|
|
3873
3902
|
}
|
|
3874
3903
|
listGarbageCollectionJobPage(afterId, limit) {
|
|
3875
3904
|
validatePageLimit(limit);
|
|
3876
|
-
const records = boundedRecordPage(this.#garbageCollectionJobs, afterId, limit).map((record) =>
|
|
3905
|
+
const records = boundedRecordPage(this.#garbageCollectionJobs, afterId, limit).map((record) => cloneRecord(record));
|
|
3877
3906
|
return { records, nextCursor: records.length === limit ? (records.at(-1)?.id ?? null) : null };
|
|
3878
3907
|
}
|
|
3879
3908
|
/**
|
|
@@ -4048,7 +4077,7 @@ export class RecordCore {
|
|
|
4048
4077
|
reclaimedTransactionIds.forEach((id) => this.#transactions.delete(id));
|
|
4049
4078
|
this.#garbageCollectionJobs.set(updated.id, updated);
|
|
4050
4079
|
return {
|
|
4051
|
-
job:
|
|
4080
|
+
job: cloneRecord(updated),
|
|
4052
4081
|
prunedManifestVersions,
|
|
4053
4082
|
alreadyPrunedManifestVersions,
|
|
4054
4083
|
retainedManifestVersions,
|
|
@@ -4095,7 +4124,7 @@ export class RecordCore {
|
|
|
4095
4124
|
});
|
|
4096
4125
|
yield {
|
|
4097
4126
|
kind: "table",
|
|
4098
|
-
record:
|
|
4127
|
+
record: cloneRecord(table),
|
|
4099
4128
|
nextRowId: this.#nextRowIds.get(table.id) ?? 1n,
|
|
4100
4129
|
autoIncrement,
|
|
4101
4130
|
};
|
|
@@ -4133,7 +4162,7 @@ export class RecordCore {
|
|
|
4133
4162
|
yield {
|
|
4134
4163
|
kind: "transaction",
|
|
4135
4164
|
record: {
|
|
4136
|
-
...
|
|
4165
|
+
...cloneRecord(record),
|
|
4137
4166
|
pendingBlockIds: [],
|
|
4138
4167
|
pendingSegmentIds: [],
|
|
4139
4168
|
},
|
|
@@ -4305,7 +4334,7 @@ export class RecordCore {
|
|
|
4305
4334
|
const manifest = this.#manifests.get(version);
|
|
4306
4335
|
if (manifest === undefined)
|
|
4307
4336
|
throw new SnapshotManifestMissingError(version);
|
|
4308
|
-
return
|
|
4337
|
+
return cloneRecord(manifest);
|
|
4309
4338
|
}
|
|
4310
4339
|
/** O(1) counters for diagnostics; callers never need to clone the full durable state. */
|
|
4311
4340
|
storageRecordStats() {
|
|
@@ -4404,7 +4433,7 @@ export class RecordCore {
|
|
|
4404
4433
|
throw new Error("Snapshot tables are not in canonical ID order");
|
|
4405
4434
|
}
|
|
4406
4435
|
previousTableId = item.record.id;
|
|
4407
|
-
const record =
|
|
4436
|
+
const record = cloneRecord(item.record);
|
|
4408
4437
|
validateTableRuntimeRecord(record, `Snapshot table ${item.record.id}`);
|
|
4409
4438
|
if (trial.#tables.has(record.id) || trial.#tableIdsByName.has(record.name)) {
|
|
4410
4439
|
throw new Error(`Snapshot repeats a table identity: ${record.id}`);
|
|
@@ -4448,7 +4477,7 @@ export class RecordCore {
|
|
|
4448
4477
|
if (record.status !== "committed" || record.committedVersion === null) {
|
|
4449
4478
|
throw new Error(`Snapshot transaction is not committed: ${record.id}`);
|
|
4450
4479
|
}
|
|
4451
|
-
trial.#setTransaction(
|
|
4480
|
+
trial.#setTransaction(cloneRecord(record));
|
|
4452
4481
|
break;
|
|
4453
4482
|
}
|
|
4454
4483
|
case "unique-generation": {
|
|
@@ -4510,7 +4539,7 @@ export class RecordCore {
|
|
|
4510
4539
|
throw new Error(`Snapshot posting chunk is out of order: ${item.storageColumnId}`);
|
|
4511
4540
|
}
|
|
4512
4541
|
retainAcceleratorChunk(item);
|
|
4513
|
-
const chunk =
|
|
4542
|
+
const chunk = cloneRecord(item.postings);
|
|
4514
4543
|
if (posting.previousTerm !== undefined &&
|
|
4515
4544
|
chunk[0] !== undefined &&
|
|
4516
4545
|
chunk[0].term <= posting.previousTerm) {
|
|
@@ -4671,7 +4700,7 @@ export class RecordCore {
|
|
|
4671
4700
|
}
|
|
4672
4701
|
/** Replaces the whole record state with a dump's content. */
|
|
4673
4702
|
load(state) {
|
|
4674
|
-
const cloned =
|
|
4703
|
+
const cloned = cloneRecord(state);
|
|
4675
4704
|
validateRecordCoreState(cloned, this.#physical);
|
|
4676
4705
|
// A recovery candidate may intentionally discard an unpublished WAL suffix whose physical
|
|
4677
4706
|
// blocks were already reclaimed. Clearing the old candidate state must therefore not need
|
|
@@ -6414,7 +6443,7 @@ export function validateTempRunPageIdentity(ownerId, runId, pageIndex) {
|
|
|
6414
6443
|
}
|
|
6415
6444
|
function emptyGarbageCollectionStep(job) {
|
|
6416
6445
|
return {
|
|
6417
|
-
job:
|
|
6446
|
+
job: cloneRecord(job),
|
|
6418
6447
|
prunedManifestVersions: [],
|
|
6419
6448
|
alreadyPrunedManifestVersions: [],
|
|
6420
6449
|
retainedManifestVersions: [],
|