@minnowdb/core 0.4.1 → 0.5.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/catalog.d.ts +2 -0
- package/dist/engine/catalog.js +4 -1
- package/dist/engine/client.d.ts +3 -3
- package/dist/engine/client.js +6 -5
- package/dist/engine/database.d.ts +20 -6
- package/dist/engine/database.js +312 -95
- package/dist/engine/errors.d.ts +6 -0
- package/dist/engine/errors.js +9 -0
- package/dist/engine/live.js +10 -1
- package/dist/engine/optimizer.js +6 -1
- package/dist/engine/query-cache.js +1 -0
- package/dist/engine/query.d.ts +13 -2
- package/dist/engine/query.js +183 -43
- package/dist/engine/result-wire.d.ts +2 -0
- package/dist/engine/result-wire.js +21 -5
- package/dist/engine/schema-wire.d.ts +7 -0
- package/dist/engine/schema-wire.js +3 -1
- package/dist/engine/schema.d.ts +17 -0
- package/dist/engine/schema.js +42 -7
- package/dist/engine/sql-domains.d.ts +3 -0
- package/dist/engine/sql-domains.js +40 -1
- package/dist/engine/sql-semantics.js +21 -3
- package/dist/engine/vector.js +28 -41
- package/dist/storage/indexeddb.js +4 -12
- package/dist/storage/toolkit/record-core.js +7 -22
- package/dist/storage/types.d.ts +19 -8
- package/dist/storage/types.js +69 -0
- package/package.json +1 -1
- package/postgres-feature-profile.json +5 -0
- package/sql-feature-matrix.json +6 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
|
+
import { unknownColumnDomains } from "./query.js";
|
|
2
3
|
export function encodeQueryResult(result) {
|
|
3
|
-
return encodeRows(result.columns, result.rows);
|
|
4
|
+
return encodeRows(result.columns, result.rows, result.columnDomains);
|
|
4
5
|
}
|
|
5
6
|
/**
|
|
6
7
|
* Encodes a bare row array (`run()` returns rows without a column list). Every row of one result
|
|
@@ -8,13 +9,18 @@ export function encodeQueryResult(result) {
|
|
|
8
9
|
*/
|
|
9
10
|
export function encodeQueryRows(rows) {
|
|
10
11
|
const first = rows[0];
|
|
11
|
-
|
|
12
|
+
const columns = first === undefined ? [] : Object.keys(first);
|
|
13
|
+
return encodeRows(columns, rows, unknownColumnDomains(columns));
|
|
12
14
|
}
|
|
13
15
|
export function decodeQueryResult(payload) {
|
|
14
16
|
if (!isWireQueryResult(payload)) {
|
|
15
17
|
throw new TypeError("Expected a columnar query result frame");
|
|
16
18
|
}
|
|
17
19
|
const columns = [...payload.columns];
|
|
20
|
+
if (payload.values.length !== columns.length || payload.columnDomains.length !== columns.length) {
|
|
21
|
+
throw new TypeError("Columnar result frame metadata is not aligned");
|
|
22
|
+
}
|
|
23
|
+
const columnDomains = structuredClone(payload.columnDomains);
|
|
18
24
|
const rows = [];
|
|
19
25
|
for (let index = 0; index < payload.rowCount; index += 1)
|
|
20
26
|
rows.push({});
|
|
@@ -26,17 +32,21 @@ export function decodeQueryResult(payload) {
|
|
|
26
32
|
throw new TypeError("Columnar result frame is missing a column");
|
|
27
33
|
fillColumn(rows, name, column);
|
|
28
34
|
}
|
|
29
|
-
return { columns, rows };
|
|
35
|
+
return { columns, columnDomains, rows };
|
|
30
36
|
}
|
|
31
37
|
export function isWireQueryResult(value) {
|
|
32
38
|
return (typeof value === "object" &&
|
|
33
39
|
value !== null &&
|
|
34
40
|
value.kind === "columnar-result" &&
|
|
35
41
|
Array.isArray(value.columns) &&
|
|
42
|
+
Array.isArray(value.columnDomains) &&
|
|
36
43
|
Array.isArray(value.values) &&
|
|
37
44
|
typeof value.rowCount === "number");
|
|
38
45
|
}
|
|
39
|
-
function encodeRows(columns, rows) {
|
|
46
|
+
function encodeRows(columns, rows, columnDomains) {
|
|
47
|
+
if (columnDomains.length !== columns.length) {
|
|
48
|
+
throw new TypeError("Query result column domains must align with the result columns");
|
|
49
|
+
}
|
|
40
50
|
const transfer = [];
|
|
41
51
|
const values = columns.map((name) => {
|
|
42
52
|
const column = encodeColumn(name, rows);
|
|
@@ -53,7 +63,13 @@ function encodeRows(columns, rows) {
|
|
|
53
63
|
return column;
|
|
54
64
|
});
|
|
55
65
|
return {
|
|
56
|
-
payload: {
|
|
66
|
+
payload: {
|
|
67
|
+
kind: "columnar-result",
|
|
68
|
+
columns: [...columns],
|
|
69
|
+
columnDomains: structuredClone([...columnDomains]),
|
|
70
|
+
rowCount: rows.length,
|
|
71
|
+
values,
|
|
72
|
+
},
|
|
57
73
|
transfer,
|
|
58
74
|
};
|
|
59
75
|
}
|
|
@@ -17,6 +17,7 @@ export interface WireColumn {
|
|
|
17
17
|
table: string;
|
|
18
18
|
column: string;
|
|
19
19
|
onDelete: ReferentialAction;
|
|
20
|
+
enforced?: boolean;
|
|
20
21
|
};
|
|
21
22
|
readonly enumValues?: readonly string[];
|
|
22
23
|
/** Already frozen by planning, so the wire carries a value and never a generator. */
|
|
@@ -83,6 +84,12 @@ export type WireMigrationStep = {
|
|
|
83
84
|
tableName: string;
|
|
84
85
|
columnName: string;
|
|
85
86
|
defaultValue: ColumnDefault | null;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "alter-foreign-keys";
|
|
89
|
+
tableName: string;
|
|
90
|
+
foreignKeys: Extract<MigrationStep, {
|
|
91
|
+
kind: "alter-foreign-keys";
|
|
92
|
+
}>["foreignKeys"];
|
|
86
93
|
} | {
|
|
87
94
|
kind: "replace-view";
|
|
88
95
|
view: WireView;
|
|
@@ -96,7 +96,9 @@ function deserializeColumn(wire) {
|
|
|
96
96
|
...(wire.sqlDomain === undefined ? {} : { sqlDomain: structuredClone(wire.sqlDomain) }),
|
|
97
97
|
...(wire.defaultSpec === undefined ? {} : { defaultSpec: wire.defaultSpec }),
|
|
98
98
|
...(wire.renamedFromName === undefined ? {} : { renamedFromName: wire.renamedFromName }),
|
|
99
|
-
...(wire.reference === undefined
|
|
99
|
+
...(wire.reference === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { reference: { ...wire.reference, enforced: wire.reference.enforced !== false } }),
|
|
100
102
|
...(wire.enumValues === undefined ? {} : { enumValues: wire.enumValues }),
|
|
101
103
|
...(wire.backfillValue === undefined ? {} : { backfillValue: wire.backfillValue }),
|
|
102
104
|
});
|
package/dist/engine/schema.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface ColumnReferenceSpec {
|
|
|
16
16
|
readonly table: string;
|
|
17
17
|
readonly column: string;
|
|
18
18
|
readonly onDelete: ReferentialAction;
|
|
19
|
+
readonly enforced: boolean;
|
|
19
20
|
}
|
|
20
21
|
/** A row condition every write must satisfy (E141-06); `sql` is a boolean expression. */
|
|
21
22
|
export interface TableCheck {
|
|
@@ -31,6 +32,8 @@ export interface TableForeignKey<TColumnName extends string = string> {
|
|
|
31
32
|
readonly columns: readonly string[];
|
|
32
33
|
};
|
|
33
34
|
readonly onDelete?: ReferentialAction;
|
|
35
|
+
/** False keeps the relationship in the catalog without validating or cascading rows. */
|
|
36
|
+
readonly enforced?: boolean;
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
39
|
* The constraint name `migrate()` gives a declared relation. It matches the name the SQL parser
|
|
@@ -91,9 +94,14 @@ export interface ColumnBuilder<TValue extends SchemaValue, TNullable extends boo
|
|
|
91
94
|
*/
|
|
92
95
|
references(this: ColumnBuilder<TValue, TNullable, TUnique, THasDefault, TInput>, table: string, column: string, options?: {
|
|
93
96
|
onDelete?: Exclude<ReferentialAction, "set null">;
|
|
97
|
+
enforced?: true;
|
|
98
|
+
} | {
|
|
99
|
+
enforced: false;
|
|
100
|
+
onDelete?: never;
|
|
94
101
|
}): ColumnBuilder<TValue, TNullable, TUnique, THasDefault, TInput>;
|
|
95
102
|
references(this: ColumnBuilder<TValue, true, TUnique, THasDefault, TInput>, table: string, column: string, options: {
|
|
96
103
|
onDelete: "set null";
|
|
104
|
+
enforced?: true;
|
|
97
105
|
}): ColumnBuilder<TValue, TNullable, TUnique, THasDefault, TInput>;
|
|
98
106
|
/** Declares a literal SQL default. Omission or SQL `DEFAULT` invokes it; NULL does not. */
|
|
99
107
|
default(value: TInput): ColumnBuilder<TValue, TNullable, TUnique, true, TInput>;
|
|
@@ -152,6 +160,8 @@ export declare const column: {
|
|
|
152
160
|
json: () => ColumnBuilder<string, false, false, false, string>;
|
|
153
161
|
jsonb: () => ColumnBuilder<string, false, false, false, string>;
|
|
154
162
|
uuid: () => ColumnBuilder<string, false, false, false, string>;
|
|
163
|
+
/** A zoneless calendar date, represented publicly as canonical `YYYY-MM-DD` text. */
|
|
164
|
+
date: () => ColumnBuilder<string, false, false, false, string>;
|
|
155
165
|
time: () => ColumnBuilder<string, false, false, false, string>;
|
|
156
166
|
interval: () => ColumnBuilder<string, false, false, false, string>;
|
|
157
167
|
/** JSON array text at the JavaScript boundary, with the SQL element type retained in metadata. */
|
|
@@ -234,6 +244,7 @@ export declare function declaredForeignKeys(definition: AnyTable): Array<{
|
|
|
234
244
|
parentTable: string;
|
|
235
245
|
parentColumns: string[];
|
|
236
246
|
onDelete: ReferentialAction;
|
|
247
|
+
enforced: boolean;
|
|
237
248
|
}>;
|
|
238
249
|
/**
|
|
239
250
|
* A view declared in the schema: the query it stands for, plus the column shape the author
|
|
@@ -366,6 +377,12 @@ export type MigrationStep = {
|
|
|
366
377
|
columnName: string;
|
|
367
378
|
defaultValue: ColumnDefault | null;
|
|
368
379
|
}
|
|
380
|
+
/** Informational relationships are catalog-only and may change without scanning stored rows. */
|
|
381
|
+
| {
|
|
382
|
+
kind: "alter-foreign-keys";
|
|
383
|
+
tableName: string;
|
|
384
|
+
foreignKeys: ReturnType<typeof declaredForeignKeys>;
|
|
385
|
+
}
|
|
369
386
|
/**
|
|
370
387
|
* A view is derived and disposable: nothing is stored under it, so replacing its body loses no
|
|
371
388
|
* data and needs none of the proofs a table alteration needs. `replace` covers both creating a
|
package/dist/engine/schema.js
CHANGED
|
@@ -71,12 +71,16 @@ function createColumn(type, state = {}) {
|
|
|
71
71
|
references: (table, referencedColumn, options = {}) => {
|
|
72
72
|
validateSchemaName(table, "Referenced table");
|
|
73
73
|
validateSchemaName(referencedColumn, "Referenced column");
|
|
74
|
+
if (options.enforced === false && options.onDelete !== undefined) {
|
|
75
|
+
throw new TypeError("An informational FOREIGN KEY cannot declare ON DELETE behavior");
|
|
76
|
+
}
|
|
74
77
|
return createColumn(type, {
|
|
75
78
|
...state,
|
|
76
79
|
reference: {
|
|
77
80
|
table,
|
|
78
81
|
column: referencedColumn,
|
|
79
82
|
onDelete: options.onDelete ?? "restrict",
|
|
83
|
+
enforced: options.enforced !== false,
|
|
80
84
|
},
|
|
81
85
|
});
|
|
82
86
|
},
|
|
@@ -160,6 +164,8 @@ export const column = {
|
|
|
160
164
|
json: () => createColumn("string", { sqlDomain: { kind: "json" } }),
|
|
161
165
|
jsonb: () => createColumn("string", { sqlDomain: { kind: "jsonb" } }),
|
|
162
166
|
uuid: () => createColumn("string", { sqlDomain: { kind: "uuid" } }),
|
|
167
|
+
/** A zoneless calendar date, represented publicly as canonical `YYYY-MM-DD` text. */
|
|
168
|
+
date: () => createColumn("string", { sqlDomain: { kind: "date" } }),
|
|
163
169
|
time: () => createColumn("string", { sqlDomain: { kind: "time" } }),
|
|
164
170
|
interval: () => createColumn("string", { sqlDomain: { kind: "interval" } }),
|
|
165
171
|
/** JSON array text at the JavaScript boundary, with the SQL element type retained in metadata. */
|
|
@@ -301,6 +307,9 @@ export function table(name, columns, options = {}) {
|
|
|
301
307
|
const foreignKeyNames = new Set();
|
|
302
308
|
for (const key of foreignKeys) {
|
|
303
309
|
validateSchemaName(key.name, "FOREIGN KEY");
|
|
310
|
+
if (key.enforced === false && key.onDelete !== undefined) {
|
|
311
|
+
throw new TypeError(`Informational FOREIGN KEY ${key.name} cannot declare ON DELETE behavior`);
|
|
312
|
+
}
|
|
304
313
|
if (foreignKeyNames.has(key.name)) {
|
|
305
314
|
throw new TypeError(`Duplicate FOREIGN KEY in table ${name}: ${key.name}`);
|
|
306
315
|
}
|
|
@@ -431,6 +440,7 @@ export function declaredForeignKeys(definition) {
|
|
|
431
440
|
parentTable: reference.table,
|
|
432
441
|
parentColumns: [reference.column],
|
|
433
442
|
onDelete: reference.onDelete,
|
|
443
|
+
enforced: reference.enforced,
|
|
434
444
|
});
|
|
435
445
|
}
|
|
436
446
|
for (const key of definition.foreignKeys) {
|
|
@@ -442,6 +452,7 @@ export function declaredForeignKeys(definition) {
|
|
|
442
452
|
parentTable: key.references.table,
|
|
443
453
|
parentColumns,
|
|
444
454
|
onDelete: key.onDelete ?? "restrict",
|
|
455
|
+
enforced: key.enforced !== false,
|
|
445
456
|
});
|
|
446
457
|
}
|
|
447
458
|
return keys;
|
|
@@ -675,34 +686,58 @@ function backfillsEqual(left, right) {
|
|
|
675
686
|
return externalLeft === externalRight;
|
|
676
687
|
}
|
|
677
688
|
/**
|
|
678
|
-
*
|
|
679
|
-
* FOREIGN KEY or CHECK to a table that already holds rows would claim something about those rows
|
|
689
|
+
* Enforced constraints on an existing table cannot change through a metadata-only step. Attaching
|
|
690
|
+
* a FOREIGN KEY or CHECK to a table that already holds rows would claim something about those rows
|
|
680
691
|
* that nobody has verified, and no validation scan exists; dropping one is refused for the mirror
|
|
681
|
-
* reason, so that a constraint never disappears because a schema file drifted.
|
|
682
|
-
*
|
|
692
|
+
* reason, so that a constraint never disappears because a schema file drifted. Informational
|
|
693
|
+
* foreign keys are catalog metadata only, so adding, dropping, or remapping one is safe.
|
|
683
694
|
*/
|
|
684
|
-
function
|
|
695
|
+
function planConstraintChanges(record, definition, steps) {
|
|
685
696
|
const describeKey = (key) => `${key.columns.join(",")} -> ${key.parentTable}.` +
|
|
686
697
|
`${key.parentColumns.join(",")} ON DELETE ${key.onDelete}`;
|
|
698
|
+
const declaredList = declaredForeignKeys(definition);
|
|
687
699
|
const existingKeys = new Map(record.foreignKeys.map((key) => [key.name, key]));
|
|
688
|
-
const declaredKeys = new Map(
|
|
700
|
+
const declaredKeys = new Map(declaredList.map((key) => [key.name, key]));
|
|
701
|
+
let informationalChanged = false;
|
|
689
702
|
for (const [name, declared] of declaredKeys) {
|
|
690
703
|
const existing = existingKeys.get(name);
|
|
691
704
|
if (existing === undefined) {
|
|
705
|
+
if (!declared.enforced) {
|
|
706
|
+
informationalChanged = true;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
692
709
|
throw new TypeError(`FOREIGN KEY cannot be added after creation: ${definition.name}.${declared.columns.join(",")}. ` +
|
|
693
710
|
`Existing rows are not known to satisfy it; recreate the table to add a relation.`);
|
|
694
711
|
}
|
|
712
|
+
if (existing.enforced !== declared.enforced) {
|
|
713
|
+
throw new TypeError(`FOREIGN KEY enforcement cannot change: ${definition.name}.${declared.columns.join(",")}`);
|
|
714
|
+
}
|
|
695
715
|
if (describeKey(existing) !== describeKey(declared)) {
|
|
716
|
+
if (!declared.enforced) {
|
|
717
|
+
informationalChanged = true;
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
696
720
|
throw new TypeError(`FOREIGN KEY cannot change: ${definition.name}.${declared.columns.join(",")} is ` +
|
|
697
721
|
`${describeKey(existing)}, schema says ${describeKey(declared)}`);
|
|
698
722
|
}
|
|
699
723
|
}
|
|
700
724
|
for (const name of existingKeys.keys()) {
|
|
701
725
|
if (!declaredKeys.has(name)) {
|
|
726
|
+
if (existingKeys.get(name)?.enforced === false) {
|
|
727
|
+
informationalChanged = true;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
702
730
|
throw new TypeError(`FOREIGN KEY cannot be dropped: ${definition.name} still has ${name}. ` +
|
|
703
731
|
`Declare the relation, or recreate the table without it.`);
|
|
704
732
|
}
|
|
705
733
|
}
|
|
734
|
+
if (informationalChanged) {
|
|
735
|
+
steps.push({
|
|
736
|
+
kind: "alter-foreign-keys",
|
|
737
|
+
tableName: definition.name,
|
|
738
|
+
foreignKeys: declaredList,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
706
741
|
const existingChecks = new Map(record.checks.map((check) => [check.name, check.sql]));
|
|
707
742
|
const declaredChecks = new Map(definition.checks.map((check) => [check.name, check.sql]));
|
|
708
743
|
for (const [name, sql] of declaredChecks) {
|
|
@@ -889,7 +924,7 @@ export function planMigration(catalog, definition, options = {}) {
|
|
|
889
924
|
desiredKeyIds.some((id, index) => id !== existingKeyIds[index])) {
|
|
890
925
|
throw new TypeError(`Primary key order cannot change: ${tableDefinition.name}`);
|
|
891
926
|
}
|
|
892
|
-
|
|
927
|
+
planConstraintChanges(record, tableDefinition, steps);
|
|
893
928
|
}
|
|
894
929
|
if (options.schemaOwnsDatabase === true) {
|
|
895
930
|
const declaredTables = new Set(definition.tables.map(({ name }) => name));
|
|
@@ -19,6 +19,9 @@ export declare function exactNumericCompare(left: unknown, right: unknown): numb
|
|
|
19
19
|
export declare function boundedJsonText(value: unknown, canonical: boolean, label?: string): string;
|
|
20
20
|
export declare function jsonDomainValue(value: unknown, binary: boolean): string | null;
|
|
21
21
|
export declare function uuidDomainValue(value: unknown): string | null;
|
|
22
|
+
/** Canonical, zoneless SQL DATE value. No JavaScript time zone participates in validation. */
|
|
23
|
+
export declare function dateDomainValue(value: unknown): string | null;
|
|
24
|
+
export declare function isDateDomainValue(value: unknown): value is string;
|
|
22
25
|
export declare function timeDomainValue(value: unknown): string | null;
|
|
23
26
|
export declare function intervalDomainValue(value: unknown): string | null;
|
|
24
27
|
export declare function arrayDomainValue(values: readonly unknown[]): string;
|
|
@@ -8,6 +8,7 @@ const NUMERIC = `${PREFIX}numeric:`;
|
|
|
8
8
|
const JSON_VALUE = `${PREFIX}json:`;
|
|
9
9
|
const JSONB_VALUE = `${PREFIX}jsonb:`;
|
|
10
10
|
const UUID_VALUE = `${PREFIX}uuid:`;
|
|
11
|
+
const DATE_VALUE = `${PREFIX}date:`;
|
|
11
12
|
const TIME_VALUE = `${PREFIX}time:`;
|
|
12
13
|
const INTERVAL_VALUE = `${PREFIX}interval:`;
|
|
13
14
|
const ARRAY_VALUE = `${PREFIX}array:`;
|
|
@@ -324,6 +325,42 @@ export function uuidDomainValue(value) {
|
|
|
324
325
|
}
|
|
325
326
|
return UUID_VALUE + source;
|
|
326
327
|
}
|
|
328
|
+
/** Canonical, zoneless SQL DATE value. No JavaScript time zone participates in validation. */
|
|
329
|
+
export function dateDomainValue(value) {
|
|
330
|
+
if (value === null || value === undefined)
|
|
331
|
+
return null;
|
|
332
|
+
let source;
|
|
333
|
+
if (value instanceof Date) {
|
|
334
|
+
if (!Number.isFinite(value.getTime()))
|
|
335
|
+
throw new TypeError("DATE accepts a valid Date value");
|
|
336
|
+
source = dateIsoString(value).slice(0, 10);
|
|
337
|
+
}
|
|
338
|
+
else if (typeof value === "string") {
|
|
339
|
+
assertBoundedDomainString(value, "DATE value");
|
|
340
|
+
source = value.startsWith(DATE_VALUE) ? value.slice(DATE_VALUE.length) : value;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
throw new TypeError("DATE accepts a YYYY-MM-DD string value");
|
|
344
|
+
}
|
|
345
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(source);
|
|
346
|
+
const year = Number(match?.[1]);
|
|
347
|
+
const month = Number(match?.[2]);
|
|
348
|
+
const day = Number(match?.[3]);
|
|
349
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
350
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
351
|
+
if (match === null ||
|
|
352
|
+
year < 1 ||
|
|
353
|
+
month < 1 ||
|
|
354
|
+
month > 12 ||
|
|
355
|
+
day < 1 ||
|
|
356
|
+
day > (days[month - 1] ?? 0)) {
|
|
357
|
+
throw new TypeError(`Invalid DATE value: ${String(value)}`);
|
|
358
|
+
}
|
|
359
|
+
return DATE_VALUE + source;
|
|
360
|
+
}
|
|
361
|
+
export function isDateDomainValue(value) {
|
|
362
|
+
return typeof value === "string" && value.startsWith(DATE_VALUE);
|
|
363
|
+
}
|
|
327
364
|
export function timeDomainValue(value) {
|
|
328
365
|
if (value === null || value === undefined)
|
|
329
366
|
return null;
|
|
@@ -464,6 +501,8 @@ export function normalizeSqlDomainValue(domain, value) {
|
|
|
464
501
|
return jsonDomainValue(value, true);
|
|
465
502
|
if (domain.kind === "uuid")
|
|
466
503
|
return uuidDomainValue(value);
|
|
504
|
+
if (domain.kind === "date")
|
|
505
|
+
return dateDomainValue(value);
|
|
467
506
|
if (domain.kind === "time")
|
|
468
507
|
return timeDomainValue(value);
|
|
469
508
|
if (domain.kind === "interval")
|
|
@@ -562,7 +601,7 @@ export function externalSqlDomainValue(value) {
|
|
|
562
601
|
return value;
|
|
563
602
|
if (value.startsWith(TEXT_VALUE))
|
|
564
603
|
return value.slice(TEXT_VALUE.length);
|
|
565
|
-
for (const prefix of [NUMERIC, JSON_VALUE, JSONB_VALUE, UUID_VALUE, TIME_VALUE]) {
|
|
604
|
+
for (const prefix of [NUMERIC, JSON_VALUE, JSONB_VALUE, UUID_VALUE, DATE_VALUE, TIME_VALUE]) {
|
|
566
605
|
if (value.startsWith(prefix))
|
|
567
606
|
return value.slice(prefix.length);
|
|
568
607
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
2
|
import { assertWellFormedString } from "../block-format/unicode.js";
|
|
3
3
|
import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS, } from "./cache-limits.js";
|
|
4
|
-
import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, } from "./sql-domains.js";
|
|
4
|
+
import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, isDateDomainValue, } from "./sql-domains.js";
|
|
5
5
|
/**
|
|
6
6
|
* SQL value semantics shared by the row and vector executors.
|
|
7
7
|
*
|
|
@@ -31,8 +31,26 @@ export function compareSqlValues(left, right) {
|
|
|
31
31
|
const exact = exactNumericCompare(left, right);
|
|
32
32
|
if (exact !== undefined)
|
|
33
33
|
return exact;
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const temporal = (value) => {
|
|
35
|
+
if (value instanceof Date)
|
|
36
|
+
return dateMilliseconds(value);
|
|
37
|
+
if (!isDateDomainValue(value))
|
|
38
|
+
return undefined;
|
|
39
|
+
const external = externalSqlDomainValue(value);
|
|
40
|
+
return typeof external === "string" ? Date.parse(`${external}T00:00:00.000Z`) : undefined;
|
|
41
|
+
};
|
|
42
|
+
const temporalLeft = temporal(left);
|
|
43
|
+
const temporalRight = temporal(right);
|
|
44
|
+
if (temporalLeft !== undefined || temporalRight !== undefined) {
|
|
45
|
+
const comparableLeft = temporalLeft ?? (typeof left === "number" ? left : undefined);
|
|
46
|
+
const comparableRight = temporalRight ?? (typeof right === "number" ? right : undefined);
|
|
47
|
+
if (comparableLeft === undefined || comparableRight === undefined) {
|
|
48
|
+
throw new TypeError("Values must have comparable SQL types");
|
|
49
|
+
}
|
|
50
|
+
return comparableLeft - comparableRight;
|
|
51
|
+
}
|
|
52
|
+
const a = left;
|
|
53
|
+
const b = right;
|
|
36
54
|
if (a === b)
|
|
37
55
|
return 0;
|
|
38
56
|
if (a === null || a === undefined)
|
package/dist/engine/vector.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
2
|
import { MAX_TEMP_RUN_BATCH_BYTES, MAX_TEMP_RUN_PAGE_BYTES, MAX_TEMP_RUN_PAGES_PER_BATCH, } from "../storage/types.js";
|
|
3
|
-
import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, } from "./query.js";
|
|
3
|
+
import { cachedListMembership, childExpressions, distinctFromComparison, nullOrder, isScalarFunctionName, likeMatches, orderOutputName, parseQuantified, quantifiedComparison, scalarFunctionValue, unknownColumnDomains, } from "./query.js";
|
|
4
4
|
import { jsonValueOf } from "./sql-json.js";
|
|
5
5
|
import { bm25DocumentScore, cachedQueryTerms, FtsStatsAccumulator, fullTermsMask, renderDocumentValue, termFrequencies, termsMask, tokenize, } from "./fts.js";
|
|
6
6
|
import { ByteGroupIndex } from "./group-index.js";
|
|
7
7
|
import { ByteJoinIndex } from "./join-index.js";
|
|
8
|
+
import { UnknownTableError } from "./errors.js";
|
|
8
9
|
import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
|
|
9
|
-
import {
|
|
10
|
-
import { exactNumericBinary,
|
|
10
|
+
import { compareSqlValues, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
|
|
11
|
+
import { exactNumericBinary, externalSqlDomainValue, isDateDomainValue, isExactNumeric, protectedSqlTextValue, } from "./sql-domains.js";
|
|
11
12
|
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
12
13
|
const DEFAULT_BATCH_ROWS = 2_048;
|
|
13
14
|
/** Above this, locating each IN member separately costs more than scanning between them. */
|
|
@@ -559,7 +560,7 @@ function bindPlan(plan, tables, memory, ftsStats) {
|
|
|
559
560
|
const sourceTables = sources.map((source) => {
|
|
560
561
|
const table = tables.get(source.table);
|
|
561
562
|
if (table === undefined)
|
|
562
|
-
throw new
|
|
563
|
+
throw new UnknownTableError(source.table);
|
|
563
564
|
return table;
|
|
564
565
|
});
|
|
565
566
|
const sourceAliases = sources.map((source) => source.alias);
|
|
@@ -1304,7 +1305,11 @@ async function executeBoundPlanBatches(plan, memory, options, consume) {
|
|
|
1304
1305
|
}
|
|
1305
1306
|
if (rows.length > 0) {
|
|
1306
1307
|
emitted += rows.length;
|
|
1307
|
-
await consume({
|
|
1308
|
+
await consume({
|
|
1309
|
+
columns: [...columns],
|
|
1310
|
+
columnDomains: unknownColumnDomains(columns),
|
|
1311
|
+
rows,
|
|
1312
|
+
});
|
|
1308
1313
|
}
|
|
1309
1314
|
}
|
|
1310
1315
|
finally {
|
|
@@ -1374,7 +1379,7 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1374
1379
|
start += length;
|
|
1375
1380
|
}
|
|
1376
1381
|
if (runs.length === 0)
|
|
1377
|
-
return { columns, rows: [] };
|
|
1382
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows: [] };
|
|
1378
1383
|
let active = runs;
|
|
1379
1384
|
while (active.length > 1) {
|
|
1380
1385
|
const merged = [];
|
|
@@ -1408,7 +1413,7 @@ async function executeBoundPlanWithSortSpill(plan, memory, options) {
|
|
|
1408
1413
|
}
|
|
1409
1414
|
if (offset > 0)
|
|
1410
1415
|
rows.splice(0, Math.min(offset, rows.length));
|
|
1411
|
-
return { columns, rows };
|
|
1416
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows };
|
|
1412
1417
|
}
|
|
1413
1418
|
finally {
|
|
1414
1419
|
await store.removeOwner(ownerId);
|
|
@@ -1563,7 +1568,7 @@ async function executeBoundPlanWithHashSpill(plan, memory, options) {
|
|
|
1563
1568
|
}
|
|
1564
1569
|
}
|
|
1565
1570
|
if (runs.length === 0)
|
|
1566
|
-
return { columns, rows: [] };
|
|
1571
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows: [] };
|
|
1567
1572
|
const finalRun = await mergeAllSpillRuns(store, ownerId, runs, columns, plan.orderBy, pageRows, () => `merge-${String(runSequence++)}`, memory);
|
|
1568
1573
|
const spillOffset = plan.offset ?? 0;
|
|
1569
1574
|
const result = await readFinalSpillRun(store, ownerId, finalRun, columns, plan.limit === undefined ? undefined : plan.limit + spillOffset);
|
|
@@ -1609,7 +1614,7 @@ async function readFinalSpillRun(store, ownerId, run, columns, requestedLimit) {
|
|
|
1609
1614
|
rows.push(row);
|
|
1610
1615
|
}
|
|
1611
1616
|
}
|
|
1612
|
-
return { columns: [...columns], rows };
|
|
1617
|
+
return { columns: [...columns], columnDomains: unknownColumnDomains(columns), rows };
|
|
1613
1618
|
}
|
|
1614
1619
|
const hashScratch = new DataView(new ArrayBuffer(8));
|
|
1615
1620
|
/**
|
|
@@ -1909,7 +1914,7 @@ function finishResult(plan, inputRows, memory) {
|
|
|
1909
1914
|
rows.length = Math.min(plan.limit, rows.length);
|
|
1910
1915
|
}
|
|
1911
1916
|
const columns = plan.wildcard ? wildcardColumnNames(plan) : plan.select.map((item) => item.alias);
|
|
1912
|
-
return { columns, rows };
|
|
1917
|
+
return { columns, columnDomains: unknownColumnDomains(columns), rows };
|
|
1913
1918
|
}
|
|
1914
1919
|
/** Source indexes a bound expression reads, for pre-join predicate placement. */
|
|
1915
1920
|
function prefilterSources(expression, into) {
|
|
@@ -3883,6 +3888,8 @@ function projectBatchRow(plan, batch, row) {
|
|
|
3883
3888
|
const rowIndex = batch.rowsBySource[source]?.[row] ?? -1;
|
|
3884
3889
|
const prefix = multiple ? `${plan.sourceAliases[source] ?? ""}.` : "";
|
|
3885
3890
|
for (const [name, vector] of table.columns) {
|
|
3891
|
+
if (name.startsWith("\0"))
|
|
3892
|
+
continue;
|
|
3886
3893
|
const outputName = multiple ? prefix + name : name;
|
|
3887
3894
|
const value = vectorValue(vector, rowIndex);
|
|
3888
3895
|
if (outputName === "__proto__")
|
|
@@ -3895,7 +3902,9 @@ function projectBatchRow(plan, batch, row) {
|
|
|
3895
3902
|
}
|
|
3896
3903
|
function wildcardColumnNames(plan) {
|
|
3897
3904
|
const multiple = plan.sourceTables.length > 1;
|
|
3898
|
-
return plan.sourceTables.flatMap((table, source) => [...table.columns.keys()]
|
|
3905
|
+
return plan.sourceTables.flatMap((table, source) => [...table.columns.keys()]
|
|
3906
|
+
.filter((name) => !name.startsWith("\0"))
|
|
3907
|
+
.map((name) => (multiple ? `${plan.sourceAliases[source] ?? ""}.${name}` : name)));
|
|
3899
3908
|
}
|
|
3900
3909
|
/** Detects `stringColumn = 'literal'` (or !=) so batches compare dictionary codes per row. */
|
|
3901
3910
|
function detectDictionaryEquality(predicate) {
|
|
@@ -4506,7 +4515,13 @@ function comparisonValue(operator, leftValue, rightValue) {
|
|
|
4506
4515
|
return comparison <= 0;
|
|
4507
4516
|
}
|
|
4508
4517
|
function comparable(value) {
|
|
4509
|
-
|
|
4518
|
+
if (value instanceof Date)
|
|
4519
|
+
return dateMilliseconds(value);
|
|
4520
|
+
if (isDateDomainValue(value)) {
|
|
4521
|
+
const external = externalSqlDomainValue(value);
|
|
4522
|
+
return typeof external === "string" ? Date.parse(`${external}T00:00:00.000Z`) : value;
|
|
4523
|
+
}
|
|
4524
|
+
return value;
|
|
4510
4525
|
}
|
|
4511
4526
|
function groupKey(value) {
|
|
4512
4527
|
const comparableValue = comparable(value);
|
|
@@ -4547,35 +4562,7 @@ function stableSortRows(rows, orderBy) {
|
|
|
4547
4562
|
}
|
|
4548
4563
|
}
|
|
4549
4564
|
function compareValues(left, right) {
|
|
4550
|
-
|
|
4551
|
-
if (collated !== undefined)
|
|
4552
|
-
return collated;
|
|
4553
|
-
const enumOrder = enumDomainCompare(left, right);
|
|
4554
|
-
if (enumOrder !== undefined)
|
|
4555
|
-
return enumOrder;
|
|
4556
|
-
const exact = exactNumericCompare(left, right);
|
|
4557
|
-
if (exact !== undefined)
|
|
4558
|
-
return exact;
|
|
4559
|
-
const a = left instanceof Date ? dateMilliseconds(left) : left;
|
|
4560
|
-
const b = right instanceof Date ? dateMilliseconds(right) : right;
|
|
4561
|
-
if (a === b)
|
|
4562
|
-
return 0;
|
|
4563
|
-
if (a === null || a === undefined)
|
|
4564
|
-
return -1;
|
|
4565
|
-
if (b === null || b === undefined)
|
|
4566
|
-
return 1;
|
|
4567
|
-
if (typeof a === "number" && typeof b === "number") {
|
|
4568
|
-
if (Number.isNaN(a))
|
|
4569
|
-
return Number.isNaN(b) ? 0 : 1;
|
|
4570
|
-
if (Number.isNaN(b))
|
|
4571
|
-
return -1;
|
|
4572
|
-
return a - b;
|
|
4573
|
-
}
|
|
4574
|
-
if (typeof a === "string" && typeof b === "string")
|
|
4575
|
-
return compareSqlStrings(a, b);
|
|
4576
|
-
if (typeof a === "boolean" && typeof b === "boolean")
|
|
4577
|
-
return Number(a) - Number(b);
|
|
4578
|
-
throw new TypeError("Values must have comparable SQL types");
|
|
4565
|
+
return compareSqlValues(left, right);
|
|
4579
4566
|
}
|
|
4580
4567
|
function numeric(value) {
|
|
4581
4568
|
if (typeof value !== "number")
|
|
@@ -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) {
|