@minnowdb/core 0.9.1 → 0.10.0
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/auto-store.d.ts +40 -0
- package/dist/engine/auto-store.js +115 -0
- package/dist/engine/buffered-writer.d.ts +2 -0
- package/dist/engine/buffered-writer.js +15 -2
- package/dist/engine/client.d.ts +55 -6
- package/dist/engine/client.js +155 -40
- package/dist/engine/database.d.ts +15 -1
- package/dist/engine/database.js +1085 -227
- package/dist/engine/errors.d.ts +61 -2
- package/dist/engine/errors.js +116 -3
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/live.d.ts +24 -1
- package/dist/engine/live.js +33 -9
- package/dist/engine/scope-write-set.js +36 -0
- package/dist/engine/worker-auto.d.ts +1 -0
- package/dist/engine/worker-auto.js +3 -0
- package/dist/engine/worker-host.d.ts +2 -1
- package/dist/engine/worker-host.js +17 -1
- package/dist/engine/worker-server.d.ts +53 -1
- package/dist/engine/worker-server.js +118 -14
- package/dist/engine/worker-store-auto.js +36 -0
- package/dist/engine/worker-store-opfs.js +3 -2
- package/dist/engine/write-coordinator.js +24 -2
- package/dist/storage/indexeddb.js +494 -207
- package/dist/storage/opfs/leader.js +201 -14
- package/dist/storage/opfs/rpc.js +23 -43
- package/dist/storage/opfs/store.d.ts +20 -0
- package/dist/storage/opfs/store.js +479 -60
- package/dist/storage/toolkit/record-core.js +67 -38
- package/dist/storage/toolkit/wire.d.ts +1 -1
- package/dist/storage/toolkit/wire.js +1 -1
- package/dist/storage/types.d.ts +29 -8
- package/dist/storage/types.js +27 -16
- package/dist/testing/opfs-shim.js +14 -6
- package/dist/transactions/index.d.ts +12 -0
- package/dist/transactions/index.js +83 -23
- package/dist/worker-protocol/index.d.ts +50 -2
- package/dist/worker-protocol/index.js +106 -4
- package/package.json +7 -2
package/dist/engine/database.js
CHANGED
|
@@ -7,6 +7,7 @@ import { crossJoinPlan } from "../plan/model.js";
|
|
|
7
7
|
import { definedVectors, toColumnarBatch } from "./batch.js";
|
|
8
8
|
import { ArtifactCache } from "./artifact-cache.js";
|
|
9
9
|
import { estimateBatchBytes, estimateRowBytes, estimateValuesBytes } from "./byte-estimates.js";
|
|
10
|
+
import { composeScopeEffect, netScopeEffect, scopeWriteSetTestHooks } from "./scope-write-set.js";
|
|
10
11
|
import { throwIfAborted } from "./cancellation.js";
|
|
11
12
|
import { BufferedTableWriter } from "./buffered-writer.js";
|
|
12
13
|
import { attachLifecycleFlush, BufferedTableWriter as BufferedTableWriter2, MAX_BUFFERED_WRITER_PENDING_ADDS } from "./buffered-writer.js";
|
|
@@ -191,6 +192,8 @@ function migrationResult(steps) {
|
|
|
191
192
|
function compactUpdateBatchInput(input) {
|
|
192
193
|
return Object.values(input.changes).includes(void 0) ? { keys: input.keys, changes: definedVectors(input.changes) } : input;
|
|
193
194
|
}
|
|
195
|
+
const SCOPE_KEY_LOOKUP_WINDOW = Math.min(MAX_SQL_PARAMETERS, 1024);
|
|
196
|
+
const SCOPE_DIRECT_STAGE_ROWS = 4096;
|
|
194
197
|
const MAX_GARBAGE_COLLECTION_RETAIN_RECENT_VERSIONS = 1024;
|
|
195
198
|
const REFERENTIAL_CASCADES = 8;
|
|
196
199
|
class TransactionRollback extends Error {
|
|
@@ -586,6 +589,7 @@ class MinnowDatabase {
|
|
|
586
589
|
#spillOwnerLeaseMs;
|
|
587
590
|
#transactionOwnerLeaseMs;
|
|
588
591
|
#createId;
|
|
592
|
+
#onBackgroundError;
|
|
589
593
|
#internalLeaseOwnerId;
|
|
590
594
|
#liveSets = /* @__PURE__ */ new Set();
|
|
591
595
|
#liveProofContexts = /* @__PURE__ */ new Map();
|
|
@@ -664,6 +668,7 @@ class MinnowDatabase {
|
|
|
664
668
|
}
|
|
665
669
|
this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
666
670
|
this.#createId = options.createId ?? (() => crypto.randomUUID());
|
|
671
|
+
this.#onBackgroundError = options.onBackgroundError;
|
|
667
672
|
this.#internalLeaseOwnerId = `minnow/${this.#createId()}`;
|
|
668
673
|
this.#spillOwnerLeaseMs = options.spillOwnerLeaseMs ?? 6e4;
|
|
669
674
|
if (!Number.isSafeInteger(this.#spillOwnerLeaseMs) || this.#spillOwnerLeaseMs <= 0 || this.#spillOwnerLeaseMs > MAX_TEMP_OWNER_TTL_MS) {
|
|
@@ -1906,7 +1911,7 @@ class MinnowDatabase {
|
|
|
1906
1911
|
keyColumnId: keyColumn.id,
|
|
1907
1912
|
level: 0,
|
|
1908
1913
|
logicalOrder: 0,
|
|
1909
|
-
commitOrdinal: transaction.
|
|
1914
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
1910
1915
|
rowIdSpans: [],
|
|
1911
1916
|
createdAt: dateIsoString(this.#now())
|
|
1912
1917
|
};
|
|
@@ -1961,7 +1966,10 @@ class MinnowDatabase {
|
|
|
1961
1966
|
}
|
|
1962
1967
|
}
|
|
1963
1968
|
bufferedWriter(tableName, options = {}) {
|
|
1964
|
-
return new BufferedTableWriter(this.#erased, tableName,
|
|
1969
|
+
return new BufferedTableWriter(this.#erased, tableName, {
|
|
1970
|
+
...options,
|
|
1971
|
+
onBackgroundError: (error, context) => this.#reportBackgroundError(error, context)
|
|
1972
|
+
});
|
|
1965
1973
|
}
|
|
1966
1974
|
async #writeUpdateBatch(table, keyColumn, input, keys) {
|
|
1967
1975
|
await this.#assertCompactionCapacity(table);
|
|
@@ -2059,7 +2067,7 @@ class MinnowDatabase {
|
|
|
2059
2067
|
keyColumnId: keyColumn.id,
|
|
2060
2068
|
level: 0,
|
|
2061
2069
|
logicalOrder: 0,
|
|
2062
|
-
commitOrdinal: transaction.
|
|
2070
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
2063
2071
|
rowIdSpans: [],
|
|
2064
2072
|
createdAt: dateIsoString(this.#now())
|
|
2065
2073
|
};
|
|
@@ -2313,7 +2321,7 @@ class MinnowDatabase {
|
|
|
2313
2321
|
...table.uniqueKeyColumnId === void 0 ? {} : { keyColumnId: table.uniqueKeyColumnId },
|
|
2314
2322
|
level: 0,
|
|
2315
2323
|
logicalOrder: 0,
|
|
2316
|
-
commitOrdinal: transaction.
|
|
2324
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
2317
2325
|
rowIdSpans: [],
|
|
2318
2326
|
createdAt: dateIsoString(this.#now())
|
|
2319
2327
|
};
|
|
@@ -3497,13 +3505,9 @@ class MinnowDatabase {
|
|
|
3497
3505
|
const kind = segment.kind;
|
|
3498
3506
|
return kind !== "insert" && kind !== "base";
|
|
3499
3507
|
})) {
|
|
3500
|
-
if (keyColumn.hidden === true
|
|
3501
|
-
const kind = segment.kind;
|
|
3502
|
-
return kind !== "insert" && kind !== "base" && kind !== "update" && kind !== "delete";
|
|
3503
|
-
})) {
|
|
3508
|
+
if (keyColumn.hidden === true)
|
|
3504
3509
|
return void 0;
|
|
3505
|
-
|
|
3506
|
-
return this.#pointReadThroughDeltas(shape, keyColumn, segments, equalityColumns, projected, snapshot, options);
|
|
3510
|
+
return this.#pointReadThroughDeltas(shape, keyColumn, segments, equalityColumns, projected, snapshot, options, visibility.overlayTransactionId === void 0 ? MAX_POINT_READ_DELTA_BLOCKS : Number.POSITIVE_INFINITY);
|
|
3507
3511
|
}
|
|
3508
3512
|
const searchColumn = keyComponents.find((component) => component.type === "number" || component.type === "datetime") ?? keyComponents.find((component) => component.type === "string") ?? keyComponents[0];
|
|
3509
3513
|
if (searchColumn === void 0)
|
|
@@ -3642,7 +3646,7 @@ class MinnowDatabase {
|
|
|
3642
3646
|
}
|
|
3643
3647
|
return void 0;
|
|
3644
3648
|
}
|
|
3645
|
-
async #pointReadThroughDeltas(shape, keyColumn, segments, equalityColumns, projected, snapshot, options) {
|
|
3649
|
+
async #pointReadThroughDeltas(shape, keyColumn, segments, equalityColumns, projected, snapshot, options, maxDecodedBlocks = MAX_POINT_READ_DELTA_BLOCKS) {
|
|
3646
3650
|
const keyEquality = shape.equalities.find((equality) => equality.column === keyColumn.name);
|
|
3647
3651
|
if (keyEquality === void 0)
|
|
3648
3652
|
return void 0;
|
|
@@ -3670,7 +3674,7 @@ class MinnowDatabase {
|
|
|
3670
3674
|
if (blockIndexes.length === 0)
|
|
3671
3675
|
continue;
|
|
3672
3676
|
decodedBlocks += blockIndexes.length;
|
|
3673
|
-
if (decodedBlocks >
|
|
3677
|
+
if (decodedBlocks > maxDecodedBlocks)
|
|
3674
3678
|
return void 0;
|
|
3675
3679
|
const keyBlocks = await this.#pointReadVectors(blockIndexes.map((blockIndex) => keyBlockIds[blockIndex] ?? ""), snapshot);
|
|
3676
3680
|
for (const [position, blockIndex] of blockIndexes.entries()) {
|
|
@@ -3699,12 +3703,12 @@ class MinnowDatabase {
|
|
|
3699
3703
|
if (blockIds.some((id) => id === void 0))
|
|
3700
3704
|
return void 0;
|
|
3701
3705
|
decodedBlocks += blockIds.length;
|
|
3702
|
-
if (decodedBlocks >
|
|
3706
|
+
if (decodedBlocks > maxDecodedBlocks)
|
|
3703
3707
|
return void 0;
|
|
3704
3708
|
const blocks = await this.#pointReadVectors(blockIds, snapshot);
|
|
3705
3709
|
const values = segment.kind === "update" && current !== void 0 ? current : /* @__PURE__ */ new Map();
|
|
3706
3710
|
if (segment.kind !== "update") {
|
|
3707
|
-
if (current !== void 0)
|
|
3711
|
+
if (current !== void 0 && segment.kind !== "upsert")
|
|
3708
3712
|
return void 0;
|
|
3709
3713
|
values.set(keyColumn.name, vectorValue(keyVector, slot));
|
|
3710
3714
|
}
|
|
@@ -3943,34 +3947,79 @@ class MinnowDatabase {
|
|
|
3943
3947
|
return this.#queryWithReadReservation(query, {}, probe, memoize);
|
|
3944
3948
|
return this.#queryWithReadReservation(query.sql, { params: query.params }, probe, memoize);
|
|
3945
3949
|
}
|
|
3946
|
-
async #liveMaintenancePlan(compiled, probe) {
|
|
3947
|
-
|
|
3950
|
+
async #liveMaintenancePlan(compiled, probe, reasons) {
|
|
3951
|
+
const decline = (reason) => {
|
|
3952
|
+
reasons?.push(reason);
|
|
3948
3953
|
return void 0;
|
|
3949
|
-
}
|
|
3954
|
+
};
|
|
3955
|
+
if (compiled.usesStatementDatetime === true)
|
|
3956
|
+
return decline("reads the statement clock");
|
|
3957
|
+
if (compiled.usesVolatileFunctions === true)
|
|
3958
|
+
return decline("calls a volatile function");
|
|
3959
|
+
if (compiled.usesSequenceCalls === true)
|
|
3960
|
+
return decline("calls a sequence");
|
|
3950
3961
|
const plan = await this.#applyCatalogRewrites(compiled, probe);
|
|
3951
|
-
if (blockCallsFunctions(plan, nonDeterministicFunctionNames))
|
|
3952
|
-
return
|
|
3962
|
+
if (blockCallsFunctions(plan, nonDeterministicFunctionNames)) {
|
|
3963
|
+
return decline("calls a non-deterministic function");
|
|
3964
|
+
}
|
|
3953
3965
|
const base = plan.base;
|
|
3954
|
-
if (base.table === DUAL_TABLE
|
|
3955
|
-
return
|
|
3966
|
+
if (base.table === DUAL_TABLE)
|
|
3967
|
+
return decline("FROM has no base table");
|
|
3968
|
+
if (base.derived !== void 0)
|
|
3969
|
+
return decline("FROM is a derived table or CTE");
|
|
3970
|
+
if (base.union !== void 0)
|
|
3971
|
+
return decline("UNION, INTERSECT, or EXCEPT");
|
|
3972
|
+
if (base.recursive !== void 0)
|
|
3973
|
+
return decline("recursive CTE");
|
|
3974
|
+
if (base.windowed !== void 0)
|
|
3975
|
+
return decline("window function");
|
|
3976
|
+
if (plan.pendingSelectShape !== void 0)
|
|
3977
|
+
return decline("unexpanded SELECT shape");
|
|
3978
|
+
if (plan.distinctWildcard === true)
|
|
3979
|
+
return decline("SELECT DISTINCT *");
|
|
3980
|
+
if (plan.limitParameter !== void 0 || plan.offsetParameter !== void 0) {
|
|
3981
|
+
return decline("LIMIT or OFFSET given as a parameter");
|
|
3956
3982
|
}
|
|
3983
|
+
if (planReadsBeyondSingleScan(plan))
|
|
3984
|
+
return decline("subquery or EXISTS");
|
|
3985
|
+
if (planContainsFts(plan))
|
|
3986
|
+
return decline("full-text MATCH or BM25");
|
|
3957
3987
|
if (plan.joins.length > 0) {
|
|
3958
3988
|
const join = plan.joins[0];
|
|
3959
|
-
if (plan.joins.length !== 1 || join === void 0
|
|
3960
|
-
return
|
|
3989
|
+
if (plan.joins.length !== 1 || join === void 0) {
|
|
3990
|
+
return decline(`${String(plan.joins.length)} joins; incremental maintenance supports one inner or left join to a unique key`);
|
|
3991
|
+
}
|
|
3992
|
+
if (!["inner", "left"].includes(join.kind)) {
|
|
3993
|
+
return decline(`${join.kind} join; only an inner or left join is maintained`);
|
|
3994
|
+
}
|
|
3995
|
+
if (join.on !== void 0) {
|
|
3996
|
+
return decline("join ON is not a single equality on the lookup table's unique key");
|
|
3997
|
+
}
|
|
3998
|
+
if (join.derived !== void 0 || join.union !== void 0 || join.windowed !== void 0 || join.recursive !== void 0) {
|
|
3999
|
+
return decline("join side is a derived table, union, window, or recursive CTE");
|
|
4000
|
+
}
|
|
3961
4001
|
const lookup = await this.#findTable(join.table);
|
|
3962
4002
|
const lookupKey = getUniqueKeyColumn(lookup);
|
|
3963
|
-
if (lookupKey === void 0
|
|
3964
|
-
return
|
|
4003
|
+
if (lookupKey === void 0)
|
|
4004
|
+
return decline(`${lookup.name} has no unique key to join on`);
|
|
4005
|
+
if (lookupKey.sqlDomain !== void 0) {
|
|
4006
|
+
return decline(`the unique key of ${lookup.name} has a SQL domain`);
|
|
4007
|
+
}
|
|
3965
4008
|
const lookupReference = `${join.alias}.${lookupKey.name}`;
|
|
3966
4009
|
const joinedKey = [join.left, join.right].find((expression) => expression.kind === "column" && expression.reference === lookupReference);
|
|
3967
|
-
if (joinedKey === void 0)
|
|
3968
|
-
return
|
|
4010
|
+
if (joinedKey === void 0) {
|
|
4011
|
+
return decline(`join ON does not name the unique key of ${lookup.name}`);
|
|
4012
|
+
}
|
|
3969
4013
|
}
|
|
3970
4014
|
const table = await this.#findTable(base.table);
|
|
3971
4015
|
const keyColumn = getUniqueKeyColumn(table);
|
|
3972
|
-
if (keyColumn === void 0
|
|
3973
|
-
return
|
|
4016
|
+
if (keyColumn === void 0)
|
|
4017
|
+
return decline(`${table.name} has no unique key`);
|
|
4018
|
+
if (keyColumn.hidden === true)
|
|
4019
|
+
return decline(`${table.name} has a composite unique key`);
|
|
4020
|
+
if (keyColumn.sqlDomain !== void 0) {
|
|
4021
|
+
return decline(`the unique key of ${table.name} has a SQL domain`);
|
|
4022
|
+
}
|
|
3974
4023
|
const aggregate = LiveAggregate.plan(plan, `${base.alias}.${keyColumn.name}`);
|
|
3975
4024
|
if (aggregate !== void 0)
|
|
3976
4025
|
return {
|
|
@@ -3992,24 +4041,27 @@ class MinnowDatabase {
|
|
|
3992
4041
|
order: [],
|
|
3993
4042
|
aggregate
|
|
3994
4043
|
};
|
|
3995
|
-
if (plan.groupBy.length > 0 || plan.having.length > 0)
|
|
3996
|
-
return
|
|
4044
|
+
if (plan.groupBy.length > 0 || plan.having.length > 0) {
|
|
4045
|
+
return decline("GROUP BY or HAVING without additive aggregates (COUNT, SUM, AVG of one row-local argument)");
|
|
4046
|
+
}
|
|
3997
4047
|
const rowLocal = (expression) => {
|
|
3998
4048
|
if (expression.kind === "subquery" || expression.kind === "exists" || expression.kind === "window" || expression.kind === "wildcard" || expression.kind === "parameter" || hasAggregate(expression)) {
|
|
3999
4049
|
return false;
|
|
4000
4050
|
}
|
|
4001
4051
|
return childExpressions(expression).every(rowLocal);
|
|
4002
4052
|
};
|
|
4053
|
+
const notRowLocal = "a SELECT, WHERE, or ORDER BY term is not row-local (an aggregate, subquery, window, wildcard, or parameter)";
|
|
4003
4054
|
if (!plan.select.every((item) => rowLocal(item.expression)))
|
|
4004
|
-
return
|
|
4055
|
+
return decline(notRowLocal);
|
|
4005
4056
|
if (!plan.predicates.every(({ left, right }) => rowLocal(left) && rowLocal(right))) {
|
|
4006
|
-
return
|
|
4057
|
+
return decline(notRowLocal);
|
|
4007
4058
|
}
|
|
4008
4059
|
if (!plan.orderBy.every((term) => rowLocal(term.expression)))
|
|
4009
|
-
return
|
|
4060
|
+
return decline(notRowLocal);
|
|
4010
4061
|
const publicColumns = plan.select.map((item) => item.alias);
|
|
4011
|
-
if (publicColumns.some((name) => name.startsWith(LIVE_HIDDEN_PREFIX)))
|
|
4012
|
-
return
|
|
4062
|
+
if (publicColumns.some((name) => name.startsWith(LIVE_HIDDEN_PREFIX))) {
|
|
4063
|
+
return decline("an output alias uses the reserved __minnow_live prefix");
|
|
4064
|
+
}
|
|
4013
4065
|
const fullPlan = clonePlanTree(plan);
|
|
4014
4066
|
const qualifiedKey = `${base.alias}.${keyColumn.name}`;
|
|
4015
4067
|
fullPlan.select.push({
|
|
@@ -4026,7 +4078,7 @@ class MinnowDatabase {
|
|
|
4026
4078
|
} else if (term.expression.kind === "literal" && typeof term.expression.value === "number") {
|
|
4027
4079
|
const position = term.expression.value;
|
|
4028
4080
|
if (!Number.isInteger(position) || position < 1 || position > publicColumns.length) {
|
|
4029
|
-
return
|
|
4081
|
+
return decline("ORDER BY ordinal is out of range");
|
|
4030
4082
|
}
|
|
4031
4083
|
alias = publicColumns[position - 1];
|
|
4032
4084
|
}
|
|
@@ -4064,9 +4116,10 @@ class MinnowDatabase {
|
|
|
4064
4116
|
}
|
|
4065
4117
|
async #liveExecuteMaintainable(compiled, context) {
|
|
4066
4118
|
const probe = context?.probe ?? await this.store.getCatalogProbe();
|
|
4067
|
-
const
|
|
4119
|
+
const reasons = [];
|
|
4120
|
+
const state = await this.#liveMaintenancePlan(compiled, probe, reasons);
|
|
4068
4121
|
if (state === void 0)
|
|
4069
|
-
return
|
|
4122
|
+
return { declined: reasons };
|
|
4070
4123
|
if (state.aggregate !== void 0) {
|
|
4071
4124
|
try {
|
|
4072
4125
|
const input = await this.#withReadReservation(() => this.#queryCompiled(state.fullPlan, {}, probe));
|
|
@@ -4828,10 +4881,16 @@ class MinnowDatabase {
|
|
|
4828
4881
|
}
|
|
4829
4882
|
}
|
|
4830
4883
|
}
|
|
4831
|
-
async #
|
|
4884
|
+
async #readStoredRows(sql, params) {
|
|
4885
|
+
const plan = await this.#applyCatalogRewrites(bindPlanParameters(this.#compileCached(sql), [...params]));
|
|
4886
|
+
return this.#queryCompiled(plan, { memoize: false });
|
|
4887
|
+
}
|
|
4888
|
+
async #upsertTriggerFirings(table, keyColumn, batch, rowCount, readRows, conflictWhere, lookup) {
|
|
4832
4889
|
const fires = (table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
|
|
4833
|
-
|
|
4890
|
+
const retiresUniqueTerms = readyUniqueSecondaryIndexes(table).length > 0;
|
|
4891
|
+
if (!fires && conflictWhere === void 0 && !retiresUniqueTerms || rowCount === 0) {
|
|
4834
4892
|
return void 0;
|
|
4893
|
+
}
|
|
4835
4894
|
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
4836
4895
|
const keyValues = batch.columns[keyColumn.name] ?? [];
|
|
4837
4896
|
const distinct = /* @__PURE__ */ new Map();
|
|
@@ -4841,13 +4900,24 @@ class MinnowDatabase {
|
|
|
4841
4900
|
}
|
|
4842
4901
|
const params = [...distinct.values()];
|
|
4843
4902
|
const rows = [];
|
|
4844
|
-
const
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4903
|
+
const projectedColumns = /* @__PURE__ */ new Set([keyColumn.name]);
|
|
4904
|
+
if (conflictWhere !== void 0)
|
|
4905
|
+
projectedColumns.add(conflictWhere.column.name);
|
|
4906
|
+
for (const { columns } of readyUniqueSecondaryIndexes(table)) {
|
|
4907
|
+
for (const column of columns)
|
|
4908
|
+
projectedColumns.add(column.name);
|
|
4909
|
+
}
|
|
4910
|
+
if (lookup !== void 0) {
|
|
4911
|
+
rows.push(...await lookup(params, fires ? "*" : [...projectedColumns]));
|
|
4912
|
+
} else {
|
|
4913
|
+
const projection = fires ? "*" : [...projectedColumns].map(quote).join(", ");
|
|
4914
|
+
for (let start = 0; start < params.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
|
|
4915
|
+
const window = params.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW);
|
|
4916
|
+
const placeholders = window.map(() => "?").join(", ");
|
|
4917
|
+
const sql = `SELECT ${projection} FROM ${quote(table.name)} WHERE ${quote(keyColumn.name)} IN (${placeholders})`;
|
|
4918
|
+
const result = readRows === void 0 ? await this.#readStoredRows(sql, window) : await readRows(sql, window);
|
|
4919
|
+
rows.push(...result.rows);
|
|
4920
|
+
}
|
|
4851
4921
|
}
|
|
4852
4922
|
const byToken = new Map(rows.map((row) => {
|
|
4853
4923
|
const key = row[keyColumn.name];
|
|
@@ -4895,14 +4965,20 @@ class MinnowDatabase {
|
|
|
4895
4965
|
return firings.oldImages[rowIndex]?.[column] ?? null;
|
|
4896
4966
|
}, timing, cascadeBudget);
|
|
4897
4967
|
}
|
|
4898
|
-
async #triggerPreImages(table, keyColumn, keys, event, readRows, force = false) {
|
|
4968
|
+
async #triggerPreImages(table, keyColumn, keys, event, readRows, force = false, lookup) {
|
|
4899
4969
|
if (!force && !(table.triggers ?? []).some((trigger) => trigger.event === event))
|
|
4900
4970
|
return [];
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4971
|
+
let rows;
|
|
4972
|
+
if (lookup !== void 0) {
|
|
4973
|
+
rows = await lookup(keys, "*");
|
|
4974
|
+
} else {
|
|
4975
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
4976
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
4977
|
+
const preImageSql = `SELECT * FROM ${quote(table.name)} WHERE ${quote(keyColumn.name)} IN (${placeholders})`;
|
|
4978
|
+
const result = readRows === void 0 ? await this.#readStoredRows(preImageSql, [...keys]) : await readRows(preImageSql, [...keys]);
|
|
4979
|
+
rows = result.rows;
|
|
4980
|
+
}
|
|
4981
|
+
const byToken = new Map(rows.map((row) => {
|
|
4906
4982
|
const key = row[keyColumn.name];
|
|
4907
4983
|
return [
|
|
4908
4984
|
key === null || key === void 0 ? "" : keyToken(keyColumn.type, key),
|
|
@@ -5099,7 +5175,7 @@ class MinnowDatabase {
|
|
|
5099
5175
|
...table.uniqueKeyColumnId === void 0 ? {} : { keyColumnId: table.uniqueKeyColumnId },
|
|
5100
5176
|
level: 0,
|
|
5101
5177
|
logicalOrder: 0,
|
|
5102
|
-
commitOrdinal: transaction.
|
|
5178
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
5103
5179
|
rowIdSpans: [],
|
|
5104
5180
|
createdAt: dateIsoString(this.#now())
|
|
5105
5181
|
};
|
|
@@ -5110,7 +5186,13 @@ class MinnowDatabase {
|
|
|
5110
5186
|
return this.#openWriteScope((session) => action(session));
|
|
5111
5187
|
}
|
|
5112
5188
|
#coordinateWrite(run) {
|
|
5113
|
-
|
|
5189
|
+
if (!this.#coordinateWrites)
|
|
5190
|
+
return run();
|
|
5191
|
+
return coordinateWrite(this.store, run, this.#shutdown.signal, {
|
|
5192
|
+
onAdmissionWaitExceeded: (waitedMs) => {
|
|
5193
|
+
this.#reportBackgroundError(new Error(`A write waited ${String(waitedMs)}ms for the cross-tab admission lock and went ahead without it; another tab holds the lock and is not letting go`), "write admission");
|
|
5194
|
+
}
|
|
5195
|
+
});
|
|
5114
5196
|
}
|
|
5115
5197
|
async #openWriteScope(action, options = {}) {
|
|
5116
5198
|
return this.#ownScope((signal) => this.#runWriteScope(action, options, signal));
|
|
@@ -5140,6 +5222,12 @@ class MinnowDatabase {
|
|
|
5140
5222
|
durableSnapshot: options.durableSnapshot ?? true,
|
|
5141
5223
|
coalesceArtifacts: true
|
|
5142
5224
|
});
|
|
5225
|
+
this.#scopeWrites.set(transaction, {
|
|
5226
|
+
tables: /* @__PURE__ */ new Map(),
|
|
5227
|
+
mirrored: true,
|
|
5228
|
+
generation: 0,
|
|
5229
|
+
flushing: Promise.resolve()
|
|
5230
|
+
});
|
|
5143
5231
|
let closed = false;
|
|
5144
5232
|
let accepting = true;
|
|
5145
5233
|
let tail = Promise.resolve();
|
|
@@ -5152,6 +5240,7 @@ class MinnowDatabase {
|
|
|
5152
5240
|
signal.throwIfAborted();
|
|
5153
5241
|
if (closed)
|
|
5154
5242
|
throw new Error("The write scope has ended");
|
|
5243
|
+
poisoned ??= this.#scopeWrites.get(transaction)?.failure;
|
|
5155
5244
|
if (poisoned !== void 0) {
|
|
5156
5245
|
throw new Error("The write scope already failed and can only roll back", {
|
|
5157
5246
|
cause: poisoned
|
|
@@ -5160,11 +5249,13 @@ class MinnowDatabase {
|
|
|
5160
5249
|
};
|
|
5161
5250
|
const guarded = async (run) => {
|
|
5162
5251
|
const before = transaction.stagedWorkCount;
|
|
5252
|
+
const generation = this.#scopeWriteGeneration(transaction);
|
|
5163
5253
|
try {
|
|
5164
5254
|
return await run();
|
|
5165
5255
|
} catch (error) {
|
|
5166
|
-
if (transaction.stagedWorkCount !== before)
|
|
5256
|
+
if (transaction.stagedWorkCount !== before || this.#scopeWriteGeneration(transaction) !== generation) {
|
|
5167
5257
|
poisoned ??= error;
|
|
5258
|
+
}
|
|
5168
5259
|
throw error;
|
|
5169
5260
|
}
|
|
5170
5261
|
};
|
|
@@ -5189,6 +5280,14 @@ class MinnowDatabase {
|
|
|
5189
5280
|
if (!isTransactionalStatement(statement)) {
|
|
5190
5281
|
throw new TypeError(`${statement.kind.toUpperCase().replace("-", " ")} is not allowed inside a write scope`);
|
|
5191
5282
|
}
|
|
5283
|
+
if (statement.kind === "update" || statement.kind === "delete") {
|
|
5284
|
+
const keyed = await this.#scopeKeyedMutation(statement);
|
|
5285
|
+
if (keyed !== void 0) {
|
|
5286
|
+
return guarded(() => this.#runScopeKeyedMutation(transaction, keyed, () => {
|
|
5287
|
+
staged += 1;
|
|
5288
|
+
}));
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5192
5291
|
return writer.executeStatement(statement);
|
|
5193
5292
|
},
|
|
5194
5293
|
insertBatch: async (tableName, input) => {
|
|
@@ -5216,18 +5315,20 @@ class MinnowDatabase {
|
|
|
5216
5315
|
...operations,
|
|
5217
5316
|
queryPlan: (plan) => this.#withReadReservation(async () => {
|
|
5218
5317
|
const probe = transaction.initialCatalogProbe;
|
|
5219
|
-
return transaction.stagedWorkCount === 0 && probe !== void 0 ? this.#queryCompiled(plan, {}, probe) : this.#sessionQueryPlan(transaction, await this.#applyCatalogRewrites(plan));
|
|
5318
|
+
return transaction.stagedWorkCount === 0 && !this.#hasPendingScopeWrites(transaction) && probe !== void 0 ? this.#queryCompiled(plan, {}, probe) : this.#sessionQueryPlan(transaction, await this.#applyCatalogRewrites(plan));
|
|
5220
5319
|
}),
|
|
5221
5320
|
queryFirstColumn: (plan) => this.#withReadReservation(async () => {
|
|
5222
5321
|
const probe = transaction.initialCatalogProbe;
|
|
5223
|
-
return transaction.stagedWorkCount === 0 && probe !== void 0 ? this.#queryCompiledFirstColumn(plan, probe) : void 0;
|
|
5322
|
+
return transaction.stagedWorkCount === 0 && !this.#hasPendingScopeWrites(transaction) && probe !== void 0 ? this.#queryCompiledFirstColumn(plan, probe) : void 0;
|
|
5224
5323
|
}),
|
|
5225
5324
|
executeStatement: (statement) => {
|
|
5226
5325
|
open();
|
|
5227
5326
|
return guarded(() => this.runStatement(statement, { writer }));
|
|
5228
5327
|
},
|
|
5229
|
-
|
|
5328
|
+
stagedKeyPresence: (table, keyColumn, keys) => this.#scopeKeyPresence(transaction, table, keyColumn, keys),
|
|
5329
|
+
checkpoint: async () => {
|
|
5230
5330
|
open();
|
|
5331
|
+
await this.#flushScopeWriteSets(transaction);
|
|
5231
5332
|
return transaction.checkpoint();
|
|
5232
5333
|
},
|
|
5233
5334
|
checkpointRetainedBytes: () => {
|
|
@@ -5237,7 +5338,15 @@ class MinnowDatabase {
|
|
|
5237
5338
|
rollbackTo: async (checkpoint) => {
|
|
5238
5339
|
if (closed)
|
|
5239
5340
|
throw new Error("The write scope has ended");
|
|
5240
|
-
|
|
5341
|
+
try {
|
|
5342
|
+
await transaction.rollbackTo(checkpoint);
|
|
5343
|
+
} catch (error) {
|
|
5344
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5345
|
+
if (state !== void 0)
|
|
5346
|
+
state.failure ??= error;
|
|
5347
|
+
throw error;
|
|
5348
|
+
}
|
|
5349
|
+
this.#discardScopeWriteSets(transaction);
|
|
5241
5350
|
poisoned = void 0;
|
|
5242
5351
|
staged = transaction.stagedWorkCount === 0 ? 0 : 1;
|
|
5243
5352
|
}
|
|
@@ -5290,6 +5399,8 @@ class MinnowDatabase {
|
|
|
5290
5399
|
queryPlan: (plan) => enqueue(() => writer.queryPlan(plan), "read"),
|
|
5291
5400
|
queryFirstColumn: (plan) => enqueue(() => writer.queryFirstColumn(plan), "read"),
|
|
5292
5401
|
executeStatement: (statement) => enqueue(() => writer.executeStatement(statement)),
|
|
5402
|
+
checkpoint: () => enqueue(() => writer.checkpoint()),
|
|
5403
|
+
stagedKeyPresence: (table, keyColumn, keys) => enqueue(async () => writer.stagedKeyPresence?.(table, keyColumn, keys), "read"),
|
|
5293
5404
|
rollbackTo: (checkpoint) => enqueue(() => writer.rollbackTo(checkpoint), "rollback")
|
|
5294
5405
|
};
|
|
5295
5406
|
try {
|
|
@@ -5298,6 +5409,7 @@ class MinnowDatabase {
|
|
|
5298
5409
|
await tail;
|
|
5299
5410
|
signal.throwIfAborted();
|
|
5300
5411
|
closed = true;
|
|
5412
|
+
poisoned ??= this.#scopeWrites.get(transaction)?.failure;
|
|
5301
5413
|
if (poisoned !== void 0) {
|
|
5302
5414
|
throw new Error(`The write scope failed mid-stage and was rolled back: ${poisoned instanceof Error ? poisoned.message : "staging failed"}`, { cause: poisoned });
|
|
5303
5415
|
}
|
|
@@ -5305,6 +5417,7 @@ class MinnowDatabase {
|
|
|
5305
5417
|
await transaction.abort();
|
|
5306
5418
|
return { result, version: await this.store.getCurrentManifestVersion() };
|
|
5307
5419
|
}
|
|
5420
|
+
await this.#finishScopeWriteSets(transaction);
|
|
5308
5421
|
for (let attempt = 0; attempt <= this.#maxCommitRetries; attempt += 1) {
|
|
5309
5422
|
try {
|
|
5310
5423
|
const manifest = await transaction.commit();
|
|
@@ -5330,9 +5443,33 @@ class MinnowDatabase {
|
|
|
5330
5443
|
}
|
|
5331
5444
|
}
|
|
5332
5445
|
async #sessionQuery(transaction, sql, options = {}) {
|
|
5446
|
+
const requested = options;
|
|
5333
5447
|
options = this.#effectiveQueryOptions(options);
|
|
5334
5448
|
throwIfAborted(options.signal);
|
|
5335
|
-
const
|
|
5449
|
+
const compiled = this.#compileCached(sql);
|
|
5450
|
+
if (!pointReadTestHooks.disabled && requested.version === void 0 && requested.spillToStorage === void 0 && requested.spillPageRows === void 0 && requested.executionMemoryBudgetBytes === void 0 && (compiled.parameterCount ?? 0) === (requested.params?.length ?? 0)) {
|
|
5451
|
+
const template = cachedPointReadTemplate(compiled);
|
|
5452
|
+
if (template !== null) {
|
|
5453
|
+
const shape = resolvePointReadShape(template, options.params ?? []);
|
|
5454
|
+
if (shape !== void 0) {
|
|
5455
|
+
let point;
|
|
5456
|
+
try {
|
|
5457
|
+
pointReadTestHooks.attempted += 1;
|
|
5458
|
+
const table = await this.#findTable(shape.table);
|
|
5459
|
+
point = await this.#withSessionVisibility(transaction, [table], options, (snapshot, visibility, realTables) => this.#pointReadAtSnapshot(shape, snapshot, realTables, visibility, options));
|
|
5460
|
+
} catch (error) {
|
|
5461
|
+
if (!(error instanceof UnknownTableError))
|
|
5462
|
+
throw error;
|
|
5463
|
+
}
|
|
5464
|
+
throwIfAborted(options.signal);
|
|
5465
|
+
if (point !== void 0) {
|
|
5466
|
+
pointReadTestHooks.served += 1;
|
|
5467
|
+
return point;
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
const bound = bindPlanParameters(compiled, options.params);
|
|
5336
5473
|
const plan = await this.#applyCatalogRewrites(bound);
|
|
5337
5474
|
throwIfAborted(options.signal);
|
|
5338
5475
|
return this.#sessionQueryPlan(transaction, plan, options);
|
|
@@ -5343,11 +5480,30 @@ class MinnowDatabase {
|
|
|
5343
5480
|
const names = collectRealTableNames(plan);
|
|
5344
5481
|
const tables = await Promise.all(names.map((name) => this.#findTable(name)));
|
|
5345
5482
|
throwIfAborted(options.signal);
|
|
5483
|
+
return this.#withSessionVisibility(transaction, tables, options, (snapshot, visibility, realTables) => this.#queryAtVisibility(plan, snapshot, visibility, realTables, true, options));
|
|
5484
|
+
}
|
|
5485
|
+
#scopeCommittedListings = /* @__PURE__ */ new WeakMap();
|
|
5486
|
+
async #scopeCommittedSegments(transaction, table) {
|
|
5487
|
+
let byTable = this.#scopeCommittedListings.get(transaction);
|
|
5488
|
+
if (byTable === void 0) {
|
|
5489
|
+
byTable = /* @__PURE__ */ new Map();
|
|
5490
|
+
this.#scopeCommittedListings.set(transaction, byTable);
|
|
5491
|
+
}
|
|
5492
|
+
const cached = byTable.get(table.id);
|
|
5493
|
+
if (cached !== void 0)
|
|
5494
|
+
return cached;
|
|
5495
|
+
const segments = (await listTableSegmentsPaged(this.store, table.id)).filter((segment) => segment.transactionId !== transaction.id);
|
|
5496
|
+
const records = await this.#transactionRecordsForSegments(segments, transaction.id);
|
|
5497
|
+
const entry = { segments, records };
|
|
5498
|
+
byTable.set(table.id, entry);
|
|
5499
|
+
return entry;
|
|
5500
|
+
}
|
|
5501
|
+
async #withSessionVisibility(transaction, tables, options, read) {
|
|
5502
|
+
await this.#flushScopeWriteSets(transaction, tables.map((table) => table.id));
|
|
5346
5503
|
const realTables = new Map(tables.map((table) => [table.name, table]));
|
|
5347
|
-
const
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
throwIfAborted(options.signal);
|
|
5504
|
+
const pendingBlocks = {
|
|
5505
|
+
has: (blockId) => transaction.hasPendingBlock(blockId)
|
|
5506
|
+
};
|
|
5351
5507
|
return this.#withLeasedSnapshot(transaction.snapshotVersion, async (snapshot) => {
|
|
5352
5508
|
const overlaySnapshot = {
|
|
5353
5509
|
get version() {
|
|
@@ -5382,7 +5538,7 @@ class MinnowDatabase {
|
|
|
5382
5538
|
committedIndexes.push(index);
|
|
5383
5539
|
}
|
|
5384
5540
|
});
|
|
5385
|
-
const [committed,
|
|
5541
|
+
const [committed, staged2] = await Promise.all([
|
|
5386
5542
|
this.#getBlocksWindowed(committedIds),
|
|
5387
5543
|
Promise.all(stagedIds.map((blockId) => transaction.getBlock(blockId)))
|
|
5388
5544
|
]);
|
|
@@ -5392,7 +5548,7 @@ class MinnowDatabase {
|
|
|
5392
5548
|
ordered[index] = committed[offset];
|
|
5393
5549
|
});
|
|
5394
5550
|
stagedIndexes.forEach((index, offset) => {
|
|
5395
|
-
ordered[index] =
|
|
5551
|
+
ordered[index] = staged2[offset];
|
|
5396
5552
|
});
|
|
5397
5553
|
blocks.push(...ordered);
|
|
5398
5554
|
}
|
|
@@ -5402,35 +5558,31 @@ class MinnowDatabase {
|
|
|
5402
5558
|
release: () => snapshot.release()
|
|
5403
5559
|
};
|
|
5404
5560
|
const segmentsByTable = /* @__PURE__ */ new Map();
|
|
5405
|
-
const transactionRecords = /* @__PURE__ */ new Map(
|
|
5406
|
-
|
|
5561
|
+
const transactionRecords = /* @__PURE__ */ new Map([
|
|
5562
|
+
[transaction.id, { committedVersion: STAGED_OVERLAY_ORDER_BASE }]
|
|
5563
|
+
]);
|
|
5564
|
+
const staged = transaction.stagedSegments;
|
|
5407
5565
|
for (const table of tables) {
|
|
5408
5566
|
throwIfAborted(options.signal);
|
|
5409
|
-
const
|
|
5410
|
-
...await listTableSegmentsPaged(this.store, table.id),
|
|
5411
|
-
...deferredSegments.filter((segment) => segment.tableId === table.id)
|
|
5412
|
-
];
|
|
5567
|
+
const committed = await this.#scopeCommittedSegments(transaction, table);
|
|
5413
5568
|
throwIfAborted(options.signal);
|
|
5414
|
-
const doctored =
|
|
5569
|
+
const doctored = [
|
|
5570
|
+
...committed.segments,
|
|
5571
|
+
...staged.filter((segment) => segment.tableId === table.id).map((segment) => ({
|
|
5572
|
+
...segment,
|
|
5573
|
+
logicalOrder: STAGED_OVERLAY_ORDER_BASE + segment.commitOrdinal
|
|
5574
|
+
}))
|
|
5575
|
+
];
|
|
5415
5576
|
segmentsByTable.set(table.id, doctored);
|
|
5416
|
-
for (const record of
|
|
5577
|
+
for (const record of committed.records)
|
|
5417
5578
|
transactionRecords.set(record.id, record);
|
|
5418
|
-
}
|
|
5419
|
-
throwIfAborted(options.signal);
|
|
5420
|
-
}
|
|
5421
|
-
if (ourRecord !== void 0) {
|
|
5422
|
-
transactionRecords.set(transaction.id, {
|
|
5423
|
-
...ourRecord,
|
|
5424
|
-
status: "committed",
|
|
5425
|
-
committedVersion: STAGED_OVERLAY_ORDER_BASE
|
|
5426
|
-
});
|
|
5427
5579
|
}
|
|
5428
5580
|
const visibility = {
|
|
5429
5581
|
transactions: transactionRecords,
|
|
5430
5582
|
segmentsByTable,
|
|
5431
5583
|
overlayTransactionId: transaction.id
|
|
5432
5584
|
};
|
|
5433
|
-
return
|
|
5585
|
+
return read(overlaySnapshot, visibility, realTables);
|
|
5434
5586
|
});
|
|
5435
5587
|
}
|
|
5436
5588
|
async #queryAtVisibility(plan, snapshot, visibility, realTables, cacheResults = true, options = {}) {
|
|
@@ -5470,23 +5622,508 @@ class MinnowDatabase {
|
|
|
5470
5622
|
memory.close();
|
|
5471
5623
|
}
|
|
5472
5624
|
}
|
|
5625
|
+
#stagedKeyOverlays = /* @__PURE__ */ new WeakMap();
|
|
5473
5626
|
#stagedKeyOverlay(transaction, tableId) {
|
|
5474
|
-
const
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5627
|
+
const entries = transaction.accumulatedUniqueKeyChanges;
|
|
5628
|
+
let cache = this.#stagedKeyOverlays.get(transaction);
|
|
5629
|
+
if (cache === void 0 || cache.consumed > entries.length) {
|
|
5630
|
+
cache = { consumed: 0, tables: /* @__PURE__ */ new Map() };
|
|
5631
|
+
this.#stagedKeyOverlays.set(transaction, cache);
|
|
5632
|
+
}
|
|
5633
|
+
for (; cache.consumed < entries.length; cache.consumed += 1) {
|
|
5634
|
+
const entry = entries[cache.consumed];
|
|
5635
|
+
if (entry === void 0)
|
|
5478
5636
|
continue;
|
|
5637
|
+
let overlay = cache.tables.get(entry.tableId);
|
|
5638
|
+
if (overlay === void 0) {
|
|
5639
|
+
overlay = { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() };
|
|
5640
|
+
cache.tables.set(entry.tableId, overlay);
|
|
5641
|
+
}
|
|
5479
5642
|
for (const token of entry.keyTokens) {
|
|
5480
5643
|
if (entry.remove === true) {
|
|
5481
|
-
added.delete(token);
|
|
5482
|
-
removed.add(token);
|
|
5644
|
+
overlay.added.delete(token);
|
|
5645
|
+
overlay.removed.add(token);
|
|
5483
5646
|
} else {
|
|
5484
|
-
removed.delete(token);
|
|
5485
|
-
added.add(token);
|
|
5647
|
+
overlay.removed.delete(token);
|
|
5648
|
+
overlay.added.add(token);
|
|
5649
|
+
}
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
return cache.tables.get(tableId) ?? { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() };
|
|
5653
|
+
}
|
|
5654
|
+
#scopeWrites = /* @__PURE__ */ new WeakMap();
|
|
5655
|
+
#scopeWriteState(transaction) {
|
|
5656
|
+
return this.#scopeWrites.get(transaction);
|
|
5657
|
+
}
|
|
5658
|
+
#scopeWriteSet(state, table) {
|
|
5659
|
+
let set = state.tables.get(table.id);
|
|
5660
|
+
if (set === void 0) {
|
|
5661
|
+
set = {
|
|
5662
|
+
table,
|
|
5663
|
+
keyColumn: getUniqueKeyColumn(table),
|
|
5664
|
+
rows: /* @__PURE__ */ new Map(),
|
|
5665
|
+
pendingRows: 0,
|
|
5666
|
+
pendingBytes: 0,
|
|
5667
|
+
mirrorBytes: 0,
|
|
5668
|
+
opaque: false,
|
|
5669
|
+
unkeyedSequence: 0
|
|
5670
|
+
};
|
|
5671
|
+
state.tables.set(table.id, set);
|
|
5672
|
+
}
|
|
5673
|
+
return set;
|
|
5674
|
+
}
|
|
5675
|
+
#hasPendingScopeWrites(transaction) {
|
|
5676
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5677
|
+
if (state === void 0)
|
|
5678
|
+
return false;
|
|
5679
|
+
for (const set of state.tables.values())
|
|
5680
|
+
if (set.pendingRows > 0)
|
|
5681
|
+
return true;
|
|
5682
|
+
return false;
|
|
5683
|
+
}
|
|
5684
|
+
#scopeWriteGeneration(transaction) {
|
|
5685
|
+
return this.#scopeWrites.get(transaction)?.generation ?? 0;
|
|
5686
|
+
}
|
|
5687
|
+
async #stageScopeDirectly(transaction, table) {
|
|
5688
|
+
const state = this.#scopeWriteState(transaction);
|
|
5689
|
+
if (state === void 0)
|
|
5690
|
+
return;
|
|
5691
|
+
await this.#flushScopeWriteSets(transaction, [table.id]);
|
|
5692
|
+
this.#scopeWriteSet(state, table).opaque = true;
|
|
5693
|
+
}
|
|
5694
|
+
async #finishScopeWriteSets(transaction) {
|
|
5695
|
+
const state = this.#scopeWriteState(transaction);
|
|
5696
|
+
if (state === void 0)
|
|
5697
|
+
return;
|
|
5698
|
+
state.mirrored = false;
|
|
5699
|
+
await this.#flushScopeWriteSets(transaction);
|
|
5700
|
+
}
|
|
5701
|
+
#applyScopeEffect(set, token, key, effect) {
|
|
5702
|
+
let entry = set.rows.get(token);
|
|
5703
|
+
if (entry === void 0) {
|
|
5704
|
+
entry = { key };
|
|
5705
|
+
set.rows.set(token, entry);
|
|
5706
|
+
}
|
|
5707
|
+
if (entry.pending === void 0)
|
|
5708
|
+
set.pendingRows += 1;
|
|
5709
|
+
entry.pending = composeScopeEffect(entry.pending, effect);
|
|
5710
|
+
}
|
|
5711
|
+
async #bufferScopeRows(transaction, table, batch, rowCount, kind) {
|
|
5712
|
+
const state = this.#scopeWriteState(transaction);
|
|
5713
|
+
if (state === void 0)
|
|
5714
|
+
throw new Error("Only a write scope buffers its statements");
|
|
5715
|
+
state.generation += 1;
|
|
5716
|
+
const set = this.#scopeWriteSet(state, table);
|
|
5717
|
+
const { keyColumn } = set;
|
|
5718
|
+
const keyValues = keyColumn === void 0 ? void 0 : batch.columns[keyColumn.name] ?? [];
|
|
5719
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
5720
|
+
const values = table.columns.map((column) => batch.columns[column.name]?.[row] ?? null);
|
|
5721
|
+
let token;
|
|
5722
|
+
let key;
|
|
5723
|
+
if (keyColumn === void 0 || keyValues === void 0) {
|
|
5724
|
+
token = `\0${String(set.unkeyedSequence)}`;
|
|
5725
|
+
set.unkeyedSequence += 1;
|
|
5726
|
+
} else {
|
|
5727
|
+
const value = keyValues[row] ?? null;
|
|
5728
|
+
if (value === null)
|
|
5729
|
+
throw new TypeError(`Unique key cannot be null: ${keyColumn.name}`);
|
|
5730
|
+
token = keyToken(keyColumn.type, value);
|
|
5731
|
+
key = value;
|
|
5732
|
+
}
|
|
5733
|
+
this.#applyScopeEffect(set, token, key, { kind, values });
|
|
5734
|
+
}
|
|
5735
|
+
set.pendingBytes += estimateBatchBytes(batch);
|
|
5736
|
+
await this.#settleScopeWriteSet(transaction, set);
|
|
5737
|
+
}
|
|
5738
|
+
async #bufferScopeUpdate(transaction, table, keyColumn, input) {
|
|
5739
|
+
const state = this.#scopeWriteState(transaction);
|
|
5740
|
+
if (state === void 0)
|
|
5741
|
+
throw new Error("Only a write scope buffers its statements");
|
|
5742
|
+
state.generation += 1;
|
|
5743
|
+
const set = this.#scopeWriteSet(state, table);
|
|
5744
|
+
const keyPosition = table.columns.findIndex((column) => column.id === keyColumn.id);
|
|
5745
|
+
const changes = Object.entries(input.changes).map(([name, values]) => ({
|
|
5746
|
+
position: table.columns.findIndex((column) => column.name === name),
|
|
5747
|
+
values
|
|
5748
|
+
}));
|
|
5749
|
+
let bytes = estimateValuesBytes(input.keys);
|
|
5750
|
+
for (const change of changes)
|
|
5751
|
+
bytes += estimateValuesBytes(change.values);
|
|
5752
|
+
for (let row = 0; row < input.keys.length; row += 1) {
|
|
5753
|
+
const key = input.keys[row] ?? null;
|
|
5754
|
+
if (key === null)
|
|
5755
|
+
throw new TypeError(`Unique key cannot be null: ${keyColumn.name}`);
|
|
5756
|
+
const values = new Array(table.columns.length).fill(void 0);
|
|
5757
|
+
values[keyPosition] = key;
|
|
5758
|
+
for (const change of changes)
|
|
5759
|
+
values[change.position] = change.values[row] ?? null;
|
|
5760
|
+
this.#applyScopeEffect(set, keyToken(keyColumn.type, key), key, { kind: "update", values });
|
|
5761
|
+
}
|
|
5762
|
+
set.pendingBytes += bytes;
|
|
5763
|
+
await this.#settleScopeWriteSet(transaction, set);
|
|
5764
|
+
}
|
|
5765
|
+
async #bufferScopeDelete(transaction, table, keys) {
|
|
5766
|
+
const state = this.#scopeWriteState(transaction);
|
|
5767
|
+
if (state === void 0)
|
|
5768
|
+
throw new Error("Only a write scope buffers its statements");
|
|
5769
|
+
state.generation += 1;
|
|
5770
|
+
const set = this.#scopeWriteSet(state, table);
|
|
5771
|
+
for (const [token, key] of keys) {
|
|
5772
|
+
this.#applyScopeEffect(set, token, key, { kind: "delete", values: [] });
|
|
5773
|
+
}
|
|
5774
|
+
set.pendingBytes += estimateValuesBytes([...keys.values()]);
|
|
5775
|
+
await this.#settleScopeWriteSet(transaction, set);
|
|
5776
|
+
}
|
|
5777
|
+
async #settleScopeWriteSet(transaction, set) {
|
|
5778
|
+
if (set.pendingRows >= this.#rowsPerBlock) {
|
|
5779
|
+
await this.#flushScopeWriteSets(transaction, [set.table.id]);
|
|
5780
|
+
return;
|
|
5781
|
+
}
|
|
5782
|
+
let pendingBytes = 0;
|
|
5783
|
+
for (const pending of this.#scopeWriteState(transaction)?.tables.values() ?? []) {
|
|
5784
|
+
pendingBytes += pending.pendingBytes;
|
|
5785
|
+
}
|
|
5786
|
+
if (pendingBytes >= scopeWriteSetTestHooks.budgetBytes) {
|
|
5787
|
+
await this.#flushScopeWriteSets(transaction);
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
#flushScopeWriteSets(transaction, tableIds) {
|
|
5791
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5792
|
+
if (state === void 0)
|
|
5793
|
+
return Promise.resolve();
|
|
5794
|
+
const ids = tableIds === void 0 ? void 0 : [...tableIds];
|
|
5795
|
+
const turn = state.flushing.then(() => this.#flushScopeWriteSetsNow(transaction, state, ids));
|
|
5796
|
+
state.flushing = turn.catch(() => void 0);
|
|
5797
|
+
return turn;
|
|
5798
|
+
}
|
|
5799
|
+
async #flushScopeWriteSetsNow(transaction, state, tableIds) {
|
|
5800
|
+
const ids = tableIds ?? [...state.tables.keys()];
|
|
5801
|
+
for (const tableId of ids) {
|
|
5802
|
+
const set = state.tables.get(tableId);
|
|
5803
|
+
if (set === void 0 || set.pendingRows === 0)
|
|
5804
|
+
continue;
|
|
5805
|
+
try {
|
|
5806
|
+
await this.#stageScopeWriteSet(transaction, state, set);
|
|
5807
|
+
} catch (error) {
|
|
5808
|
+
state.failure ??= error;
|
|
5809
|
+
throw error;
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
if (!state.mirrored)
|
|
5813
|
+
return;
|
|
5814
|
+
let retained = 0;
|
|
5815
|
+
for (const set of state.tables.values())
|
|
5816
|
+
retained += set.mirrorBytes + set.pendingBytes;
|
|
5817
|
+
if (retained <= scopeWriteSetTestHooks.budgetBytes)
|
|
5818
|
+
return;
|
|
5819
|
+
state.mirrored = false;
|
|
5820
|
+
for (const set of state.tables.values()) {
|
|
5821
|
+
for (const [token, entry] of set.rows) {
|
|
5822
|
+
if (entry.pending === void 0)
|
|
5823
|
+
set.rows.delete(token);
|
|
5824
|
+
else
|
|
5825
|
+
delete entry.staged;
|
|
5826
|
+
}
|
|
5827
|
+
set.mirrorBytes = 0;
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
async #stageScopeWriteSet(transaction, state, set) {
|
|
5831
|
+
const { table, keyColumn } = set;
|
|
5832
|
+
const keyPosition = keyColumn === void 0 ? -1 : table.columns.findIndex((column) => column.id === keyColumn.id);
|
|
5833
|
+
const deletes = [];
|
|
5834
|
+
const updates = /* @__PURE__ */ new Map();
|
|
5835
|
+
const upserts = [];
|
|
5836
|
+
const inserts = [];
|
|
5837
|
+
for (const entry of set.rows.values()) {
|
|
5838
|
+
const pending = entry.pending;
|
|
5839
|
+
if (pending === void 0)
|
|
5840
|
+
continue;
|
|
5841
|
+
switch (pending.kind) {
|
|
5842
|
+
case "delete":
|
|
5843
|
+
if (entry.key !== void 0)
|
|
5844
|
+
deletes.push(entry.key);
|
|
5845
|
+
break;
|
|
5846
|
+
case "insert":
|
|
5847
|
+
inserts.push(pending);
|
|
5848
|
+
break;
|
|
5849
|
+
case "upsert":
|
|
5850
|
+
upserts.push(pending);
|
|
5851
|
+
break;
|
|
5852
|
+
case "update": {
|
|
5853
|
+
const positions = [];
|
|
5854
|
+
pending.values.forEach((value, position) => {
|
|
5855
|
+
if (value !== void 0 && position !== keyPosition)
|
|
5856
|
+
positions.push(position);
|
|
5857
|
+
});
|
|
5858
|
+
const signature = positions.join(",");
|
|
5859
|
+
let group = updates.get(signature);
|
|
5860
|
+
if (group === void 0) {
|
|
5861
|
+
group = { positions, entries: [] };
|
|
5862
|
+
updates.set(signature, group);
|
|
5863
|
+
}
|
|
5864
|
+
group.entries.push(entry);
|
|
5865
|
+
break;
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5869
|
+
const batchOf = (effects) => ({
|
|
5870
|
+
columns: Object.fromEntries(table.columns.map((column, position) => [
|
|
5871
|
+
column.name,
|
|
5872
|
+
effects.map((effect) => effect.values[position] ?? null)
|
|
5873
|
+
])),
|
|
5874
|
+
rowCount: effects.length
|
|
5875
|
+
});
|
|
5876
|
+
if (keyColumn !== void 0) {
|
|
5877
|
+
if (deletes.length > 0) {
|
|
5878
|
+
await this.#stageDeleteSegment(transaction, table, keyColumn, deletes);
|
|
5879
|
+
}
|
|
5880
|
+
for (const group of updates.values()) {
|
|
5881
|
+
const changes = {};
|
|
5882
|
+
for (const position of group.positions) {
|
|
5883
|
+
const column = table.columns[position];
|
|
5884
|
+
if (column === void 0)
|
|
5885
|
+
continue;
|
|
5886
|
+
changes[column.name] = group.entries.map((entry) => entry.pending?.values[position] ?? null);
|
|
5887
|
+
}
|
|
5888
|
+
await this.#stageUpdateSegment(transaction, table, keyColumn, {
|
|
5889
|
+
keys: group.entries.map((entry) => entry.key ?? null),
|
|
5890
|
+
changes
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
if (upserts.length > 0) {
|
|
5895
|
+
await this.#stageInsertSegment(transaction, table, batchOf(upserts), upserts.length, "upsert");
|
|
5896
|
+
}
|
|
5897
|
+
if (inserts.length > 0) {
|
|
5898
|
+
await this.#stageInsertSegment(transaction, table, batchOf(inserts), inserts.length, "insert");
|
|
5899
|
+
}
|
|
5900
|
+
const mirror = keyColumn !== void 0 && state.mirrored && !set.opaque;
|
|
5901
|
+
for (const [token, entry] of set.rows) {
|
|
5902
|
+
if (entry.pending === void 0)
|
|
5903
|
+
continue;
|
|
5904
|
+
if (!mirror) {
|
|
5905
|
+
set.rows.delete(token);
|
|
5906
|
+
continue;
|
|
5907
|
+
}
|
|
5908
|
+
entry.staged = composeScopeEffect(entry.staged, entry.pending);
|
|
5909
|
+
delete entry.pending;
|
|
5910
|
+
}
|
|
5911
|
+
if (mirror)
|
|
5912
|
+
set.mirrorBytes += set.pendingBytes;
|
|
5913
|
+
set.pendingBytes = 0;
|
|
5914
|
+
set.pendingRows = 0;
|
|
5915
|
+
}
|
|
5916
|
+
#discardScopeWriteSets(transaction) {
|
|
5917
|
+
const generation = this.#scopeWriteGeneration(transaction) + 1;
|
|
5918
|
+
this.#scopeWrites.set(transaction, {
|
|
5919
|
+
tables: /* @__PURE__ */ new Map(),
|
|
5920
|
+
mirrored: false,
|
|
5921
|
+
generation,
|
|
5922
|
+
flushing: Promise.resolve()
|
|
5923
|
+
});
|
|
5924
|
+
this.#stagedKeyOverlays.delete(transaction);
|
|
5925
|
+
}
|
|
5926
|
+
async #scopeRowsByKey(transaction, table, keyColumn, keys, projection) {
|
|
5927
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5928
|
+
if (!state?.mirrored)
|
|
5929
|
+
return void 0;
|
|
5930
|
+
const set = state.tables.get(table.id);
|
|
5931
|
+
if (set?.opaque === true)
|
|
5932
|
+
return void 0;
|
|
5933
|
+
const names = projection === "*" ? visibleTableColumns(table).map(({ name }) => name) : [...projection];
|
|
5934
|
+
if (!names.includes(keyColumn.name))
|
|
5935
|
+
names.push(keyColumn.name);
|
|
5936
|
+
const wanted = new Set(names);
|
|
5937
|
+
const answered = /* @__PURE__ */ new Map();
|
|
5938
|
+
const committed = [];
|
|
5939
|
+
const patches = /* @__PURE__ */ new Map();
|
|
5940
|
+
for (const key of keys) {
|
|
5941
|
+
const token = keyToken(keyColumn.type, key);
|
|
5942
|
+
const entry = set?.rows.get(token);
|
|
5943
|
+
const effect = entry === void 0 ? void 0 : netScopeEffect(entry);
|
|
5944
|
+
if (effect === void 0) {
|
|
5945
|
+
committed.push(key);
|
|
5946
|
+
continue;
|
|
5947
|
+
}
|
|
5948
|
+
if (effect.kind === "delete")
|
|
5949
|
+
continue;
|
|
5950
|
+
if (effect.kind === "update") {
|
|
5951
|
+
committed.push(key);
|
|
5952
|
+
patches.set(token, effect);
|
|
5953
|
+
continue;
|
|
5954
|
+
}
|
|
5955
|
+
const row = {};
|
|
5956
|
+
table.columns.forEach((column, position) => {
|
|
5957
|
+
if (wanted.has(column.name))
|
|
5958
|
+
row[column.name] = effect.values[position] ?? null;
|
|
5959
|
+
});
|
|
5960
|
+
answered.set(token, row);
|
|
5961
|
+
}
|
|
5962
|
+
if (committed.length > 0 && transaction.snapshotVersion !== null) {
|
|
5963
|
+
for (let start = 0; start < committed.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
|
|
5964
|
+
const window = committed.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW);
|
|
5965
|
+
const plan = {
|
|
5966
|
+
sql: "(scope keyed lookup)",
|
|
5967
|
+
base: { table: table.name, alias: table.name },
|
|
5968
|
+
joins: [],
|
|
5969
|
+
select: names.map((name) => ({
|
|
5970
|
+
expression: { kind: "column", reference: name },
|
|
5971
|
+
alias: name
|
|
5972
|
+
})),
|
|
5973
|
+
predicates: [
|
|
5974
|
+
{
|
|
5975
|
+
left: { kind: "column", reference: keyColumn.name },
|
|
5976
|
+
operator: "IN",
|
|
5977
|
+
right: { kind: "list", items: window.map((value) => ({ kind: "literal", value })) }
|
|
5978
|
+
}
|
|
5979
|
+
],
|
|
5980
|
+
groupBy: [],
|
|
5981
|
+
having: [],
|
|
5982
|
+
orderBy: []
|
|
5983
|
+
};
|
|
5984
|
+
const result = await this.#queryCompiled(plan, {
|
|
5985
|
+
version: transaction.snapshotVersion,
|
|
5986
|
+
memoize: false
|
|
5987
|
+
});
|
|
5988
|
+
for (const row of result.rows) {
|
|
5989
|
+
const key = row[keyColumn.name] ?? null;
|
|
5990
|
+
if (key === null)
|
|
5991
|
+
continue;
|
|
5992
|
+
const token = keyToken(keyColumn.type, key);
|
|
5993
|
+
const patch = patches.get(token);
|
|
5994
|
+
if (patch !== void 0) {
|
|
5995
|
+
table.columns.forEach((column, position) => {
|
|
5996
|
+
const value = patch.values[position];
|
|
5997
|
+
if (value !== void 0 && wanted.has(column.name))
|
|
5998
|
+
row[column.name] = value;
|
|
5999
|
+
});
|
|
6000
|
+
}
|
|
6001
|
+
answered.set(token, row);
|
|
5486
6002
|
}
|
|
5487
6003
|
}
|
|
5488
6004
|
}
|
|
5489
|
-
return
|
|
6005
|
+
return [...answered.values()];
|
|
6006
|
+
}
|
|
6007
|
+
#scopeKeyLookup(transaction, table, keyColumn) {
|
|
6008
|
+
return async (keys, projection) => {
|
|
6009
|
+
const direct = await this.#scopeRowsByKey(transaction, table, keyColumn, keys, projection);
|
|
6010
|
+
if (direct !== void 0)
|
|
6011
|
+
return direct;
|
|
6012
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
6013
|
+
const selected = projection === "*" ? "*" : projection.map(quote).join(", ");
|
|
6014
|
+
const rows = [];
|
|
6015
|
+
for (let start = 0; start < keys.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
|
|
6016
|
+
const window = keys.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW);
|
|
6017
|
+
const placeholders = window.map(() => "?").join(", ");
|
|
6018
|
+
const sql = `SELECT ${selected} FROM ${quote(table.name)} WHERE ${quote(keyColumn.name)} IN (${placeholders})`;
|
|
6019
|
+
rows.push(...(await this.#sessionQuery(transaction, sql, { params: [...window] })).rows);
|
|
6020
|
+
}
|
|
6021
|
+
return rows;
|
|
6022
|
+
};
|
|
6023
|
+
}
|
|
6024
|
+
async #scopeKeyedMutation(statement) {
|
|
6025
|
+
if (statement.from !== void 0 || statement.returning !== void 0 || statement.returningItems !== void 0 || statement.predicates.length !== 1) {
|
|
6026
|
+
return void 0;
|
|
6027
|
+
}
|
|
6028
|
+
const table = await this.#findTable(statement.table);
|
|
6029
|
+
const keyColumn = getUniqueKeyColumn(table);
|
|
6030
|
+
if (keyColumn === void 0 || keyColumn.hidden === true || keyColumn.sqlDomain !== void 0) {
|
|
6031
|
+
return void 0;
|
|
6032
|
+
}
|
|
6033
|
+
const predicate = statement.predicates[0];
|
|
6034
|
+
if (predicate === void 0)
|
|
6035
|
+
return void 0;
|
|
6036
|
+
const keyNames = /* @__PURE__ */ new Set([
|
|
6037
|
+
keyColumn.name,
|
|
6038
|
+
`${statement.alias ?? table.name}.${keyColumn.name}`
|
|
6039
|
+
]);
|
|
6040
|
+
const isKey = (expression) => expression.kind === "column" && keyNames.has(expression.reference);
|
|
6041
|
+
const keys = [];
|
|
6042
|
+
const accept = (expression) => {
|
|
6043
|
+
const value = plainLiteralValue(expression);
|
|
6044
|
+
if (value === void 0 || value === null || !valueMatchesColumnType(keyColumn, value)) {
|
|
6045
|
+
return false;
|
|
6046
|
+
}
|
|
6047
|
+
keys.push(value);
|
|
6048
|
+
return true;
|
|
6049
|
+
};
|
|
6050
|
+
if (predicate.operator === "=") {
|
|
6051
|
+
if (isKey(predicate.left)) {
|
|
6052
|
+
if (!accept(predicate.right))
|
|
6053
|
+
return void 0;
|
|
6054
|
+
} else if (isKey(predicate.right)) {
|
|
6055
|
+
if (!accept(predicate.left))
|
|
6056
|
+
return void 0;
|
|
6057
|
+
} else {
|
|
6058
|
+
return void 0;
|
|
6059
|
+
}
|
|
6060
|
+
} else if (predicate.operator === "IN" && isKey(predicate.left)) {
|
|
6061
|
+
if (predicate.right.kind !== "list")
|
|
6062
|
+
return void 0;
|
|
6063
|
+
for (const item of predicate.right.items)
|
|
6064
|
+
if (!accept(item))
|
|
6065
|
+
return void 0;
|
|
6066
|
+
} else {
|
|
6067
|
+
return void 0;
|
|
6068
|
+
}
|
|
6069
|
+
if (statement.kind === "delete")
|
|
6070
|
+
return { kind: "delete", table, keyColumn, keys, changes: {} };
|
|
6071
|
+
const changes = {};
|
|
6072
|
+
for (const assignment of statement.assignments) {
|
|
6073
|
+
const column = table.columns.find((candidate) => candidate.name === assignment.column);
|
|
6074
|
+
if (column === void 0 || column.sqlDomain !== void 0 || column.generatedValue !== void 0 || column.id === keyColumn.id || (table.primaryKeyColumnIds ?? []).includes(column.id) || Object.hasOwn(changes, column.name)) {
|
|
6075
|
+
return void 0;
|
|
6076
|
+
}
|
|
6077
|
+
const value = plainLiteralValue(assignment.expression);
|
|
6078
|
+
if (value === void 0 || value !== null && !valueMatchesColumnType(column, value)) {
|
|
6079
|
+
return void 0;
|
|
6080
|
+
}
|
|
6081
|
+
changes[column.name] = value;
|
|
6082
|
+
}
|
|
6083
|
+
if (Object.keys(changes).length === 0)
|
|
6084
|
+
return void 0;
|
|
6085
|
+
return { kind: "update", table, keyColumn, keys, changes };
|
|
6086
|
+
}
|
|
6087
|
+
async #scopeKeyPresence(transaction, table, keyColumn, keys) {
|
|
6088
|
+
const overlay = this.#stagedKeyOverlay(transaction, table.id);
|
|
6089
|
+
const present = /* @__PURE__ */ new Set();
|
|
6090
|
+
const unresolved = [];
|
|
6091
|
+
for (const key of keys) {
|
|
6092
|
+
const token = keyToken(keyColumn.type, key);
|
|
6093
|
+
if (overlay.added.has(token))
|
|
6094
|
+
present.add(token);
|
|
6095
|
+
else if (!overlay.removed.has(token))
|
|
6096
|
+
unresolved.push(token);
|
|
6097
|
+
}
|
|
6098
|
+
if (unresolved.length > 0) {
|
|
6099
|
+
const existing = await this.#existingKeyTokens(table, transaction.snapshotVersion, unresolved);
|
|
6100
|
+
for (const token of existing)
|
|
6101
|
+
present.add(token);
|
|
6102
|
+
}
|
|
6103
|
+
return present;
|
|
6104
|
+
}
|
|
6105
|
+
async #runScopeKeyedMutation(transaction, keyed, beforeMutation) {
|
|
6106
|
+
const { table, keyColumn } = keyed;
|
|
6107
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
6108
|
+
for (const key of keyed.keys)
|
|
6109
|
+
byToken.set(keyToken(keyColumn.type, key), key);
|
|
6110
|
+
const presence = await this.#scopeKeyPresence(transaction, table, keyColumn, [
|
|
6111
|
+
...byToken.values()
|
|
6112
|
+
]);
|
|
6113
|
+
const present = [];
|
|
6114
|
+
for (const [token, key] of byToken)
|
|
6115
|
+
if (presence.has(token))
|
|
6116
|
+
present.push(key);
|
|
6117
|
+
if (present.length === 0)
|
|
6118
|
+
return { kind: keyed.kind, table: table.name, rowCount: 0 };
|
|
6119
|
+
beforeMutation();
|
|
6120
|
+
if (keyed.kind === "delete") {
|
|
6121
|
+
const deleted = await this.#sessionDelete(transaction, table.name, { keys: present });
|
|
6122
|
+
return { kind: "delete", table: table.name, rowCount: deleted.rowCount };
|
|
6123
|
+
}
|
|
6124
|
+
const changes = Object.fromEntries(Object.entries(keyed.changes).map(([name, value]) => [name, present.map(() => value)]));
|
|
6125
|
+
const updated = await this.#sessionUpdate(transaction, table.name, { keys: present, changes });
|
|
6126
|
+
return { kind: "update", table: table.name, rowCount: updated.rowCount };
|
|
5490
6127
|
}
|
|
5491
6128
|
async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
|
|
5492
6129
|
const table = await this.#findTable(tableName);
|
|
@@ -5507,7 +6144,7 @@ class MinnowDatabase {
|
|
|
5507
6144
|
validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
|
|
5508
6145
|
}
|
|
5509
6146
|
}
|
|
5510
|
-
let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount,
|
|
6147
|
+
let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, void 0, normalizedConflictWhere, this.#scopeKeyLookup(transaction, table, sessionUpsertKeyColumn));
|
|
5511
6148
|
let skippedRowCount = 0;
|
|
5512
6149
|
if (normalizedConflictWhere !== void 0) {
|
|
5513
6150
|
if (sessionUpsertFirings === void 0) {
|
|
@@ -5535,6 +6172,15 @@ class MinnowDatabase {
|
|
|
5535
6172
|
await this.#assertCompactionCapacity(table, transaction);
|
|
5536
6173
|
const keys = batchKeys(table, batch);
|
|
5537
6174
|
if (keys !== void 0) {
|
|
6175
|
+
if (kind === "insert") {
|
|
6176
|
+
const keyColumn = getUniqueKeyColumn(table);
|
|
6177
|
+
const overlay = this.#stagedKeyOverlay(transaction, table.id);
|
|
6178
|
+
for (const [token, value] of keys) {
|
|
6179
|
+
if (overlay.added.has(token) && keyColumn !== void 0) {
|
|
6180
|
+
throw new UniqueConstraintError(table.name, keyColumn.name, value);
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
5538
6184
|
transaction.setUniqueKeyChanges({
|
|
5539
6185
|
tableId: table.id,
|
|
5540
6186
|
keyTokens: [...keys.keys()],
|
|
@@ -5542,9 +6188,22 @@ class MinnowDatabase {
|
|
|
5542
6188
|
});
|
|
5543
6189
|
}
|
|
5544
6190
|
await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
|
|
5545
|
-
const rowIds = await this.store.reserveRowIds(table.id, rowCount);
|
|
5546
6191
|
const insertValueAt = (source, column, rowIndex) => source === "new" ? batch.columns[column]?.[rowIndex] ?? null : null;
|
|
5547
6192
|
stageSecondaryUniqueInsertChanges(transaction, table, batch, kind === "upsert" ? sessionUpsertFirings?.oldImages : void 0);
|
|
6193
|
+
const buffered = this.#scopeWrites.has(transaction) && rowCount < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
|
|
6194
|
+
if (buffered) {
|
|
6195
|
+
await this.#bufferScopeRows(transaction, table, batch, rowCount, kind);
|
|
6196
|
+
collectAutoIncrementGenerated(batch, generated, autoIncrement);
|
|
6197
|
+
return {
|
|
6198
|
+
tableName: table.name,
|
|
6199
|
+
segmentId: null,
|
|
6200
|
+
rowCount,
|
|
6201
|
+
skippedRowCount,
|
|
6202
|
+
...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
|
|
6203
|
+
};
|
|
6204
|
+
}
|
|
6205
|
+
await this.#stageScopeDirectly(transaction, table);
|
|
6206
|
+
const rowIds = await this.store.reserveRowIds(table.id, rowCount);
|
|
5548
6207
|
if (kind === "insert") {
|
|
5549
6208
|
await this.#stageTriggerDerivedInserts(transaction, table, "insert", rowCount, insertValueAt, "before", cascadeBudget);
|
|
5550
6209
|
} else if (sessionUpsertFirings !== void 0) {
|
|
@@ -5587,7 +6246,7 @@ class MinnowDatabase {
|
|
|
5587
6246
|
}
|
|
5588
6247
|
const sessionChecks = table.checks ?? [];
|
|
5589
6248
|
let changedForeignKey = (table.foreignKeys ?? []).some((key) => key.enforced !== false && foreignKeyColumns(key).some((column) => input.changes[column] !== void 0));
|
|
5590
|
-
const preImages = await this.#triggerPreImages(table, keyColumn, input.keys.filter((key) => key !== null), "update",
|
|
6249
|
+
const preImages = await this.#triggerPreImages(table, keyColumn, input.keys.filter((key) => key !== null), "update", void 0, sessionChecks.length > 0 || changedForeignKey || tableGeneratedExpressions(table).length > 0 || secondaryIndexUpdateNeedsPreImages(table, input) || readyUniqueSecondaryIndexes(table).some(({ columns }) => columns.some((column) => input.changes[column.name] !== void 0)), this.#scopeKeyLookup(transaction, table, keyColumn));
|
|
5591
6250
|
input = applyStoredGeneratedUpdateChanges(table, input, preImages);
|
|
5592
6251
|
validateUpdateBatch(table, keyColumn, input);
|
|
5593
6252
|
changedForeignKey = (table.foreignKeys ?? []).some((key) => key.enforced !== false && foreignKeyColumns(key).some((column) => input.changes[column] !== void 0));
|
|
@@ -5617,56 +6276,14 @@ class MinnowDatabase {
|
|
|
5617
6276
|
}
|
|
5618
6277
|
}
|
|
5619
6278
|
await this.#stageTriggerDerivedInserts(transaction, table, "update", input.keys.length, sessionUpdateValueAt, "before", cascadeBudget);
|
|
5620
|
-
const
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
for (const [columnIndex, column] of columns.entries()) {
|
|
5628
|
-
const values = column.id === keyColumn.id ? input.keys : input.changes[column.name] ?? [];
|
|
5629
|
-
const plannedColumn = plannedColumns[columnIndex];
|
|
5630
|
-
if (plannedColumn === void 0)
|
|
5631
|
-
throw new Error(`Write column disappeared: ${column.name}`);
|
|
5632
|
-
const blockIds = [];
|
|
5633
|
-
for (const [part, { start, end }] of ranges.entries()) {
|
|
5634
|
-
await blockStager.prepare(maximumWriteBlockStoredBytes(plannedColumn, start, end, this.#compression));
|
|
5635
|
-
const slice = values.slice(start, end);
|
|
5636
|
-
const bytes = await this.#encodeColumnBlock(column.id, asColumnInput(column.type, slice));
|
|
5637
|
-
const blockId = [
|
|
5638
|
-
"table",
|
|
5639
|
-
table.id,
|
|
5640
|
-
"segment",
|
|
5641
|
-
segmentId,
|
|
5642
|
-
"update-column",
|
|
5643
|
-
column.id,
|
|
5644
|
-
"part",
|
|
5645
|
-
String(part).padStart(6, "0")
|
|
5646
|
-
].join("/");
|
|
5647
|
-
blockStager.add({ id: blockId, bytes });
|
|
5648
|
-
blockIds.push(blockId);
|
|
5649
|
-
}
|
|
5650
|
-
columnBlockIds[column.id] = blockIds;
|
|
6279
|
+
const buffered = this.#scopeWrites.has(transaction) && input.keys.length < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "update");
|
|
6280
|
+
let segmentId = null;
|
|
6281
|
+
if (buffered) {
|
|
6282
|
+
await this.#bufferScopeUpdate(transaction, table, keyColumn, input);
|
|
6283
|
+
} else {
|
|
6284
|
+
await this.#stageScopeDirectly(transaction, table);
|
|
6285
|
+
segmentId = await this.#stageUpdateSegment(transaction, table, keyColumn, input);
|
|
5651
6286
|
}
|
|
5652
|
-
await blockStager.stageWithSegments([
|
|
5653
|
-
{
|
|
5654
|
-
id: segmentId,
|
|
5655
|
-
tableId: table.id,
|
|
5656
|
-
transactionId: transaction.id,
|
|
5657
|
-
rowCount: input.keys.length,
|
|
5658
|
-
rowIdStart: 0n,
|
|
5659
|
-
rowIdEndExclusive: 0n,
|
|
5660
|
-
columnBlockIds,
|
|
5661
|
-
kind: "update",
|
|
5662
|
-
keyColumnId: keyColumn.id,
|
|
5663
|
-
level: 0,
|
|
5664
|
-
logicalOrder: 0,
|
|
5665
|
-
commitOrdinal: transaction.pendingSegmentIds.length,
|
|
5666
|
-
rowIdSpans: [],
|
|
5667
|
-
createdAt: dateIsoString(this.#now())
|
|
5668
|
-
}
|
|
5669
|
-
]);
|
|
5670
6287
|
await this.#stageTriggerDerivedInserts(transaction, table, "update", input.keys.length, sessionUpdateValueAt, "after", cascadeBudget);
|
|
5671
6288
|
return {
|
|
5672
6289
|
tableName: table.name,
|
|
@@ -5727,14 +6344,79 @@ class MinnowDatabase {
|
|
|
5727
6344
|
if (secondaryCoverage.length > 0) {
|
|
5728
6345
|
transaction.setFtsChanges({ tableId: table.id, columns: secondaryCoverage });
|
|
5729
6346
|
}
|
|
5730
|
-
const preImages = (await this.#triggerPreImages(table, keyColumn, [...keys.values()], "delete",
|
|
6347
|
+
const preImages = (await this.#triggerPreImages(table, keyColumn, [...keys.values()], "delete", void 0, readyUniqueSecondaryIndexes(table).length > 0, this.#scopeKeyLookup(transaction, table, keyColumn))).filter((row) => row !== void 0);
|
|
5731
6348
|
stageSecondaryUniqueMutationChanges(transaction, table, void 0, preImages);
|
|
5732
6349
|
const sessionDeleteValueAt = (source, column, rowIndex) => source === "old" ? preImages[rowIndex]?.[column] ?? null : null;
|
|
5733
6350
|
await this.#stageTriggerDerivedInserts(transaction, table, "delete", preImages.length, sessionDeleteValueAt, "before", cascadeBudget);
|
|
5734
|
-
const
|
|
5735
|
-
|
|
5736
|
-
|
|
6351
|
+
const buffered = this.#scopeWrites.has(transaction) && keys.size < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "delete");
|
|
6352
|
+
let segmentId = null;
|
|
6353
|
+
if (buffered) {
|
|
6354
|
+
await this.#bufferScopeDelete(transaction, table, keys);
|
|
6355
|
+
} else {
|
|
6356
|
+
await this.#stageScopeDirectly(transaction, table);
|
|
6357
|
+
const values = [...keys.values()];
|
|
6358
|
+
if (keyStringByteLengths !== void 0) {
|
|
6359
|
+
validatedStringByteLengths.set(values, keyStringByteLengths);
|
|
6360
|
+
}
|
|
6361
|
+
segmentId = await this.#stageDeleteSegment(transaction, table, keyColumn, values);
|
|
5737
6362
|
}
|
|
6363
|
+
await this.#stageTriggerDerivedInserts(transaction, table, "delete", preImages.length, sessionDeleteValueAt, "after", cascadeBudget);
|
|
6364
|
+
return { tableName: table.name, segmentId, rowCount: keys.size };
|
|
6365
|
+
}
|
|
6366
|
+
async #stageUpdateSegment(transaction, table, keyColumn, input) {
|
|
6367
|
+
const changedColumns = Object.keys(input.changes).sort();
|
|
6368
|
+
const columns = [keyColumn, ...changedColumns.map((name) => findColumn(table, name))];
|
|
6369
|
+
const segmentId = this.#createId();
|
|
6370
|
+
const columnBlockIds = {};
|
|
6371
|
+
const blockStager = new BoundedWriteBlockStager(transaction);
|
|
6372
|
+
const plannedColumns = columns.map((column) => writeColumnValues(column.type, column.id === keyColumn.id ? input.keys : input.changes[column.name] ?? []));
|
|
6373
|
+
const ranges = writeBlockRanges(plannedColumns, input.keys.length, this.#rowsPerBlock, this.#targetBlockBytes);
|
|
6374
|
+
for (const [columnIndex, column] of columns.entries()) {
|
|
6375
|
+
const values = column.id === keyColumn.id ? input.keys : input.changes[column.name] ?? [];
|
|
6376
|
+
const plannedColumn = plannedColumns[columnIndex];
|
|
6377
|
+
if (plannedColumn === void 0)
|
|
6378
|
+
throw new Error(`Write column disappeared: ${column.name}`);
|
|
6379
|
+
const blockIds = [];
|
|
6380
|
+
for (const [part, { start, end }] of ranges.entries()) {
|
|
6381
|
+
await blockStager.prepare(maximumWriteBlockStoredBytes(plannedColumn, start, end, this.#compression));
|
|
6382
|
+
const slice = values.slice(start, end);
|
|
6383
|
+
const bytes = await this.#encodeColumnBlock(column.id, asColumnInput(column.type, slice));
|
|
6384
|
+
const blockId = [
|
|
6385
|
+
"table",
|
|
6386
|
+
table.id,
|
|
6387
|
+
"segment",
|
|
6388
|
+
segmentId,
|
|
6389
|
+
"update-column",
|
|
6390
|
+
column.id,
|
|
6391
|
+
"part",
|
|
6392
|
+
String(part).padStart(6, "0")
|
|
6393
|
+
].join("/");
|
|
6394
|
+
blockStager.add({ id: blockId, bytes });
|
|
6395
|
+
blockIds.push(blockId);
|
|
6396
|
+
}
|
|
6397
|
+
columnBlockIds[column.id] = blockIds;
|
|
6398
|
+
}
|
|
6399
|
+
await blockStager.stageWithSegments([
|
|
6400
|
+
{
|
|
6401
|
+
id: segmentId,
|
|
6402
|
+
tableId: table.id,
|
|
6403
|
+
transactionId: transaction.id,
|
|
6404
|
+
rowCount: input.keys.length,
|
|
6405
|
+
rowIdStart: 0n,
|
|
6406
|
+
rowIdEndExclusive: 0n,
|
|
6407
|
+
columnBlockIds,
|
|
6408
|
+
kind: "update",
|
|
6409
|
+
keyColumnId: keyColumn.id,
|
|
6410
|
+
level: 0,
|
|
6411
|
+
logicalOrder: 0,
|
|
6412
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
6413
|
+
rowIdSpans: [],
|
|
6414
|
+
createdAt: dateIsoString(this.#now())
|
|
6415
|
+
}
|
|
6416
|
+
]);
|
|
6417
|
+
return segmentId;
|
|
6418
|
+
}
|
|
6419
|
+
async #stageDeleteSegment(transaction, table, keyColumn, values) {
|
|
5738
6420
|
const segmentId = this.#createId();
|
|
5739
6421
|
const blockIds = [];
|
|
5740
6422
|
const blockStager = new BoundedWriteBlockStager(transaction);
|
|
@@ -5762,7 +6444,7 @@ class MinnowDatabase {
|
|
|
5762
6444
|
id: segmentId,
|
|
5763
6445
|
tableId: table.id,
|
|
5764
6446
|
transactionId: transaction.id,
|
|
5765
|
-
rowCount:
|
|
6447
|
+
rowCount: values.length,
|
|
5766
6448
|
rowIdStart: 0n,
|
|
5767
6449
|
rowIdEndExclusive: 0n,
|
|
5768
6450
|
columnBlockIds: { [keyColumn.id]: blockIds },
|
|
@@ -5770,13 +6452,12 @@ class MinnowDatabase {
|
|
|
5770
6452
|
keyColumnId: keyColumn.id,
|
|
5771
6453
|
level: 0,
|
|
5772
6454
|
logicalOrder: 0,
|
|
5773
|
-
commitOrdinal: transaction.
|
|
6455
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
5774
6456
|
rowIdSpans: [],
|
|
5775
6457
|
createdAt: dateIsoString(this.#now())
|
|
5776
6458
|
}
|
|
5777
6459
|
]);
|
|
5778
|
-
|
|
5779
|
-
return { tableName: table.name, segmentId, rowCount: keys.size };
|
|
6460
|
+
return segmentId;
|
|
5780
6461
|
}
|
|
5781
6462
|
#notifyLiveCommit() {
|
|
5782
6463
|
for (const set of this.#liveSets)
|
|
@@ -6181,6 +6862,9 @@ class MinnowDatabase {
|
|
|
6181
6862
|
notes.push("BM25 scoring reads the full scan for corpus statistics; index pruning does not apply");
|
|
6182
6863
|
}
|
|
6183
6864
|
}
|
|
6865
|
+
const liveReasons = [];
|
|
6866
|
+
const liveState = await this.#liveMaintenancePlan(plan, await this.store.getCatalogProbe(), liveReasons);
|
|
6867
|
+
notes.push(liveState !== void 0 ? "live: maintained incrementally on change" : `live: re-executes on change: ${liveReasons.join("; ") || "the shape is not maintainable"}`);
|
|
6184
6868
|
return `${renderPlan(plan)}
|
|
6185
6869
|
${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
6186
6870
|
});
|
|
@@ -6505,7 +7189,8 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6505
7189
|
if (totalBytes > MAX_TRANSACTION_SAVEPOINT_BYTES) {
|
|
6506
7190
|
throw new RangeError(`Transaction savepoints cannot retain more than ${String(MAX_TRANSACTION_SAVEPOINT_BYTES)} bytes`);
|
|
6507
7191
|
}
|
|
6508
|
-
|
|
7192
|
+
const checkpoint = await open.session.checkpoint();
|
|
7193
|
+
open.savepoints.push({ name, checkpoint, retainedBytes });
|
|
6509
7194
|
});
|
|
6510
7195
|
return { kind: "transaction", action, name };
|
|
6511
7196
|
}
|
|
@@ -6767,6 +7452,24 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6767
7452
|
const generatedColumns = new Set(table.columns.flatMap((column) => column.generatedValue === void 0 ? [] : [column.name]));
|
|
6768
7453
|
const materializedRows = Array.from({ length: statement.rows.length }, (_, rowIndex) => columns.map((name) => generatedColumns.has(name) || name === filled.autoIncrement?.column.name && deferredAutoIncrement.has(rowIndex) ? { default: true } : filled.batch.columns[name]?.[rowIndex] ?? null));
|
|
6769
7454
|
const keys = filled.batch.columns[keyColumn.name] ?? [];
|
|
7455
|
+
const presence = await writer?.stagedKeyPresence?.(table, keyColumn, keys.filter((value) => value !== null));
|
|
7456
|
+
if (presence !== void 0) {
|
|
7457
|
+
const taken = new Set(presence);
|
|
7458
|
+
return {
|
|
7459
|
+
...statement,
|
|
7460
|
+
columns,
|
|
7461
|
+
rows: materializedRows.filter((_, index) => {
|
|
7462
|
+
const value = keys[index] ?? null;
|
|
7463
|
+
if (value === null)
|
|
7464
|
+
return true;
|
|
7465
|
+
const token = keyToken(keyColumn.type, value);
|
|
7466
|
+
if (taken.has(token))
|
|
7467
|
+
return false;
|
|
7468
|
+
taken.add(token);
|
|
7469
|
+
return true;
|
|
7470
|
+
})
|
|
7471
|
+
};
|
|
7472
|
+
}
|
|
6770
7473
|
const plan = {
|
|
6771
7474
|
sql: "(on conflict do nothing)",
|
|
6772
7475
|
base: { table: table.name, alias: table.name },
|
|
@@ -6809,6 +7512,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6809
7512
|
const keys = insertBatchKeyValues(input, keyColumn.name);
|
|
6810
7513
|
if (keys.length === 0)
|
|
6811
7514
|
return;
|
|
7515
|
+
const present = await writer.stagedKeyPresence?.(table, keyColumn, keys.filter((value) => value !== null));
|
|
7516
|
+
if (present !== void 0) {
|
|
7517
|
+
for (const value of keys) {
|
|
7518
|
+
if (value !== null && present.has(keyToken(keyColumn.type, value))) {
|
|
7519
|
+
throw new UniqueConstraintError(table.name, keyColumn.name, value);
|
|
7520
|
+
}
|
|
7521
|
+
}
|
|
7522
|
+
return;
|
|
7523
|
+
}
|
|
6812
7524
|
const plan = {
|
|
6813
7525
|
sql: "(staged insert keys)",
|
|
6814
7526
|
base: { table: table.name, alias: table.name },
|
|
@@ -7115,6 +7827,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7115
7827
|
normalizeDomainBatch(pendingTable, batch);
|
|
7116
7828
|
validateBatch(pendingTable, batch);
|
|
7117
7829
|
const endExclusive = nextRowId + BigInt(rows2.length);
|
|
7830
|
+
await this.#stageScopeDirectly(transaction, pendingTable);
|
|
7118
7831
|
await this.#stageInsertSegment(transaction, pendingTable, batch, rows2.length, "insert", { start: nextRowId, endExclusive });
|
|
7119
7832
|
nextRowId = endExclusive;
|
|
7120
7833
|
stagedRows += rows2.length;
|
|
@@ -7947,13 +8660,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7947
8660
|
#streamedViewFactory(table, projectedColumns, segments, snapshot, storedBlocks, zonePruned = false) {
|
|
7948
8661
|
const scanSegments = segments.filter((segment) => {
|
|
7949
8662
|
const kind = segment.kind;
|
|
7950
|
-
return kind === "insert" || kind === "base";
|
|
8663
|
+
return kind === "insert" || kind === "base" || kind === "upsert";
|
|
7951
8664
|
});
|
|
7952
8665
|
const mutationSegments = segments.filter((segment) => segment.kind === "update" || segment.kind === "delete");
|
|
7953
8666
|
if (scanSegments.length + mutationSegments.length !== segments.length)
|
|
7954
8667
|
return void 0;
|
|
8668
|
+
const replays = mutationSegments.length > 0 || segments.some((segment) => segment.kind === "upsert");
|
|
7955
8669
|
const keyColumn = getUniqueKeyColumn(table);
|
|
7956
|
-
if (
|
|
8670
|
+
if (replays) {
|
|
7957
8671
|
if (keyColumn === void 0)
|
|
7958
8672
|
return void 0;
|
|
7959
8673
|
const keyBlocksPresent = segments.every((segment) => segment.rowCount === 0 || (segment.columnBlockIds[keyColumn.id]?.length ?? 0) > 0);
|
|
@@ -7965,7 +8679,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7965
8679
|
return void 0;
|
|
7966
8680
|
const scanRowCount = scanSegments.reduce((total, segment) => total + segment.rowCount, 0);
|
|
7967
8681
|
return {
|
|
7968
|
-
create: async (memory) =>
|
|
8682
|
+
create: async (memory) => replays && keyColumn !== void 0 ? this.#createStreamedMutationTable(table, keyColumn, projectedColumns, segments, snapshot, memory, zonePruned, storedBlocks) : this.#createStreamedTable(table, projectedColumns, scanSegments, snapshot, scanRowCount, memory, storedBlocks)
|
|
7969
8683
|
};
|
|
7970
8684
|
}
|
|
7971
8685
|
async #runPartitionedJoin(plan, budgetBytes, estimatedBuildBytes, baseView, buildView, buildTableName, buildKeyName, signal) {
|
|
@@ -8239,11 +8953,71 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8239
8953
|
async #createStreamedMutationTable(table, keyColumn, projectedColumns, baseSegments, snapshot, memory, zonePruned = false, storedBlocks) {
|
|
8240
8954
|
const scanSegments = baseSegments.filter((segment) => {
|
|
8241
8955
|
const kind = segment.kind;
|
|
8242
|
-
return kind === "insert" || kind === "base";
|
|
8956
|
+
return kind === "insert" || kind === "base" || kind === "upsert";
|
|
8243
8957
|
});
|
|
8244
8958
|
const overlay = await this.#streamedOverlayState(table, keyColumn, baseSegments, scanSegments, snapshot, memory, zonePruned);
|
|
8245
|
-
const { baseRows, dead, deadCount,
|
|
8959
|
+
const { baseRows, dead, deadCount, lazyPatches, patchedSlots } = overlay;
|
|
8246
8960
|
const hasPatches = patchedSlots.length > 0;
|
|
8961
|
+
const windowPatches = async (from, to) => {
|
|
8962
|
+
const needed = /* @__PURE__ */ new Set();
|
|
8963
|
+
const inRange = [];
|
|
8964
|
+
const winning = (layers, columnId) => {
|
|
8965
|
+
for (let index = layers.length - 1; index >= 0; index -= 1) {
|
|
8966
|
+
const candidate = layers[index];
|
|
8967
|
+
if (candidate === void 0)
|
|
8968
|
+
continue;
|
|
8969
|
+
const blockId = candidate.segment.columnBlockIds[columnId]?.[candidate.blockIndex];
|
|
8970
|
+
if (blockId !== void 0)
|
|
8971
|
+
return { blockId, row: candidate.row };
|
|
8972
|
+
}
|
|
8973
|
+
return void 0;
|
|
8974
|
+
};
|
|
8975
|
+
for (const [slot, layers] of lazyPatches) {
|
|
8976
|
+
if (slot < from || slot >= to)
|
|
8977
|
+
continue;
|
|
8978
|
+
inRange.push([slot, layers]);
|
|
8979
|
+
for (const column of projectedColumns) {
|
|
8980
|
+
const hit = winning(layers, column.id);
|
|
8981
|
+
if (hit !== void 0)
|
|
8982
|
+
needed.add(hit.blockId);
|
|
8983
|
+
}
|
|
8984
|
+
}
|
|
8985
|
+
const empty = /* @__PURE__ */ new Map();
|
|
8986
|
+
if (inRange.length === 0)
|
|
8987
|
+
return empty;
|
|
8988
|
+
const ids = [...needed];
|
|
8989
|
+
const decoded = await this.#decodedBlocksThroughCache(ids, snapshot);
|
|
8990
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
8991
|
+
ids.forEach((id, index) => {
|
|
8992
|
+
const block = decoded[index];
|
|
8993
|
+
if (block === void 0)
|
|
8994
|
+
throw new Error(`Visible block is missing: ${id}`);
|
|
8995
|
+
vectors.set(id, this.#blockColumnVector(id, block));
|
|
8996
|
+
});
|
|
8997
|
+
memory.tally(inRange.length * 96, "Streamed mutation replay");
|
|
8998
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
8999
|
+
const plain = /* @__PURE__ */ new Map();
|
|
9000
|
+
for (const [slot, layers] of inRange) {
|
|
9001
|
+
const slotPatches = /* @__PURE__ */ new Map();
|
|
9002
|
+
for (const column of projectedColumns) {
|
|
9003
|
+
const hit = winning(layers, column.id);
|
|
9004
|
+
if (hit === void 0)
|
|
9005
|
+
continue;
|
|
9006
|
+
let vector = plain.get(hit.blockId);
|
|
9007
|
+
if (vector === void 0) {
|
|
9008
|
+
const raw = vectors.get(hit.blockId);
|
|
9009
|
+
if (raw === void 0)
|
|
9010
|
+
throw new Error(`Visible block is missing: ${hit.blockId}`);
|
|
9011
|
+
vector = plainTextExecutionVector(column, raw);
|
|
9012
|
+
plain.set(hit.blockId, vector);
|
|
9013
|
+
}
|
|
9014
|
+
slotPatches.set(column.id, { vector, row: hit.row });
|
|
9015
|
+
}
|
|
9016
|
+
if (slotPatches.size > 0)
|
|
9017
|
+
resolved.set(slot, slotPatches);
|
|
9018
|
+
}
|
|
9019
|
+
return resolved;
|
|
9020
|
+
};
|
|
8247
9021
|
const outputRows = baseRows - deadCount;
|
|
8248
9022
|
const inner = this.#createStreamedTable(table, projectedColumns, scanSegments, snapshot, baseRows, memory, storedBlocks);
|
|
8249
9023
|
if (deadCount === 0 && !hasPatches)
|
|
@@ -8282,14 +9056,30 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8282
9056
|
}
|
|
8283
9057
|
const baseStart = cursorBase;
|
|
8284
9058
|
const innerEnd = await inner.load(baseStart, baseRows - baseStart);
|
|
8285
|
-
const
|
|
8286
|
-
if (
|
|
9059
|
+
const innerBaseEnd = typeof innerEnd === "number" ? Math.min(innerEnd, baseRows) : baseRows;
|
|
9060
|
+
if (innerBaseEnd <= baseStart)
|
|
8287
9061
|
throw new Error(`Column row count mismatch: ${table.name}`);
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
9062
|
+
let baseEnd = innerBaseEnd;
|
|
9063
|
+
let deadInWindow = bitmapCountRange(dead, baseStart, baseEnd);
|
|
9064
|
+
let patchedInWindow = hasPatches ? sortedCountRange(patchedSlots, baseStart, baseEnd) : 0;
|
|
8291
9065
|
const untouched = deadInWindow === 0 && patchedInWindow === 0;
|
|
9066
|
+
if (!untouched) {
|
|
9067
|
+
let live = 0;
|
|
9068
|
+
let row = baseStart;
|
|
9069
|
+
while (row < innerBaseEnd && live < length) {
|
|
9070
|
+
if (!bitmapHasValue(dead, row))
|
|
9071
|
+
live += 1;
|
|
9072
|
+
row += 1;
|
|
9073
|
+
}
|
|
9074
|
+
if (row < baseEnd) {
|
|
9075
|
+
baseEnd = row;
|
|
9076
|
+
deadInWindow = bitmapCountRange(dead, baseStart, baseEnd);
|
|
9077
|
+
patchedInWindow = hasPatches ? sortedCountRange(patchedSlots, baseStart, baseEnd) : 0;
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
const liveRows = baseEnd - baseStart - deadInWindow;
|
|
8292
9081
|
const runs = untouched ? void 0 : overlayWindowRuns(dead, patchedSlots, baseStart, baseEnd, patchedInWindow);
|
|
9082
|
+
const resolvedPatches = runs === void 0 || !hasPatches ? void 0 : await windowPatches(baseStart, baseEnd);
|
|
8293
9083
|
const targets = [];
|
|
8294
9084
|
try {
|
|
8295
9085
|
for (const state of states) {
|
|
@@ -8303,7 +9093,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8303
9093
|
throw new Error(`Column row count mismatch: ${state.column.name}`);
|
|
8304
9094
|
}
|
|
8305
9095
|
const replacements = [];
|
|
8306
|
-
const fields = runs === void 0 ? overlayWindowView(innerVector, offset, liveRows, memory, state.column, replacements) : overlayWindowCompacted(innerVector, innerWindow.start, runs, liveRows,
|
|
9096
|
+
const fields = runs === void 0 ? overlayWindowView(innerVector, offset, liveRows, memory, state.column, replacements) : overlayWindowCompacted(innerVector, innerWindow.start, runs, liveRows, resolvedPatches, state.column, memory, replacements);
|
|
8307
9097
|
fields.window = { start, length: liveRows };
|
|
8308
9098
|
targets.push({ state, fields, replacements });
|
|
8309
9099
|
}
|
|
@@ -8377,12 +9167,11 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8377
9167
|
}
|
|
8378
9168
|
async #buildStreamedOverlayState(table, keyColumn, baseSegments, scanSegments, snapshot, memory, zonePruned) {
|
|
8379
9169
|
const deltaSegments = baseSegments.filter(mutationSegmentKind);
|
|
9170
|
+
const upsertSegments = baseSegments.filter((segment) => segment.kind === "upsert");
|
|
8380
9171
|
const deltaBlockIds = /* @__PURE__ */ new Set();
|
|
8381
|
-
for (const segment of deltaSegments) {
|
|
8382
|
-
for (const
|
|
8383
|
-
|
|
8384
|
-
deltaBlockIds.add(blockId);
|
|
8385
|
-
}
|
|
9172
|
+
for (const segment of [...deltaSegments, ...upsertSegments]) {
|
|
9173
|
+
for (const blockId of segment.columnBlockIds[keyColumn.id] ?? [])
|
|
9174
|
+
deltaBlockIds.add(blockId);
|
|
8386
9175
|
}
|
|
8387
9176
|
const decodedDeltaBlocks = /* @__PURE__ */ new Map();
|
|
8388
9177
|
if (deltaBlockIds.size > 0) {
|
|
@@ -8413,11 +9202,32 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8413
9202
|
return vector;
|
|
8414
9203
|
};
|
|
8415
9204
|
const mutationKeyVectors = /* @__PURE__ */ new Map();
|
|
8416
|
-
const
|
|
9205
|
+
const blockStarts = /* @__PURE__ */ new Map();
|
|
9206
|
+
const layoutOf = (segment) => {
|
|
9207
|
+
const starts = [];
|
|
9208
|
+
let start = 0;
|
|
9209
|
+
for (const blockId of segment.columnBlockIds[keyColumn.id] ?? []) {
|
|
9210
|
+
starts.push(start);
|
|
9211
|
+
const decoded = decodedDeltaBlocks.get(blockId);
|
|
9212
|
+
if (decoded === void 0)
|
|
9213
|
+
throw new Error(`Visible block is missing: ${blockId}`);
|
|
9214
|
+
start += this.#blockColumnVector(blockId, decoded).length;
|
|
9215
|
+
}
|
|
9216
|
+
if (start !== segment.rowCount) {
|
|
9217
|
+
throw new Error(`Column row count mismatch: ${keyColumn.name}`);
|
|
9218
|
+
}
|
|
9219
|
+
return starts;
|
|
9220
|
+
};
|
|
9221
|
+
const locate = (segment, row) => {
|
|
9222
|
+
const starts = blockStarts.get(segment.id) ?? [];
|
|
9223
|
+
let blockIndex = starts.length - 1;
|
|
9224
|
+
while (blockIndex > 0 && (starts[blockIndex] ?? 0) > row)
|
|
9225
|
+
blockIndex -= 1;
|
|
9226
|
+
return { blockIndex, rowInBlock: row - (starts[blockIndex] ?? 0) };
|
|
9227
|
+
};
|
|
8417
9228
|
const touched = /* @__PURE__ */ new Set();
|
|
8418
|
-
|
|
9229
|
+
const retainedBytes = 0;
|
|
8419
9230
|
for (const segment of deltaSegments) {
|
|
8420
|
-
const kind = segment.kind;
|
|
8421
9231
|
const keyVector = await deltaVector(keyColumn, segment);
|
|
8422
9232
|
memory.reserve(columnVectorRetainedBytes(keyVector), "Streamed mutation replay");
|
|
8423
9233
|
mutationKeyVectors.set(segment.id, keyVector);
|
|
@@ -8425,21 +9235,16 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8425
9235
|
for (let row = 0; row < segment.rowCount; row += 1) {
|
|
8426
9236
|
touched.add(readMutationKey(row));
|
|
8427
9237
|
}
|
|
8428
|
-
if (kind === "update")
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
retainedBytes += bytes;
|
|
8439
|
-
changed.set(column.id, plainTextExecutionVector(column, vector));
|
|
8440
|
-
}
|
|
8441
|
-
mutationChangedVectors.set(segment.id, changed);
|
|
8442
|
-
}
|
|
9238
|
+
if (segment.kind === "update")
|
|
9239
|
+
blockStarts.set(segment.id, layoutOf(segment));
|
|
9240
|
+
}
|
|
9241
|
+
for (const segment of upsertSegments) {
|
|
9242
|
+
const keyVector = await deltaVector(keyColumn, segment);
|
|
9243
|
+
memory.reserve(columnVectorRetainedBytes(keyVector), "Streamed mutation replay");
|
|
9244
|
+
mutationKeyVectors.set(segment.id, keyVector);
|
|
9245
|
+
const readMutationKey = requiredColumnVectorKeyReader(keyVector);
|
|
9246
|
+
for (let row = 0; row < segment.rowCount; row += 1)
|
|
9247
|
+
touched.add(readMutationKey(row));
|
|
8443
9248
|
}
|
|
8444
9249
|
const touchedByScanSegment = /* @__PURE__ */ new Map();
|
|
8445
9250
|
const touchedPredicate = touchedKeyPredicate(keyColumn, touched);
|
|
@@ -8449,7 +9254,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8449
9254
|
const entries = [];
|
|
8450
9255
|
touchedByScanSegment.set(segment.id, entries);
|
|
8451
9256
|
let segmentRows = 0;
|
|
8452
|
-
for (const blockId of segment.columnBlockIds[keyColumn.id] ?? []) {
|
|
9257
|
+
for (const [blockIndex, blockId] of (segment.columnBlockIds[keyColumn.id] ?? []).entries()) {
|
|
8453
9258
|
const description = keyDescriptions.get(blockId);
|
|
8454
9259
|
if (touchedPredicate !== void 0 && description !== void 0) {
|
|
8455
9260
|
if (description.type !== keyColumn.type) {
|
|
@@ -8471,8 +9276,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8471
9276
|
const readBlockKey = requiredColumnVectorKeyReader(blockVector);
|
|
8472
9277
|
for (let row = 0; row < rows; row += 1) {
|
|
8473
9278
|
const key = readBlockKey(row);
|
|
8474
|
-
if (touched.has(key))
|
|
8475
|
-
entries.push({ key, slot: baseRows + segmentRows + row });
|
|
9279
|
+
if (touched.has(key)) {
|
|
9280
|
+
entries.push({ key, slot: baseRows + segmentRows + row, blockIndex, row });
|
|
9281
|
+
}
|
|
8476
9282
|
}
|
|
8477
9283
|
segmentRows += rows;
|
|
8478
9284
|
}
|
|
@@ -8485,8 +9291,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8485
9291
|
const dead = new Uint8Array(Math.ceil(baseRows / 8));
|
|
8486
9292
|
memory.reserve(dead.byteLength, "Streamed mutation replay");
|
|
8487
9293
|
const slotByKey = /* @__PURE__ */ new Map();
|
|
8488
|
-
const
|
|
9294
|
+
const lazyPatches = /* @__PURE__ */ new Map();
|
|
8489
9295
|
let deadCount = 0;
|
|
9296
|
+
const layer = (slot, patch) => {
|
|
9297
|
+
const layers2 = lazyPatches.get(slot);
|
|
9298
|
+
if (layers2 === void 0)
|
|
9299
|
+
lazyPatches.set(slot, [patch]);
|
|
9300
|
+
else
|
|
9301
|
+
layers2.push(patch);
|
|
9302
|
+
};
|
|
8490
9303
|
for (const segment of baseSegments) {
|
|
8491
9304
|
const kind = segment.kind;
|
|
8492
9305
|
if (kind === "insert" || kind === "base") {
|
|
@@ -8498,6 +9311,21 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8498
9311
|
}
|
|
8499
9312
|
continue;
|
|
8500
9313
|
}
|
|
9314
|
+
if (kind === "upsert") {
|
|
9315
|
+
for (const entry of touchedByScanSegment.get(segment.id) ?? []) {
|
|
9316
|
+
const existing = slotByKey.get(entry.key);
|
|
9317
|
+
if (existing === void 0) {
|
|
9318
|
+
slotByKey.set(entry.key, entry.slot);
|
|
9319
|
+
continue;
|
|
9320
|
+
}
|
|
9321
|
+
layer(existing, { segment, blockIndex: entry.blockIndex, row: entry.row });
|
|
9322
|
+
if (!bitmapHasValue(dead, entry.slot)) {
|
|
9323
|
+
setBitmapValue(dead, entry.slot);
|
|
9324
|
+
deadCount += 1;
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
continue;
|
|
9328
|
+
}
|
|
8501
9329
|
const keyVector = mutationKeyVectors.get(segment.id);
|
|
8502
9330
|
if (keyVector === void 0) {
|
|
8503
9331
|
throw new Error(`Mutation segment key vector is missing: ${segment.id}`);
|
|
@@ -8510,13 +9338,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8510
9338
|
if (slot !== void 0 && !bitmapHasValue(dead, slot)) {
|
|
8511
9339
|
setBitmapValue(dead, slot);
|
|
8512
9340
|
deadCount += 1;
|
|
8513
|
-
|
|
9341
|
+
lazyPatches.delete(slot);
|
|
8514
9342
|
}
|
|
8515
9343
|
slotByKey.delete(key);
|
|
8516
9344
|
}
|
|
8517
9345
|
continue;
|
|
8518
9346
|
}
|
|
8519
|
-
const changed = mutationChangedVectors.get(segment.id) ?? /* @__PURE__ */ new Map();
|
|
8520
9347
|
for (let row = 0; row < segment.rowCount; row += 1) {
|
|
8521
9348
|
const slot = slotByKey.get(readKey(row));
|
|
8522
9349
|
if (slot === void 0) {
|
|
@@ -8524,27 +9351,22 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8524
9351
|
continue;
|
|
8525
9352
|
throw new Error(`Update segment references a missing key: ${segment.id}`);
|
|
8526
9353
|
}
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
slotPatches = /* @__PURE__ */ new Map();
|
|
8530
|
-
patches.set(slot, slotPatches);
|
|
8531
|
-
}
|
|
8532
|
-
for (const [columnId, vector] of changed)
|
|
8533
|
-
slotPatches.set(columnId, { vector, row });
|
|
9354
|
+
const { blockIndex, rowInBlock } = locate(segment, row);
|
|
9355
|
+
layer(slot, { segment, blockIndex, row: rowInBlock });
|
|
8534
9356
|
}
|
|
8535
9357
|
}
|
|
8536
|
-
let
|
|
8537
|
-
for (const
|
|
8538
|
-
|
|
8539
|
-
memory.tally(
|
|
8540
|
-
const patchedSlots = Uint32Array.from(
|
|
9358
|
+
let layers = 0;
|
|
9359
|
+
for (const slotLayers of lazyPatches.values())
|
|
9360
|
+
layers += slotLayers.length;
|
|
9361
|
+
memory.tally(lazyPatches.size * 96 + layers * 48, "Streamed mutation replay");
|
|
9362
|
+
const patchedSlots = Uint32Array.from(lazyPatches.keys()).sort();
|
|
8541
9363
|
return {
|
|
8542
9364
|
baseRows,
|
|
8543
9365
|
deadCount,
|
|
8544
9366
|
dead,
|
|
8545
|
-
|
|
9367
|
+
lazyPatches,
|
|
8546
9368
|
patchedSlots,
|
|
8547
|
-
bytes: dead.byteLength + retainedBytes + patchedSlots.byteLength +
|
|
9369
|
+
bytes: dead.byteLength + retainedBytes + patchedSlots.byteLength + lazyPatches.size * 96 + layers * 48
|
|
8548
9370
|
};
|
|
8549
9371
|
}
|
|
8550
9372
|
async #executeBlock(block, snapshot, visibility, memory, realTables, typedSchemas, extraInputs, cacheResults = true, allowSpill = true, forceSpill = false, spillPageRows, signal) {
|
|
@@ -9197,6 +10019,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9197
10019
|
message: error instanceof Error ? error.message : String(error),
|
|
9198
10020
|
at
|
|
9199
10021
|
};
|
|
10022
|
+
this.#reportBackgroundError(error, "auto collection");
|
|
9200
10023
|
this.#scheduleAutoCollectionRetry();
|
|
9201
10024
|
}).finally(() => {
|
|
9202
10025
|
if (this.#autoCollectionTask === run)
|
|
@@ -9217,6 +10040,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9217
10040
|
this.#autoCollectionTask = run;
|
|
9218
10041
|
void run;
|
|
9219
10042
|
}
|
|
10043
|
+
#reportBackgroundError(error, context) {
|
|
10044
|
+
try {
|
|
10045
|
+
this.#onBackgroundError?.(error, context);
|
|
10046
|
+
} catch {
|
|
10047
|
+
}
|
|
10048
|
+
}
|
|
9220
10049
|
#scheduleAutoCollectionRetry() {
|
|
9221
10050
|
if (this.#closed || !this.#autoCollect || this.#autoCollectionRetryTimer !== void 0)
|
|
9222
10051
|
return;
|
|
@@ -13660,14 +14489,17 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13660
14489
|
async #visibleSegmentRecordsUncached(table, snapshot, catalog) {
|
|
13661
14490
|
const segments = catalog === void 0 ? await listTableSegmentsPaged(this.store, table.id) : catalog.segmentsByTable.get(table.id) ?? [];
|
|
13662
14491
|
const transactions = catalog?.transactions ?? new Map((await this.#transactionRecordsForSegments(segments)).map((record) => [record.id, record]));
|
|
14492
|
+
const overlay = [];
|
|
13663
14493
|
const versionEligible = segments.filter((segment) => {
|
|
13664
|
-
if (segment.transactionId === catalog?.overlayTransactionId)
|
|
13665
|
-
|
|
14494
|
+
if (segment.transactionId === catalog?.overlayTransactionId) {
|
|
14495
|
+
overlay.push(segment);
|
|
14496
|
+
return false;
|
|
14497
|
+
}
|
|
13666
14498
|
const committedVersion = transactions.get(segment.transactionId)?.committedVersion;
|
|
13667
14499
|
return snapshot.version !== null && committedVersion !== null && committedVersion !== void 0 && committedVersion <= snapshot.version;
|
|
13668
14500
|
});
|
|
13669
|
-
const visible = await filterSnapshotSegments(snapshot, versionEligible);
|
|
13670
|
-
return
|
|
14501
|
+
const visible = [...await filterSnapshotSegments(snapshot, versionEligible), ...overlay];
|
|
14502
|
+
return visible.sort((left, right) => {
|
|
13671
14503
|
const leftVersion = transactions.get(left.transactionId)?.committedVersion ?? -1;
|
|
13672
14504
|
const rightVersion = transactions.get(right.transactionId)?.committedVersion ?? -1;
|
|
13673
14505
|
const leftOrder = left.logicalOrder;
|
|
@@ -13675,8 +14507,8 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13675
14507
|
return leftOrder - rightOrder || leftVersion - rightVersion || left.commitOrdinal - right.commitOrdinal || left.id.localeCompare(right.id);
|
|
13676
14508
|
});
|
|
13677
14509
|
}
|
|
13678
|
-
async #transactionRecordsForSegments(segments) {
|
|
13679
|
-
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))];
|
|
14510
|
+
async #transactionRecordsForSegments(segments, excludeTransactionId) {
|
|
14511
|
+
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))].filter((id) => id !== excludeTransactionId);
|
|
13680
14512
|
const records = [];
|
|
13681
14513
|
for (let start = 0; start < transactionIds.length; start += 64) {
|
|
13682
14514
|
const window = transactionIds.slice(start, start + 64);
|
|
@@ -15593,6 +16425,24 @@ function boundedExpiryMilliseconds(nowMs, ttlMs) {
|
|
|
15593
16425
|
}
|
|
15594
16426
|
return expiresAt;
|
|
15595
16427
|
}
|
|
16428
|
+
function plainLiteralValue(expression) {
|
|
16429
|
+
if (expression.kind !== "literal" || expression.internalSqlValue === true || expression.sqlDomain !== void 0 || expression.exactText !== void 0 || expression.decimal === true) {
|
|
16430
|
+
return void 0;
|
|
16431
|
+
}
|
|
16432
|
+
return expression.value;
|
|
16433
|
+
}
|
|
16434
|
+
function valueMatchesColumnType(column, value) {
|
|
16435
|
+
switch (column.type) {
|
|
16436
|
+
case "number":
|
|
16437
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
16438
|
+
case "string":
|
|
16439
|
+
return typeof value === "string";
|
|
16440
|
+
case "boolean":
|
|
16441
|
+
return typeof value === "boolean";
|
|
16442
|
+
case "datetime":
|
|
16443
|
+
return value instanceof Date;
|
|
16444
|
+
}
|
|
16445
|
+
}
|
|
15596
16446
|
function keyToken(type, value) {
|
|
15597
16447
|
if (value === null)
|
|
15598
16448
|
throw new TypeError("Unique key cannot be null");
|
|
@@ -16240,9 +17090,17 @@ function overlayWindowCompacted(inner, innerWindowStart, steps, rows, patches, c
|
|
|
16240
17090
|
const values = inner.kind === "boolean" ? new Uint8Array(rows) : inner.kind === "string" ? void 0 : new Float64Array(rows);
|
|
16241
17091
|
const codes = inner.kind === "string" ? new Uint32Array(rows) : void 0;
|
|
16242
17092
|
codes?.fill(NULL_STRING_VECTOR_CODE);
|
|
16243
|
-
let
|
|
16244
|
-
|
|
16245
|
-
|
|
17093
|
+
let patched = false;
|
|
17094
|
+
if (inner.kind === "string" && patches !== void 0) {
|
|
17095
|
+
for (let index = 0; index < steps.length; index += 2) {
|
|
17096
|
+
if ((steps[index + 1] ?? 0) === 0 && patches.get(steps[index] ?? 0)?.has(column.id) === true) {
|
|
17097
|
+
patched = true;
|
|
17098
|
+
break;
|
|
17099
|
+
}
|
|
17100
|
+
}
|
|
17101
|
+
}
|
|
17102
|
+
const dictionary = inner.kind === "string" ? patched ? [] : inner.dictionary : void 0;
|
|
17103
|
+
const dictionaryIndex = patched ? /* @__PURE__ */ new Map() : void 0;
|
|
16246
17104
|
const target = codes !== void 0 ? { kind: "string", length: rows, validity, codes, dictionary: dictionary ?? [] } : { kind: inner.kind, length: rows, validity, values };
|
|
16247
17105
|
let out = 0;
|
|
16248
17106
|
for (let index = 0; index < steps.length; index += 2) {
|
|
@@ -16251,22 +17109,22 @@ function overlayWindowCompacted(inner, innerWindowStart, steps, rows, patches, c
|
|
|
16251
17109
|
const patch = length === 0 ? patches?.get(start)?.get(column.id) : void 0;
|
|
16252
17110
|
if (patch === void 0) {
|
|
16253
17111
|
const count = Math.max(1, length);
|
|
16254
|
-
|
|
17112
|
+
if (dictionaryIndex === void 0) {
|
|
17113
|
+
copyVectorSpan(inner, start - innerWindowStart, count, target, out);
|
|
17114
|
+
} else {
|
|
17115
|
+
for (let row = 0; row < count; row += 1) {
|
|
17116
|
+
copyColumnVectorValue(inner, start - innerWindowStart + row, target, out + row, dictionaryIndex);
|
|
17117
|
+
}
|
|
17118
|
+
}
|
|
16255
17119
|
out += count;
|
|
16256
17120
|
continue;
|
|
16257
17121
|
}
|
|
16258
|
-
if (target.kind === "string" && !dictionaryCopied) {
|
|
16259
|
-
dictionary = [...dictionary ?? []];
|
|
16260
|
-
target.dictionary = dictionary;
|
|
16261
|
-
dictionaryIndex = new Map(dictionary.map((value, code) => [value, code]));
|
|
16262
|
-
dictionaryCopied = true;
|
|
16263
|
-
}
|
|
16264
17122
|
copyColumnVectorValue(patch.vector, patch.row, target, out, dictionaryIndex);
|
|
16265
17123
|
out += 1;
|
|
16266
17124
|
}
|
|
16267
17125
|
if (out !== rows)
|
|
16268
17126
|
throw new Error(`Column row count mismatch: ${column.name}`);
|
|
16269
|
-
if (
|
|
17127
|
+
if (patched && dictionary !== void 0) {
|
|
16270
17128
|
let dictionaryBytes = 0;
|
|
16271
17129
|
for (const value of dictionary)
|
|
16272
17130
|
dictionaryBytes += 16 + value.length * 2;
|