@minnowdb/core 0.9.1 → 0.10.1
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 +52 -0
- package/dist/engine/auto-store.js +157 -0
- package/dist/engine/buffered-writer.d.ts +2 -0
- package/dist/engine/buffered-writer.js +15 -2
- package/dist/engine/client-audit-harness.js +123 -0
- package/dist/engine/client.d.ts +55 -6
- package/dist/engine/client.js +176 -46
- package/dist/engine/database.d.ts +15 -1
- package/dist/engine/database.js +1276 -251
- 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 +19 -1
- package/dist/engine/worker-server.d.ts +53 -1
- package/dist/engine/worker-server.js +122 -19
- package/dist/engine/worker-store-auto.js +36 -0
- package/dist/engine/worker-store-opfs.js +3 -2
- package/dist/engine/write-coordinator.js +44 -2
- package/dist/storage/indexeddb-audit-helpers.js +269 -0
- package/dist/storage/indexeddb.js +599 -374
- package/dist/storage/opfs/coordination-helpers.js +54 -0
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +3 -2
- package/dist/storage/opfs/leader.js +243 -17
- package/dist/storage/opfs/power-loss-model.js +62 -0
- package/dist/storage/opfs/rpc.js +24 -43
- package/dist/storage/opfs/store.d.ts +32 -0
- package/dist/storage/opfs/store.js +531 -65
- package/dist/storage/toolkit/record-core.js +67 -38
- package/dist/storage/toolkit/wal.js +16 -0
- package/dist/storage/toolkit/wire.d.ts +1 -1
- package/dist/storage/toolkit/wire.js +4 -4
- package/dist/storage/types.d.ts +31 -10
- package/dist/storage/types.js +27 -16
- package/dist/testing/opfs-shim.js +14 -6
- package/dist/transactions/index.d.ts +19 -0
- package/dist/transactions/index.js +99 -25
- 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,13 @@ 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
|
+
flushedWork: 0,
|
|
5230
|
+
flushing: Promise.resolve()
|
|
5231
|
+
});
|
|
5143
5232
|
let closed = false;
|
|
5144
5233
|
let accepting = true;
|
|
5145
5234
|
let tail = Promise.resolve();
|
|
@@ -5152,26 +5241,30 @@ class MinnowDatabase {
|
|
|
5152
5241
|
signal.throwIfAborted();
|
|
5153
5242
|
if (closed)
|
|
5154
5243
|
throw new Error("The write scope has ended");
|
|
5244
|
+
poisoned ??= this.#scopeWrites.get(transaction)?.failure;
|
|
5155
5245
|
if (poisoned !== void 0) {
|
|
5156
5246
|
throw new Error("The write scope already failed and can only roll back", {
|
|
5157
5247
|
cause: poisoned
|
|
5158
5248
|
});
|
|
5159
5249
|
}
|
|
5160
5250
|
};
|
|
5251
|
+
const ownStagedWork = () => transaction.stagedWorkCount - (this.#scopeWrites.get(transaction)?.flushedWork ?? 0);
|
|
5161
5252
|
const guarded = async (run) => {
|
|
5162
|
-
const before =
|
|
5253
|
+
const before = ownStagedWork();
|
|
5254
|
+
const generation = this.#scopeWriteGeneration(transaction);
|
|
5163
5255
|
try {
|
|
5164
5256
|
return await run();
|
|
5165
5257
|
} catch (error) {
|
|
5166
|
-
if (
|
|
5258
|
+
if (ownStagedWork() !== before || this.#scopeWriteGeneration(transaction) !== generation) {
|
|
5167
5259
|
poisoned ??= error;
|
|
5260
|
+
}
|
|
5168
5261
|
throw error;
|
|
5169
5262
|
}
|
|
5170
5263
|
};
|
|
5171
5264
|
const operations = {
|
|
5172
5265
|
query: async (sql, options2) => {
|
|
5173
5266
|
open();
|
|
5174
|
-
return this.#withReadReservation(() => this.#sessionQuery(transaction, sql, options2));
|
|
5267
|
+
return externalizeQueryResult(await this.#withReadReservation(() => this.#sessionQuery(transaction, sql, options2)));
|
|
5175
5268
|
},
|
|
5176
5269
|
execute: async (sql, params) => {
|
|
5177
5270
|
open();
|
|
@@ -5179,7 +5272,7 @@ class MinnowDatabase {
|
|
|
5179
5272
|
if (compiled.kind === "select") {
|
|
5180
5273
|
return this.#withReadReservation(async () => ({
|
|
5181
5274
|
kind: "rows",
|
|
5182
|
-
result: await this.#sessionQuery(transaction, compiled.sql, params === void 0 ? {} : { params })
|
|
5275
|
+
result: externalizeQueryResult(await this.#sessionQuery(transaction, compiled.sql, params === void 0 ? {} : { params }))
|
|
5183
5276
|
}));
|
|
5184
5277
|
}
|
|
5185
5278
|
const statement = bindStatementParameters(compiled, params);
|
|
@@ -5189,7 +5282,15 @@ class MinnowDatabase {
|
|
|
5189
5282
|
if (!isTransactionalStatement(statement)) {
|
|
5190
5283
|
throw new TypeError(`${statement.kind.toUpperCase().replace("-", " ")} is not allowed inside a write scope`);
|
|
5191
5284
|
}
|
|
5192
|
-
|
|
5285
|
+
if (statement.kind === "update" || statement.kind === "delete") {
|
|
5286
|
+
const keyed = await this.#scopeKeyedMutation(transaction, statement);
|
|
5287
|
+
if (keyed !== void 0) {
|
|
5288
|
+
return guarded(() => this.#runScopeKeyedMutation(transaction, keyed, () => {
|
|
5289
|
+
staged += 1;
|
|
5290
|
+
}));
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
return externalizeExecuteResult(await writer.executeStatement(statement));
|
|
5193
5294
|
},
|
|
5194
5295
|
insertBatch: async (tableName, input) => {
|
|
5195
5296
|
open();
|
|
@@ -5216,18 +5317,20 @@ class MinnowDatabase {
|
|
|
5216
5317
|
...operations,
|
|
5217
5318
|
queryPlan: (plan) => this.#withReadReservation(async () => {
|
|
5218
5319
|
const probe = transaction.initialCatalogProbe;
|
|
5219
|
-
return transaction.stagedWorkCount === 0 && probe !== void 0 ? this.#queryCompiled(plan, {}, probe) : this.#sessionQueryPlan(transaction, await this.#applyCatalogRewrites(plan));
|
|
5320
|
+
return transaction.stagedWorkCount === 0 && !this.#hasPendingScopeWrites(transaction) && probe !== void 0 ? this.#queryCompiled(plan, {}, probe) : this.#sessionQueryPlan(transaction, await this.#applyCatalogRewrites(plan));
|
|
5220
5321
|
}),
|
|
5221
5322
|
queryFirstColumn: (plan) => this.#withReadReservation(async () => {
|
|
5222
5323
|
const probe = transaction.initialCatalogProbe;
|
|
5223
|
-
return transaction.stagedWorkCount === 0 && probe !== void 0 ? this.#queryCompiledFirstColumn(plan, probe) : void 0;
|
|
5324
|
+
return transaction.stagedWorkCount === 0 && !this.#hasPendingScopeWrites(transaction) && probe !== void 0 ? this.#queryCompiledFirstColumn(plan, probe) : void 0;
|
|
5224
5325
|
}),
|
|
5225
5326
|
executeStatement: (statement) => {
|
|
5226
5327
|
open();
|
|
5227
5328
|
return guarded(() => this.runStatement(statement, { writer }));
|
|
5228
5329
|
},
|
|
5229
|
-
|
|
5330
|
+
stagedKeyPresence: (table, keyColumn, keys) => this.#scopeKeyPresence(transaction, table, keyColumn, keys),
|
|
5331
|
+
checkpoint: async () => {
|
|
5230
5332
|
open();
|
|
5333
|
+
await this.#flushScopeWriteSets(transaction);
|
|
5231
5334
|
return transaction.checkpoint();
|
|
5232
5335
|
},
|
|
5233
5336
|
checkpointRetainedBytes: () => {
|
|
@@ -5237,7 +5340,15 @@ class MinnowDatabase {
|
|
|
5237
5340
|
rollbackTo: async (checkpoint) => {
|
|
5238
5341
|
if (closed)
|
|
5239
5342
|
throw new Error("The write scope has ended");
|
|
5240
|
-
|
|
5343
|
+
try {
|
|
5344
|
+
await transaction.rollbackTo(checkpoint);
|
|
5345
|
+
} catch (error) {
|
|
5346
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5347
|
+
if (state !== void 0)
|
|
5348
|
+
state.failure ??= error;
|
|
5349
|
+
throw error;
|
|
5350
|
+
}
|
|
5351
|
+
this.#discardScopeWriteSets(transaction);
|
|
5241
5352
|
poisoned = void 0;
|
|
5242
5353
|
staged = transaction.stagedWorkCount === 0 ? 0 : 1;
|
|
5243
5354
|
}
|
|
@@ -5290,6 +5401,8 @@ class MinnowDatabase {
|
|
|
5290
5401
|
queryPlan: (plan) => enqueue(() => writer.queryPlan(plan), "read"),
|
|
5291
5402
|
queryFirstColumn: (plan) => enqueue(() => writer.queryFirstColumn(plan), "read"),
|
|
5292
5403
|
executeStatement: (statement) => enqueue(() => writer.executeStatement(statement)),
|
|
5404
|
+
checkpoint: () => enqueue(() => writer.checkpoint()),
|
|
5405
|
+
stagedKeyPresence: (table, keyColumn, keys) => enqueue(async () => writer.stagedKeyPresence?.(table, keyColumn, keys), "read"),
|
|
5293
5406
|
rollbackTo: (checkpoint) => enqueue(() => writer.rollbackTo(checkpoint), "rollback")
|
|
5294
5407
|
};
|
|
5295
5408
|
try {
|
|
@@ -5298,6 +5411,7 @@ class MinnowDatabase {
|
|
|
5298
5411
|
await tail;
|
|
5299
5412
|
signal.throwIfAborted();
|
|
5300
5413
|
closed = true;
|
|
5414
|
+
poisoned ??= this.#scopeWrites.get(transaction)?.failure;
|
|
5301
5415
|
if (poisoned !== void 0) {
|
|
5302
5416
|
throw new Error(`The write scope failed mid-stage and was rolled back: ${poisoned instanceof Error ? poisoned.message : "staging failed"}`, { cause: poisoned });
|
|
5303
5417
|
}
|
|
@@ -5305,6 +5419,7 @@ class MinnowDatabase {
|
|
|
5305
5419
|
await transaction.abort();
|
|
5306
5420
|
return { result, version: await this.store.getCurrentManifestVersion() };
|
|
5307
5421
|
}
|
|
5422
|
+
await this.#finishScopeWriteSets(transaction);
|
|
5308
5423
|
for (let attempt = 0; attempt <= this.#maxCommitRetries; attempt += 1) {
|
|
5309
5424
|
try {
|
|
5310
5425
|
const manifest = await transaction.commit();
|
|
@@ -5330,9 +5445,33 @@ class MinnowDatabase {
|
|
|
5330
5445
|
}
|
|
5331
5446
|
}
|
|
5332
5447
|
async #sessionQuery(transaction, sql, options = {}) {
|
|
5448
|
+
const requested = options;
|
|
5333
5449
|
options = this.#effectiveQueryOptions(options);
|
|
5334
5450
|
throwIfAborted(options.signal);
|
|
5335
|
-
const
|
|
5451
|
+
const compiled = this.#compileCached(sql);
|
|
5452
|
+
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)) {
|
|
5453
|
+
const template = cachedPointReadTemplate(compiled);
|
|
5454
|
+
if (template !== null) {
|
|
5455
|
+
const shape = resolvePointReadShape(template, options.params ?? []);
|
|
5456
|
+
if (shape !== void 0) {
|
|
5457
|
+
let point;
|
|
5458
|
+
try {
|
|
5459
|
+
pointReadTestHooks.attempted += 1;
|
|
5460
|
+
const table = await this.#findTable(shape.table, transaction);
|
|
5461
|
+
point = await this.#withSessionVisibility(transaction, [table], options, (snapshot, visibility, realTables) => this.#pointReadAtSnapshot(shape, snapshot, realTables, visibility, options));
|
|
5462
|
+
} catch (error) {
|
|
5463
|
+
if (!(error instanceof UnknownTableError))
|
|
5464
|
+
throw error;
|
|
5465
|
+
}
|
|
5466
|
+
throwIfAborted(options.signal);
|
|
5467
|
+
if (point !== void 0) {
|
|
5468
|
+
pointReadTestHooks.served += 1;
|
|
5469
|
+
return point;
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
const bound = bindPlanParameters(compiled, options.params);
|
|
5336
5475
|
const plan = await this.#applyCatalogRewrites(bound);
|
|
5337
5476
|
throwIfAborted(options.signal);
|
|
5338
5477
|
return this.#sessionQueryPlan(transaction, plan, options);
|
|
@@ -5341,13 +5480,32 @@ class MinnowDatabase {
|
|
|
5341
5480
|
options = this.#effectiveQueryOptions(options);
|
|
5342
5481
|
throwIfAborted(options.signal);
|
|
5343
5482
|
const names = collectRealTableNames(plan);
|
|
5344
|
-
const tables = await Promise.all(names.map((name) => this.#findTable(name)));
|
|
5483
|
+
const tables = await Promise.all(names.map((name) => this.#findTable(name, transaction)));
|
|
5345
5484
|
throwIfAborted(options.signal);
|
|
5485
|
+
return this.#withSessionVisibility(transaction, tables, options, (snapshot, visibility, realTables) => this.#queryAtVisibility(plan, snapshot, visibility, realTables, true, options));
|
|
5486
|
+
}
|
|
5487
|
+
#scopeCommittedListings = /* @__PURE__ */ new WeakMap();
|
|
5488
|
+
async #scopeCommittedSegments(transaction, table) {
|
|
5489
|
+
let byTable = this.#scopeCommittedListings.get(transaction);
|
|
5490
|
+
if (byTable === void 0) {
|
|
5491
|
+
byTable = /* @__PURE__ */ new Map();
|
|
5492
|
+
this.#scopeCommittedListings.set(transaction, byTable);
|
|
5493
|
+
}
|
|
5494
|
+
const cached = byTable.get(table.id);
|
|
5495
|
+
if (cached !== void 0)
|
|
5496
|
+
return cached;
|
|
5497
|
+
const segments = (await listTableSegmentsPaged(this.store, table.id)).filter((segment) => segment.transactionId !== transaction.id);
|
|
5498
|
+
const records = await this.#transactionRecordsForSegments(segments, transaction.id);
|
|
5499
|
+
const entry = { segments, records };
|
|
5500
|
+
byTable.set(table.id, entry);
|
|
5501
|
+
return entry;
|
|
5502
|
+
}
|
|
5503
|
+
async #withSessionVisibility(transaction, tables, options, read) {
|
|
5504
|
+
await this.#flushScopeWriteSets(transaction, tables.map((table) => table.id));
|
|
5346
5505
|
const realTables = new Map(tables.map((table) => [table.name, table]));
|
|
5347
|
-
const
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
throwIfAborted(options.signal);
|
|
5506
|
+
const pendingBlocks = {
|
|
5507
|
+
has: (blockId) => transaction.hasPendingBlock(blockId)
|
|
5508
|
+
};
|
|
5351
5509
|
return this.#withLeasedSnapshot(transaction.snapshotVersion, async (snapshot) => {
|
|
5352
5510
|
const overlaySnapshot = {
|
|
5353
5511
|
get version() {
|
|
@@ -5382,7 +5540,7 @@ class MinnowDatabase {
|
|
|
5382
5540
|
committedIndexes.push(index);
|
|
5383
5541
|
}
|
|
5384
5542
|
});
|
|
5385
|
-
const [committed,
|
|
5543
|
+
const [committed, staged2] = await Promise.all([
|
|
5386
5544
|
this.#getBlocksWindowed(committedIds),
|
|
5387
5545
|
Promise.all(stagedIds.map((blockId) => transaction.getBlock(blockId)))
|
|
5388
5546
|
]);
|
|
@@ -5392,7 +5550,7 @@ class MinnowDatabase {
|
|
|
5392
5550
|
ordered[index] = committed[offset];
|
|
5393
5551
|
});
|
|
5394
5552
|
stagedIndexes.forEach((index, offset) => {
|
|
5395
|
-
ordered[index] =
|
|
5553
|
+
ordered[index] = staged2[offset];
|
|
5396
5554
|
});
|
|
5397
5555
|
blocks.push(...ordered);
|
|
5398
5556
|
}
|
|
@@ -5402,35 +5560,31 @@ class MinnowDatabase {
|
|
|
5402
5560
|
release: () => snapshot.release()
|
|
5403
5561
|
};
|
|
5404
5562
|
const segmentsByTable = /* @__PURE__ */ new Map();
|
|
5405
|
-
const transactionRecords = /* @__PURE__ */ new Map(
|
|
5406
|
-
|
|
5563
|
+
const transactionRecords = /* @__PURE__ */ new Map([
|
|
5564
|
+
[transaction.id, { committedVersion: STAGED_OVERLAY_ORDER_BASE }]
|
|
5565
|
+
]);
|
|
5566
|
+
const staged = transaction.stagedSegments;
|
|
5407
5567
|
for (const table of tables) {
|
|
5408
5568
|
throwIfAborted(options.signal);
|
|
5409
|
-
const
|
|
5410
|
-
...await listTableSegmentsPaged(this.store, table.id),
|
|
5411
|
-
...deferredSegments.filter((segment) => segment.tableId === table.id)
|
|
5412
|
-
];
|
|
5569
|
+
const committed = await this.#scopeCommittedSegments(transaction, table);
|
|
5413
5570
|
throwIfAborted(options.signal);
|
|
5414
|
-
const doctored =
|
|
5571
|
+
const doctored = [
|
|
5572
|
+
...committed.segments,
|
|
5573
|
+
...staged.filter((segment) => segment.tableId === table.id).map((segment) => ({
|
|
5574
|
+
...segment,
|
|
5575
|
+
logicalOrder: STAGED_OVERLAY_ORDER_BASE + segment.commitOrdinal
|
|
5576
|
+
}))
|
|
5577
|
+
];
|
|
5415
5578
|
segmentsByTable.set(table.id, doctored);
|
|
5416
|
-
for (const record of
|
|
5579
|
+
for (const record of committed.records)
|
|
5417
5580
|
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
5581
|
}
|
|
5428
5582
|
const visibility = {
|
|
5429
5583
|
transactions: transactionRecords,
|
|
5430
5584
|
segmentsByTable,
|
|
5431
5585
|
overlayTransactionId: transaction.id
|
|
5432
5586
|
};
|
|
5433
|
-
return
|
|
5587
|
+
return read(overlaySnapshot, visibility, realTables);
|
|
5434
5588
|
});
|
|
5435
5589
|
}
|
|
5436
5590
|
async #queryAtVisibility(plan, snapshot, visibility, realTables, cacheResults = true, options = {}) {
|
|
@@ -5470,23 +5624,634 @@ class MinnowDatabase {
|
|
|
5470
5624
|
memory.close();
|
|
5471
5625
|
}
|
|
5472
5626
|
}
|
|
5627
|
+
#stagedKeyOverlays = /* @__PURE__ */ new WeakMap();
|
|
5473
5628
|
#stagedKeyOverlay(transaction, tableId) {
|
|
5474
|
-
const
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5629
|
+
const entries = transaction.accumulatedUniqueKeyChanges;
|
|
5630
|
+
let cache = this.#stagedKeyOverlays.get(transaction);
|
|
5631
|
+
if (cache === void 0 || cache.consumed > entries.length) {
|
|
5632
|
+
cache = { consumed: 0, tables: /* @__PURE__ */ new Map() };
|
|
5633
|
+
this.#stagedKeyOverlays.set(transaction, cache);
|
|
5634
|
+
}
|
|
5635
|
+
for (; cache.consumed < entries.length; cache.consumed += 1) {
|
|
5636
|
+
const entry = entries[cache.consumed];
|
|
5637
|
+
if (entry === void 0)
|
|
5478
5638
|
continue;
|
|
5639
|
+
let overlay = cache.tables.get(entry.tableId);
|
|
5640
|
+
if (overlay === void 0) {
|
|
5641
|
+
overlay = { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() };
|
|
5642
|
+
cache.tables.set(entry.tableId, overlay);
|
|
5643
|
+
}
|
|
5479
5644
|
for (const token of entry.keyTokens) {
|
|
5480
5645
|
if (entry.remove === true) {
|
|
5481
|
-
added.delete(token);
|
|
5482
|
-
removed.add(token);
|
|
5646
|
+
overlay.added.delete(token);
|
|
5647
|
+
overlay.removed.add(token);
|
|
5483
5648
|
} else {
|
|
5484
|
-
removed.delete(token);
|
|
5485
|
-
added.add(token);
|
|
5649
|
+
overlay.removed.delete(token);
|
|
5650
|
+
overlay.added.add(token);
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
return cache.tables.get(tableId) ?? { added: /* @__PURE__ */ new Set(), removed: /* @__PURE__ */ new Set() };
|
|
5655
|
+
}
|
|
5656
|
+
#scopeWrites = /* @__PURE__ */ new WeakMap();
|
|
5657
|
+
#scopeWriteState(transaction) {
|
|
5658
|
+
return this.#scopeWrites.get(transaction);
|
|
5659
|
+
}
|
|
5660
|
+
#scopeWriteSet(state, table) {
|
|
5661
|
+
let set = state.tables.get(table.id);
|
|
5662
|
+
if (set === void 0) {
|
|
5663
|
+
set = {
|
|
5664
|
+
table,
|
|
5665
|
+
keyColumn: getUniqueKeyColumn(table),
|
|
5666
|
+
rows: /* @__PURE__ */ new Map(),
|
|
5667
|
+
pendingRows: 0,
|
|
5668
|
+
pendingBytes: 0,
|
|
5669
|
+
mirrorBytes: 0,
|
|
5670
|
+
opaque: false,
|
|
5671
|
+
unkeyedSequence: 0
|
|
5672
|
+
};
|
|
5673
|
+
state.tables.set(table.id, set);
|
|
5674
|
+
}
|
|
5675
|
+
return set;
|
|
5676
|
+
}
|
|
5677
|
+
#hasPendingScopeWrites(transaction) {
|
|
5678
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5679
|
+
if (state === void 0)
|
|
5680
|
+
return false;
|
|
5681
|
+
for (const set of state.tables.values())
|
|
5682
|
+
if (set.pendingRows > 0)
|
|
5683
|
+
return true;
|
|
5684
|
+
return false;
|
|
5685
|
+
}
|
|
5686
|
+
#scopeWriteGeneration(transaction) {
|
|
5687
|
+
return this.#scopeWrites.get(transaction)?.generation ?? 0;
|
|
5688
|
+
}
|
|
5689
|
+
async #stageScopeDirectly(transaction, table) {
|
|
5690
|
+
const state = this.#scopeWriteState(transaction);
|
|
5691
|
+
if (state === void 0)
|
|
5692
|
+
return;
|
|
5693
|
+
await this.#flushScopeWriteSets(transaction, [table.id]);
|
|
5694
|
+
this.#scopeWriteSet(state, table).opaque = true;
|
|
5695
|
+
}
|
|
5696
|
+
async #finishScopeWriteSets(transaction) {
|
|
5697
|
+
const state = this.#scopeWriteState(transaction);
|
|
5698
|
+
if (state === void 0)
|
|
5699
|
+
return;
|
|
5700
|
+
state.mirrored = false;
|
|
5701
|
+
await this.#flushScopeWriteSets(transaction);
|
|
5702
|
+
}
|
|
5703
|
+
#applyScopeEffect(set, token, key, effect) {
|
|
5704
|
+
let entry = set.rows.get(token);
|
|
5705
|
+
if (entry === void 0) {
|
|
5706
|
+
entry = { key };
|
|
5707
|
+
set.rows.set(token, entry);
|
|
5708
|
+
}
|
|
5709
|
+
if (entry.pending === void 0)
|
|
5710
|
+
set.pendingRows += 1;
|
|
5711
|
+
entry.pending = composeScopeEffect(entry.pending, effect);
|
|
5712
|
+
}
|
|
5713
|
+
async #bufferScopeRows(transaction, table, batch, rowCount, kind) {
|
|
5714
|
+
const state = this.#scopeWriteState(transaction);
|
|
5715
|
+
if (state === void 0)
|
|
5716
|
+
throw new Error("Only a write scope buffers its statements");
|
|
5717
|
+
state.generation += 1;
|
|
5718
|
+
const set = this.#scopeWriteSet(state, table);
|
|
5719
|
+
const { keyColumn } = set;
|
|
5720
|
+
const keyValues = keyColumn === void 0 ? void 0 : batch.columns[keyColumn.name] ?? [];
|
|
5721
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
5722
|
+
const values = table.columns.map((column) => batch.columns[column.name]?.[row] ?? null);
|
|
5723
|
+
let token;
|
|
5724
|
+
let key;
|
|
5725
|
+
if (keyColumn === void 0 || keyValues === void 0) {
|
|
5726
|
+
token = `\0${String(set.unkeyedSequence)}`;
|
|
5727
|
+
set.unkeyedSequence += 1;
|
|
5728
|
+
} else {
|
|
5729
|
+
const value = keyValues[row] ?? null;
|
|
5730
|
+
if (value === null)
|
|
5731
|
+
throw new TypeError(`Unique key cannot be null: ${keyColumn.name}`);
|
|
5732
|
+
token = keyToken(keyColumn.type, value);
|
|
5733
|
+
key = value;
|
|
5734
|
+
}
|
|
5735
|
+
this.#applyScopeEffect(set, token, key, { kind, values });
|
|
5736
|
+
}
|
|
5737
|
+
set.pendingBytes += estimateBatchBytes(batch);
|
|
5738
|
+
await this.#settleScopeWriteSet(transaction, set);
|
|
5739
|
+
}
|
|
5740
|
+
async #bufferScopeUpdate(transaction, table, keyColumn, input) {
|
|
5741
|
+
const state = this.#scopeWriteState(transaction);
|
|
5742
|
+
if (state === void 0)
|
|
5743
|
+
throw new Error("Only a write scope buffers its statements");
|
|
5744
|
+
state.generation += 1;
|
|
5745
|
+
const set = this.#scopeWriteSet(state, table);
|
|
5746
|
+
const keyPosition = table.columns.findIndex((column) => column.id === keyColumn.id);
|
|
5747
|
+
const changes = Object.entries(input.changes).map(([name, values]) => ({
|
|
5748
|
+
position: table.columns.findIndex((column) => column.name === name),
|
|
5749
|
+
values
|
|
5750
|
+
}));
|
|
5751
|
+
let bytes = estimateValuesBytes(input.keys);
|
|
5752
|
+
for (const change of changes)
|
|
5753
|
+
bytes += estimateValuesBytes(change.values);
|
|
5754
|
+
for (let row = 0; row < input.keys.length; row += 1) {
|
|
5755
|
+
const key = input.keys[row] ?? null;
|
|
5756
|
+
if (key === null)
|
|
5757
|
+
throw new TypeError(`Unique key cannot be null: ${keyColumn.name}`);
|
|
5758
|
+
const values = new Array(table.columns.length).fill(void 0);
|
|
5759
|
+
values[keyPosition] = key;
|
|
5760
|
+
for (const change of changes)
|
|
5761
|
+
values[change.position] = change.values[row] ?? null;
|
|
5762
|
+
this.#applyScopeEffect(set, keyToken(keyColumn.type, key), key, { kind: "update", values });
|
|
5763
|
+
}
|
|
5764
|
+
set.pendingBytes += bytes;
|
|
5765
|
+
await this.#settleScopeWriteSet(transaction, set);
|
|
5766
|
+
}
|
|
5767
|
+
async #bufferScopeDelete(transaction, table, keys) {
|
|
5768
|
+
const state = this.#scopeWriteState(transaction);
|
|
5769
|
+
if (state === void 0)
|
|
5770
|
+
throw new Error("Only a write scope buffers its statements");
|
|
5771
|
+
state.generation += 1;
|
|
5772
|
+
const set = this.#scopeWriteSet(state, table);
|
|
5773
|
+
for (const [token, key] of keys) {
|
|
5774
|
+
this.#applyScopeEffect(set, token, key, { kind: "delete", values: [] });
|
|
5775
|
+
}
|
|
5776
|
+
set.pendingBytes += estimateValuesBytes([...keys.values()]);
|
|
5777
|
+
await this.#settleScopeWriteSet(transaction, set);
|
|
5778
|
+
}
|
|
5779
|
+
async #settleScopeWriteSet(transaction, set) {
|
|
5780
|
+
await transaction.renewIfDue();
|
|
5781
|
+
if (set.pendingRows >= this.#rowsPerBlock) {
|
|
5782
|
+
await this.#flushScopeWriteSets(transaction, [set.table.id]);
|
|
5783
|
+
return;
|
|
5784
|
+
}
|
|
5785
|
+
let pendingBytes = 0;
|
|
5786
|
+
for (const pending of this.#scopeWriteState(transaction)?.tables.values() ?? []) {
|
|
5787
|
+
pendingBytes += pending.pendingBytes;
|
|
5788
|
+
}
|
|
5789
|
+
if (pendingBytes >= scopeWriteSetTestHooks.budgetBytes) {
|
|
5790
|
+
await this.#flushScopeWriteSets(transaction);
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
#flushScopeWriteSets(transaction, tableIds) {
|
|
5794
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5795
|
+
if (state === void 0)
|
|
5796
|
+
return Promise.resolve();
|
|
5797
|
+
const ids = tableIds === void 0 ? void 0 : [...tableIds];
|
|
5798
|
+
const turn = state.flushing.then(() => this.#flushScopeWriteSetsNow(transaction, state, ids));
|
|
5799
|
+
state.flushing = turn.catch(() => void 0);
|
|
5800
|
+
return turn;
|
|
5801
|
+
}
|
|
5802
|
+
async #flushScopeWriteSetsNow(transaction, state, tableIds) {
|
|
5803
|
+
const ids = tableIds ?? [...state.tables.keys()];
|
|
5804
|
+
for (const tableId of ids) {
|
|
5805
|
+
const set = state.tables.get(tableId);
|
|
5806
|
+
if (set === void 0 || set.pendingRows === 0)
|
|
5807
|
+
continue;
|
|
5808
|
+
const before = transaction.stagedWorkCount;
|
|
5809
|
+
try {
|
|
5810
|
+
await this.#stageScopeWriteSet(transaction, state, set);
|
|
5811
|
+
} catch (error) {
|
|
5812
|
+
state.failure ??= error;
|
|
5813
|
+
throw error;
|
|
5814
|
+
} finally {
|
|
5815
|
+
state.flushedWork += transaction.stagedWorkCount - before;
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
if (!state.mirrored)
|
|
5819
|
+
return;
|
|
5820
|
+
let retained = 0;
|
|
5821
|
+
for (const set of state.tables.values())
|
|
5822
|
+
retained += set.mirrorBytes + set.pendingBytes;
|
|
5823
|
+
if (retained <= scopeWriteSetTestHooks.budgetBytes)
|
|
5824
|
+
return;
|
|
5825
|
+
state.mirrored = false;
|
|
5826
|
+
for (const set of state.tables.values()) {
|
|
5827
|
+
for (const [token, entry] of set.rows) {
|
|
5828
|
+
if (entry.pending === void 0)
|
|
5829
|
+
set.rows.delete(token);
|
|
5830
|
+
else
|
|
5831
|
+
delete entry.staged;
|
|
5832
|
+
}
|
|
5833
|
+
set.mirrorBytes = 0;
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
async #stageScopeWriteSet(transaction, state, set) {
|
|
5837
|
+
const { table, keyColumn } = set;
|
|
5838
|
+
const keyPosition = keyColumn === void 0 ? -1 : table.columns.findIndex((column) => column.id === keyColumn.id);
|
|
5839
|
+
const deletes = [];
|
|
5840
|
+
const updates = /* @__PURE__ */ new Map();
|
|
5841
|
+
const upserts = [];
|
|
5842
|
+
const inserts = [];
|
|
5843
|
+
for (const entry of set.rows.values()) {
|
|
5844
|
+
const pending = entry.pending;
|
|
5845
|
+
if (pending === void 0)
|
|
5846
|
+
continue;
|
|
5847
|
+
switch (pending.kind) {
|
|
5848
|
+
case "delete":
|
|
5849
|
+
if (entry.key !== void 0)
|
|
5850
|
+
deletes.push(entry.key);
|
|
5851
|
+
break;
|
|
5852
|
+
case "insert":
|
|
5853
|
+
inserts.push(pending);
|
|
5854
|
+
break;
|
|
5855
|
+
case "upsert":
|
|
5856
|
+
upserts.push(pending);
|
|
5857
|
+
break;
|
|
5858
|
+
case "update": {
|
|
5859
|
+
const positions = [];
|
|
5860
|
+
pending.values.forEach((value, position) => {
|
|
5861
|
+
if (value !== void 0 && position !== keyPosition)
|
|
5862
|
+
positions.push(position);
|
|
5863
|
+
});
|
|
5864
|
+
const signature = positions.join(",");
|
|
5865
|
+
let group = updates.get(signature);
|
|
5866
|
+
if (group === void 0) {
|
|
5867
|
+
group = { positions, entries: [] };
|
|
5868
|
+
updates.set(signature, group);
|
|
5869
|
+
}
|
|
5870
|
+
group.entries.push(entry);
|
|
5871
|
+
break;
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
const batchOf = (effects) => ({
|
|
5876
|
+
columns: Object.fromEntries(table.columns.map((column, position) => [
|
|
5877
|
+
column.name,
|
|
5878
|
+
effects.map((effect) => effect.values[position] ?? null)
|
|
5879
|
+
])),
|
|
5880
|
+
rowCount: effects.length
|
|
5881
|
+
});
|
|
5882
|
+
if (keyColumn !== void 0) {
|
|
5883
|
+
if (deletes.length > 0) {
|
|
5884
|
+
await this.#stageDeleteSegment(transaction, table, keyColumn, deletes);
|
|
5885
|
+
}
|
|
5886
|
+
for (const group of updates.values()) {
|
|
5887
|
+
const changes = {};
|
|
5888
|
+
for (const position of group.positions) {
|
|
5889
|
+
const column = table.columns[position];
|
|
5890
|
+
if (column === void 0)
|
|
5891
|
+
continue;
|
|
5892
|
+
changes[column.name] = group.entries.map((entry) => entry.pending?.values[position] ?? null);
|
|
5893
|
+
}
|
|
5894
|
+
await this.#stageUpdateSegment(transaction, table, keyColumn, {
|
|
5895
|
+
keys: group.entries.map((entry) => entry.key ?? null),
|
|
5896
|
+
changes
|
|
5897
|
+
});
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5900
|
+
if (upserts.length > 0) {
|
|
5901
|
+
await this.#stageInsertSegment(transaction, table, batchOf(upserts), upserts.length, "upsert");
|
|
5902
|
+
}
|
|
5903
|
+
if (inserts.length > 0) {
|
|
5904
|
+
await this.#stageInsertSegment(transaction, table, batchOf(inserts), inserts.length, "insert");
|
|
5905
|
+
}
|
|
5906
|
+
const mirror = keyColumn !== void 0 && state.mirrored && !set.opaque;
|
|
5907
|
+
for (const [token, entry] of set.rows) {
|
|
5908
|
+
if (entry.pending === void 0)
|
|
5909
|
+
continue;
|
|
5910
|
+
if (!mirror) {
|
|
5911
|
+
set.rows.delete(token);
|
|
5912
|
+
continue;
|
|
5913
|
+
}
|
|
5914
|
+
entry.staged = composeScopeEffect(entry.staged, entry.pending);
|
|
5915
|
+
delete entry.pending;
|
|
5916
|
+
}
|
|
5917
|
+
if (mirror)
|
|
5918
|
+
set.mirrorBytes += set.pendingBytes;
|
|
5919
|
+
set.pendingBytes = 0;
|
|
5920
|
+
set.pendingRows = 0;
|
|
5921
|
+
}
|
|
5922
|
+
#discardScopeWriteSets(transaction) {
|
|
5923
|
+
const generation = this.#scopeWriteGeneration(transaction) + 1;
|
|
5924
|
+
this.#scopeWrites.set(transaction, {
|
|
5925
|
+
tables: /* @__PURE__ */ new Map(),
|
|
5926
|
+
mirrored: false,
|
|
5927
|
+
generation,
|
|
5928
|
+
flushedWork: 0,
|
|
5929
|
+
flushing: Promise.resolve()
|
|
5930
|
+
});
|
|
5931
|
+
this.#stagedKeyOverlays.delete(transaction);
|
|
5932
|
+
}
|
|
5933
|
+
async #scopeRowsByKey(transaction, table, keyColumn, keys, projection) {
|
|
5934
|
+
const state = this.#scopeWrites.get(transaction);
|
|
5935
|
+
if (!state?.mirrored)
|
|
5936
|
+
return void 0;
|
|
5937
|
+
const set = state.tables.get(table.id);
|
|
5938
|
+
if (set?.opaque === true)
|
|
5939
|
+
return void 0;
|
|
5940
|
+
const names = projection === "*" ? visibleTableColumns(table).map(({ name }) => name) : [...projection];
|
|
5941
|
+
if (!names.includes(keyColumn.name))
|
|
5942
|
+
names.push(keyColumn.name);
|
|
5943
|
+
const wanted = new Set(names);
|
|
5944
|
+
const answered = /* @__PURE__ */ new Map();
|
|
5945
|
+
const committed = [];
|
|
5946
|
+
const patches = /* @__PURE__ */ new Map();
|
|
5947
|
+
for (const key of keys) {
|
|
5948
|
+
const token = keyToken(keyColumn.type, key);
|
|
5949
|
+
const entry = set?.rows.get(token);
|
|
5950
|
+
const effect = entry === void 0 ? void 0 : netScopeEffect(entry);
|
|
5951
|
+
if (effect === void 0) {
|
|
5952
|
+
committed.push(key);
|
|
5953
|
+
continue;
|
|
5954
|
+
}
|
|
5955
|
+
if (effect.kind === "delete")
|
|
5956
|
+
continue;
|
|
5957
|
+
if (effect.kind === "update") {
|
|
5958
|
+
committed.push(key);
|
|
5959
|
+
patches.set(token, effect);
|
|
5960
|
+
continue;
|
|
5961
|
+
}
|
|
5962
|
+
const row = {};
|
|
5963
|
+
table.columns.forEach((column, position) => {
|
|
5964
|
+
if (wanted.has(column.name))
|
|
5965
|
+
row[column.name] = effect.values[position] ?? null;
|
|
5966
|
+
});
|
|
5967
|
+
answered.set(token, row);
|
|
5968
|
+
}
|
|
5969
|
+
if (committed.length > 0 && transaction.snapshotVersion !== null) {
|
|
5970
|
+
const pointRows = await this.#scopeCommittedRowsByKey(transaction, table, keyColumn, committed, names);
|
|
5971
|
+
const windows = [];
|
|
5972
|
+
if (pointRows === void 0) {
|
|
5973
|
+
for (let start = 0; start < committed.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
|
|
5974
|
+
windows.push(committed.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW));
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
const patched = (rows) => {
|
|
5978
|
+
for (const row of rows) {
|
|
5979
|
+
const key = row[keyColumn.name] ?? null;
|
|
5980
|
+
if (key === null)
|
|
5981
|
+
continue;
|
|
5982
|
+
const token = keyToken(keyColumn.type, key);
|
|
5983
|
+
const patch = patches.get(token);
|
|
5984
|
+
if (patch !== void 0) {
|
|
5985
|
+
table.columns.forEach((column, position) => {
|
|
5986
|
+
const value = patch.values[position];
|
|
5987
|
+
if (value !== void 0 && wanted.has(column.name))
|
|
5988
|
+
row[column.name] = value;
|
|
5989
|
+
});
|
|
5990
|
+
}
|
|
5991
|
+
answered.set(token, row);
|
|
5992
|
+
}
|
|
5993
|
+
};
|
|
5994
|
+
if (pointRows !== void 0)
|
|
5995
|
+
patched(pointRows);
|
|
5996
|
+
for (const window of windows) {
|
|
5997
|
+
const plan = {
|
|
5998
|
+
sql: "(scope keyed lookup)",
|
|
5999
|
+
base: { table: table.name, alias: table.name },
|
|
6000
|
+
joins: [],
|
|
6001
|
+
select: names.map((name) => ({
|
|
6002
|
+
expression: { kind: "column", reference: name },
|
|
6003
|
+
alias: name
|
|
6004
|
+
})),
|
|
6005
|
+
predicates: [
|
|
6006
|
+
{
|
|
6007
|
+
left: { kind: "column", reference: keyColumn.name },
|
|
6008
|
+
operator: "IN",
|
|
6009
|
+
right: { kind: "list", items: window.map((value) => ({ kind: "literal", value })) }
|
|
6010
|
+
}
|
|
6011
|
+
],
|
|
6012
|
+
groupBy: [],
|
|
6013
|
+
having: [],
|
|
6014
|
+
orderBy: []
|
|
6015
|
+
};
|
|
6016
|
+
const result = await this.#queryCompiled(plan, {
|
|
6017
|
+
version: transaction.snapshotVersion,
|
|
6018
|
+
memoize: false
|
|
6019
|
+
});
|
|
6020
|
+
patched(result.rows);
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
return [...answered.values()];
|
|
6024
|
+
}
|
|
6025
|
+
async #scopeCommittedRowsByKey(transaction, table, keyColumn, keys, names) {
|
|
6026
|
+
if (pointReadTestHooks.disabled)
|
|
6027
|
+
return void 0;
|
|
6028
|
+
const values = [];
|
|
6029
|
+
for (const key of keys) {
|
|
6030
|
+
if (typeof key !== "number" && typeof key !== "string" && typeof key !== "boolean" && !(key instanceof Date)) {
|
|
6031
|
+
return void 0;
|
|
6032
|
+
}
|
|
6033
|
+
values.push(key);
|
|
6034
|
+
}
|
|
6035
|
+
const { segments, records } = await this.#scopeCommittedSegments(transaction, table);
|
|
6036
|
+
const visibility = {
|
|
6037
|
+
transactions: new Map(records.map((record) => [record.id, record])),
|
|
6038
|
+
segmentsByTable: /* @__PURE__ */ new Map([[table.id, segments]])
|
|
6039
|
+
};
|
|
6040
|
+
const realTables = /* @__PURE__ */ new Map([[table.name, table]]);
|
|
6041
|
+
const select = names.map((name) => ({ column: name, alias: name }));
|
|
6042
|
+
return this.#withLeasedSnapshot(transaction.snapshotVersion, async (snapshot) => {
|
|
6043
|
+
const rows = [];
|
|
6044
|
+
for (const value of values) {
|
|
6045
|
+
const result = await this.#pointReadAtSnapshot({ table: table.name, equalities: [{ column: keyColumn.name, value }], select }, snapshot, realTables, visibility, {});
|
|
6046
|
+
if (result === void 0)
|
|
6047
|
+
return void 0;
|
|
6048
|
+
rows.push(...result.rows);
|
|
6049
|
+
}
|
|
6050
|
+
return rows;
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
#scopeKeyLookup(transaction, table, keyColumn) {
|
|
6054
|
+
return async (keys, projection) => {
|
|
6055
|
+
const direct = await this.#scopeRowsByKey(transaction, table, keyColumn, keys, projection);
|
|
6056
|
+
if (direct !== void 0)
|
|
6057
|
+
return direct;
|
|
6058
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
6059
|
+
const selected = projection === "*" ? "*" : projection.map(quote).join(", ");
|
|
6060
|
+
const rows = [];
|
|
6061
|
+
for (let start = 0; start < keys.length; start += SCOPE_KEY_LOOKUP_WINDOW) {
|
|
6062
|
+
const window = keys.slice(start, start + SCOPE_KEY_LOOKUP_WINDOW);
|
|
6063
|
+
const placeholders = window.map(() => "?").join(", ");
|
|
6064
|
+
const sql = `SELECT ${selected} FROM ${quote(table.name)} WHERE ${quote(keyColumn.name)} IN (${placeholders})`;
|
|
6065
|
+
rows.push(...(await this.#sessionQuery(transaction, sql, { params: [...window] })).rows);
|
|
6066
|
+
}
|
|
6067
|
+
return rows;
|
|
6068
|
+
};
|
|
6069
|
+
}
|
|
6070
|
+
async #scopeKeyedMutation(transaction, statement) {
|
|
6071
|
+
if (statement.from !== void 0 || statement.returning !== void 0 || statement.returningItems !== void 0 || statement.predicates.length !== 1) {
|
|
6072
|
+
return void 0;
|
|
6073
|
+
}
|
|
6074
|
+
const table = await this.#findTable(statement.table, transaction);
|
|
6075
|
+
const keyColumn = getUniqueKeyColumn(table);
|
|
6076
|
+
if (keyColumn === void 0 || keyColumn.hidden === true || keyColumn.sqlDomain !== void 0) {
|
|
6077
|
+
return void 0;
|
|
6078
|
+
}
|
|
6079
|
+
const predicate = statement.predicates[0];
|
|
6080
|
+
if (predicate === void 0)
|
|
6081
|
+
return void 0;
|
|
6082
|
+
const keyNames = /* @__PURE__ */ new Set([
|
|
6083
|
+
keyColumn.name,
|
|
6084
|
+
`${statement.alias ?? table.name}.${keyColumn.name}`
|
|
6085
|
+
]);
|
|
6086
|
+
const isKey = (expression) => expression.kind === "column" && keyNames.has(expression.reference);
|
|
6087
|
+
const keys = [];
|
|
6088
|
+
const accept = (expression) => {
|
|
6089
|
+
const value = plainLiteralValue(expression);
|
|
6090
|
+
if (value === void 0 || value === null || !valueMatchesColumnType(keyColumn, value)) {
|
|
6091
|
+
return false;
|
|
6092
|
+
}
|
|
6093
|
+
keys.push(value);
|
|
6094
|
+
return true;
|
|
6095
|
+
};
|
|
6096
|
+
if (predicate.operator === "=") {
|
|
6097
|
+
if (isKey(predicate.left)) {
|
|
6098
|
+
if (!accept(predicate.right))
|
|
6099
|
+
return void 0;
|
|
6100
|
+
} else if (isKey(predicate.right)) {
|
|
6101
|
+
if (!accept(predicate.left))
|
|
6102
|
+
return void 0;
|
|
6103
|
+
} else {
|
|
6104
|
+
return void 0;
|
|
6105
|
+
}
|
|
6106
|
+
} else if (predicate.operator === "IN" && isKey(predicate.left)) {
|
|
6107
|
+
if (predicate.right.kind !== "list")
|
|
6108
|
+
return void 0;
|
|
6109
|
+
for (const item of predicate.right.items)
|
|
6110
|
+
if (!accept(item))
|
|
6111
|
+
return void 0;
|
|
6112
|
+
} else {
|
|
6113
|
+
return void 0;
|
|
6114
|
+
}
|
|
6115
|
+
if (statement.kind === "delete")
|
|
6116
|
+
return { kind: "delete", table, keyColumn, keys, changes: {} };
|
|
6117
|
+
const changes = {};
|
|
6118
|
+
for (const assignment of statement.assignments) {
|
|
6119
|
+
const column = table.columns.find((candidate) => candidate.name === assignment.column);
|
|
6120
|
+
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)) {
|
|
6121
|
+
return void 0;
|
|
6122
|
+
}
|
|
6123
|
+
const value = plainLiteralValue(assignment.expression);
|
|
6124
|
+
if (value === void 0 || value !== null && !valueMatchesColumnType(column, value)) {
|
|
6125
|
+
return void 0;
|
|
6126
|
+
}
|
|
6127
|
+
changes[column.name] = value;
|
|
6128
|
+
}
|
|
6129
|
+
if (Object.keys(changes).length === 0)
|
|
6130
|
+
return void 0;
|
|
6131
|
+
return { kind: "update", table, keyColumn, keys, changes };
|
|
6132
|
+
}
|
|
6133
|
+
async #assertInsertKeysAbsent(transaction, table, batch, keys) {
|
|
6134
|
+
const inScope = this.#scopeWrites.has(transaction);
|
|
6135
|
+
const keyColumn = getUniqueKeyColumn(table);
|
|
6136
|
+
if (keys !== void 0 && keyColumn !== void 0) {
|
|
6137
|
+
const present = inScope ? await this.#scopeKeyPresence(transaction, table, keyColumn, [...keys.values()]) : this.#stagedKeyOverlay(transaction, table.id).added;
|
|
6138
|
+
for (const [token, value] of keys) {
|
|
6139
|
+
if (present.has(token)) {
|
|
6140
|
+
throw new UniqueConstraintError(table.name, publicKeyName(table, keyColumn), value);
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
if (!inScope)
|
|
6145
|
+
return;
|
|
6146
|
+
const rowCount = batch.rowCount ?? Object.values(batch.columns)[0]?.length ?? 0;
|
|
6147
|
+
for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
|
|
6148
|
+
const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
|
|
6149
|
+
const terms = [];
|
|
6150
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
6151
|
+
const term = secondaryUniqueTerm(index, columns, columns.map((column) => batch.columns[column.name]?.[row] ?? null));
|
|
6152
|
+
if (term !== void 0)
|
|
6153
|
+
terms.push(term);
|
|
6154
|
+
}
|
|
6155
|
+
assertNoDuplicateUniqueTerms(index, terms);
|
|
6156
|
+
const overlay = this.#stagedKeyOverlay(transaction, namespaceId);
|
|
6157
|
+
const unresolved = [];
|
|
6158
|
+
for (const term of terms) {
|
|
6159
|
+
if (overlay.added.has(term)) {
|
|
6160
|
+
throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, term));
|
|
5486
6161
|
}
|
|
6162
|
+
if (!overlay.removed.has(term))
|
|
6163
|
+
unresolved.push(term);
|
|
5487
6164
|
}
|
|
6165
|
+
if (unresolved.length === 0)
|
|
6166
|
+
continue;
|
|
6167
|
+
const [existing] = await this.#existingUniqueKeysWindowed(namespaceId, unresolved);
|
|
6168
|
+
if (existing !== void 0) {
|
|
6169
|
+
throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, existing));
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
async #assertUpdateUniqueTermsAbsent(transaction, table, input, preImages) {
|
|
6174
|
+
for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
|
|
6175
|
+
if (!columns.some((column) => input.changes[column.name] !== void 0))
|
|
6176
|
+
continue;
|
|
6177
|
+
const namespaceId = secondaryUniqueKeyNamespace(table.id, indexId);
|
|
6178
|
+
const released = /* @__PURE__ */ new Set();
|
|
6179
|
+
const taken = [];
|
|
6180
|
+
preImages.forEach((old, row) => {
|
|
6181
|
+
if (old === void 0)
|
|
6182
|
+
return;
|
|
6183
|
+
const before = secondaryUniqueTerm(index, columns, columns.map((column) => old[column.name] ?? null));
|
|
6184
|
+
const after = secondaryUniqueTerm(index, columns, columns.map((column) => {
|
|
6185
|
+
const assigned = input.changes[column.name];
|
|
6186
|
+
return assigned === void 0 ? old[column.name] ?? null : assigned[row] ?? null;
|
|
6187
|
+
}));
|
|
6188
|
+
if (before === after)
|
|
6189
|
+
return;
|
|
6190
|
+
if (before !== void 0)
|
|
6191
|
+
released.add(before);
|
|
6192
|
+
if (after !== void 0)
|
|
6193
|
+
taken.push(after);
|
|
6194
|
+
});
|
|
6195
|
+
assertNoDuplicateUniqueTerms(index, taken);
|
|
6196
|
+
const overlay = this.#stagedKeyOverlay(transaction, namespaceId);
|
|
6197
|
+
const unresolved = [];
|
|
6198
|
+
for (const term of taken) {
|
|
6199
|
+
if (released.has(term))
|
|
6200
|
+
continue;
|
|
6201
|
+
if (overlay.added.has(term)) {
|
|
6202
|
+
throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, term));
|
|
6203
|
+
}
|
|
6204
|
+
if (!overlay.removed.has(term))
|
|
6205
|
+
unresolved.push(term);
|
|
6206
|
+
}
|
|
6207
|
+
if (unresolved.length === 0)
|
|
6208
|
+
continue;
|
|
6209
|
+
const [existing] = await this.#existingUniqueKeysWindowed(namespaceId, unresolved);
|
|
6210
|
+
if (existing !== void 0) {
|
|
6211
|
+
throw await this.#translateUniqueConflict(new UniqueKeyConflictError(namespaceId, existing));
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
async #scopeKeyPresence(transaction, table, keyColumn, keys) {
|
|
6216
|
+
const overlay = this.#stagedKeyOverlay(transaction, table.id);
|
|
6217
|
+
const present = /* @__PURE__ */ new Set();
|
|
6218
|
+
const unresolved = [];
|
|
6219
|
+
for (const key of keys) {
|
|
6220
|
+
const token = keyToken(keyColumn.type, key);
|
|
6221
|
+
if (overlay.added.has(token))
|
|
6222
|
+
present.add(token);
|
|
6223
|
+
else if (!overlay.removed.has(token))
|
|
6224
|
+
unresolved.push(token);
|
|
6225
|
+
}
|
|
6226
|
+
if (unresolved.length > 0) {
|
|
6227
|
+
const existing = await this.#existingKeyTokens(table, transaction.snapshotVersion, unresolved);
|
|
6228
|
+
for (const token of existing)
|
|
6229
|
+
present.add(token);
|
|
5488
6230
|
}
|
|
5489
|
-
return
|
|
6231
|
+
return present;
|
|
6232
|
+
}
|
|
6233
|
+
async #runScopeKeyedMutation(transaction, keyed, beforeMutation) {
|
|
6234
|
+
const { table, keyColumn } = keyed;
|
|
6235
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
6236
|
+
for (const key of keyed.keys)
|
|
6237
|
+
byToken.set(keyToken(keyColumn.type, key), key);
|
|
6238
|
+
const presence = await this.#scopeKeyPresence(transaction, table, keyColumn, [
|
|
6239
|
+
...byToken.values()
|
|
6240
|
+
]);
|
|
6241
|
+
const present = [];
|
|
6242
|
+
for (const [token, key] of byToken)
|
|
6243
|
+
if (presence.has(token))
|
|
6244
|
+
present.push(key);
|
|
6245
|
+
if (present.length === 0)
|
|
6246
|
+
return { kind: keyed.kind, table: table.name, rowCount: 0 };
|
|
6247
|
+
beforeMutation();
|
|
6248
|
+
if (keyed.kind === "delete") {
|
|
6249
|
+
const deleted = await this.#sessionDelete(transaction, table.name, { keys: present });
|
|
6250
|
+
return { kind: "delete", table: table.name, rowCount: deleted.rowCount };
|
|
6251
|
+
}
|
|
6252
|
+
const changes = Object.fromEntries(Object.entries(keyed.changes).map(([name, value]) => [name, present.map(() => value)]));
|
|
6253
|
+
const updated = await this.#sessionUpdate(transaction, table.name, { keys: present, changes }, 1, true);
|
|
6254
|
+
return { kind: "update", table: table.name, rowCount: updated.rowCount };
|
|
5490
6255
|
}
|
|
5491
6256
|
async #sessionInsert(transaction, tableName, input, kind, options, cascadeBudget = 1) {
|
|
5492
6257
|
const table = await this.#findTable(tableName);
|
|
@@ -5507,7 +6272,7 @@ class MinnowDatabase {
|
|
|
5507
6272
|
validateValue(autoIncrement.column, patched[rowIndex] ?? null, rowIndex);
|
|
5508
6273
|
}
|
|
5509
6274
|
}
|
|
5510
|
-
let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount,
|
|
6275
|
+
let sessionUpsertFirings = sessionUpsertKeyColumn === void 0 ? void 0 : await this.#upsertTriggerFirings(table, sessionUpsertKeyColumn, batch, rowCount, void 0, normalizedConflictWhere, this.#scopeKeyLookup(transaction, table, sessionUpsertKeyColumn));
|
|
5511
6276
|
let skippedRowCount = 0;
|
|
5512
6277
|
if (normalizedConflictWhere !== void 0) {
|
|
5513
6278
|
if (sessionUpsertFirings === void 0) {
|
|
@@ -5533,7 +6298,12 @@ class MinnowDatabase {
|
|
|
5533
6298
|
}
|
|
5534
6299
|
}
|
|
5535
6300
|
await this.#assertCompactionCapacity(table, transaction);
|
|
6301
|
+
await this.#assertForeignKeysPresent(table, (column) => batch.columns[column] ?? [], (sql, params) => this.#sessionQuery(transaction, sql, { params }), transaction);
|
|
5536
6302
|
const keys = batchKeys(table, batch);
|
|
6303
|
+
if (kind === "insert")
|
|
6304
|
+
await this.#assertInsertKeysAbsent(transaction, table, batch, keys);
|
|
6305
|
+
else
|
|
6306
|
+
assertBatchSecondaryTermsDistinct(table, batch);
|
|
5537
6307
|
if (keys !== void 0) {
|
|
5538
6308
|
transaction.setUniqueKeyChanges({
|
|
5539
6309
|
tableId: table.id,
|
|
@@ -5541,10 +6311,22 @@ class MinnowDatabase {
|
|
|
5541
6311
|
requireAbsent: kind === "insert"
|
|
5542
6312
|
});
|
|
5543
6313
|
}
|
|
5544
|
-
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
6314
|
const insertValueAt = (source, column, rowIndex) => source === "new" ? batch.columns[column]?.[rowIndex] ?? null : null;
|
|
5547
6315
|
stageSecondaryUniqueInsertChanges(transaction, table, batch, kind === "upsert" ? sessionUpsertFirings?.oldImages : void 0);
|
|
6316
|
+
const buffered = this.#scopeWrites.has(transaction) && rowCount < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "insert" || trigger.event === "update");
|
|
6317
|
+
if (buffered) {
|
|
6318
|
+
await this.#bufferScopeRows(transaction, table, batch, rowCount, kind);
|
|
6319
|
+
collectAutoIncrementGenerated(batch, generated, autoIncrement);
|
|
6320
|
+
return {
|
|
6321
|
+
tableName: table.name,
|
|
6322
|
+
segmentId: null,
|
|
6323
|
+
rowCount,
|
|
6324
|
+
skippedRowCount,
|
|
6325
|
+
...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
|
|
6326
|
+
};
|
|
6327
|
+
}
|
|
6328
|
+
await this.#stageScopeDirectly(transaction, table);
|
|
6329
|
+
const rowIds = await this.store.reserveRowIds(table.id, rowCount);
|
|
5548
6330
|
if (kind === "insert") {
|
|
5549
6331
|
await this.#stageTriggerDerivedInserts(transaction, table, "insert", rowCount, insertValueAt, "before", cascadeBudget);
|
|
5550
6332
|
} else if (sessionUpsertFirings !== void 0) {
|
|
@@ -5565,8 +6347,8 @@ class MinnowDatabase {
|
|
|
5565
6347
|
...generated.size === 0 ? {} : { generatedColumns: Object.fromEntries(generated) }
|
|
5566
6348
|
};
|
|
5567
6349
|
}
|
|
5568
|
-
async #sessionUpdate(transaction, tableName, input, cascadeBudget = 1) {
|
|
5569
|
-
const table = await this.#findTable(tableName);
|
|
6350
|
+
async #sessionUpdate(transaction, tableName, input, cascadeBudget = 1, keysVerified = false) {
|
|
6351
|
+
const table = await this.#findTable(tableName, transaction);
|
|
5570
6352
|
await this.#assertCompactionCapacity(table, transaction);
|
|
5571
6353
|
const keyColumn = getUniqueKeyColumn(table);
|
|
5572
6354
|
if (keyColumn === void 0) {
|
|
@@ -5576,21 +6358,26 @@ class MinnowDatabase {
|
|
|
5576
6358
|
rejectGeneratedUpdateAssignments(table, input);
|
|
5577
6359
|
const keys = validateUpdateBatch(table, keyColumn, input);
|
|
5578
6360
|
const overlay = this.#stagedKeyOverlay(transaction, table.id);
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
6361
|
+
if (!keysVerified) {
|
|
6362
|
+
for (const [token, value] of keys) {
|
|
6363
|
+
if (overlay.removed.has(token)) {
|
|
6364
|
+
throw new MissingKeyError(table.name, keyColumn.name, value);
|
|
6365
|
+
}
|
|
6366
|
+
}
|
|
6367
|
+
const committedKeys = new Map([...keys].filter(([token]) => !overlay.added.has(token)));
|
|
6368
|
+
if (committedKeys.size > 0) {
|
|
6369
|
+
await this.#assertKeysExist(table, keyColumn, transaction.snapshotVersion, committedKeys);
|
|
5582
6370
|
}
|
|
5583
|
-
}
|
|
5584
|
-
const committedKeys = new Map([...keys].filter(([token]) => !overlay.added.has(token)));
|
|
5585
|
-
if (committedKeys.size > 0) {
|
|
5586
|
-
await this.#assertKeysExist(table, keyColumn, transaction.snapshotVersion, committedKeys);
|
|
5587
6371
|
}
|
|
5588
6372
|
const sessionChecks = table.checks ?? [];
|
|
5589
6373
|
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",
|
|
6374
|
+
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
6375
|
input = applyStoredGeneratedUpdateChanges(table, input, preImages);
|
|
5592
6376
|
validateUpdateBatch(table, keyColumn, input);
|
|
5593
6377
|
changedForeignKey = (table.foreignKeys ?? []).some((key) => key.enforced !== false && foreignKeyColumns(key).some((column) => input.changes[column] !== void 0));
|
|
6378
|
+
if (this.#scopeWrites.has(transaction)) {
|
|
6379
|
+
await this.#assertUpdateUniqueTermsAbsent(transaction, table, input, preImages);
|
|
6380
|
+
}
|
|
5594
6381
|
stageSecondaryUniqueMutationChanges(transaction, table, input, preImages);
|
|
5595
6382
|
const secondaryDeltas = buildSecondaryUpdateDeltas(table, input, preImages);
|
|
5596
6383
|
if (secondaryDeltas.length > 0) {
|
|
@@ -5617,56 +6404,14 @@ class MinnowDatabase {
|
|
|
5617
6404
|
}
|
|
5618
6405
|
}
|
|
5619
6406
|
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;
|
|
6407
|
+
const buffered = this.#scopeWrites.has(transaction) && input.keys.length < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "update");
|
|
6408
|
+
let segmentId = null;
|
|
6409
|
+
if (buffered) {
|
|
6410
|
+
await this.#bufferScopeUpdate(transaction, table, keyColumn, input);
|
|
6411
|
+
} else {
|
|
6412
|
+
await this.#stageScopeDirectly(transaction, table);
|
|
6413
|
+
segmentId = await this.#stageUpdateSegment(transaction, table, keyColumn, input);
|
|
5651
6414
|
}
|
|
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
6415
|
await this.#stageTriggerDerivedInserts(transaction, table, "update", input.keys.length, sessionUpdateValueAt, "after", cascadeBudget);
|
|
5671
6416
|
return {
|
|
5672
6417
|
tableName: table.name,
|
|
@@ -5681,7 +6426,7 @@ class MinnowDatabase {
|
|
|
5681
6426
|
};
|
|
5682
6427
|
}
|
|
5683
6428
|
async #sessionDelete(transaction, tableName, input, cascadeBudget = 1, referentialBudget = REFERENTIAL_CASCADES) {
|
|
5684
|
-
const table = await this.#findTable(tableName);
|
|
6429
|
+
const table = await this.#findTable(tableName, transaction);
|
|
5685
6430
|
await this.#assertCompactionCapacity(table, transaction);
|
|
5686
6431
|
const keyColumn = getUniqueKeyColumn(table);
|
|
5687
6432
|
if (keyColumn === void 0) {
|
|
@@ -5727,14 +6472,79 @@ class MinnowDatabase {
|
|
|
5727
6472
|
if (secondaryCoverage.length > 0) {
|
|
5728
6473
|
transaction.setFtsChanges({ tableId: table.id, columns: secondaryCoverage });
|
|
5729
6474
|
}
|
|
5730
|
-
const preImages = (await this.#triggerPreImages(table, keyColumn, [...keys.values()], "delete",
|
|
6475
|
+
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
6476
|
stageSecondaryUniqueMutationChanges(transaction, table, void 0, preImages);
|
|
5732
6477
|
const sessionDeleteValueAt = (source, column, rowIndex) => source === "old" ? preImages[rowIndex]?.[column] ?? null : null;
|
|
5733
6478
|
await this.#stageTriggerDerivedInserts(transaction, table, "delete", preImages.length, sessionDeleteValueAt, "before", cascadeBudget);
|
|
5734
|
-
const
|
|
5735
|
-
|
|
5736
|
-
|
|
6479
|
+
const buffered = this.#scopeWrites.has(transaction) && keys.size < SCOPE_DIRECT_STAGE_ROWS && !(table.triggers ?? []).some((trigger) => trigger.event === "delete");
|
|
6480
|
+
let segmentId = null;
|
|
6481
|
+
if (buffered) {
|
|
6482
|
+
await this.#bufferScopeDelete(transaction, table, keys);
|
|
6483
|
+
} else {
|
|
6484
|
+
await this.#stageScopeDirectly(transaction, table);
|
|
6485
|
+
const values = [...keys.values()];
|
|
6486
|
+
if (keyStringByteLengths !== void 0) {
|
|
6487
|
+
validatedStringByteLengths.set(values, keyStringByteLengths);
|
|
6488
|
+
}
|
|
6489
|
+
segmentId = await this.#stageDeleteSegment(transaction, table, keyColumn, values);
|
|
6490
|
+
}
|
|
6491
|
+
await this.#stageTriggerDerivedInserts(transaction, table, "delete", preImages.length, sessionDeleteValueAt, "after", cascadeBudget);
|
|
6492
|
+
return { tableName: table.name, segmentId, rowCount: keys.size };
|
|
6493
|
+
}
|
|
6494
|
+
async #stageUpdateSegment(transaction, table, keyColumn, input) {
|
|
6495
|
+
const changedColumns = Object.keys(input.changes).sort();
|
|
6496
|
+
const columns = [keyColumn, ...changedColumns.map((name) => findColumn(table, name))];
|
|
6497
|
+
const segmentId = this.#createId();
|
|
6498
|
+
const columnBlockIds = {};
|
|
6499
|
+
const blockStager = new BoundedWriteBlockStager(transaction);
|
|
6500
|
+
const plannedColumns = columns.map((column) => writeColumnValues(column.type, column.id === keyColumn.id ? input.keys : input.changes[column.name] ?? []));
|
|
6501
|
+
const ranges = writeBlockRanges(plannedColumns, input.keys.length, this.#rowsPerBlock, this.#targetBlockBytes);
|
|
6502
|
+
for (const [columnIndex, column] of columns.entries()) {
|
|
6503
|
+
const values = column.id === keyColumn.id ? input.keys : input.changes[column.name] ?? [];
|
|
6504
|
+
const plannedColumn = plannedColumns[columnIndex];
|
|
6505
|
+
if (plannedColumn === void 0)
|
|
6506
|
+
throw new Error(`Write column disappeared: ${column.name}`);
|
|
6507
|
+
const blockIds = [];
|
|
6508
|
+
for (const [part, { start, end }] of ranges.entries()) {
|
|
6509
|
+
await blockStager.prepare(maximumWriteBlockStoredBytes(plannedColumn, start, end, this.#compression));
|
|
6510
|
+
const slice = values.slice(start, end);
|
|
6511
|
+
const bytes = await this.#encodeColumnBlock(column.id, asColumnInput(column.type, slice));
|
|
6512
|
+
const blockId = [
|
|
6513
|
+
"table",
|
|
6514
|
+
table.id,
|
|
6515
|
+
"segment",
|
|
6516
|
+
segmentId,
|
|
6517
|
+
"update-column",
|
|
6518
|
+
column.id,
|
|
6519
|
+
"part",
|
|
6520
|
+
String(part).padStart(6, "0")
|
|
6521
|
+
].join("/");
|
|
6522
|
+
blockStager.add({ id: blockId, bytes });
|
|
6523
|
+
blockIds.push(blockId);
|
|
6524
|
+
}
|
|
6525
|
+
columnBlockIds[column.id] = blockIds;
|
|
5737
6526
|
}
|
|
6527
|
+
await blockStager.stageWithSegments([
|
|
6528
|
+
{
|
|
6529
|
+
id: segmentId,
|
|
6530
|
+
tableId: table.id,
|
|
6531
|
+
transactionId: transaction.id,
|
|
6532
|
+
rowCount: input.keys.length,
|
|
6533
|
+
rowIdStart: 0n,
|
|
6534
|
+
rowIdEndExclusive: 0n,
|
|
6535
|
+
columnBlockIds,
|
|
6536
|
+
kind: "update",
|
|
6537
|
+
keyColumnId: keyColumn.id,
|
|
6538
|
+
level: 0,
|
|
6539
|
+
logicalOrder: 0,
|
|
6540
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
6541
|
+
rowIdSpans: [],
|
|
6542
|
+
createdAt: dateIsoString(this.#now())
|
|
6543
|
+
}
|
|
6544
|
+
]);
|
|
6545
|
+
return segmentId;
|
|
6546
|
+
}
|
|
6547
|
+
async #stageDeleteSegment(transaction, table, keyColumn, values) {
|
|
5738
6548
|
const segmentId = this.#createId();
|
|
5739
6549
|
const blockIds = [];
|
|
5740
6550
|
const blockStager = new BoundedWriteBlockStager(transaction);
|
|
@@ -5762,7 +6572,7 @@ class MinnowDatabase {
|
|
|
5762
6572
|
id: segmentId,
|
|
5763
6573
|
tableId: table.id,
|
|
5764
6574
|
transactionId: transaction.id,
|
|
5765
|
-
rowCount:
|
|
6575
|
+
rowCount: values.length,
|
|
5766
6576
|
rowIdStart: 0n,
|
|
5767
6577
|
rowIdEndExclusive: 0n,
|
|
5768
6578
|
columnBlockIds: { [keyColumn.id]: blockIds },
|
|
@@ -5770,13 +6580,12 @@ class MinnowDatabase {
|
|
|
5770
6580
|
keyColumnId: keyColumn.id,
|
|
5771
6581
|
level: 0,
|
|
5772
6582
|
logicalOrder: 0,
|
|
5773
|
-
commitOrdinal: transaction.
|
|
6583
|
+
commitOrdinal: transaction.pendingSegmentCount,
|
|
5774
6584
|
rowIdSpans: [],
|
|
5775
6585
|
createdAt: dateIsoString(this.#now())
|
|
5776
6586
|
}
|
|
5777
6587
|
]);
|
|
5778
|
-
|
|
5779
|
-
return { tableName: table.name, segmentId, rowCount: keys.size };
|
|
6588
|
+
return segmentId;
|
|
5780
6589
|
}
|
|
5781
6590
|
#notifyLiveCommit() {
|
|
5782
6591
|
for (const set of this.#liveSets)
|
|
@@ -6181,6 +6990,9 @@ class MinnowDatabase {
|
|
|
6181
6990
|
notes.push("BM25 scoring reads the full scan for corpus statistics; index pruning does not apply");
|
|
6182
6991
|
}
|
|
6183
6992
|
}
|
|
6993
|
+
const liveReasons = [];
|
|
6994
|
+
const liveState = await this.#liveMaintenancePlan(plan, await this.store.getCatalogProbe(), liveReasons);
|
|
6995
|
+
notes.push(liveState !== void 0 ? "live: maintained incrementally on change" : `live: re-executes on change: ${liveReasons.join("; ") || "the shape is not maintainable"}`);
|
|
6184
6996
|
return `${renderPlan(plan)}
|
|
6185
6997
|
${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
6186
6998
|
});
|
|
@@ -6505,7 +7317,8 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6505
7317
|
if (totalBytes > MAX_TRANSACTION_SAVEPOINT_BYTES) {
|
|
6506
7318
|
throw new RangeError(`Transaction savepoints cannot retain more than ${String(MAX_TRANSACTION_SAVEPOINT_BYTES)} bytes`);
|
|
6507
7319
|
}
|
|
6508
|
-
|
|
7320
|
+
const checkpoint = await open.session.checkpoint();
|
|
7321
|
+
open.savepoints.push({ name, checkpoint, retainedBytes });
|
|
6509
7322
|
});
|
|
6510
7323
|
return { kind: "transaction", action, name };
|
|
6511
7324
|
}
|
|
@@ -6767,6 +7580,24 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6767
7580
|
const generatedColumns = new Set(table.columns.flatMap((column) => column.generatedValue === void 0 ? [] : [column.name]));
|
|
6768
7581
|
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
7582
|
const keys = filled.batch.columns[keyColumn.name] ?? [];
|
|
7583
|
+
const presence = await writer?.stagedKeyPresence?.(table, keyColumn, keys.filter((value) => value !== null));
|
|
7584
|
+
if (presence !== void 0) {
|
|
7585
|
+
const taken = new Set(presence);
|
|
7586
|
+
return {
|
|
7587
|
+
...statement,
|
|
7588
|
+
columns,
|
|
7589
|
+
rows: materializedRows.filter((_, index) => {
|
|
7590
|
+
const value = keys[index] ?? null;
|
|
7591
|
+
if (value === null)
|
|
7592
|
+
return true;
|
|
7593
|
+
const token = keyToken(keyColumn.type, value);
|
|
7594
|
+
if (taken.has(token))
|
|
7595
|
+
return false;
|
|
7596
|
+
taken.add(token);
|
|
7597
|
+
return true;
|
|
7598
|
+
})
|
|
7599
|
+
};
|
|
7600
|
+
}
|
|
6770
7601
|
const plan = {
|
|
6771
7602
|
sql: "(on conflict do nothing)",
|
|
6772
7603
|
base: { table: table.name, alias: table.name },
|
|
@@ -6809,6 +7640,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
6809
7640
|
const keys = insertBatchKeyValues(input, keyColumn.name);
|
|
6810
7641
|
if (keys.length === 0)
|
|
6811
7642
|
return;
|
|
7643
|
+
const present = await writer.stagedKeyPresence?.(table, keyColumn, keys.filter((value) => value !== null));
|
|
7644
|
+
if (present !== void 0) {
|
|
7645
|
+
for (const value of keys) {
|
|
7646
|
+
if (value !== null && present.has(keyToken(keyColumn.type, value))) {
|
|
7647
|
+
throw new UniqueConstraintError(table.name, keyColumn.name, value);
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7650
|
+
return;
|
|
7651
|
+
}
|
|
6812
7652
|
const plan = {
|
|
6813
7653
|
sql: "(staged insert keys)",
|
|
6814
7654
|
base: { table: table.name, alias: table.name },
|
|
@@ -7115,6 +7955,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7115
7955
|
normalizeDomainBatch(pendingTable, batch);
|
|
7116
7956
|
validateBatch(pendingTable, batch);
|
|
7117
7957
|
const endExclusive = nextRowId + BigInt(rows2.length);
|
|
7958
|
+
await this.#stageScopeDirectly(transaction, pendingTable);
|
|
7118
7959
|
await this.#stageInsertSegment(transaction, pendingTable, batch, rows2.length, "insert", { start: nextRowId, endExclusive });
|
|
7119
7960
|
nextRowId = endExclusive;
|
|
7120
7961
|
stagedRows += rows2.length;
|
|
@@ -7691,9 +8532,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7691
8532
|
return void 0;
|
|
7692
8533
|
const visibleBaseSegments = visibleByTable.get(freshBaseTable.name) ?? await this.#visibleSegmentRecords(freshBaseTable, snapshot, visibility);
|
|
7693
8534
|
this.#maybeScheduleAutoCompaction(freshBaseTable, visibleBaseSegments);
|
|
7694
|
-
const
|
|
7695
|
-
throwIfAborted(options.signal);
|
|
7696
|
-
const indexed = await this.#secondaryIndexPrunedSegments(freshBaseTable, ftsSegments, plan, snapshot);
|
|
8535
|
+
const indexed = await this.#indexPrunedSegments(freshBaseTable, visibleBaseSegments, plan, snapshot, visibility);
|
|
7697
8536
|
throwIfAborted(options.signal);
|
|
7698
8537
|
const baseSegments = indexed.segments;
|
|
7699
8538
|
const zonePruned = indexed.rows === void 0 ? await this.#zonePrunedStreamSegments(plan, freshBaseTable, projectedBaseColumns, baseSegments, snapshot) : void 0;
|
|
@@ -7947,13 +8786,14 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7947
8786
|
#streamedViewFactory(table, projectedColumns, segments, snapshot, storedBlocks, zonePruned = false) {
|
|
7948
8787
|
const scanSegments = segments.filter((segment) => {
|
|
7949
8788
|
const kind = segment.kind;
|
|
7950
|
-
return kind === "insert" || kind === "base";
|
|
8789
|
+
return kind === "insert" || kind === "base" || kind === "upsert";
|
|
7951
8790
|
});
|
|
7952
8791
|
const mutationSegments = segments.filter((segment) => segment.kind === "update" || segment.kind === "delete");
|
|
7953
8792
|
if (scanSegments.length + mutationSegments.length !== segments.length)
|
|
7954
8793
|
return void 0;
|
|
8794
|
+
const replays = mutationSegments.length > 0 || segments.some((segment) => segment.kind === "upsert");
|
|
7955
8795
|
const keyColumn = getUniqueKeyColumn(table);
|
|
7956
|
-
if (
|
|
8796
|
+
if (replays) {
|
|
7957
8797
|
if (keyColumn === void 0)
|
|
7958
8798
|
return void 0;
|
|
7959
8799
|
const keyBlocksPresent = segments.every((segment) => segment.rowCount === 0 || (segment.columnBlockIds[keyColumn.id]?.length ?? 0) > 0);
|
|
@@ -7965,7 +8805,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
7965
8805
|
return void 0;
|
|
7966
8806
|
const scanRowCount = scanSegments.reduce((total, segment) => total + segment.rowCount, 0);
|
|
7967
8807
|
return {
|
|
7968
|
-
create: async (memory) =>
|
|
8808
|
+
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
8809
|
};
|
|
7970
8810
|
}
|
|
7971
8811
|
async #runPartitionedJoin(plan, budgetBytes, estimatedBuildBytes, baseView, buildView, buildTableName, buildKeyName, signal) {
|
|
@@ -8239,11 +9079,71 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8239
9079
|
async #createStreamedMutationTable(table, keyColumn, projectedColumns, baseSegments, snapshot, memory, zonePruned = false, storedBlocks) {
|
|
8240
9080
|
const scanSegments = baseSegments.filter((segment) => {
|
|
8241
9081
|
const kind = segment.kind;
|
|
8242
|
-
return kind === "insert" || kind === "base";
|
|
9082
|
+
return kind === "insert" || kind === "base" || kind === "upsert";
|
|
8243
9083
|
});
|
|
8244
9084
|
const overlay = await this.#streamedOverlayState(table, keyColumn, baseSegments, scanSegments, snapshot, memory, zonePruned);
|
|
8245
|
-
const { baseRows, dead, deadCount,
|
|
9085
|
+
const { baseRows, dead, deadCount, lazyPatches, patchedSlots } = overlay;
|
|
8246
9086
|
const hasPatches = patchedSlots.length > 0;
|
|
9087
|
+
const windowPatches = async (from, to) => {
|
|
9088
|
+
const needed = /* @__PURE__ */ new Set();
|
|
9089
|
+
const inRange = [];
|
|
9090
|
+
const winning = (layers, columnId) => {
|
|
9091
|
+
for (let index = layers.length - 1; index >= 0; index -= 1) {
|
|
9092
|
+
const candidate = layers[index];
|
|
9093
|
+
if (candidate === void 0)
|
|
9094
|
+
continue;
|
|
9095
|
+
const blockId = candidate.segment.columnBlockIds[columnId]?.[candidate.blockIndex];
|
|
9096
|
+
if (blockId !== void 0)
|
|
9097
|
+
return { blockId, row: candidate.row };
|
|
9098
|
+
}
|
|
9099
|
+
return void 0;
|
|
9100
|
+
};
|
|
9101
|
+
for (const [slot, layers] of lazyPatches) {
|
|
9102
|
+
if (slot < from || slot >= to)
|
|
9103
|
+
continue;
|
|
9104
|
+
inRange.push([slot, layers]);
|
|
9105
|
+
for (const column of projectedColumns) {
|
|
9106
|
+
const hit = winning(layers, column.id);
|
|
9107
|
+
if (hit !== void 0)
|
|
9108
|
+
needed.add(hit.blockId);
|
|
9109
|
+
}
|
|
9110
|
+
}
|
|
9111
|
+
const empty = /* @__PURE__ */ new Map();
|
|
9112
|
+
if (inRange.length === 0)
|
|
9113
|
+
return empty;
|
|
9114
|
+
const ids = [...needed];
|
|
9115
|
+
const decoded = await this.#decodedBlocksThroughCache(ids, snapshot);
|
|
9116
|
+
const vectors = /* @__PURE__ */ new Map();
|
|
9117
|
+
ids.forEach((id, index) => {
|
|
9118
|
+
const block = decoded[index];
|
|
9119
|
+
if (block === void 0)
|
|
9120
|
+
throw new Error(`Visible block is missing: ${id}`);
|
|
9121
|
+
vectors.set(id, this.#blockColumnVector(id, block));
|
|
9122
|
+
});
|
|
9123
|
+
memory.tally(inRange.length * 96, "Streamed mutation replay");
|
|
9124
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
9125
|
+
const plain = /* @__PURE__ */ new Map();
|
|
9126
|
+
for (const [slot, layers] of inRange) {
|
|
9127
|
+
const slotPatches = /* @__PURE__ */ new Map();
|
|
9128
|
+
for (const column of projectedColumns) {
|
|
9129
|
+
const hit = winning(layers, column.id);
|
|
9130
|
+
if (hit === void 0)
|
|
9131
|
+
continue;
|
|
9132
|
+
let vector = plain.get(hit.blockId);
|
|
9133
|
+
if (vector === void 0) {
|
|
9134
|
+
const raw = vectors.get(hit.blockId);
|
|
9135
|
+
if (raw === void 0)
|
|
9136
|
+
throw new Error(`Visible block is missing: ${hit.blockId}`);
|
|
9137
|
+
vector = plainTextExecutionVector(column, raw);
|
|
9138
|
+
plain.set(hit.blockId, vector);
|
|
9139
|
+
}
|
|
9140
|
+
slotPatches.set(column.id, { vector, row: hit.row });
|
|
9141
|
+
}
|
|
9142
|
+
if (slotPatches.size > 0)
|
|
9143
|
+
resolved.set(slot, slotPatches);
|
|
9144
|
+
}
|
|
9145
|
+
return resolved;
|
|
9146
|
+
};
|
|
8247
9147
|
const outputRows = baseRows - deadCount;
|
|
8248
9148
|
const inner = this.#createStreamedTable(table, projectedColumns, scanSegments, snapshot, baseRows, memory, storedBlocks);
|
|
8249
9149
|
if (deadCount === 0 && !hasPatches)
|
|
@@ -8282,14 +9182,30 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8282
9182
|
}
|
|
8283
9183
|
const baseStart = cursorBase;
|
|
8284
9184
|
const innerEnd = await inner.load(baseStart, baseRows - baseStart);
|
|
8285
|
-
const
|
|
8286
|
-
if (
|
|
9185
|
+
const innerBaseEnd = typeof innerEnd === "number" ? Math.min(innerEnd, baseRows) : baseRows;
|
|
9186
|
+
if (innerBaseEnd <= baseStart)
|
|
8287
9187
|
throw new Error(`Column row count mismatch: ${table.name}`);
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
9188
|
+
let baseEnd = innerBaseEnd;
|
|
9189
|
+
let deadInWindow = bitmapCountRange(dead, baseStart, baseEnd);
|
|
9190
|
+
let patchedInWindow = hasPatches ? sortedCountRange(patchedSlots, baseStart, baseEnd) : 0;
|
|
8291
9191
|
const untouched = deadInWindow === 0 && patchedInWindow === 0;
|
|
9192
|
+
if (!untouched) {
|
|
9193
|
+
let live = 0;
|
|
9194
|
+
let row = baseStart;
|
|
9195
|
+
while (row < innerBaseEnd && live < length) {
|
|
9196
|
+
if (!bitmapHasValue(dead, row))
|
|
9197
|
+
live += 1;
|
|
9198
|
+
row += 1;
|
|
9199
|
+
}
|
|
9200
|
+
if (row < baseEnd) {
|
|
9201
|
+
baseEnd = row;
|
|
9202
|
+
deadInWindow = bitmapCountRange(dead, baseStart, baseEnd);
|
|
9203
|
+
patchedInWindow = hasPatches ? sortedCountRange(patchedSlots, baseStart, baseEnd) : 0;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
const liveRows = baseEnd - baseStart - deadInWindow;
|
|
8292
9207
|
const runs = untouched ? void 0 : overlayWindowRuns(dead, patchedSlots, baseStart, baseEnd, patchedInWindow);
|
|
9208
|
+
const resolvedPatches = runs === void 0 || !hasPatches ? void 0 : await windowPatches(baseStart, baseEnd);
|
|
8293
9209
|
const targets = [];
|
|
8294
9210
|
try {
|
|
8295
9211
|
for (const state of states) {
|
|
@@ -8303,7 +9219,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8303
9219
|
throw new Error(`Column row count mismatch: ${state.column.name}`);
|
|
8304
9220
|
}
|
|
8305
9221
|
const replacements = [];
|
|
8306
|
-
const fields = runs === void 0 ? overlayWindowView(innerVector, offset, liveRows, memory, state.column, replacements) : overlayWindowCompacted(innerVector, innerWindow.start, runs, liveRows,
|
|
9222
|
+
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
9223
|
fields.window = { start, length: liveRows };
|
|
8308
9224
|
targets.push({ state, fields, replacements });
|
|
8309
9225
|
}
|
|
@@ -8377,12 +9293,11 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8377
9293
|
}
|
|
8378
9294
|
async #buildStreamedOverlayState(table, keyColumn, baseSegments, scanSegments, snapshot, memory, zonePruned) {
|
|
8379
9295
|
const deltaSegments = baseSegments.filter(mutationSegmentKind);
|
|
9296
|
+
const upsertSegments = baseSegments.filter((segment) => segment.kind === "upsert");
|
|
8380
9297
|
const deltaBlockIds = /* @__PURE__ */ new Set();
|
|
8381
|
-
for (const segment of deltaSegments) {
|
|
8382
|
-
for (const
|
|
8383
|
-
|
|
8384
|
-
deltaBlockIds.add(blockId);
|
|
8385
|
-
}
|
|
9298
|
+
for (const segment of [...deltaSegments, ...upsertSegments]) {
|
|
9299
|
+
for (const blockId of segment.columnBlockIds[keyColumn.id] ?? [])
|
|
9300
|
+
deltaBlockIds.add(blockId);
|
|
8386
9301
|
}
|
|
8387
9302
|
const decodedDeltaBlocks = /* @__PURE__ */ new Map();
|
|
8388
9303
|
if (deltaBlockIds.size > 0) {
|
|
@@ -8413,11 +9328,32 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8413
9328
|
return vector;
|
|
8414
9329
|
};
|
|
8415
9330
|
const mutationKeyVectors = /* @__PURE__ */ new Map();
|
|
8416
|
-
const
|
|
9331
|
+
const blockStarts = /* @__PURE__ */ new Map();
|
|
9332
|
+
const layoutOf = (segment) => {
|
|
9333
|
+
const starts = [];
|
|
9334
|
+
let start = 0;
|
|
9335
|
+
for (const blockId of segment.columnBlockIds[keyColumn.id] ?? []) {
|
|
9336
|
+
starts.push(start);
|
|
9337
|
+
const decoded = decodedDeltaBlocks.get(blockId);
|
|
9338
|
+
if (decoded === void 0)
|
|
9339
|
+
throw new Error(`Visible block is missing: ${blockId}`);
|
|
9340
|
+
start += this.#blockColumnVector(blockId, decoded).length;
|
|
9341
|
+
}
|
|
9342
|
+
if (start !== segment.rowCount) {
|
|
9343
|
+
throw new Error(`Column row count mismatch: ${keyColumn.name}`);
|
|
9344
|
+
}
|
|
9345
|
+
return starts;
|
|
9346
|
+
};
|
|
9347
|
+
const locate = (segment, row) => {
|
|
9348
|
+
const starts = blockStarts.get(segment.id) ?? [];
|
|
9349
|
+
let blockIndex = starts.length - 1;
|
|
9350
|
+
while (blockIndex > 0 && (starts[blockIndex] ?? 0) > row)
|
|
9351
|
+
blockIndex -= 1;
|
|
9352
|
+
return { blockIndex, rowInBlock: row - (starts[blockIndex] ?? 0) };
|
|
9353
|
+
};
|
|
8417
9354
|
const touched = /* @__PURE__ */ new Set();
|
|
8418
|
-
|
|
9355
|
+
const retainedBytes = 0;
|
|
8419
9356
|
for (const segment of deltaSegments) {
|
|
8420
|
-
const kind = segment.kind;
|
|
8421
9357
|
const keyVector = await deltaVector(keyColumn, segment);
|
|
8422
9358
|
memory.reserve(columnVectorRetainedBytes(keyVector), "Streamed mutation replay");
|
|
8423
9359
|
mutationKeyVectors.set(segment.id, keyVector);
|
|
@@ -8425,21 +9361,16 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8425
9361
|
for (let row = 0; row < segment.rowCount; row += 1) {
|
|
8426
9362
|
touched.add(readMutationKey(row));
|
|
8427
9363
|
}
|
|
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
|
-
}
|
|
9364
|
+
if (segment.kind === "update")
|
|
9365
|
+
blockStarts.set(segment.id, layoutOf(segment));
|
|
9366
|
+
}
|
|
9367
|
+
for (const segment of upsertSegments) {
|
|
9368
|
+
const keyVector = await deltaVector(keyColumn, segment);
|
|
9369
|
+
memory.reserve(columnVectorRetainedBytes(keyVector), "Streamed mutation replay");
|
|
9370
|
+
mutationKeyVectors.set(segment.id, keyVector);
|
|
9371
|
+
const readMutationKey = requiredColumnVectorKeyReader(keyVector);
|
|
9372
|
+
for (let row = 0; row < segment.rowCount; row += 1)
|
|
9373
|
+
touched.add(readMutationKey(row));
|
|
8443
9374
|
}
|
|
8444
9375
|
const touchedByScanSegment = /* @__PURE__ */ new Map();
|
|
8445
9376
|
const touchedPredicate = touchedKeyPredicate(keyColumn, touched);
|
|
@@ -8449,7 +9380,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8449
9380
|
const entries = [];
|
|
8450
9381
|
touchedByScanSegment.set(segment.id, entries);
|
|
8451
9382
|
let segmentRows = 0;
|
|
8452
|
-
for (const blockId of segment.columnBlockIds[keyColumn.id] ?? []) {
|
|
9383
|
+
for (const [blockIndex, blockId] of (segment.columnBlockIds[keyColumn.id] ?? []).entries()) {
|
|
8453
9384
|
const description = keyDescriptions.get(blockId);
|
|
8454
9385
|
if (touchedPredicate !== void 0 && description !== void 0) {
|
|
8455
9386
|
if (description.type !== keyColumn.type) {
|
|
@@ -8471,8 +9402,9 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8471
9402
|
const readBlockKey = requiredColumnVectorKeyReader(blockVector);
|
|
8472
9403
|
for (let row = 0; row < rows; row += 1) {
|
|
8473
9404
|
const key = readBlockKey(row);
|
|
8474
|
-
if (touched.has(key))
|
|
8475
|
-
entries.push({ key, slot: baseRows + segmentRows + row });
|
|
9405
|
+
if (touched.has(key)) {
|
|
9406
|
+
entries.push({ key, slot: baseRows + segmentRows + row, blockIndex, row });
|
|
9407
|
+
}
|
|
8476
9408
|
}
|
|
8477
9409
|
segmentRows += rows;
|
|
8478
9410
|
}
|
|
@@ -8485,8 +9417,15 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8485
9417
|
const dead = new Uint8Array(Math.ceil(baseRows / 8));
|
|
8486
9418
|
memory.reserve(dead.byteLength, "Streamed mutation replay");
|
|
8487
9419
|
const slotByKey = /* @__PURE__ */ new Map();
|
|
8488
|
-
const
|
|
9420
|
+
const lazyPatches = /* @__PURE__ */ new Map();
|
|
8489
9421
|
let deadCount = 0;
|
|
9422
|
+
const layer = (slot, patch) => {
|
|
9423
|
+
const layers2 = lazyPatches.get(slot);
|
|
9424
|
+
if (layers2 === void 0)
|
|
9425
|
+
lazyPatches.set(slot, [patch]);
|
|
9426
|
+
else
|
|
9427
|
+
layers2.push(patch);
|
|
9428
|
+
};
|
|
8490
9429
|
for (const segment of baseSegments) {
|
|
8491
9430
|
const kind = segment.kind;
|
|
8492
9431
|
if (kind === "insert" || kind === "base") {
|
|
@@ -8498,6 +9437,21 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8498
9437
|
}
|
|
8499
9438
|
continue;
|
|
8500
9439
|
}
|
|
9440
|
+
if (kind === "upsert") {
|
|
9441
|
+
for (const entry of touchedByScanSegment.get(segment.id) ?? []) {
|
|
9442
|
+
const existing = slotByKey.get(entry.key);
|
|
9443
|
+
if (existing === void 0) {
|
|
9444
|
+
slotByKey.set(entry.key, entry.slot);
|
|
9445
|
+
continue;
|
|
9446
|
+
}
|
|
9447
|
+
layer(existing, { segment, blockIndex: entry.blockIndex, row: entry.row });
|
|
9448
|
+
if (!bitmapHasValue(dead, entry.slot)) {
|
|
9449
|
+
setBitmapValue(dead, entry.slot);
|
|
9450
|
+
deadCount += 1;
|
|
9451
|
+
}
|
|
9452
|
+
}
|
|
9453
|
+
continue;
|
|
9454
|
+
}
|
|
8501
9455
|
const keyVector = mutationKeyVectors.get(segment.id);
|
|
8502
9456
|
if (keyVector === void 0) {
|
|
8503
9457
|
throw new Error(`Mutation segment key vector is missing: ${segment.id}`);
|
|
@@ -8510,13 +9464,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8510
9464
|
if (slot !== void 0 && !bitmapHasValue(dead, slot)) {
|
|
8511
9465
|
setBitmapValue(dead, slot);
|
|
8512
9466
|
deadCount += 1;
|
|
8513
|
-
|
|
9467
|
+
lazyPatches.delete(slot);
|
|
8514
9468
|
}
|
|
8515
9469
|
slotByKey.delete(key);
|
|
8516
9470
|
}
|
|
8517
9471
|
continue;
|
|
8518
9472
|
}
|
|
8519
|
-
const changed = mutationChangedVectors.get(segment.id) ?? /* @__PURE__ */ new Map();
|
|
8520
9473
|
for (let row = 0; row < segment.rowCount; row += 1) {
|
|
8521
9474
|
const slot = slotByKey.get(readKey(row));
|
|
8522
9475
|
if (slot === void 0) {
|
|
@@ -8524,27 +9477,22 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
8524
9477
|
continue;
|
|
8525
9478
|
throw new Error(`Update segment references a missing key: ${segment.id}`);
|
|
8526
9479
|
}
|
|
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 });
|
|
9480
|
+
const { blockIndex, rowInBlock } = locate(segment, row);
|
|
9481
|
+
layer(slot, { segment, blockIndex, row: rowInBlock });
|
|
8534
9482
|
}
|
|
8535
9483
|
}
|
|
8536
|
-
let
|
|
8537
|
-
for (const
|
|
8538
|
-
|
|
8539
|
-
memory.tally(
|
|
8540
|
-
const patchedSlots = Uint32Array.from(
|
|
9484
|
+
let layers = 0;
|
|
9485
|
+
for (const slotLayers of lazyPatches.values())
|
|
9486
|
+
layers += slotLayers.length;
|
|
9487
|
+
memory.tally(lazyPatches.size * 96 + layers * 48, "Streamed mutation replay");
|
|
9488
|
+
const patchedSlots = Uint32Array.from(lazyPatches.keys()).sort();
|
|
8541
9489
|
return {
|
|
8542
9490
|
baseRows,
|
|
8543
9491
|
deadCount,
|
|
8544
9492
|
dead,
|
|
8545
|
-
|
|
9493
|
+
lazyPatches,
|
|
8546
9494
|
patchedSlots,
|
|
8547
|
-
bytes: dead.byteLength + retainedBytes + patchedSlots.byteLength +
|
|
9495
|
+
bytes: dead.byteLength + retainedBytes + patchedSlots.byteLength + lazyPatches.size * 96 + layers * 48
|
|
8548
9496
|
};
|
|
8549
9497
|
}
|
|
8550
9498
|
async #executeBlock(block, snapshot, visibility, memory, realTables, typedSchemas, extraInputs, cacheResults = true, allowSpill = true, forceSpill = false, spillPageRows, signal) {
|
|
@@ -9197,6 +10145,7 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9197
10145
|
message: error instanceof Error ? error.message : String(error),
|
|
9198
10146
|
at
|
|
9199
10147
|
};
|
|
10148
|
+
this.#reportBackgroundError(error, "auto collection");
|
|
9200
10149
|
this.#scheduleAutoCollectionRetry();
|
|
9201
10150
|
}).finally(() => {
|
|
9202
10151
|
if (this.#autoCollectionTask === run)
|
|
@@ -9217,6 +10166,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
9217
10166
|
this.#autoCollectionTask = run;
|
|
9218
10167
|
void run;
|
|
9219
10168
|
}
|
|
10169
|
+
#reportBackgroundError(error, context) {
|
|
10170
|
+
try {
|
|
10171
|
+
this.#onBackgroundError?.(error, context);
|
|
10172
|
+
} catch {
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
9220
10175
|
#scheduleAutoCollectionRetry() {
|
|
9221
10176
|
if (this.#closed || !this.#autoCollect || this.#autoCollectionRetryTimer !== void 0)
|
|
9222
10177
|
return;
|
|
@@ -12494,6 +13449,19 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
12494
13449
|
}
|
|
12495
13450
|
return surviving;
|
|
12496
13451
|
}
|
|
13452
|
+
async #indexPrunedSegments(table, segments, plan, snapshot, visibility) {
|
|
13453
|
+
const own = overlayOwnedSegments(segments, visibility);
|
|
13454
|
+
if (own.length === 0) {
|
|
13455
|
+
const ftsSegments2 = await this.#ftsPrunedSegments(table, segments, plan, snapshot);
|
|
13456
|
+
return this.#secondaryIndexPrunedSegments(table, ftsSegments2, plan, snapshot);
|
|
13457
|
+
}
|
|
13458
|
+
if (own.some((segment) => segment.kind !== "insert"))
|
|
13459
|
+
return { segments, pruned: false };
|
|
13460
|
+
const committed = segments.filter((segment) => segment.transactionId !== visibility?.overlayTransactionId);
|
|
13461
|
+
const ftsSegments = await this.#ftsPrunedSegments(table, committed, plan, snapshot);
|
|
13462
|
+
const indexed = await this.#secondaryIndexPrunedSegments(table, ftsSegments, plan, snapshot);
|
|
13463
|
+
return { segments: [...indexed.segments, ...own], pruned: indexed.pruned };
|
|
13464
|
+
}
|
|
12497
13465
|
async #secondaryIndexPrunedSegments(table, segments, plan, snapshot) {
|
|
12498
13466
|
const predicates = secondaryIndexPredicates(plan, table);
|
|
12499
13467
|
if (predicates.length === 0) {
|
|
@@ -12769,13 +13737,12 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
12769
13737
|
}
|
|
12770
13738
|
async #materializeColumnarTableAtSnapshot(table, snapshot, projectedColumns, visibility, plan) {
|
|
12771
13739
|
const visibleSegments = await this.#visibleSegmentRecords(table, snapshot, visibility);
|
|
12772
|
-
if (plan !== void 0) {
|
|
13740
|
+
if (plan !== void 0 && overlayOwnedSegments(visibleSegments, visibility).length === 0) {
|
|
12773
13741
|
const covering = await this.#secondaryIndexCoveringTable(table, projectedColumns, visibleSegments, plan, snapshot);
|
|
12774
13742
|
if (covering !== void 0)
|
|
12775
13743
|
return covering;
|
|
12776
13744
|
}
|
|
12777
|
-
const
|
|
12778
|
-
const indexed = plan === void 0 ? { segments: ftsSegments, pruned: false } : await this.#secondaryIndexPrunedSegments(table, ftsSegments, plan, snapshot);
|
|
13745
|
+
const indexed = plan === void 0 ? { segments: visibleSegments, pruned: false } : await this.#indexPrunedSegments(table, visibleSegments, plan, snapshot, visibility);
|
|
12779
13746
|
const segments = indexed.segments;
|
|
12780
13747
|
const keyColumn = getUniqueKeyColumn(table);
|
|
12781
13748
|
if (segments.every((segment) => {
|
|
@@ -13660,14 +14627,17 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13660
14627
|
async #visibleSegmentRecordsUncached(table, snapshot, catalog) {
|
|
13661
14628
|
const segments = catalog === void 0 ? await listTableSegmentsPaged(this.store, table.id) : catalog.segmentsByTable.get(table.id) ?? [];
|
|
13662
14629
|
const transactions = catalog?.transactions ?? new Map((await this.#transactionRecordsForSegments(segments)).map((record) => [record.id, record]));
|
|
14630
|
+
const overlay = [];
|
|
13663
14631
|
const versionEligible = segments.filter((segment) => {
|
|
13664
|
-
if (segment.transactionId === catalog?.overlayTransactionId)
|
|
13665
|
-
|
|
14632
|
+
if (segment.transactionId === catalog?.overlayTransactionId) {
|
|
14633
|
+
overlay.push(segment);
|
|
14634
|
+
return false;
|
|
14635
|
+
}
|
|
13666
14636
|
const committedVersion = transactions.get(segment.transactionId)?.committedVersion;
|
|
13667
14637
|
return snapshot.version !== null && committedVersion !== null && committedVersion !== void 0 && committedVersion <= snapshot.version;
|
|
13668
14638
|
});
|
|
13669
|
-
const visible = await filterSnapshotSegments(snapshot, versionEligible);
|
|
13670
|
-
return
|
|
14639
|
+
const visible = [...await filterSnapshotSegments(snapshot, versionEligible), ...overlay];
|
|
14640
|
+
return visible.sort((left, right) => {
|
|
13671
14641
|
const leftVersion = transactions.get(left.transactionId)?.committedVersion ?? -1;
|
|
13672
14642
|
const rightVersion = transactions.get(right.transactionId)?.committedVersion ?? -1;
|
|
13673
14643
|
const leftOrder = left.logicalOrder;
|
|
@@ -13675,8 +14645,8 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13675
14645
|
return leftOrder - rightOrder || leftVersion - rightVersion || left.commitOrdinal - right.commitOrdinal || left.id.localeCompare(right.id);
|
|
13676
14646
|
});
|
|
13677
14647
|
}
|
|
13678
|
-
async #transactionRecordsForSegments(segments) {
|
|
13679
|
-
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))];
|
|
14648
|
+
async #transactionRecordsForSegments(segments, excludeTransactionId) {
|
|
14649
|
+
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))].filter((id) => id !== excludeTransactionId);
|
|
13680
14650
|
const records = [];
|
|
13681
14651
|
for (let start = 0; start < transactionIds.length; start += 64) {
|
|
13682
14652
|
const window = transactionIds.slice(start, start + 64);
|
|
@@ -13782,13 +14752,23 @@ ${notes.map((note) => `-- ${note}`).join("\n")}`;
|
|
|
13782
14752
|
this.#gzipVerdicts.delete(columnId);
|
|
13783
14753
|
return bytes;
|
|
13784
14754
|
}
|
|
13785
|
-
|
|
14755
|
+
#scopeTableRecords = /* @__PURE__ */ new WeakMap();
|
|
14756
|
+
async #findTable(name, scope) {
|
|
13786
14757
|
validateName(name, "Table");
|
|
14758
|
+
const cache = scope === void 0 || !this.#scopeWrites.has(scope) ? void 0 : this.#scopeTableRecords.get(scope) ?? (() => {
|
|
14759
|
+
const created = /* @__PURE__ */ new Map();
|
|
14760
|
+
this.#scopeTableRecords.set(scope, created);
|
|
14761
|
+
return created;
|
|
14762
|
+
})();
|
|
14763
|
+
const cached = cache?.get(name);
|
|
14764
|
+
if (cached !== void 0)
|
|
14765
|
+
return cached;
|
|
13787
14766
|
const table = await this.store.getTableByName(name) ?? await this.store.getTableByName(await this.#foldTableName(name));
|
|
13788
14767
|
if (table === void 0)
|
|
13789
14768
|
throw new UnknownTableError(name);
|
|
13790
14769
|
if (table.view !== void 0)
|
|
13791
14770
|
throw new TypeError(`${name} is a view, not a table`);
|
|
14771
|
+
cache?.set(name, table);
|
|
13792
14772
|
return table;
|
|
13793
14773
|
}
|
|
13794
14774
|
async #assertVisibleSegmentCursorTable(tableName, capturedTableId) {
|
|
@@ -14875,6 +15855,18 @@ function assertNoDuplicateUniqueTerms(index, terms) {
|
|
|
14875
15855
|
seen.add(term);
|
|
14876
15856
|
}
|
|
14877
15857
|
}
|
|
15858
|
+
function assertBatchSecondaryTermsDistinct(table, input) {
|
|
15859
|
+
const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
|
|
15860
|
+
for (const { index, columns } of readyUniqueSecondaryIndexes(table)) {
|
|
15861
|
+
const terms = [];
|
|
15862
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
15863
|
+
const term = secondaryUniqueTerm(index, columns, columns.map((column) => input.columns[column.name]?.[row] ?? null));
|
|
15864
|
+
if (term !== void 0)
|
|
15865
|
+
terms.push(term);
|
|
15866
|
+
}
|
|
15867
|
+
assertNoDuplicateUniqueTerms(index, terms);
|
|
15868
|
+
}
|
|
15869
|
+
}
|
|
14878
15870
|
function stageSecondaryUniqueInsertChanges(transaction, table, input, oldImages) {
|
|
14879
15871
|
const rowCount = input.rowCount ?? Object.values(input.columns)[0]?.length ?? 0;
|
|
14880
15872
|
for (const { indexId, index, columns } of readyUniqueSecondaryIndexes(table)) {
|
|
@@ -14927,7 +15919,10 @@ function stageSecondaryUniqueMutationChanges(transaction, table, input, oldImage
|
|
|
14927
15919
|
const added = oldImages.flatMap((old, row) => {
|
|
14928
15920
|
if (old === void 0)
|
|
14929
15921
|
return [];
|
|
14930
|
-
const term = secondaryUniqueTerm(index, columns, columns.map((column) =>
|
|
15922
|
+
const term = secondaryUniqueTerm(index, columns, columns.map((column) => {
|
|
15923
|
+
const assigned = input.changes[column.name];
|
|
15924
|
+
return assigned === void 0 ? old[column.name] ?? null : assigned[row] ?? null;
|
|
15925
|
+
}));
|
|
14931
15926
|
return term === void 0 ? [] : [term];
|
|
14932
15927
|
});
|
|
14933
15928
|
assertNoDuplicateUniqueTerms(index, added);
|
|
@@ -15593,6 +16588,24 @@ function boundedExpiryMilliseconds(nowMs, ttlMs) {
|
|
|
15593
16588
|
}
|
|
15594
16589
|
return expiresAt;
|
|
15595
16590
|
}
|
|
16591
|
+
function plainLiteralValue(expression) {
|
|
16592
|
+
if (expression.kind !== "literal" || expression.internalSqlValue === true || expression.sqlDomain !== void 0 || expression.exactText !== void 0 || expression.decimal === true) {
|
|
16593
|
+
return void 0;
|
|
16594
|
+
}
|
|
16595
|
+
return expression.value;
|
|
16596
|
+
}
|
|
16597
|
+
function valueMatchesColumnType(column, value) {
|
|
16598
|
+
switch (column.type) {
|
|
16599
|
+
case "number":
|
|
16600
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
16601
|
+
case "string":
|
|
16602
|
+
return typeof value === "string";
|
|
16603
|
+
case "boolean":
|
|
16604
|
+
return typeof value === "boolean";
|
|
16605
|
+
case "datetime":
|
|
16606
|
+
return value instanceof Date;
|
|
16607
|
+
}
|
|
16608
|
+
}
|
|
15596
16609
|
function keyToken(type, value) {
|
|
15597
16610
|
if (value === null)
|
|
15598
16611
|
throw new TypeError("Unique key cannot be null");
|
|
@@ -16240,9 +17253,17 @@ function overlayWindowCompacted(inner, innerWindowStart, steps, rows, patches, c
|
|
|
16240
17253
|
const values = inner.kind === "boolean" ? new Uint8Array(rows) : inner.kind === "string" ? void 0 : new Float64Array(rows);
|
|
16241
17254
|
const codes = inner.kind === "string" ? new Uint32Array(rows) : void 0;
|
|
16242
17255
|
codes?.fill(NULL_STRING_VECTOR_CODE);
|
|
16243
|
-
let
|
|
16244
|
-
|
|
16245
|
-
|
|
17256
|
+
let patched = false;
|
|
17257
|
+
if (inner.kind === "string" && patches !== void 0) {
|
|
17258
|
+
for (let index = 0; index < steps.length; index += 2) {
|
|
17259
|
+
if ((steps[index + 1] ?? 0) === 0 && patches.get(steps[index] ?? 0)?.has(column.id) === true) {
|
|
17260
|
+
patched = true;
|
|
17261
|
+
break;
|
|
17262
|
+
}
|
|
17263
|
+
}
|
|
17264
|
+
}
|
|
17265
|
+
const dictionary = inner.kind === "string" ? patched ? [] : inner.dictionary : void 0;
|
|
17266
|
+
const dictionaryIndex = patched ? /* @__PURE__ */ new Map() : void 0;
|
|
16246
17267
|
const target = codes !== void 0 ? { kind: "string", length: rows, validity, codes, dictionary: dictionary ?? [] } : { kind: inner.kind, length: rows, validity, values };
|
|
16247
17268
|
let out = 0;
|
|
16248
17269
|
for (let index = 0; index < steps.length; index += 2) {
|
|
@@ -16251,22 +17272,22 @@ function overlayWindowCompacted(inner, innerWindowStart, steps, rows, patches, c
|
|
|
16251
17272
|
const patch = length === 0 ? patches?.get(start)?.get(column.id) : void 0;
|
|
16252
17273
|
if (patch === void 0) {
|
|
16253
17274
|
const count = Math.max(1, length);
|
|
16254
|
-
|
|
17275
|
+
if (dictionaryIndex === void 0) {
|
|
17276
|
+
copyVectorSpan(inner, start - innerWindowStart, count, target, out);
|
|
17277
|
+
} else {
|
|
17278
|
+
for (let row = 0; row < count; row += 1) {
|
|
17279
|
+
copyColumnVectorValue(inner, start - innerWindowStart + row, target, out + row, dictionaryIndex);
|
|
17280
|
+
}
|
|
17281
|
+
}
|
|
16255
17282
|
out += count;
|
|
16256
17283
|
continue;
|
|
16257
17284
|
}
|
|
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
17285
|
copyColumnVectorValue(patch.vector, patch.row, target, out, dictionaryIndex);
|
|
16265
17286
|
out += 1;
|
|
16266
17287
|
}
|
|
16267
17288
|
if (out !== rows)
|
|
16268
17289
|
throw new Error(`Column row count mismatch: ${column.name}`);
|
|
16269
|
-
if (
|
|
17290
|
+
if (patched && dictionary !== void 0) {
|
|
16270
17291
|
let dictionaryBytes = 0;
|
|
16271
17292
|
for (const value of dictionary)
|
|
16272
17293
|
dictionaryBytes += 16 + value.length * 2;
|
|
@@ -17667,6 +18688,10 @@ function garbageCollectionProgress(job) {
|
|
|
17667
18688
|
function firesAfterTriggers(table, ...events) {
|
|
17668
18689
|
return (table.triggers ?? []).some((trigger) => trigger.timing === "after" && events.includes(trigger.event));
|
|
17669
18690
|
}
|
|
18691
|
+
function overlayOwnedSegments(segments, visibility) {
|
|
18692
|
+
const id = visibility?.overlayTransactionId;
|
|
18693
|
+
return id === void 0 ? [] : segments.filter((segment) => segment.transactionId === id);
|
|
18694
|
+
}
|
|
17670
18695
|
function collectRealTableNames(plan) {
|
|
17671
18696
|
const names = /* @__PURE__ */ new Set();
|
|
17672
18697
|
const excluded = /* @__PURE__ */ new Set();
|