@minnowdb/core 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/engine/cancellation.d.ts +2 -0
- package/dist/engine/cancellation.js +4 -0
- package/dist/engine/catalog.d.ts +5 -1
- package/dist/engine/catalog.js +5 -1
- package/dist/engine/client.d.ts +34 -6
- package/dist/engine/client.js +87 -19
- package/dist/engine/database.d.ts +43 -20
- package/dist/engine/database.js +823 -164
- package/dist/engine/defaults.js +11 -0
- package/dist/engine/errors.d.ts +19 -0
- package/dist/engine/errors.js +31 -0
- package/dist/engine/fts.d.ts +2 -15
- package/dist/engine/live.d.ts +1 -7
- package/dist/engine/live.js +12 -13
- package/dist/engine/optimizer.js +546 -39
- package/dist/engine/query-cache.js +1 -0
- package/dist/engine/query.d.ts +16 -278
- package/dist/engine/query.js +260 -74
- package/dist/engine/result-wire.d.ts +2 -0
- package/dist/engine/result-wire.js +21 -5
- package/dist/engine/schema-wire.d.ts +14 -1
- package/dist/engine/schema-wire.js +7 -1
- package/dist/engine/schema.d.ts +83 -32
- package/dist/engine/schema.js +180 -14
- package/dist/engine/sql-domains.d.ts +11 -0
- package/dist/engine/sql-domains.js +65 -1
- package/dist/engine/sql-json.js +22 -3
- package/dist/engine/sql-semantics.js +21 -3
- package/dist/engine/vector.d.ts +2 -2
- package/dist/engine/vector.js +328 -79
- package/dist/engine/worker-host.js +119 -44
- package/dist/plan/index.d.ts +5 -4
- package/dist/plan/index.js +3 -3
- package/dist/plan/model.d.ts +218 -0
- package/dist/plan/model.js +1 -0
- package/dist/storage/indexeddb.js +4 -12
- package/dist/storage/toolkit/record-core.js +7 -22
- package/dist/storage/types.d.ts +26 -8
- package/dist/storage/types.js +85 -0
- package/dist/transactions/index.d.ts +5 -3
- package/dist/transactions/index.js +58 -8
- package/dist/worker-protocol/index.d.ts +6 -1
- package/dist/worker-protocol/index.js +5 -2
- package/package.json +1 -1
- package/postgres-feature-profile.json +5 -0
- package/sql-feature-matrix.json +75 -19
|
@@ -12,6 +12,54 @@ const stageOps = ["insertBatch", "upsertBatch", "updateBatch", "deleteBatch"];
|
|
|
12
12
|
function isStageOp(value) {
|
|
13
13
|
return typeof value === "string" && stageOps.includes(value);
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Root methods whose arguments and results are already structured-clone-safe. `satisfies` makes
|
|
17
|
+
* a removed or renamed database method a compile error, while the runtime guard below fails
|
|
18
|
+
* closed if a caller supplies a malformed database object through the manual host seam.
|
|
19
|
+
*/
|
|
20
|
+
const directRootMethods = [
|
|
21
|
+
"createTable",
|
|
22
|
+
"createView",
|
|
23
|
+
"dropView",
|
|
24
|
+
"dropColumn",
|
|
25
|
+
"dropTable",
|
|
26
|
+
"createIndex",
|
|
27
|
+
"dropIndex",
|
|
28
|
+
"buildFtsIndex",
|
|
29
|
+
"introspect",
|
|
30
|
+
"listTables",
|
|
31
|
+
"insertBatch",
|
|
32
|
+
"insert",
|
|
33
|
+
"upsertBatch",
|
|
34
|
+
"upsert",
|
|
35
|
+
"updateBatch",
|
|
36
|
+
"update",
|
|
37
|
+
"deleteBatch",
|
|
38
|
+
"delete",
|
|
39
|
+
"runStatement",
|
|
40
|
+
"explain",
|
|
41
|
+
"execute",
|
|
42
|
+
"listVisibleSegmentPage",
|
|
43
|
+
"cleanupQuerySpill",
|
|
44
|
+
"compactTable",
|
|
45
|
+
"compactTableStep",
|
|
46
|
+
"resumeCompactionJob",
|
|
47
|
+
"listCompactionJobs",
|
|
48
|
+
"cancelCompactionJob",
|
|
49
|
+
"collectGarbage",
|
|
50
|
+
"collectGarbageStep",
|
|
51
|
+
"resumeGarbageCollectionJob",
|
|
52
|
+
"bufferPoolStats",
|
|
53
|
+
"maintenanceStatus",
|
|
54
|
+
"checkIntegrity",
|
|
55
|
+
"storageStats",
|
|
56
|
+
"inspectInterruptedImport",
|
|
57
|
+
"abortInterruptedImport",
|
|
58
|
+
"listGarbageCollectionJobs",
|
|
59
|
+
];
|
|
60
|
+
function isDirectRootMethod(value) {
|
|
61
|
+
return directRootMethods.includes(value);
|
|
62
|
+
}
|
|
15
63
|
/**
|
|
16
64
|
* A call result that travels as a columnar frame with its buffers transferred. Only query
|
|
17
65
|
* results take this path: rows of objects are what structured clone is slowest at, so they are
|
|
@@ -120,10 +168,17 @@ class SnapshotImportChunkQueue {
|
|
|
120
168
|
function snapshotQueueError(value) {
|
|
121
169
|
return value instanceof Error ? value : new Error("Snapshot transfer failed", { cause: value });
|
|
122
170
|
}
|
|
171
|
+
function abortError(message) {
|
|
172
|
+
const error = new Error(message);
|
|
173
|
+
error.name = "AbortError";
|
|
174
|
+
return error;
|
|
175
|
+
}
|
|
123
176
|
/** Hard per-connection ceiling for resident worker resources abandoned by a client. */
|
|
124
177
|
export const MAX_WORKER_HANDLES_PER_CONNECTION = 256;
|
|
125
178
|
const DEFAULT_WRITE_HANDLE_IDLE_TIMEOUT_MS = 30_000;
|
|
126
179
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
180
|
+
/** Shared by RPC methods that do not expose cancellation, avoiding one controller per call. */
|
|
181
|
+
const passiveRpcSignal = new AbortController().signal;
|
|
127
182
|
class DatabaseRpcServer {
|
|
128
183
|
database;
|
|
129
184
|
scope;
|
|
@@ -132,6 +187,7 @@ class DatabaseRpcServer {
|
|
|
132
187
|
#reservedHandleIds = new Set();
|
|
133
188
|
#openingHandlePromises = new Set();
|
|
134
189
|
#settlingWritePromises = new Set();
|
|
190
|
+
#requestAborts = new Map();
|
|
135
191
|
#disposed = false;
|
|
136
192
|
#inFlightRpcCount = 0;
|
|
137
193
|
#inFlightRpcDrain;
|
|
@@ -154,6 +210,12 @@ class DatabaseRpcServer {
|
|
|
154
210
|
this.scope.postMessage(rpcResult(request.requestId, { ready: true }));
|
|
155
211
|
return;
|
|
156
212
|
}
|
|
213
|
+
if (request.kind === "rpc-cancel") {
|
|
214
|
+
this.#requestAborts
|
|
215
|
+
.get(request.requestId)
|
|
216
|
+
?.abort(abortError("Database request was cancelled"));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
157
219
|
const bypassLimit = request.method === "dispose";
|
|
158
220
|
if (!bypassLimit && this.#inFlightRpcCount >= MAX_DATABASE_RPC_IN_FLIGHT) {
|
|
159
221
|
this.scope.postMessage(rpcFailure(request.requestId, new RangeError(`A database worker connection cannot hold more than ${String(MAX_DATABASE_RPC_IN_FLIGHT)} in-flight requests`)));
|
|
@@ -161,12 +223,19 @@ class DatabaseRpcServer {
|
|
|
161
223
|
}
|
|
162
224
|
if (!bypassLimit)
|
|
163
225
|
this.#inFlightRpcCount += 1;
|
|
226
|
+
const abort = request.method === "query" ? new AbortController() : undefined;
|
|
227
|
+
if (abort !== undefined)
|
|
228
|
+
this.#requestAborts.set(request.requestId, abort);
|
|
229
|
+
const context = {
|
|
230
|
+
requestId: request.requestId,
|
|
231
|
+
signal: abort?.signal ?? passiveRpcSignal,
|
|
232
|
+
};
|
|
164
233
|
try {
|
|
165
234
|
if (this.#disposed)
|
|
166
235
|
throw new Error("Database connection is disposed");
|
|
167
236
|
const result = request.handleId === null
|
|
168
|
-
? await this.#callRoot(request.method, request.args)
|
|
169
|
-
: await this.#callHandle(request.handleId, request.method, request.args);
|
|
237
|
+
? await this.#callRoot(request.method, request.args, context)
|
|
238
|
+
: await this.#callHandle(request.handleId, request.method, request.args, context);
|
|
170
239
|
if (result instanceof ColumnarResult) {
|
|
171
240
|
this.scope.postMessage(rpcResult(request.requestId, result.encoded.payload), {
|
|
172
241
|
transfer: result.encoded.transfer,
|
|
@@ -190,6 +259,8 @@ class DatabaseRpcServer {
|
|
|
190
259
|
this.scope.postMessage(rpcFailure(request.requestId, error));
|
|
191
260
|
}
|
|
192
261
|
finally {
|
|
262
|
+
if (abort !== undefined)
|
|
263
|
+
this.#requestAborts.delete(request.requestId);
|
|
193
264
|
if (!bypassLimit)
|
|
194
265
|
this.#inFlightRpcCount -= 1;
|
|
195
266
|
if (this.#inFlightRpcCount === 0 && this.#resolveInFlightRpcDrain !== undefined) {
|
|
@@ -200,50 +271,44 @@ class DatabaseRpcServer {
|
|
|
200
271
|
}
|
|
201
272
|
}
|
|
202
273
|
}
|
|
203
|
-
async #callRoot(method, args) {
|
|
274
|
+
async #callRoot(method, args, context) {
|
|
204
275
|
const database = this.database;
|
|
276
|
+
if (isDirectRootMethod(method)) {
|
|
277
|
+
const operation = database[method];
|
|
278
|
+
if (operation === undefined) {
|
|
279
|
+
throw new Error(`Database root method is unavailable: ${method}`);
|
|
280
|
+
}
|
|
281
|
+
return operation.apply(this.database, args);
|
|
282
|
+
}
|
|
205
283
|
switch (method) {
|
|
206
|
-
case "createTable":
|
|
207
|
-
case "introspect":
|
|
208
|
-
case "listTables":
|
|
209
|
-
case "insertBatch":
|
|
210
|
-
case "insert":
|
|
211
|
-
case "upsertBatch":
|
|
212
|
-
case "upsert":
|
|
213
|
-
case "updateBatch":
|
|
214
|
-
case "update":
|
|
215
|
-
case "deleteBatch":
|
|
216
|
-
case "delete":
|
|
217
|
-
case "runStatement":
|
|
218
|
-
case "explain":
|
|
219
|
-
case "execute":
|
|
220
|
-
case "listVisibleSegmentPage":
|
|
221
|
-
case "cleanupQuerySpill":
|
|
222
|
-
case "compactTable":
|
|
223
|
-
case "compactTableStep":
|
|
224
|
-
case "resumeCompactionJob":
|
|
225
|
-
case "listCompactionJobs":
|
|
226
|
-
case "cancelCompactionJob":
|
|
227
|
-
case "collectGarbage":
|
|
228
|
-
case "collectGarbageStep":
|
|
229
|
-
case "resumeGarbageCollectionJob":
|
|
230
|
-
case "bufferPoolStats":
|
|
231
|
-
case "maintenanceStatus":
|
|
232
|
-
case "checkIntegrity":
|
|
233
|
-
case "storageStats":
|
|
234
|
-
case "inspectInterruptedImport":
|
|
235
|
-
case "abortInterruptedImport":
|
|
236
|
-
case "listGarbageCollectionJobs":
|
|
237
|
-
return database[method]?.(...args);
|
|
238
284
|
case "query": {
|
|
239
|
-
const [sql, options] = args;
|
|
240
|
-
return new ColumnarResult(encodeQueryResult(await this.database.query(sql,
|
|
285
|
+
const [sql, options, reportStats = false] = args;
|
|
286
|
+
return new ColumnarResult(encodeQueryResult(await this.database.query(sql, {
|
|
287
|
+
...options,
|
|
288
|
+
signal: context.signal,
|
|
289
|
+
...(reportStats
|
|
290
|
+
? {
|
|
291
|
+
onStats: (stats) => {
|
|
292
|
+
this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
: {}),
|
|
296
|
+
})));
|
|
241
297
|
}
|
|
242
298
|
case "queryCursorOpen": {
|
|
243
299
|
const handleId = this.#claimHandleId(args[0]);
|
|
244
|
-
const [sql, options] = args.slice(1);
|
|
300
|
+
const [sql, options, reportStats = false] = args.slice(1);
|
|
245
301
|
try {
|
|
246
|
-
const iterator = this.database.queryCursor(sql,
|
|
302
|
+
const iterator = this.database.queryCursor(sql, {
|
|
303
|
+
...options,
|
|
304
|
+
...(reportStats
|
|
305
|
+
? {
|
|
306
|
+
onStats: (stats) => {
|
|
307
|
+
this.scope.postMessage(rpcEvent(handleId, "stats", stats));
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
: {}),
|
|
311
|
+
});
|
|
247
312
|
this.#publishHandle(handleId, { type: "query-cursor", iterator });
|
|
248
313
|
}
|
|
249
314
|
catch (error) {
|
|
@@ -465,14 +530,14 @@ class DatabaseRpcServer {
|
|
|
465
530
|
void opening.then(() => this.#openingHandlePromises.delete(opening), () => this.#openingHandlePromises.delete(opening));
|
|
466
531
|
return opening;
|
|
467
532
|
}
|
|
468
|
-
async #callHandle(handleId, method, args) {
|
|
533
|
+
async #callHandle(handleId, method, args, context) {
|
|
469
534
|
const handle = this.#handles.get(handleId);
|
|
470
535
|
if (handle === undefined)
|
|
471
536
|
throw new Error(`Unknown handle: ${handleId}`);
|
|
472
537
|
if (handle.type === "write") {
|
|
473
538
|
this.#beginWriteHandleCall(handle);
|
|
474
539
|
try {
|
|
475
|
-
return await this.#callWriteHandle(handleId, handle, method, args);
|
|
540
|
+
return await this.#callWriteHandle(handleId, handle, method, args, context);
|
|
476
541
|
}
|
|
477
542
|
finally {
|
|
478
543
|
this.#endWriteHandleCall(handleId, handle);
|
|
@@ -523,10 +588,20 @@ class DatabaseRpcServer {
|
|
|
523
588
|
}
|
|
524
589
|
}
|
|
525
590
|
}
|
|
526
|
-
async #callWriteHandle(handleId, handle, method, args) {
|
|
591
|
+
async #callWriteHandle(handleId, handle, method, args, context) {
|
|
527
592
|
if (method === "query") {
|
|
528
|
-
const [sql, options] = args;
|
|
529
|
-
return new ColumnarResult(encodeQueryResult(await handle.session.query(sql,
|
|
593
|
+
const [sql, options, reportStats = false] = args;
|
|
594
|
+
return new ColumnarResult(encodeQueryResult(await handle.session.query(sql, {
|
|
595
|
+
...options,
|
|
596
|
+
signal: context.signal,
|
|
597
|
+
...(reportStats
|
|
598
|
+
? {
|
|
599
|
+
onStats: (stats) => {
|
|
600
|
+
this.scope.postMessage(rpcEvent(context.requestId, "stats", stats));
|
|
601
|
+
},
|
|
602
|
+
}
|
|
603
|
+
: {}),
|
|
604
|
+
})));
|
|
530
605
|
}
|
|
531
606
|
if (method === "execute") {
|
|
532
607
|
const [sql, params] = args;
|
package/dist/plan/index.d.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* Exposing them here lets an external builder produce plans the engine treats as indistinguishable
|
|
8
8
|
* from parsed SQL.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The recursive logical model lives in the dependency-light `model` leaf; executable assembly
|
|
11
|
+
* and validation stay with the compiler. This entry point keeps that distinction invisible to
|
|
12
|
+
* consumers.
|
|
13
13
|
*/
|
|
14
|
-
export { assembleSelectBlock, compoundSelectBlock, derivedTableSource, hasAggregate, splitCondition, validateLimit, validateOffset, type
|
|
14
|
+
export { assembleSelectBlock, compoundSelectBlock, derivedTableSource, hasAggregate, splitCondition, validateLimit, validateOffset, type CompiledStatement, } from "../engine/query.js";
|
|
15
|
+
export type { AggregateName, CompiledQuery, Expression, JoinPlan, Predicate, PredicateOperator, QueryResult, QueryRow, QueryValue, SelectItem, SetOperator, TableSource, WindowFunctionName, } from "./model.js";
|
|
15
16
|
export { optimizePlan, renderPlan } from "../engine/optimizer.js";
|
|
16
17
|
export { validateFtsQuery } from "../engine/fts.js";
|
package/dist/plan/index.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* Exposing them here lets an external builder produce plans the engine treats as indistinguishable
|
|
8
8
|
* from parsed SQL.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The recursive logical model lives in the dependency-light `model` leaf; executable assembly
|
|
11
|
+
* and validation stay with the compiler. This entry point keeps that distinction invisible to
|
|
12
|
+
* consumers.
|
|
13
13
|
*/
|
|
14
14
|
export { assembleSelectBlock, compoundSelectBlock, derivedTableSource, hasAggregate, splitCondition, validateLimit, validateOffset, } from "../engine/query.js";
|
|
15
15
|
export { optimizePlan, renderPlan } from "../engine/optimizer.js";
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dependency-light logical plan shared by the SQL compiler, optimizer, vector executor, and
|
|
3
|
+
* external typed-query builders. Keep executable parser/engine code out of this module: importing
|
|
4
|
+
* these types must never create a runtime dependency edge.
|
|
5
|
+
*/
|
|
6
|
+
import type { SqlDomain } from "../storage/types.js";
|
|
7
|
+
export type QueryValue = boolean | number | string | Date | null;
|
|
8
|
+
export type QueryRow = Record<string, QueryValue>;
|
|
9
|
+
export interface QueryResult {
|
|
10
|
+
columns: string[];
|
|
11
|
+
/** Logical SQL domain for each output column, positionally aligned with `columns`. */
|
|
12
|
+
columnDomains: Array<SqlDomain | null>;
|
|
13
|
+
rows: QueryRow[];
|
|
14
|
+
}
|
|
15
|
+
export type BinaryOperator = "+" | "-" | "*" | "/" | "%" | "||";
|
|
16
|
+
export type ComparisonOperator = "=" | "!=" | "<>" | ">" | ">=" | "<" | "<=";
|
|
17
|
+
export type AggregateName = "COUNT" | "SUM" | "AVG" | "MIN" | "MAX" | "JSON_ARRAYAGG" | "STRING_AGG";
|
|
18
|
+
export type ScalarFunctionName = "ROUND" | "COALESCE" | "DATE_TRUNC" | "DATE_ADD" | "UPPER" | "LOWER" | "LENGTH" | "ABS" | "TRIM" | "LTRIM" | "RTRIM" | "SUBSTR" | "REPLACE" | "INSTR" | "NULLIF" | "GREATEST" | "LEAST" | "FLOOR" | "CEIL" | "MOD" | "POWER" | "SQRT" | "EXTRACT" | "CAST" | "OCTET_LENGTH" | "LPAD" | "RPAD" | "OVERLAY" | "CURRENT_DATE" | "CURRENT_TIMESTAMP" | "LOCALTIME" | "GROUPING" | "JSON_VALUE" | "JSON_QUERY" | "JSON_EXISTS" | "JSON_OBJECT" | "JSON_ARRAY" | "IS_JSON" | "ARRAY"
|
|
19
|
+
/** Optimizer-only, prefix-free equality key for hashable multi-column decorrelation. */
|
|
20
|
+
| "MINNOW_TUPLE_KEY"
|
|
21
|
+
/** Parser-produced wrapper carrying one explicit collation through ordering/comparison. */
|
|
22
|
+
| "MINNOW_COLLATE" | "NEXTVAL" | "CURRVAL" | "RANDOM" | "GEN_RANDOM_UUID";
|
|
23
|
+
/** Exact BM25 corpus statistics attached to a cloned scoring node before execution. */
|
|
24
|
+
export interface FtsStats {
|
|
25
|
+
/** Every row of the corpus, including all-null documents. */
|
|
26
|
+
docCount: number;
|
|
27
|
+
/** Total tokens across all documents. */
|
|
28
|
+
totalTokens: number;
|
|
29
|
+
/** Documents containing each query term, aligned with the query's term order. */
|
|
30
|
+
dfByTerm: number[];
|
|
31
|
+
}
|
|
32
|
+
export type Expression = {
|
|
33
|
+
kind: "literal";
|
|
34
|
+
value: QueryValue;
|
|
35
|
+
internalSqlValue?: true;
|
|
36
|
+
sqlDomain?: SqlDomain;
|
|
37
|
+
}
|
|
38
|
+
/** A `?` or `$n` placeholder; `index` is 0-based. Replaced by a literal at bind time. */
|
|
39
|
+
| {
|
|
40
|
+
kind: "parameter";
|
|
41
|
+
index: number;
|
|
42
|
+
} | {
|
|
43
|
+
kind: "column";
|
|
44
|
+
reference: string;
|
|
45
|
+
}
|
|
46
|
+
/** `*`, or `alias.*` when `table` is set. */
|
|
47
|
+
| {
|
|
48
|
+
kind: "wildcard";
|
|
49
|
+
table?: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "binary";
|
|
52
|
+
operator: BinaryOperator;
|
|
53
|
+
left: Expression;
|
|
54
|
+
right: Expression;
|
|
55
|
+
} | {
|
|
56
|
+
kind: "call";
|
|
57
|
+
name: AggregateName | ScalarFunctionName;
|
|
58
|
+
arguments: Expression[];
|
|
59
|
+
distinct?: boolean;
|
|
60
|
+
aggregateOrderBy?: Array<{
|
|
61
|
+
expression: Expression;
|
|
62
|
+
direction: "asc" | "desc";
|
|
63
|
+
nulls?: "first" | "last";
|
|
64
|
+
}>;
|
|
65
|
+
} | {
|
|
66
|
+
kind: "list";
|
|
67
|
+
items: Expression[];
|
|
68
|
+
} | {
|
|
69
|
+
kind: "subquery";
|
|
70
|
+
block: CompiledQuery;
|
|
71
|
+
} | {
|
|
72
|
+
kind: "condition";
|
|
73
|
+
operator: PredicateOperator;
|
|
74
|
+
left: Expression;
|
|
75
|
+
right: Expression;
|
|
76
|
+
escape?: string;
|
|
77
|
+
} | {
|
|
78
|
+
kind: "logical";
|
|
79
|
+
operator: "and" | "or";
|
|
80
|
+
left: Expression;
|
|
81
|
+
right: Expression;
|
|
82
|
+
} | {
|
|
83
|
+
kind: "not";
|
|
84
|
+
operand: Expression;
|
|
85
|
+
} | {
|
|
86
|
+
kind: "exists";
|
|
87
|
+
block: CompiledQuery;
|
|
88
|
+
negated: boolean;
|
|
89
|
+
} | {
|
|
90
|
+
kind: "case";
|
|
91
|
+
branches: Array<{
|
|
92
|
+
when: Expression;
|
|
93
|
+
then: Expression;
|
|
94
|
+
}>;
|
|
95
|
+
otherwise?: Expression;
|
|
96
|
+
} | {
|
|
97
|
+
kind: "window";
|
|
98
|
+
name: WindowFunctionName;
|
|
99
|
+
partitionBy: Expression[];
|
|
100
|
+
orderBy: Array<{
|
|
101
|
+
expression: Expression;
|
|
102
|
+
direction: "asc" | "desc";
|
|
103
|
+
nulls?: "first" | "last";
|
|
104
|
+
}>;
|
|
105
|
+
argument?: Expression;
|
|
106
|
+
offset?: number;
|
|
107
|
+
fallback?: QueryValue;
|
|
108
|
+
frame?: WindowFrame;
|
|
109
|
+
} | {
|
|
110
|
+
kind: "fts";
|
|
111
|
+
op: "match" | "bm25";
|
|
112
|
+
/** Column references forming the document, or `*` for all searchable scan columns. */
|
|
113
|
+
columns: Expression[] | "*";
|
|
114
|
+
query: string;
|
|
115
|
+
queryParameter?: number;
|
|
116
|
+
stats?: FtsStats;
|
|
117
|
+
};
|
|
118
|
+
export type WindowFunctionName = "ROW_NUMBER" | "RANK" | "DENSE_RANK" | "PERCENT_RANK" | "CUME_DIST" | "NTILE" | "LAG" | "LEAD" | "FIRST_VALUE" | "LAST_VALUE" | "NTH_VALUE" | AggregateName;
|
|
119
|
+
export interface WindowFrameBound {
|
|
120
|
+
kind: "unbounded-preceding" | "preceding" | "current-row" | "following" | "unbounded-following";
|
|
121
|
+
offset?: number;
|
|
122
|
+
}
|
|
123
|
+
export type WindowFrameExclusion = "no-others" | "current-row" | "group" | "ties";
|
|
124
|
+
export interface WindowFrame {
|
|
125
|
+
unit: "rows" | "range" | "groups";
|
|
126
|
+
start: WindowFrameBound;
|
|
127
|
+
end: WindowFrameBound;
|
|
128
|
+
exclude?: WindowFrameExclusion;
|
|
129
|
+
}
|
|
130
|
+
export interface WindowSpec {
|
|
131
|
+
alias: string;
|
|
132
|
+
name: WindowFunctionName;
|
|
133
|
+
partitionAliases: string[];
|
|
134
|
+
orderAliases: Array<{
|
|
135
|
+
alias: string;
|
|
136
|
+
direction: "asc" | "desc";
|
|
137
|
+
nulls?: "first" | "last";
|
|
138
|
+
}>;
|
|
139
|
+
argumentAlias?: string;
|
|
140
|
+
offset?: number;
|
|
141
|
+
fallback?: QueryValue;
|
|
142
|
+
frame?: WindowFrame;
|
|
143
|
+
}
|
|
144
|
+
export interface SelectItem {
|
|
145
|
+
expression: Expression;
|
|
146
|
+
alias: string;
|
|
147
|
+
}
|
|
148
|
+
export interface TableSource {
|
|
149
|
+
table: string;
|
|
150
|
+
alias: string;
|
|
151
|
+
derived?: CompiledQuery;
|
|
152
|
+
union?: {
|
|
153
|
+
blocks: CompiledQuery[];
|
|
154
|
+
ops: SetOperator[];
|
|
155
|
+
};
|
|
156
|
+
recursive?: RecursiveCte;
|
|
157
|
+
windowed?: {
|
|
158
|
+
block: CompiledQuery;
|
|
159
|
+
windows: WindowSpec[];
|
|
160
|
+
};
|
|
161
|
+
columnAliases?: string[];
|
|
162
|
+
lateral?: true;
|
|
163
|
+
}
|
|
164
|
+
export interface JoinPlan extends TableSource {
|
|
165
|
+
kind: "inner" | "left" | "semi" | "anti";
|
|
166
|
+
left: Expression;
|
|
167
|
+
right: Expression;
|
|
168
|
+
on?: Expression;
|
|
169
|
+
full?: boolean;
|
|
170
|
+
natural?: boolean;
|
|
171
|
+
}
|
|
172
|
+
export type SetOperator = "union" | "union all" | "intersect" | "intersect all" | "except" | "except all";
|
|
173
|
+
export interface RecursiveCte {
|
|
174
|
+
reference: string;
|
|
175
|
+
base: CompiledQuery;
|
|
176
|
+
step: CompiledQuery;
|
|
177
|
+
all: boolean;
|
|
178
|
+
}
|
|
179
|
+
export type PredicateOperator = ComparisonOperator | `${ComparisonOperator} ANY` | `${ComparisonOperator} ALL` | "IN" | "NOT IN" | "IS NULL" | "IS NOT NULL" | "LIKE" | "NOT LIKE" | "ILIKE" | "NOT ILIKE" | "SIMILAR TO" | "NOT SIMILAR TO" | "IS DISTINCT FROM" | "IS NOT DISTINCT FROM" | "IS TRUE";
|
|
180
|
+
export interface Predicate {
|
|
181
|
+
left: Expression;
|
|
182
|
+
operator: PredicateOperator;
|
|
183
|
+
right: Expression;
|
|
184
|
+
escape?: string;
|
|
185
|
+
}
|
|
186
|
+
export interface CompiledQuery {
|
|
187
|
+
sql: string;
|
|
188
|
+
base: TableSource;
|
|
189
|
+
joins: JoinPlan[];
|
|
190
|
+
select: SelectItem[];
|
|
191
|
+
predicates: Predicate[];
|
|
192
|
+
groupBy: Expression[];
|
|
193
|
+
having: Predicate[];
|
|
194
|
+
orderBy: Array<{
|
|
195
|
+
expression: Expression;
|
|
196
|
+
direction: "asc" | "desc";
|
|
197
|
+
nulls?: "first" | "last";
|
|
198
|
+
}>;
|
|
199
|
+
limit?: number;
|
|
200
|
+
offset?: number;
|
|
201
|
+
distinctWildcard?: boolean;
|
|
202
|
+
limitParameter?: number;
|
|
203
|
+
offsetParameter?: number;
|
|
204
|
+
limitWithTies?: boolean;
|
|
205
|
+
parameterCount?: number;
|
|
206
|
+
usesStatementDatetime?: boolean;
|
|
207
|
+
usesSequenceCalls?: boolean;
|
|
208
|
+
usesVolatileFunctions?: boolean;
|
|
209
|
+
}
|
|
210
|
+
/** ORDER BY / LIMIT / OFFSET tail of a select or set operation. */
|
|
211
|
+
export interface SelectTail {
|
|
212
|
+
orderBy: CompiledQuery["orderBy"];
|
|
213
|
+
limit?: number;
|
|
214
|
+
offset?: number;
|
|
215
|
+
limitParameter?: number;
|
|
216
|
+
offsetParameter?: number;
|
|
217
|
+
limitWithTies?: boolean;
|
|
218
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, boundedMaintenanceBatchItems, canonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, collectFtsPostings, ftsPostingQueryMatches, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_FTS_CANDIDATE_ROW_IDS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_ORDERED_READ_BYTES, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_BLOCK_READ_BATCH_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TRANSACTIONS, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_RETIRED_HISTORY_BYTES, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, MAX_LEVEL_ZERO_SEGMENTS, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, StorageCorruptionError, StorageFormatVersionError, IndexedDbSchemaUpgradeBlockedError, StorageResourceLimitError, SnapshotManifestMissingError, SnapshotImportConflictError, PostingBuildConflictError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, MAX_TEMP_OWNER_TTL_MS, MAX_ACTIVE_TEMP_OWNERS, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, SchemaConflictError, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord as updateCompactionJobRecordUnchecked, updateGarbageCollectionPlanningRecord, updateTransactionRecord as updateTransactionRecordUnchecked, validateColumnDefault, validateCatalogName, validateCanonicalManifestChangedTableIds, validateEnumValues, validateFtsOrderedReadLimits, validateFtsPostingQueries, validateStorageId, validateStorageDatabaseName, validateTableColumns, validateSecondaryIndexes, validateTableRecordBounds, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, WriteConflictError, } from "./types.js";
|
|
1
|
+
import { CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, CompactionBacklogError, createManifest, createGarbageCollectionJobRecord, storeNames, advanceGarbageCollectionJobRecord as advanceGarbageCollectionJobRecordUnchecked, BlockReadBatchTooLargeError, activePostingStorageColumnIds, assertTempRunPageBatchLimits, assertStorageBulkReadItems, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, boundedMaintenanceBatchItems, canonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, collectFtsPostings, ftsPostingQueryMatches, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_FTS_CANDIDATE_ROW_IDS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_ORDERED_READ_BYTES, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_BLOCK_READ_BATCH_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TRANSACTIONS, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_RETIRED_HISTORY_BYTES, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_GARBAGE_COLLECTION_JOBS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, MAX_SNAPSHOT_METADATA_FRAME_BYTES, SNAPSHOT_FRAME_KINDS, MAX_LEVEL_ZERO_SEGMENTS, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, StorageCorruptionError, StorageFormatVersionError, IndexedDbSchemaUpgradeBlockedError, StorageResourceLimitError, SnapshotManifestMissingError, SnapshotImportConflictError, PostingBuildConflictError, validateTableForeignKey, TableInUseError, TableRecordConflictError, TempOwnerConflictError, MAX_TEMP_OWNER_TTL_MS, MAX_ACTIVE_TEMP_OWNERS, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_RUNS_TOTAL, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, SchemaConflictError, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord as updateCompactionJobRecordUnchecked, updateGarbageCollectionPlanningRecord, updateTransactionRecord as updateTransactionRecordUnchecked, validateColumnDefault, validateCatalogName, validateCanonicalManifestChangedTableIds, validateEnumValues, validateFtsOrderedReadLimits, validateFtsPostingQueries, validateStorageId, validateStorageDatabaseName, validateTableColumns, validateSecondaryIndexes, validateTableRecordBounds, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, WriteConflictError, } from "./types.js";
|
|
2
2
|
import { crc32, verifyStoredBlock } from "../block-format/index.js";
|
|
3
3
|
import { dateIsoString } from "../date-value.js";
|
|
4
4
|
import { decodeSnapshotMetadataItems, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity, } from "./snapshot-stream.js";
|
|
@@ -9678,7 +9678,7 @@ function asTableRecord(value, location = "catalog/table") {
|
|
|
9678
9678
|
}
|
|
9679
9679
|
for (const [index, foreignKey] of rawForeignKeys.entries()) {
|
|
9680
9680
|
if (isRecord(foreignKey)) {
|
|
9681
|
-
assertKnownFields(foreignKey, ["name", "columns", "parentTable", "parentColumns", "onDelete"], `${location}/foreignKeys/${String(index)}`);
|
|
9681
|
+
assertKnownFields(foreignKey, ["name", "columns", "parentTable", "parentColumns", "onDelete", "enforced"], `${location}/foreignKeys/${String(index)}`);
|
|
9682
9682
|
}
|
|
9683
9683
|
if (!isRecord(foreignKey) ||
|
|
9684
9684
|
!isCatalogName(foreignKey.name) ||
|
|
@@ -9690,6 +9690,7 @@ function asTableRecord(value, location = "catalog/table") {
|
|
|
9690
9690
|
foreignKey.parentColumns.length === 0 ||
|
|
9691
9691
|
foreignKey.columns.length !== foreignKey.parentColumns.length ||
|
|
9692
9692
|
!["restrict", "cascade", "set null"].includes(String(foreignKey.onDelete)) ||
|
|
9693
|
+
(foreignKey.enforced !== undefined && typeof foreignKey.enforced !== "boolean") ||
|
|
9693
9694
|
foreignKey.parentColumns.some((columnName) => !isCatalogName(columnName))) {
|
|
9694
9695
|
throw corruption(`${location}/foreignKeys/${String(index)}`, "foreign-key metadata is invalid");
|
|
9695
9696
|
}
|
|
@@ -11965,16 +11966,7 @@ async function assertTableForeignKeysInTransaction(catalog, record) {
|
|
|
11965
11966
|
if (parent === undefined) {
|
|
11966
11967
|
throw new TypeError(`FOREIGN KEY ${key.name} references a missing table: ${key.parentTable}`);
|
|
11967
11968
|
}
|
|
11968
|
-
|
|
11969
|
-
? parent.primaryKeyColumnIds
|
|
11970
|
-
: parent.uniqueKeyColumnId === undefined
|
|
11971
|
-
? []
|
|
11972
|
-
: [parent.uniqueKeyColumnId];
|
|
11973
|
-
const addressNames = addressIds.map((id) => parent.columns.find((column) => column.id === id)?.name ?? "");
|
|
11974
|
-
if (addressNames.length !== key.parentColumns.length ||
|
|
11975
|
-
addressNames.some((name, index) => name !== key.parentColumns[index])) {
|
|
11976
|
-
throw new TypeError(`FOREIGN KEY ${key.name} must reference the parent primary or unique key`);
|
|
11977
|
-
}
|
|
11969
|
+
validateTableForeignKey(record, key, parent);
|
|
11978
11970
|
}
|
|
11979
11971
|
}
|
|
11980
11972
|
async function updateRetiredHistoryLedger(store, deltaBytes) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
|
|
1
|
+
import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateTableForeignKey, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
|
|
2
2
|
import { dateIsoString } from "../../date-value.js";
|
|
3
3
|
import { assertWellFormedString, crc32, MAX_STORED_BLOCK_BYTE_LENGTH, } from "../../block-format/index.js";
|
|
4
4
|
const postingTextEncoder = new TextEncoder();
|
|
@@ -1300,16 +1300,7 @@ export class RecordCore {
|
|
|
1300
1300
|
if (parent === undefined) {
|
|
1301
1301
|
throw new TypeError(`FOREIGN KEY ${key.name} references a missing table: ${key.parentTable}`);
|
|
1302
1302
|
}
|
|
1303
|
-
|
|
1304
|
-
? parent.primaryKeyColumnIds
|
|
1305
|
-
: parent.uniqueKeyColumnId === undefined
|
|
1306
|
-
? []
|
|
1307
|
-
: [parent.uniqueKeyColumnId];
|
|
1308
|
-
const addressNames = addressIds.map((id) => parent.columns.find((column) => column.id === id)?.name ?? "");
|
|
1309
|
-
if (addressNames.length !== key.parentColumns.length ||
|
|
1310
|
-
addressNames.some((name, index) => name !== key.parentColumns[index])) {
|
|
1311
|
-
throw new TypeError(`FOREIGN KEY ${key.name} must reference the parent primary or unique key`);
|
|
1312
|
-
}
|
|
1303
|
+
validateTableForeignKey(record, key, parent);
|
|
1313
1304
|
}
|
|
1314
1305
|
}
|
|
1315
1306
|
getTable(id) {
|
|
@@ -1342,7 +1333,7 @@ export class RecordCore {
|
|
|
1342
1333
|
}
|
|
1343
1334
|
if (update.columns !== undefined)
|
|
1344
1335
|
validateTableColumns(update.columns);
|
|
1345
|
-
const { ftsColumns: previousFts, secondaryIndexes: previousSecondary, triggers: previousTriggers, view: previousView, ...base } = record;
|
|
1336
|
+
const { ftsColumns: previousFts, secondaryIndexes: previousSecondary, triggers: previousTriggers, view: previousView, foreignKeys: previousForeignKeys, ...base } = record;
|
|
1346
1337
|
let nextFts = update.ftsColumns === undefined ? previousFts : update.ftsColumns;
|
|
1347
1338
|
let nextSecondary = update.secondaryIndexes === undefined ? previousSecondary : update.secondaryIndexes;
|
|
1348
1339
|
const retainedColumnIds = update.columns === undefined
|
|
@@ -1373,6 +1364,7 @@ export class RecordCore {
|
|
|
1373
1364
|
}
|
|
1374
1365
|
const nextTriggers = update.triggers === undefined ? previousTriggers : update.triggers;
|
|
1375
1366
|
const nextView = update.view === undefined ? previousView : update.view;
|
|
1367
|
+
const nextForeignKeys = update.foreignKeys ?? previousForeignKeys;
|
|
1376
1368
|
validateTableView(nextView ?? undefined);
|
|
1377
1369
|
const updated = {
|
|
1378
1370
|
...base,
|
|
@@ -1386,10 +1378,12 @@ export class RecordCore {
|
|
|
1386
1378
|
...(nextTriggers === null || nextTriggers === undefined
|
|
1387
1379
|
? {}
|
|
1388
1380
|
: { triggers: structuredClone(nextTriggers) }),
|
|
1381
|
+
...(nextForeignKeys === undefined ? {} : { foreignKeys: structuredClone(nextForeignKeys) }),
|
|
1389
1382
|
...(nextView === null || nextView === undefined ? {} : { view: structuredClone(nextView) }),
|
|
1390
1383
|
revision: safeWholeIncrement(expectedRevision, "Table revision"),
|
|
1391
1384
|
};
|
|
1392
1385
|
validateSecondaryIndexes(updated);
|
|
1386
|
+
this.#assertTableForeignKeys(updated);
|
|
1393
1387
|
this.#assertTableTriggerOwnership(updated);
|
|
1394
1388
|
let autoIncrementCounter;
|
|
1395
1389
|
if (update.autoIncrementSeed !== undefined) {
|
|
@@ -5552,16 +5546,7 @@ function validateRecordCoreState(state, physical) {
|
|
|
5552
5546
|
if (parent === undefined) {
|
|
5553
5547
|
throw new TypeError(`FOREIGN KEY ${key.name} references a missing table: ${key.parentTable}`);
|
|
5554
5548
|
}
|
|
5555
|
-
|
|
5556
|
-
? parent.primaryKeyColumnIds
|
|
5557
|
-
: parent.uniqueKeyColumnId === undefined
|
|
5558
|
-
? []
|
|
5559
|
-
: [parent.uniqueKeyColumnId];
|
|
5560
|
-
const addressNames = addressIds.map((id) => parent.columns.find((column) => column.id === id)?.name ?? "");
|
|
5561
|
-
if (addressNames.length !== key.parentColumns.length ||
|
|
5562
|
-
addressNames.some((name, index) => name !== key.parentColumns[index])) {
|
|
5563
|
-
throw new TypeError(`FOREIGN KEY ${key.name} must reference the parent primary or unique key`);
|
|
5564
|
-
}
|
|
5549
|
+
validateTableForeignKey(table, key, parent);
|
|
5565
5550
|
}
|
|
5566
5551
|
}
|
|
5567
5552
|
const terminalTransactionCount = state.transactions.filter((transaction) => transaction.status !== "active").length;
|