@minnowdb/core 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/block-format/codecs.d.ts +0 -8
- package/dist/block-format/codecs.js +1 -1
- package/dist/engine/group-index.d.ts +2 -0
- package/dist/engine/group-index.js +4 -8
- package/dist/engine/join-index.js +1 -15
- package/dist/engine/keyed-live.js +2 -36
- package/dist/engine/live-equal.d.ts +7 -0
- package/dist/engine/live-equal.js +42 -0
- package/dist/engine/optimizer.js +2 -14
- package/dist/engine/query.js +6 -15
- package/dist/engine/schema.d.ts +0 -12
- package/dist/engine/schema.js +0 -17
- package/dist/engine/typed-live.js +3 -41
- package/dist/engine/vector.js +3 -26
- package/dist/plan/model.d.ts +7 -0
- package/dist/plan/model.js +29 -1
- package/dist/storage/indexeddb.js +13 -23
- package/dist/storage/opfs/leader.js +3 -12
- package/dist/storage/toolkit/record-core.js +3 -12
- package/dist/storage/types.d.ts +4 -3
- package/dist/storage/types.js +13 -2
- package/dist/testing/block-store-conformance.js +28 -0
- package/dist/worker-protocol/index.d.ts +0 -32
- package/dist/worker-protocol/index.js +0 -39
- package/package.json +1 -1
|
@@ -16,14 +16,6 @@ export interface CompressionMemoryBound {
|
|
|
16
16
|
/** Additional JavaScript-owned byte storage retained while producing the result. */
|
|
17
17
|
readonly scratchBytes: number;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* The raw codec returns its input view unchanged in both directions: callers treat compressed
|
|
21
|
-
* and decompressed payloads as read-only (encode copies the payload into the block envelope,
|
|
22
|
-
* decode consumers only read or copy out), so a full-payload defensive copy per block would be
|
|
23
|
-
* pure overhead on the default compression path. The returned view may share the caller's
|
|
24
|
-
* buffer at a non-zero byte offset.
|
|
25
|
-
*/
|
|
26
|
-
export declare const rawCodec: CompressionCodec;
|
|
27
19
|
export declare function getCompressionMemoryBound(compression: Compression, inputLength: number): CompressionMemoryBound;
|
|
28
20
|
export declare const gzipCodec: CompressionCodec;
|
|
29
21
|
export declare function getCodec(id: Compression): CompressionCodec;
|
|
@@ -16,7 +16,7 @@ export class CompressionOutputLimitError extends RangeError {
|
|
|
16
16
|
* pure overhead on the default compression path. The returned view may share the caller's
|
|
17
17
|
* buffer at a non-zero byte offset.
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
const rawCodec = {
|
|
20
20
|
id: "raw",
|
|
21
21
|
async compress(bytes, maximumOutputLength) {
|
|
22
22
|
if (maximumOutputLength !== undefined) {
|
|
@@ -29,3 +29,5 @@ export declare function hashScratch(length: number): number;
|
|
|
29
29
|
export declare function equalsScratch(arena: Uint8Array, offset: number, length: number): boolean;
|
|
30
30
|
/** Copies the scratch arena's first `length` bytes into owned storage. Exported for the join index. */
|
|
31
31
|
export declare function copyScratchKey(length: number): Uint8Array;
|
|
32
|
+
export declare function safeDouble(value: number, label: string): number;
|
|
33
|
+
export declare function safeProduct(left: number, right: number, label: string): number;
|
|
@@ -49,7 +49,7 @@ export class ByteGroupIndex {
|
|
|
49
49
|
return this.get([]);
|
|
50
50
|
}
|
|
51
51
|
getOne(key) {
|
|
52
|
-
const length =
|
|
52
|
+
const length = encodeSingleScalarKey(key);
|
|
53
53
|
const index = this.#find(length, hashScratch(length));
|
|
54
54
|
return index < 0 ? undefined : this.#values[index];
|
|
55
55
|
}
|
|
@@ -68,7 +68,7 @@ export class ByteGroupIndex {
|
|
|
68
68
|
return this.#getOrInsertScratch(length, create);
|
|
69
69
|
}
|
|
70
70
|
getOrInsertOne(key, create) {
|
|
71
|
-
const length =
|
|
71
|
+
const length = encodeSingleScalarKey(key);
|
|
72
72
|
return this.#getOrInsertScratch(length, create);
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
@@ -231,10 +231,6 @@ export function encodeSingleScalarKey(key) {
|
|
|
231
231
|
reclaimScratch();
|
|
232
232
|
return writeGroupKey(key, 0);
|
|
233
233
|
}
|
|
234
|
-
function encodeSingleGroupKey(key) {
|
|
235
|
-
reclaimScratch();
|
|
236
|
-
return writeGroupKey(key, 0);
|
|
237
|
-
}
|
|
238
234
|
function writeGroupKey(key, offset) {
|
|
239
235
|
if (key === null) {
|
|
240
236
|
ensureScratchCapacity(offset + 1);
|
|
@@ -341,13 +337,13 @@ export function equalsScratch(arena, offset, length) {
|
|
|
341
337
|
export function copyScratchKey(length) {
|
|
342
338
|
return scratch.slice(0, length);
|
|
343
339
|
}
|
|
344
|
-
function safeDouble(value, label) {
|
|
340
|
+
export function safeDouble(value, label) {
|
|
345
341
|
const doubled = value * 2;
|
|
346
342
|
if (!Number.isSafeInteger(doubled))
|
|
347
343
|
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
348
344
|
return doubled;
|
|
349
345
|
}
|
|
350
|
-
function safeProduct(left, right, label) {
|
|
346
|
+
export function safeProduct(left, right, label) {
|
|
351
347
|
const product = left * right;
|
|
352
348
|
if (!Number.isSafeInteger(product))
|
|
353
349
|
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
-
import { copyScratchKey, encodeSingleScalarKey, equalsScratch, hashScratch, } from "./group-index.js";
|
|
2
|
+
import { copyScratchKey, encodeSingleScalarKey, equalsScratch, hashScratch, safeDouble, safeProduct, } from "./group-index.js";
|
|
3
3
|
const INITIAL_ENTRY_CAPACITY = 4;
|
|
4
4
|
const INITIAL_BUCKET_CAPACITY = 8;
|
|
5
5
|
const INITIAL_KEY_CAPACITY = 32;
|
|
@@ -201,17 +201,3 @@ function encodeJoinKey(value) {
|
|
|
201
201
|
}
|
|
202
202
|
throw new TypeError("Join keys must be SQL scalar values");
|
|
203
203
|
}
|
|
204
|
-
function safeDouble(value, label) {
|
|
205
|
-
const doubled = value * 2;
|
|
206
|
-
if (!Number.isSafeInteger(doubled)) {
|
|
207
|
-
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
208
|
-
}
|
|
209
|
-
return doubled;
|
|
210
|
-
}
|
|
211
|
-
function safeProduct(left, right, label) {
|
|
212
|
-
const product = left * right;
|
|
213
|
-
if (!Number.isSafeInteger(product)) {
|
|
214
|
-
throw new RangeError(`${label} exceeds the safe integer range`);
|
|
215
|
-
}
|
|
216
|
-
return product;
|
|
217
|
-
}
|
|
@@ -1,39 +1,5 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
-
|
|
3
|
-
if (Object.is(left, right))
|
|
4
|
-
return true;
|
|
5
|
-
if (left instanceof Date || right instanceof Date) {
|
|
6
|
-
return (left instanceof Date &&
|
|
7
|
-
right instanceof Date &&
|
|
8
|
-
Object.is(dateMilliseconds(left), dateMilliseconds(right)));
|
|
9
|
-
}
|
|
10
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
11
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
|
12
|
-
return false;
|
|
13
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
14
|
-
if (!sameValue(left[index], right[index]))
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
const leftRecord = left;
|
|
23
|
-
const rightRecord = right;
|
|
24
|
-
const leftKeys = Object.keys(leftRecord);
|
|
25
|
-
const rightKeys = Object.keys(rightRecord);
|
|
26
|
-
if (leftKeys.length !== rightKeys.length)
|
|
27
|
-
return false;
|
|
28
|
-
for (let index = 0; index < leftKeys.length; index += 1) {
|
|
29
|
-
const key = leftKeys[index];
|
|
30
|
-
if (key === undefined || key !== rightKeys[index])
|
|
31
|
-
return false;
|
|
32
|
-
if (!sameValue(leftRecord[key], rightRecord[key]))
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
2
|
+
import { sameLiveValue } from "./live-equal.js";
|
|
37
3
|
function keyToken(value, name) {
|
|
38
4
|
if (typeof value === "string")
|
|
39
5
|
return `s:${String(value.length)}:${value}`;
|
|
@@ -84,7 +50,7 @@ function diffRows(previousRows, rows, key) {
|
|
|
84
50
|
changes.push({ type: "insert", row: next.row, index: next.index });
|
|
85
51
|
continue;
|
|
86
52
|
}
|
|
87
|
-
if (!
|
|
53
|
+
if (!sameLiveValue(old.row, next.row)) {
|
|
88
54
|
changes.push({ type: "update", row: next.row, previous: old.row, index: next.index });
|
|
89
55
|
}
|
|
90
56
|
if (old.index !== next.index) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural equality over live result values: dates by instant, arrays by element, objects by
|
|
3
|
+
* own keys in insertion order. Shared by the typed live store's exact suppression and the keyed
|
|
4
|
+
* live window's diffing; internal on purpose — live-api re-exports its modules wholesale, and
|
|
5
|
+
* this helper is not part of the live API.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sameLiveValue(left: unknown, right: unknown): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
/**
|
|
3
|
+
* Structural equality over live result values: dates by instant, arrays by element, objects by
|
|
4
|
+
* own keys in insertion order. Shared by the typed live store's exact suppression and the keyed
|
|
5
|
+
* live window's diffing; internal on purpose — live-api re-exports its modules wholesale, and
|
|
6
|
+
* this helper is not part of the live API.
|
|
7
|
+
*/
|
|
8
|
+
export function sameLiveValue(left, right) {
|
|
9
|
+
if (Object.is(left, right))
|
|
10
|
+
return true;
|
|
11
|
+
if (left instanceof Date || right instanceof Date) {
|
|
12
|
+
return (left instanceof Date &&
|
|
13
|
+
right instanceof Date &&
|
|
14
|
+
Object.is(dateMilliseconds(left), dateMilliseconds(right)));
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
17
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
|
18
|
+
return false;
|
|
19
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
20
|
+
if (!sameLiveValue(left[index], right[index]))
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const leftRecord = left;
|
|
29
|
+
const rightRecord = right;
|
|
30
|
+
const leftKeys = Object.keys(leftRecord);
|
|
31
|
+
const rightKeys = Object.keys(rightRecord);
|
|
32
|
+
if (leftKeys.length !== rightKeys.length)
|
|
33
|
+
return false;
|
|
34
|
+
for (let index = 0; index < leftKeys.length; index += 1) {
|
|
35
|
+
const key = leftKeys[index];
|
|
36
|
+
if (key === undefined || key !== rightKeys[index])
|
|
37
|
+
return false;
|
|
38
|
+
if (!sameLiveValue(leftRecord[key], rightRecord[key]))
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
package/dist/engine/optimizer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dateIsoString, dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { crossJoinPlan } from "../plan/model.js";
|
|
2
3
|
import { blockHasSubqueries, childExpressions, DUAL_TABLE, expressionAliases, forEachBlockExpression, forEachNestedBlock, hasAggregate, isAggregateCall, isScalarFunctionName, mapBlockExpressions, mapChildExpressions, parseQuantified, scalarFunctionNames, scalarFunctionValue, splitCondition, volatileScalarFunctionNames, } from "./query.js";
|
|
3
4
|
import { concatenatedSqlValue, isSqlDomainValue } from "./sql-domains.js";
|
|
4
5
|
/**
|
|
@@ -1776,20 +1777,7 @@ function decorrelateExistsWithProbes(block, exists, scope, nextAlias) {
|
|
|
1776
1777
|
reference: `${probesAlias}.${correlationProbeAlias(index)}`,
|
|
1777
1778
|
}));
|
|
1778
1779
|
replacePlanReferences(inner, new Map(references.map((reference, index) => [reference, probeReferences[index] ?? null])));
|
|
1779
|
-
inner.joins.push({
|
|
1780
|
-
table: probesAlias,
|
|
1781
|
-
alias: probesAlias,
|
|
1782
|
-
derived: probes,
|
|
1783
|
-
kind: "inner",
|
|
1784
|
-
left: { kind: "literal", value: null },
|
|
1785
|
-
right: { kind: "literal", value: null },
|
|
1786
|
-
on: {
|
|
1787
|
-
kind: "condition",
|
|
1788
|
-
operator: "=",
|
|
1789
|
-
left: { kind: "literal", value: 1 },
|
|
1790
|
-
right: { kind: "literal", value: 1 },
|
|
1791
|
-
},
|
|
1792
|
-
});
|
|
1780
|
+
inner.joins.push(crossJoinPlan({ table: probesAlias, alias: probesAlias, derived: probes }));
|
|
1793
1781
|
const flagsAlias = nextAlias();
|
|
1794
1782
|
const flags = {
|
|
1795
1783
|
sql: "(probe-lifted correlated exists flags)",
|
package/dist/engine/query.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { copyDate, dateIsoString, dateMilliseconds, dateUtcDate, dateUtcDay, dateUtcFullYear, dateUtcHours, dateUtcMinutes, dateUtcMonth, dateUtcSeconds, setDateUtcDate, setDateUtcMonth, } from "../date-value.js";
|
|
2
|
+
import { crossJoinPlan } from "../plan/model.js";
|
|
2
3
|
import { assertWellFormedString, wellFormedUtf8ByteLength } from "../block-format/unicode.js";
|
|
3
4
|
import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PARAMETERS, MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_TEXT_CHARACTERS, MAX_SQL_TOKENS, } from "./cache-limits.js";
|
|
4
5
|
import { SqlCompileError } from "./errors.js";
|
|
@@ -2549,6 +2550,9 @@ function createPreparedRowQuery(plan, tables, memory) {
|
|
|
2549
2550
|
},
|
|
2550
2551
|
};
|
|
2551
2552
|
}
|
|
2553
|
+
// protectText defaults OFF here — the row engine's own tables are already inside the SQL
|
|
2554
|
+
// boundary — but ON in vector.ts's columnarTableFromRows, which ingests caller-owned rows.
|
|
2555
|
+
// A new call site must pick deliberately, not inherit whichever default is nearest.
|
|
2552
2556
|
function cloneRowTables(tables, protectText = false) {
|
|
2553
2557
|
return new Map([...tables].map(([name, rows]) => [
|
|
2554
2558
|
name,
|
|
@@ -6163,19 +6167,6 @@ class Parser {
|
|
|
6163
6167
|
}
|
|
6164
6168
|
const joins = [];
|
|
6165
6169
|
let rightJoins = 0;
|
|
6166
|
-
/** A cross join: the nested-loop path with a condition every row pair satisfies. */
|
|
6167
|
-
const crossJoin = (source) => ({
|
|
6168
|
-
...source,
|
|
6169
|
-
kind: "inner",
|
|
6170
|
-
left: { kind: "literal", value: null },
|
|
6171
|
-
right: { kind: "literal", value: null },
|
|
6172
|
-
on: {
|
|
6173
|
-
kind: "condition",
|
|
6174
|
-
operator: "=",
|
|
6175
|
-
left: { kind: "literal", value: 1 },
|
|
6176
|
-
right: { kind: "literal", value: 1 },
|
|
6177
|
-
},
|
|
6178
|
-
});
|
|
6179
6170
|
while (this.#peek().text === "," ||
|
|
6180
6171
|
this.#isKeyword("NATURAL") ||
|
|
6181
6172
|
this.#isKeyword("JOIN") ||
|
|
@@ -6186,13 +6177,13 @@ class Parser {
|
|
|
6186
6177
|
this.#isKeyword("CROSS")) {
|
|
6187
6178
|
if (this.#punctuation(",")) {
|
|
6188
6179
|
// F041-07: a comma between table references is a cross join.
|
|
6189
|
-
joins.push(
|
|
6180
|
+
joins.push(crossJoinPlan(this.#source()));
|
|
6190
6181
|
continue;
|
|
6191
6182
|
}
|
|
6192
6183
|
if (this.#isKeyword("CROSS")) {
|
|
6193
6184
|
this.#keyword("CROSS");
|
|
6194
6185
|
this.#keyword("JOIN");
|
|
6195
|
-
joins.push(
|
|
6186
|
+
joins.push(crossJoinPlan(this.#source()));
|
|
6196
6187
|
continue;
|
|
6197
6188
|
}
|
|
6198
6189
|
let kind = "inner";
|
package/dist/engine/schema.d.ts
CHANGED
|
@@ -48,13 +48,6 @@ export interface TableForeignKey<TColumnName extends string = string> {
|
|
|
48
48
|
* derives for an unnamed inline REFERENCES, so a table built either way has the same catalog.
|
|
49
49
|
*/
|
|
50
50
|
export declare function foreignKeyName(tableName: string, columnName: string): string;
|
|
51
|
-
/**
|
|
52
|
-
* A flavored value: a column whose slot the engine can fill, so inserts may omit it. The brand
|
|
53
|
-
* is an optional phantom property — plain values stay assignable in both directions.
|
|
54
|
-
*/
|
|
55
|
-
export type HasDefault<TValue> = TValue & {
|
|
56
|
-
readonly __minnowHasDefault?: true;
|
|
57
|
-
};
|
|
58
51
|
/**
|
|
59
52
|
* A JSON column's select value carrying a declared document shape. The value is still JSON
|
|
60
53
|
* text — the brand is an optional phantom property, so plain strings stay assignable in both
|
|
@@ -171,11 +164,6 @@ interface AnyColumn {
|
|
|
171
164
|
readonly renamedFromName?: string;
|
|
172
165
|
readonly reference?: ColumnReferenceSpec;
|
|
173
166
|
}
|
|
174
|
-
/**
|
|
175
|
-
* Rebuilds a column carrying an exact default spec — the wire layer's escape hatch, since the
|
|
176
|
-
* public `.default()` accepts literals and cannot express auto-increment catalog metadata.
|
|
177
|
-
*/
|
|
178
|
-
export declare function columnWithDefaultSpec(base: Pick<AnyColumn, "type" | "isNullable" | "isUnique" | "renamedFromName" | "reference" | "enumValues"> & Partial<Pick<AnyColumn, "integer" | "sqlDomain">>, spec: ColumnDefault): AnyColumn;
|
|
179
167
|
/** Rebuilds a fluent column from structured-clone-safe metadata. */
|
|
180
168
|
export declare function columnFromState(state: Pick<AnyColumn, "type" | "isNullable" | "isUnique"> & Partial<Pick<AnyColumn, "integer" | "sqlDomain" | "renamedFromName" | "reference" | "defaultSpec" | "generatedSpec" | "enumValues" | "backfillValue">>): AnyColumn;
|
|
181
169
|
export declare const column: {
|
package/dist/engine/schema.js
CHANGED
|
@@ -184,23 +184,6 @@ function validateSchemaName(name, kind) {
|
|
|
184
184
|
throw new TypeError(`${kind} name cannot start or end with whitespace: ${JSON.stringify(name)}`);
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Rebuilds a column carrying an exact default spec — the wire layer's escape hatch, since the
|
|
189
|
-
* public `.default()` accepts literals and cannot express auto-increment catalog metadata.
|
|
190
|
-
*/
|
|
191
|
-
export function columnWithDefaultSpec(base, spec) {
|
|
192
|
-
return columnFromState({
|
|
193
|
-
type: base.type,
|
|
194
|
-
isNullable: base.isNullable,
|
|
195
|
-
isUnique: base.isUnique,
|
|
196
|
-
integer: base.integer ?? false,
|
|
197
|
-
...(base.sqlDomain === undefined ? {} : { sqlDomain: base.sqlDomain }),
|
|
198
|
-
...(base.renamedFromName === undefined ? {} : { renamedFromName: base.renamedFromName }),
|
|
199
|
-
...(base.reference === undefined ? {} : { reference: base.reference }),
|
|
200
|
-
...(base.enumValues === undefined ? {} : { enumValues: base.enumValues }),
|
|
201
|
-
defaultSpec: spec,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
187
|
/** Rebuilds a fluent column from structured-clone-safe metadata. */
|
|
205
188
|
export function columnFromState(state) {
|
|
206
189
|
return createColumn(state.type, {
|
|
@@ -1,42 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function sameLiveValue(left, right) {
|
|
3
|
-
if (Object.is(left, right))
|
|
4
|
-
return true;
|
|
5
|
-
if (left instanceof Date || right instanceof Date) {
|
|
6
|
-
return (left instanceof Date &&
|
|
7
|
-
right instanceof Date &&
|
|
8
|
-
Object.is(dateMilliseconds(left), dateMilliseconds(right)));
|
|
9
|
-
}
|
|
10
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
11
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
|
|
12
|
-
return false;
|
|
13
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
14
|
-
if (!sameLiveValue(left[index], right[index]))
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
const leftRecord = left;
|
|
23
|
-
const rightRecord = right;
|
|
24
|
-
const leftKeys = Object.keys(leftRecord);
|
|
25
|
-
const rightKeys = Object.keys(rightRecord);
|
|
26
|
-
if (leftKeys.length !== rightKeys.length)
|
|
27
|
-
return false;
|
|
28
|
-
for (let index = 0; index < leftKeys.length; index += 1) {
|
|
29
|
-
const key = leftKeys[index];
|
|
30
|
-
if (key === undefined || key !== rightKeys[index])
|
|
31
|
-
return false;
|
|
32
|
-
if (!sameLiveValue(leftRecord[key], rightRecord[key]))
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
function sameRows(left, right) {
|
|
38
|
-
return sameLiveValue(left, right);
|
|
39
|
-
}
|
|
1
|
+
import { sameLiveValue } from "./live-equal.js";
|
|
40
2
|
function immutableRows(rows) {
|
|
41
3
|
// Adapters own their values. Copy the array so mutating the returned builder result cannot
|
|
42
4
|
// change the snapshot identity retained for exact suppression.
|
|
@@ -191,10 +153,10 @@ export class LiveQuery {
|
|
|
191
153
|
const previous = this.#snapshot.rows;
|
|
192
154
|
if (this.#snapshot.status === "ready" &&
|
|
193
155
|
this.#snapshot.version === invalidation.manifestVersion &&
|
|
194
|
-
|
|
156
|
+
sameLiveValue(previous, rows)) {
|
|
195
157
|
continue;
|
|
196
158
|
}
|
|
197
|
-
if (
|
|
159
|
+
if (sameLiveValue(previous, rows) && this.#snapshot.status !== "loading") {
|
|
198
160
|
// Advance the version without replacing the immutable row array.
|
|
199
161
|
this.#snapshot = {
|
|
200
162
|
status: "ready",
|
package/dist/engine/vector.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { crossJoinPlan, isCrossJoinPlan } from "../plan/model.js";
|
|
2
3
|
import { MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, } from "../storage/types.js";
|
|
3
4
|
import { throwIfAborted } from "./cancellation.js";
|
|
4
5
|
import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, unknownColumnDomains, } from "./query.js";
|
|
@@ -409,7 +410,7 @@ function disjunctiveNormalForm(predicate) {
|
|
|
409
410
|
function orderCartesianJoins(plan, tables) {
|
|
410
411
|
if (plan.joins.length < 2 ||
|
|
411
412
|
plan.select.some((item) => item.expression.kind === "wildcard") ||
|
|
412
|
-
plan.joins.some((join) => !
|
|
413
|
+
plan.joins.some((join) => !isCrossJoinPlan(join))) {
|
|
413
414
|
return plan;
|
|
414
415
|
}
|
|
415
416
|
const entries = [
|
|
@@ -454,21 +455,11 @@ function orderCartesianJoins(plan, tables) {
|
|
|
454
455
|
return plan;
|
|
455
456
|
const keyIndex = predicates.findIndex((predicate) => predicateJoinsSource(predicate, sourceIndex, available, entries, sourceTables));
|
|
456
457
|
const key = keyIndex < 0 ? undefined : predicates.splice(keyIndex, 1)[0];
|
|
457
|
-
joins.push(key === undefined ?
|
|
458
|
+
joins.push(key === undefined ? crossJoinPlan(source) : keyedInnerJoin(source, key));
|
|
458
459
|
available.add(sourceIndex);
|
|
459
460
|
}
|
|
460
461
|
return { ...plan, base: plan.base, joins, predicates };
|
|
461
462
|
}
|
|
462
|
-
function isCartesianJoin(join) {
|
|
463
|
-
const condition = join.on;
|
|
464
|
-
return (join.kind === "inner" &&
|
|
465
|
-
condition?.kind === "condition" &&
|
|
466
|
-
condition.operator === "=" &&
|
|
467
|
-
condition.left.kind === "literal" &&
|
|
468
|
-
condition.left.value === 1 &&
|
|
469
|
-
condition.right.kind === "literal" &&
|
|
470
|
-
condition.right.value === 1);
|
|
471
|
-
}
|
|
472
463
|
function tableSourceOf(join) {
|
|
473
464
|
const { kind, left, right, on, full, natural, ...source } = join;
|
|
474
465
|
void kind;
|
|
@@ -479,20 +470,6 @@ function tableSourceOf(join) {
|
|
|
479
470
|
void natural;
|
|
480
471
|
return source;
|
|
481
472
|
}
|
|
482
|
-
function cartesianJoin(source) {
|
|
483
|
-
return {
|
|
484
|
-
...source,
|
|
485
|
-
kind: "inner",
|
|
486
|
-
left: { kind: "literal", value: null },
|
|
487
|
-
right: { kind: "literal", value: null },
|
|
488
|
-
on: {
|
|
489
|
-
kind: "condition",
|
|
490
|
-
operator: "=",
|
|
491
|
-
left: { kind: "literal", value: 1 },
|
|
492
|
-
right: { kind: "literal", value: 1 },
|
|
493
|
-
},
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
473
|
function keyedInnerJoin(source, predicate) {
|
|
497
474
|
return {
|
|
498
475
|
...source,
|
package/dist/plan/model.d.ts
CHANGED
|
@@ -190,6 +190,13 @@ export interface JoinPlan extends TableSource {
|
|
|
190
190
|
full?: boolean;
|
|
191
191
|
natural?: boolean;
|
|
192
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* A cross join rides the nested-loop inner-join path with a condition every row pair satisfies:
|
|
195
|
+
* 1 = 1 over null literal key expressions. Producers build the shape with crossJoinPlan and
|
|
196
|
+
* consumers recognize it with isCrossJoinPlan, so the encoding lives in exactly one place.
|
|
197
|
+
*/
|
|
198
|
+
export declare function crossJoinPlan(source: TableSource): JoinPlan;
|
|
199
|
+
export declare function isCrossJoinPlan(join: JoinPlan): boolean;
|
|
193
200
|
export type SetOperator = "union" | "union all" | "intersect" | "intersect all" | "except" | "except all";
|
|
194
201
|
export interface RecursiveCte {
|
|
195
202
|
reference: string;
|
package/dist/plan/model.js
CHANGED
|
@@ -1 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* A cross join rides the nested-loop inner-join path with a condition every row pair satisfies:
|
|
3
|
+
* 1 = 1 over null literal key expressions. Producers build the shape with crossJoinPlan and
|
|
4
|
+
* consumers recognize it with isCrossJoinPlan, so the encoding lives in exactly one place.
|
|
5
|
+
*/
|
|
6
|
+
export function crossJoinPlan(source) {
|
|
7
|
+
return {
|
|
8
|
+
...source,
|
|
9
|
+
kind: "inner",
|
|
10
|
+
left: { kind: "literal", value: null },
|
|
11
|
+
right: { kind: "literal", value: null },
|
|
12
|
+
on: {
|
|
13
|
+
kind: "condition",
|
|
14
|
+
operator: "=",
|
|
15
|
+
left: { kind: "literal", value: 1 },
|
|
16
|
+
right: { kind: "literal", value: 1 },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function isCrossJoinPlan(join) {
|
|
21
|
+
const condition = join.on;
|
|
22
|
+
return (join.kind === "inner" &&
|
|
23
|
+
condition?.kind === "condition" &&
|
|
24
|
+
condition.operator === "=" &&
|
|
25
|
+
condition.left.kind === "literal" &&
|
|
26
|
+
condition.left.value === 1 &&
|
|
27
|
+
condition.right.kind === "literal" &&
|
|
28
|
+
condition.right.value === 1);
|
|
29
|
+
}
|
|
@@ -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, 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";
|
|
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, validateFtsCandidateLimit, validateFtsOrderedReadLimits, validateFtsReadVersion, 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";
|
|
@@ -1769,11 +1769,8 @@ export class IndexedDbBlockStore {
|
|
|
1769
1769
|
validateId(tableId, "Table ID");
|
|
1770
1770
|
validateId(columnId, "Column ID");
|
|
1771
1771
|
validateFtsPostingQueries(terms);
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
maxRowIds > MAX_FTS_CANDIDATE_ROW_IDS) {
|
|
1775
|
-
throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
|
|
1776
|
-
}
|
|
1772
|
+
validateFtsReadVersion(upToVersion);
|
|
1773
|
+
validateFtsCandidateLimit(maxRowIds);
|
|
1777
1774
|
const transaction = this.#transaction("catalog", "readonly");
|
|
1778
1775
|
const store = transaction.objectStore("catalog");
|
|
1779
1776
|
const [rawToc, rawDeltaIndex] = await Promise.all([
|
|
@@ -1870,9 +1867,7 @@ export class IndexedDbBlockStore {
|
|
|
1870
1867
|
async readFtsPostings(tableId, columnId, upToVersion, maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes = MAX_FTS_ORDERED_READ_BYTES) {
|
|
1871
1868
|
validateId(tableId, "Table ID");
|
|
1872
1869
|
validateId(columnId, "Column ID");
|
|
1873
|
-
|
|
1874
|
-
throw new RangeError("Full-text snapshot version must be a non-negative safe integer");
|
|
1875
|
-
}
|
|
1870
|
+
validateFtsReadVersion(upToVersion);
|
|
1876
1871
|
validateFtsOrderedReadLimits(maxRowIds, maxRetainedBytes);
|
|
1877
1872
|
const transaction = this.#transaction("catalog", "readonly");
|
|
1878
1873
|
const store = transaction.objectStore("catalog");
|
|
@@ -6983,20 +6978,15 @@ function createCurrentIndexedDbSchema(database, upgrade) {
|
|
|
6983
6978
|
for (const storeName of storeNames)
|
|
6984
6979
|
database.createObjectStore(storeName);
|
|
6985
6980
|
database.createObjectStore(SNAPSHOT_HEADER_STORE);
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
upgrade.objectStore("catalog").createIndex(UNIQUE_KEY_BUILD_ACTIVE_INDEX, "activeBuildState");
|
|
6996
|
-
upgrade.objectStore("catalog").createIndex(UNIQUE_KEY_BUILD_EXPIRY_INDEX, "activeExpiry");
|
|
6997
|
-
upgrade.objectStore("catalog").createIndex(MANIFEST_BLOCK_ID_INDEX, "blockId", {
|
|
6998
|
-
unique: true,
|
|
6999
|
-
});
|
|
6981
|
+
// Creation is driven by the same declaration validateCurrentIndexedDbSchema verifies
|
|
6982
|
+
// against, so the two can never disagree about what the current schema is.
|
|
6983
|
+
for (const [storeName, indexes] of Object.entries(indexedDbIndexSchema)) {
|
|
6984
|
+
const store = upgrade.objectStore(storeName);
|
|
6985
|
+
for (const index of indexes) {
|
|
6986
|
+
const keyPath = typeof index.keyPath === "string" ? index.keyPath : [...index.keyPath];
|
|
6987
|
+
store.createIndex(index.name, keyPath, { unique: index.unique ?? false });
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
7000
6990
|
upgrade.objectStore("gc").add(emptyMaintenanceQuota(), MAINTENANCE_QUOTA_KEY);
|
|
7001
6991
|
upgrade.objectStore("statistics").add(emptyResourceLedger(), RESOURCE_LEDGER_KEY);
|
|
7002
6992
|
upgrade.objectStore("statistics").add(emptyCatalogResourceLedger(), CATALOG_RESOURCE_LEDGER_KEY);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { activePostingStorageColumnIds, assertStorageBulkReadItems, assertTempRunPageBatchLimits, BlockReadBatchTooLargeError, collectFtsPostingsBounded, ftsPostingQueryMatches, MAX_BLOCK_READ_BATCH_BYTES, MAX_FTS_BASE_CHUNKS, MAX_POSTING_BUILD_TTL_MS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, MAX_TEMP_RUN_BATCH_BYTES, 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, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_ORDERED_READ_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_SEGMENTS, SnapshotImportConflictError, PostingBuildConflictError, StorageResourceLimitError, StorageCorruptionError, StorageFormatVersionError, validateFtsPostingQueries, validateFtsOrderedReadLimits, uniqueKeyBuildChunkRetainedBytes, } from "../types.js";
|
|
1
|
+
import { activePostingStorageColumnIds, assertStorageBulkReadItems, assertTempRunPageBatchLimits, BlockReadBatchTooLargeError, collectFtsPostingsBounded, ftsPostingQueryMatches, MAX_BLOCK_READ_BATCH_BYTES, MAX_FTS_BASE_CHUNKS, MAX_POSTING_BUILD_TTL_MS, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, MAX_TEMP_RUN_BATCH_BYTES, 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, MAX_ACTIVE_FTS_BASE_BUILDS, MAX_ACTIVE_SECONDARY_INDEX_BUILDS, MAX_ACCELERATOR_BUILD_STAGED_BYTES_TOTAL, MAX_ACCELERATOR_BUILD_STAGED_ENTRIES_TOTAL, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_ORDERED_READ_BYTES, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, MAX_TRANSACTION_STAGE_BLOCKS, MAX_TRANSACTION_STAGE_SEGMENTS, SnapshotImportConflictError, PostingBuildConflictError, StorageResourceLimitError, StorageCorruptionError, StorageFormatVersionError, validateFtsPostingQueries, validateFtsCandidateLimit, validateFtsOrderedReadLimits, validateFtsReadVersion, uniqueKeyBuildChunkRetainedBytes, } from "../types.js";
|
|
2
2
|
import { dateIsoString } from "../../date-value.js";
|
|
3
3
|
import { decodeSnapshotMetadataItems, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity, } from "../snapshot-stream.js";
|
|
4
4
|
import { RecordCore, validateBeginTransactionInput, validateBlockWriteBytes, validateFtsBaseInput, validateFtsPostingChunks, validateId, validateTempRunPage, validateTempRunPageIdentity, } from "../toolkit/record-core.js";
|
|
@@ -2485,9 +2485,7 @@ export class OpfsLeader {
|
|
|
2485
2485
|
validateId(tableId);
|
|
2486
2486
|
validateId(columnId);
|
|
2487
2487
|
validateFtsPostingQueries(terms);
|
|
2488
|
-
|
|
2489
|
-
throw new RangeError("Full-text query version must be a safe integer at least -1");
|
|
2490
|
-
}
|
|
2488
|
+
validateFtsReadVersion(upToVersion);
|
|
2491
2489
|
validateFtsCandidateLimit(maxRowIds);
|
|
2492
2490
|
return this.#run(async () => {
|
|
2493
2491
|
// Unlike immutable table blocks, derived-index extents are not protected by reader leases:
|
|
@@ -2553,9 +2551,7 @@ export class OpfsLeader {
|
|
|
2553
2551
|
async readFtsPostings(tableId, columnId, upToVersion, maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes = MAX_FTS_ORDERED_READ_BYTES) {
|
|
2554
2552
|
validateId(tableId);
|
|
2555
2553
|
validateId(columnId);
|
|
2556
|
-
|
|
2557
|
-
throw new RangeError("Full-text query version must be a safe integer at least -1");
|
|
2558
|
-
}
|
|
2554
|
+
validateFtsReadVersion(upToVersion);
|
|
2559
2555
|
validateFtsOrderedReadLimits(maxRowIds, maxRetainedBytes);
|
|
2560
2556
|
return this.#run(async () => {
|
|
2561
2557
|
const key = postingStorageKey(tableId, columnId);
|
|
@@ -4041,11 +4037,6 @@ function requireCoverageVersion(value, label) {
|
|
|
4041
4037
|
if (!Number.isSafeInteger(value) || value < -1)
|
|
4042
4038
|
throw new Error(`Invalid ${label}`);
|
|
4043
4039
|
}
|
|
4044
|
-
function validateFtsCandidateLimit(value) {
|
|
4045
|
-
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_FTS_CANDIDATE_ROW_IDS) {
|
|
4046
|
-
throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
4040
|
function requirePositiveInteger(value, label) {
|
|
4050
4041
|
if (!Number.isSafeInteger(value) || value < 1)
|
|
4051
4042
|
throw new Error(`Invalid ${label}`);
|
|
@@ -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, 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";
|
|
1
|
+
import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, validateFtsCandidateLimit, validateFtsReadVersion, 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
|
/**
|
|
@@ -1729,9 +1729,7 @@ export class RecordCore {
|
|
|
1729
1729
|
validateId(tableId);
|
|
1730
1730
|
validateId(columnId);
|
|
1731
1731
|
validateFtsPostingQueries(terms);
|
|
1732
|
-
|
|
1733
|
-
throw new RangeError("Full-text query version must be a safe integer at least -1");
|
|
1734
|
-
}
|
|
1732
|
+
validateFtsReadVersion(upToVersion);
|
|
1735
1733
|
validateFtsCandidateLimit(maxRowIds);
|
|
1736
1734
|
const key = `${tableId}/${columnId}`;
|
|
1737
1735
|
const base = this.#ftsBases.get(key);
|
|
@@ -1834,9 +1832,7 @@ export class RecordCore {
|
|
|
1834
1832
|
readFtsPostings(tableId, columnId, upToVersion, maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes) {
|
|
1835
1833
|
validateId(tableId);
|
|
1836
1834
|
validateId(columnId);
|
|
1837
|
-
|
|
1838
|
-
throw new RangeError("Full-text query version must be a safe integer at least -1");
|
|
1839
|
-
}
|
|
1835
|
+
validateFtsReadVersion(upToVersion);
|
|
1840
1836
|
const base = this.#ftsBases.get(`${tableId}/${columnId}`);
|
|
1841
1837
|
const coversVersion = base?.coversVersion ?? -1;
|
|
1842
1838
|
const delta = this.readFtsDeltas(tableId, columnId, coversVersion, upToVersion);
|
|
@@ -6489,11 +6485,6 @@ function sortedVersionInInterval(versions, addedVersion, removedVersion) {
|
|
|
6489
6485
|
const version = versions[low];
|
|
6490
6486
|
return version !== undefined && (removedVersion === null || version < removedVersion);
|
|
6491
6487
|
}
|
|
6492
|
-
function validateFtsCandidateLimit(value) {
|
|
6493
|
-
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_FTS_CANDIDATE_ROW_IDS) {
|
|
6494
|
-
throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
|
|
6495
|
-
}
|
|
6496
|
-
}
|
|
6497
6488
|
function varuintByteLength(value) {
|
|
6498
6489
|
let length = 1;
|
|
6499
6490
|
while (value >= 0x80n) {
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -534,8 +534,6 @@ export interface CompactionJobCursor {
|
|
|
534
534
|
sourceSegmentIndex: number;
|
|
535
535
|
sourceBlockIndex: number;
|
|
536
536
|
}
|
|
537
|
-
export declare const compactionRewritePlanKinds: readonly ["copy-v1", "rechunk-v1", "merge-v1"];
|
|
538
|
-
export type CompactionRewritePlanKind = (typeof compactionRewritePlanKinds)[number];
|
|
539
537
|
export interface CopyCompactionRewritePlan {
|
|
540
538
|
readonly kind: "copy-v1";
|
|
541
539
|
}
|
|
@@ -1669,6 +1667,10 @@ export declare function ftsPostingQueryMatches(term: string, query: FtsPostingQu
|
|
|
1669
1667
|
* never drift between backends — pruning would silently differ per store.
|
|
1670
1668
|
*/
|
|
1671
1669
|
export declare function collectFtsCandidates(chunkLists: Iterable<readonly FtsPosting[]>, terms: readonly FtsPostingQuery[], maxRowIds?: number): FtsCandidates;
|
|
1670
|
+
/** Validates the snapshot bound of one full-text read; -1 selects the base view alone. */
|
|
1671
|
+
export declare function validateFtsReadVersion(upToVersion: number): void;
|
|
1672
|
+
/** Validates the candidate row-ID ceiling accepted by one full-text candidate read. */
|
|
1673
|
+
export declare function validateFtsCandidateLimit(maxRowIds: number): void;
|
|
1672
1674
|
/** Validates the fixed memory ceilings accepted by one ordered postings read. */
|
|
1673
1675
|
export declare function validateFtsOrderedReadLimits(maxRowIds?: number, maxRetainedBytes?: number): void;
|
|
1674
1676
|
/**
|
|
@@ -2422,7 +2424,6 @@ export declare function createManifest(input: CreateManifestInput): Manifest;
|
|
|
2422
2424
|
export declare function normalizeSegmentRecord(record: SegmentRecord): SegmentRecord;
|
|
2423
2425
|
export declare function updateTransactionRecord(record: TransactionRecord, update: TransactionRecordUpdate): TransactionRecord;
|
|
2424
2426
|
export declare function createGarbageCollectionJobRecord(input: CreateGarbageCollectionJobInput): GarbageCollectionJobRecord;
|
|
2425
|
-
export declare function normalizeGarbageCollectionDiscovery(discovery: GarbageCollectionDiscovery): GarbageCollectionDiscovery;
|
|
2426
2427
|
export declare function updateGarbageCollectionPlanningRecord(record: GarbageCollectionJobRecord, input: UpdateGarbageCollectionPlanningInput): GarbageCollectionJobRecord;
|
|
2427
2428
|
export declare function normalizeGarbageCollectionJobRecord(record: GarbageCollectionJobRecord): GarbageCollectionJobRecord;
|
|
2428
2429
|
export declare function advanceGarbageCollectionJobRecord(record: GarbageCollectionJobRecord, accounting: GarbageCollectionStepAccounting): GarbageCollectionJobRecord;
|
package/dist/storage/types.js
CHANGED
|
@@ -726,7 +726,6 @@ export const compactionJobStates = [
|
|
|
726
726
|
"cancelled",
|
|
727
727
|
"aborted",
|
|
728
728
|
];
|
|
729
|
-
export const compactionRewritePlanKinds = ["copy-v1", "rechunk-v1", "merge-v1"];
|
|
730
729
|
export const compactionOutputCompressions = ["raw", "gzip"];
|
|
731
730
|
/** Every segment ID owned by a compaction job, including merge partition outputs. */
|
|
732
731
|
export function compactionOutputSegmentIds(job) {
|
|
@@ -1427,6 +1426,18 @@ export function collectFtsCandidates(chunkLists, terms, maxRowIds = MAX_FTS_CAND
|
|
|
1427
1426
|
overflow: false,
|
|
1428
1427
|
};
|
|
1429
1428
|
}
|
|
1429
|
+
/** Validates the snapshot bound of one full-text read; -1 selects the base view alone. */
|
|
1430
|
+
export function validateFtsReadVersion(upToVersion) {
|
|
1431
|
+
if (!Number.isSafeInteger(upToVersion) || upToVersion < -1) {
|
|
1432
|
+
throw new RangeError("Full-text query version must be a safe integer at least -1");
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
/** Validates the candidate row-ID ceiling accepted by one full-text candidate read. */
|
|
1436
|
+
export function validateFtsCandidateLimit(maxRowIds) {
|
|
1437
|
+
if (!Number.isSafeInteger(maxRowIds) || maxRowIds < 1 || maxRowIds > MAX_FTS_CANDIDATE_ROW_IDS) {
|
|
1438
|
+
throw new RangeError(`Full-text candidate limit must be between 1 and ${String(MAX_FTS_CANDIDATE_ROW_IDS)}`);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1430
1441
|
/** Validates the fixed memory ceilings accepted by one ordered postings read. */
|
|
1431
1442
|
export function validateFtsOrderedReadLimits(maxRowIds = MAX_FTS_CANDIDATE_ROW_IDS, maxRetainedBytes = MAX_FTS_ORDERED_READ_BYTES) {
|
|
1432
1443
|
if (!Number.isSafeInteger(maxRowIds) || maxRowIds < 1 || maxRowIds > MAX_FTS_CANDIDATE_ROW_IDS) {
|
|
@@ -1940,7 +1951,7 @@ export function createGarbageCollectionJobRecord(input) {
|
|
|
1940
1951
|
...(discovery === undefined ? {} : { discovery }),
|
|
1941
1952
|
};
|
|
1942
1953
|
}
|
|
1943
|
-
|
|
1954
|
+
function normalizeGarbageCollectionDiscovery(discovery) {
|
|
1944
1955
|
const runtime = discovery;
|
|
1945
1956
|
if (typeof runtime !== "object" || runtime === null) {
|
|
1946
1957
|
throw new TypeError("Garbage collection discovery must be an object");
|
|
@@ -1008,6 +1008,34 @@ export function blockStoreConformanceCases() {
|
|
|
1008
1008
|
{ term: "minnows", rowIds: [2n], tf: [1] },
|
|
1009
1009
|
{ term: "shark", rowIds: [4n], tf: [1] },
|
|
1010
1010
|
], "ordered postings must merge into canonical term and row-ID order");
|
|
1011
|
+
// The read-argument contract is uniform across adapters: -1 selects the base view
|
|
1012
|
+
// alone, anything below it (or non-integral) is refused, and the candidate limit is
|
|
1013
|
+
// validated on every read path.
|
|
1014
|
+
const baseOnly = await store.readFtsPostings("table-t", "col-v", -1);
|
|
1015
|
+
check(baseOnly.hasBase && baseOnly.postings.length === 3, "an upToVersion of -1 must serve the base view alone");
|
|
1016
|
+
for (const version of [-2, Number.NaN, 1.5]) {
|
|
1017
|
+
try {
|
|
1018
|
+
await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], version);
|
|
1019
|
+
throw new Error(`candidate read accepted invalid version ${String(version)}`);
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
check(error instanceof RangeError, "invalid candidate versions must throw RangeError");
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
await store.readFtsPostings("table-t", "col-v", version);
|
|
1026
|
+
throw new Error(`postings read accepted invalid version ${String(version)}`);
|
|
1027
|
+
}
|
|
1028
|
+
catch (error) {
|
|
1029
|
+
check(error instanceof RangeError, "invalid postings versions must throw RangeError");
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], 10, 0);
|
|
1034
|
+
throw new Error("candidate read accepted a zero row-ID limit");
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
check(error instanceof RangeError, "invalid candidate limits must throw RangeError");
|
|
1038
|
+
}
|
|
1011
1039
|
store.close();
|
|
1012
1040
|
},
|
|
1013
1041
|
},
|
|
@@ -1,38 +1,6 @@
|
|
|
1
1
|
export declare const protocolVersion: 3;
|
|
2
2
|
/** Outstanding request/response pairs retained by either side of one database RPC connection. */
|
|
3
3
|
export declare const MAX_DATABASE_RPC_IN_FLIGHT = 256;
|
|
4
|
-
export type WorkerOperation = "benchmark" | "cancelBenchmark" | "datasetList" | "datasetCreate" | "datasetDelete" | "runQuery" | "suiteReference" | "suiteWrite" | "suiteFeatureMatrix" | "suiteLive";
|
|
5
|
-
export interface WorkerRequest<T = unknown> {
|
|
6
|
-
version: typeof protocolVersion;
|
|
7
|
-
requestId: string;
|
|
8
|
-
operation: WorkerOperation;
|
|
9
|
-
payload: T;
|
|
10
|
-
}
|
|
11
|
-
export interface SuccessResponse<T = unknown> {
|
|
12
|
-
version: typeof protocolVersion;
|
|
13
|
-
requestId: string;
|
|
14
|
-
kind: "success";
|
|
15
|
-
result: T;
|
|
16
|
-
}
|
|
17
|
-
export interface FailureResponse {
|
|
18
|
-
version: typeof protocolVersion;
|
|
19
|
-
requestId: string;
|
|
20
|
-
kind: "failure";
|
|
21
|
-
error: {
|
|
22
|
-
name: string;
|
|
23
|
-
message: string;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export interface ProgressResponse<T = unknown> {
|
|
27
|
-
version: typeof protocolVersion;
|
|
28
|
-
requestId: string;
|
|
29
|
-
kind: "progress";
|
|
30
|
-
progress: T;
|
|
31
|
-
}
|
|
32
|
-
export type WorkerResponse<T = unknown> = SuccessResponse<T> | FailureResponse | ProgressResponse;
|
|
33
|
-
export declare function parseRequest(value: unknown): WorkerRequest;
|
|
34
|
-
export declare function success<T>(requestId: string, result: T): SuccessResponse<T>;
|
|
35
|
-
export declare function failure(requestId: string, error: unknown): FailureResponse;
|
|
36
4
|
export type RpcRequest = {
|
|
37
5
|
version: typeof protocolVersion;
|
|
38
6
|
requestId: string;
|
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
export const protocolVersion = 3;
|
|
2
2
|
/** Outstanding request/response pairs retained by either side of one database RPC connection. */
|
|
3
3
|
export const MAX_DATABASE_RPC_IN_FLIGHT = 256;
|
|
4
|
-
export function parseRequest(value) {
|
|
5
|
-
if (typeof value !== "object" || value === null)
|
|
6
|
-
throw new TypeError("Request must be an object");
|
|
7
|
-
const candidate = value;
|
|
8
|
-
if (candidate.version !== protocolVersion)
|
|
9
|
-
throw new Error("Unsupported protocol version");
|
|
10
|
-
if (typeof candidate.requestId !== "string" || candidate.requestId.length === 0) {
|
|
11
|
-
throw new TypeError("Request ID must be a non-empty string");
|
|
12
|
-
}
|
|
13
|
-
if (!isOperation(candidate.operation))
|
|
14
|
-
throw new Error("Unsupported worker operation");
|
|
15
|
-
return candidate;
|
|
16
|
-
}
|
|
17
|
-
export function success(requestId, result) {
|
|
18
|
-
return { version: protocolVersion, requestId, kind: "success", result };
|
|
19
|
-
}
|
|
20
|
-
export function failure(requestId, error) {
|
|
21
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
22
|
-
return {
|
|
23
|
-
version: protocolVersion,
|
|
24
|
-
requestId,
|
|
25
|
-
kind: "failure",
|
|
26
|
-
error: { name: normalized.name, message: normalized.message },
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
4
|
export function serializeError(error) {
|
|
30
5
|
if (!(error instanceof Error)) {
|
|
31
6
|
return { name: "Error", message: String(error) };
|
|
@@ -101,17 +76,3 @@ export function parseRpcResponse(value) {
|
|
|
101
76
|
throw new Error("Unsupported protocol version");
|
|
102
77
|
return candidate;
|
|
103
78
|
}
|
|
104
|
-
function isOperation(value) {
|
|
105
|
-
return [
|
|
106
|
-
"benchmark",
|
|
107
|
-
"cancelBenchmark",
|
|
108
|
-
"datasetList",
|
|
109
|
-
"datasetCreate",
|
|
110
|
-
"datasetDelete",
|
|
111
|
-
"runQuery",
|
|
112
|
-
"suiteReference",
|
|
113
|
-
"suiteWrite",
|
|
114
|
-
"suiteFeatureMatrix",
|
|
115
|
-
"suiteLive",
|
|
116
|
-
].includes(String(value));
|
|
117
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minnowdb/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "A columnar SQL database for the browser: PostgreSQL-style SQL over durable IndexedDB or OPFS data, with no server or WebAssembly module.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Wilhite",
|