@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,4 +1,4 @@
|
|
|
1
|
-
import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
|
|
1
|
+
import { CompactionBacklogError, CompactionJobConflictError, assertCompactionOutputProvenance, compactionOutputSegmentIds, createManifest, createGarbageCollectionJobRecord, advanceGarbageCollectionJobRecord, collectFtsCandidates, collectFtsPostingsBounded, activePostingStorageColumnIds, invalidateUncoveredFtsColumns, invalidateUncoveredSecondaryIndexes, GarbageCollectionJobConflictError, LeaseConflictError, LeaseExpiredError, LeaseOwnerConflictError, MAX_LEASE_TTL_MS, MAX_ACTIVE_LEASES, MAX_ACTIVE_TEMP_OWNERS, MAX_ACTIVE_COMPACTION_JOBS, MAX_ACTIVE_UNIQUE_KEY_BUILDS, MAX_ACTIVE_TRANSACTIONS, MAX_TERMINAL_TRANSACTION_RECORDS, MAX_TERMINAL_COMPACTION_JOB_RECORDS, MAX_COMPLETED_GARBAGE_COLLECTION_JOB_RECORDS, MAX_PINNED_MANIFEST_VERSION_LAG, MAX_PINNED_RETIRED_BLOCKS, MAX_PINNED_RETIRED_BYTES, MAX_RETIRED_HISTORY_BYTES, MAX_GLOBAL_STAGED_ARTIFACT_BYTES, MAX_GLOBAL_STAGED_BLOCKS, MAX_GLOBAL_STAGED_SEGMENTS, MAX_TEMP_OWNER_TTL_MS, MAX_ROW_ID, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_ID_CHARACTERS, MAX_CATALOG_RECORDS, MAX_CATALOG_RETAINED_BYTES, MAX_MANIFEST_RECORDS, MAX_MANIFEST_RETAINED_BYTES, MAX_SEGMENT_RECORDS, MAX_SEGMENT_RETAINED_BYTES, MAX_FTS_CANDIDATE_ROW_IDS, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_TOKENS_PER_DOCUMENT, MAX_FTS_BASE_CHUNKS, MAX_FTS_DELTA_CHUNKS, SnapshotManifestMissingError, StorageResourceLimitError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, UniqueKeyBuildConflictError, UniqueIndexCoverageError, assertTransactionArtifactBatchLimits, assertTransactionArtifactJournalLimits, assertTempRunPageBatchLimits, assertStorageBulkReadItems, boundedMaintenanceBatchItems, normalizeCompactionJobRecord, normalizeGarbageCollectionJobRecord, normalizeSegmentRecord, updateCompactionJobRecord, updateTransactionRecord, updateGarbageCollectionPlanningRecord, validateTableColumns, validateTableForeignKey, validateSecondaryIndexes, secondaryIndexColumnIds, secondaryIndexWriteContractChanged, secondaryUniqueKeyNamespace, transactionCommitDeltaRetainedBytes, uniqueKeyBuildChunkRetainedBytes, MAX_UNIQUE_KEY_BUILD_TTL_MS, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES, MAX_UNIQUE_KEY_BUILD_STAGED_BYTES_TOTAL, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, validateFtsPostingQueries, validateCanonicalManifestChangedTableIds, catalogRecordRetainedBytes, manifestRecordRetainedReservationBytes, segmentRecordRetainedBytes, snapshotAcceleratorItemRetainedUsage, assertSnapshotImportAcceleratorUsage, SchemaConflictError, WriteConflictError, } from "../types.js";
|
|
2
2
|
import { dateIsoString } from "../../date-value.js";
|
|
3
3
|
import { assertWellFormedString, crc32, MAX_STORED_BLOCK_BYTE_LENGTH, } from "../../block-format/index.js";
|
|
4
4
|
const postingTextEncoder = new TextEncoder();
|
|
@@ -1300,16 +1300,7 @@ export class RecordCore {
|
|
|
1300
1300
|
if (parent === undefined) {
|
|
1301
1301
|
throw new TypeError(`FOREIGN KEY ${key.name} references a missing table: ${key.parentTable}`);
|
|
1302
1302
|
}
|
|
1303
|
-
|
|
1304
|
-
? parent.primaryKeyColumnIds
|
|
1305
|
-
: parent.uniqueKeyColumnId === undefined
|
|
1306
|
-
? []
|
|
1307
|
-
: [parent.uniqueKeyColumnId];
|
|
1308
|
-
const addressNames = addressIds.map((id) => parent.columns.find((column) => column.id === id)?.name ?? "");
|
|
1309
|
-
if (addressNames.length !== key.parentColumns.length ||
|
|
1310
|
-
addressNames.some((name, index) => name !== key.parentColumns[index])) {
|
|
1311
|
-
throw new TypeError(`FOREIGN KEY ${key.name} must reference the parent primary or unique key`);
|
|
1312
|
-
}
|
|
1303
|
+
validateTableForeignKey(record, key, parent);
|
|
1313
1304
|
}
|
|
1314
1305
|
}
|
|
1315
1306
|
getTable(id) {
|
|
@@ -1342,7 +1333,7 @@ export class RecordCore {
|
|
|
1342
1333
|
}
|
|
1343
1334
|
if (update.columns !== undefined)
|
|
1344
1335
|
validateTableColumns(update.columns);
|
|
1345
|
-
const { ftsColumns: previousFts, secondaryIndexes: previousSecondary, triggers: previousTriggers, view: previousView, ...base } = record;
|
|
1336
|
+
const { ftsColumns: previousFts, secondaryIndexes: previousSecondary, triggers: previousTriggers, view: previousView, foreignKeys: previousForeignKeys, ...base } = record;
|
|
1346
1337
|
let nextFts = update.ftsColumns === undefined ? previousFts : update.ftsColumns;
|
|
1347
1338
|
let nextSecondary = update.secondaryIndexes === undefined ? previousSecondary : update.secondaryIndexes;
|
|
1348
1339
|
const retainedColumnIds = update.columns === undefined
|
|
@@ -1373,6 +1364,7 @@ export class RecordCore {
|
|
|
1373
1364
|
}
|
|
1374
1365
|
const nextTriggers = update.triggers === undefined ? previousTriggers : update.triggers;
|
|
1375
1366
|
const nextView = update.view === undefined ? previousView : update.view;
|
|
1367
|
+
const nextForeignKeys = update.foreignKeys ?? previousForeignKeys;
|
|
1376
1368
|
validateTableView(nextView ?? undefined);
|
|
1377
1369
|
const updated = {
|
|
1378
1370
|
...base,
|
|
@@ -1386,10 +1378,12 @@ export class RecordCore {
|
|
|
1386
1378
|
...(nextTriggers === null || nextTriggers === undefined
|
|
1387
1379
|
? {}
|
|
1388
1380
|
: { triggers: structuredClone(nextTriggers) }),
|
|
1381
|
+
...(nextForeignKeys === undefined ? {} : { foreignKeys: structuredClone(nextForeignKeys) }),
|
|
1389
1382
|
...(nextView === null || nextView === undefined ? {} : { view: structuredClone(nextView) }),
|
|
1390
1383
|
revision: safeWholeIncrement(expectedRevision, "Table revision"),
|
|
1391
1384
|
};
|
|
1392
1385
|
validateSecondaryIndexes(updated);
|
|
1386
|
+
this.#assertTableForeignKeys(updated);
|
|
1393
1387
|
this.#assertTableTriggerOwnership(updated);
|
|
1394
1388
|
let autoIncrementCounter;
|
|
1395
1389
|
if (update.autoIncrementSeed !== undefined) {
|
|
@@ -5552,16 +5546,7 @@ function validateRecordCoreState(state, physical) {
|
|
|
5552
5546
|
if (parent === undefined) {
|
|
5553
5547
|
throw new TypeError(`FOREIGN KEY ${key.name} references a missing table: ${key.parentTable}`);
|
|
5554
5548
|
}
|
|
5555
|
-
|
|
5556
|
-
? parent.primaryKeyColumnIds
|
|
5557
|
-
: parent.uniqueKeyColumnId === undefined
|
|
5558
|
-
? []
|
|
5559
|
-
: [parent.uniqueKeyColumnId];
|
|
5560
|
-
const addressNames = addressIds.map((id) => parent.columns.find((column) => column.id === id)?.name ?? "");
|
|
5561
|
-
if (addressNames.length !== key.parentColumns.length ||
|
|
5562
|
-
addressNames.some((name, index) => name !== key.parentColumns[index])) {
|
|
5563
|
-
throw new TypeError(`FOREIGN KEY ${key.name} must reference the parent primary or unique key`);
|
|
5564
|
-
}
|
|
5549
|
+
validateTableForeignKey(table, key, parent);
|
|
5565
5550
|
}
|
|
5566
5551
|
}
|
|
5567
5552
|
const terminalTransactionCount = state.transactions.filter((transaction) => transaction.status !== "active").length;
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -111,7 +111,7 @@ export type SqlDomain = {
|
|
|
111
111
|
precision?: number;
|
|
112
112
|
scale?: number;
|
|
113
113
|
} | {
|
|
114
|
-
kind: "json" | "jsonb" | "uuid" | "time" | "interval";
|
|
114
|
+
kind: "json" | "jsonb" | "uuid" | "date" | "time" | "interval";
|
|
115
115
|
} | {
|
|
116
116
|
kind: "array";
|
|
117
117
|
element: string;
|
|
@@ -264,6 +264,15 @@ export interface SecondaryIndexRecord {
|
|
|
264
264
|
* accelerator. Index identity, key shape, and UNIQUE enforcement are structural.
|
|
265
265
|
*/
|
|
266
266
|
export declare function secondaryIndexWriteContractChanged(previous: Readonly<Record<string, SecondaryIndexRecord>> | null | undefined, next: Readonly<Record<string, SecondaryIndexRecord>> | null | undefined): boolean;
|
|
267
|
+
export interface TableForeignKeyRecord {
|
|
268
|
+
name: string;
|
|
269
|
+
columns: string[];
|
|
270
|
+
parentTable: string;
|
|
271
|
+
parentColumns: string[];
|
|
272
|
+
onDelete: "restrict" | "cascade" | "set null";
|
|
273
|
+
/** False records relationship metadata without write/delete enforcement. Absent means true. */
|
|
274
|
+
enforced?: boolean;
|
|
275
|
+
}
|
|
267
276
|
export interface TableRecord {
|
|
268
277
|
id: string;
|
|
269
278
|
name: string;
|
|
@@ -279,13 +288,7 @@ export interface TableRecord {
|
|
|
279
288
|
/** BEFORE/AFTER triggers on this table, fired by the committing writer in its transaction. */
|
|
280
289
|
triggers?: TriggerRecord[];
|
|
281
290
|
/** FOREIGN KEY constraints. Column tuples are always explicit, including scalar keys. */
|
|
282
|
-
foreignKeys?:
|
|
283
|
-
name: string;
|
|
284
|
-
columns: string[];
|
|
285
|
-
parentTable: string;
|
|
286
|
-
parentColumns: string[];
|
|
287
|
-
onDelete: "restrict" | "cascade" | "set null";
|
|
288
|
-
}>;
|
|
291
|
+
foreignKeys?: TableForeignKeyRecord[];
|
|
289
292
|
/**
|
|
290
293
|
* Row-level CHECK constraints (E141-06), each the text of a boolean expression over this
|
|
291
294
|
* table's own columns. Text rather than a compiled form because the record crosses the worker
|
|
@@ -329,6 +332,12 @@ export interface TableRecord {
|
|
|
329
332
|
/** Compare-and-swap revision for catalog evolution. */
|
|
330
333
|
revision: number;
|
|
331
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Validates one relationship against its child and parent records. This is shared by every
|
|
337
|
+
* storage adapter and snapshot restore path so informational relationships cannot bypass the
|
|
338
|
+
* same structural and domain checks as enforced foreign keys.
|
|
339
|
+
*/
|
|
340
|
+
export declare function validateTableForeignKey(childTable: TableRecord, key: TableForeignKeyRecord, parentTable: TableRecord): void;
|
|
332
341
|
/** Exact UTF-8 bytes used when the canonical record-wire JSON codec persists a table record. */
|
|
333
342
|
export declare function catalogRecordRetainedBytes(record: TableRecord): number;
|
|
334
343
|
export declare function manifestRecordRetainedBytes(record: Manifest): number;
|
|
@@ -1815,6 +1824,8 @@ export interface CatalogMutationOptions {
|
|
|
1815
1824
|
/** Atomic replacement fields for one catalog record. */
|
|
1816
1825
|
export interface TableRecordUpdate extends CatalogMutationOptions {
|
|
1817
1826
|
columns?: TableColumnRecord[];
|
|
1827
|
+
/** Replaces the complete relationship catalog. */
|
|
1828
|
+
foreignKeys?: TableRecord["foreignKeys"];
|
|
1818
1829
|
/** Replaces the full-text index state map; null clears it. */
|
|
1819
1830
|
ftsColumns?: Record<string, FtsColumnIndexRecord> | null;
|
|
1820
1831
|
/** Replaces the secondary-index state map; null clears it. */
|
package/dist/storage/types.js
CHANGED
|
@@ -258,6 +258,70 @@ export function secondaryIndexWriteContractChanged(previous, next) {
|
|
|
258
258
|
left.locator !== right.locator);
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Validates one relationship against its child and parent records. This is shared by every
|
|
263
|
+
* storage adapter and snapshot restore path so informational relationships cannot bypass the
|
|
264
|
+
* same structural and domain checks as enforced foreign keys.
|
|
265
|
+
*/
|
|
266
|
+
export function validateTableForeignKey(childTable, key, parentTable) {
|
|
267
|
+
if (typeof key.name !== "string" ||
|
|
268
|
+
key.name.length === 0 ||
|
|
269
|
+
typeof key.parentTable !== "string" ||
|
|
270
|
+
key.parentTable.length === 0 ||
|
|
271
|
+
!Array.isArray(key.columns) ||
|
|
272
|
+
!Array.isArray(key.parentColumns) ||
|
|
273
|
+
key.columns.length === 0 ||
|
|
274
|
+
key.columns.some((column) => typeof column !== "string" || column.length === 0) ||
|
|
275
|
+
key.parentColumns.some((column) => typeof column !== "string" || column.length === 0) ||
|
|
276
|
+
new Set(key.columns).size !== key.columns.length ||
|
|
277
|
+
new Set(key.parentColumns).size !== key.parentColumns.length ||
|
|
278
|
+
!["restrict", "cascade", "set null"].includes(key.onDelete) ||
|
|
279
|
+
(key.enforced !== undefined && typeof key.enforced !== "boolean")) {
|
|
280
|
+
throw new TypeError("FOREIGN KEY metadata is invalid");
|
|
281
|
+
}
|
|
282
|
+
if (key.parentTable !== parentTable.name) {
|
|
283
|
+
throw new TypeError(`FOREIGN KEY ${key.name} resolved to the wrong parent table`);
|
|
284
|
+
}
|
|
285
|
+
if (key.enforced === false && key.onDelete !== "restrict") {
|
|
286
|
+
throw new TypeError(`Informational FOREIGN KEY ${key.name} cannot declare ON DELETE actions`);
|
|
287
|
+
}
|
|
288
|
+
const children = key.columns.map((name) => childTable.columns.find((column) => column.name === name && column.hidden !== true));
|
|
289
|
+
if (children.some((column) => column === undefined)) {
|
|
290
|
+
throw new TypeError(`FOREIGN KEY ${key.name} names a column this table does not have`);
|
|
291
|
+
}
|
|
292
|
+
const addressIds = parentTable.primaryKeyColumnIds?.length
|
|
293
|
+
? parentTable.primaryKeyColumnIds
|
|
294
|
+
: parentTable.uniqueKeyColumnId === undefined
|
|
295
|
+
? []
|
|
296
|
+
: [parentTable.uniqueKeyColumnId];
|
|
297
|
+
const parents = addressIds.map((id) => parentTable.columns.find((column) => column.id === id && column.hidden !== true));
|
|
298
|
+
const addressNames = parents.map((column) => column?.name ?? "");
|
|
299
|
+
if (addressNames.length !== key.parentColumns.length ||
|
|
300
|
+
addressNames.some((name, index) => name !== key.parentColumns[index])) {
|
|
301
|
+
throw new TypeError(`FOREIGN KEY ${key.name} must reference the parent primary or unique key`);
|
|
302
|
+
}
|
|
303
|
+
if (children.length !== parents.length) {
|
|
304
|
+
throw new TypeError(`FOREIGN KEY ${key.name} has ${String(children.length)} child columns for ${String(parents.length)} parent columns`);
|
|
305
|
+
}
|
|
306
|
+
children.forEach((child, index) => {
|
|
307
|
+
const parent = parents[index];
|
|
308
|
+
if (child === undefined || parent === undefined) {
|
|
309
|
+
throw new TypeError(`FOREIGN KEY ${key.name} is missing a key column`);
|
|
310
|
+
}
|
|
311
|
+
if (child.type !== parent.type) {
|
|
312
|
+
throw new TypeError(`FOREIGN KEY ${key.name} compares ${child.type} with ${parent.type}`);
|
|
313
|
+
}
|
|
314
|
+
if ((child.integer === true) !== (parent.integer === true)) {
|
|
315
|
+
throw new TypeError(`FOREIGN KEY ${key.name} compares an integer domain with an approximate number domain`);
|
|
316
|
+
}
|
|
317
|
+
if (JSON.stringify(child.sqlDomain ?? null) !== JSON.stringify(parent.sqlDomain ?? null)) {
|
|
318
|
+
throw new TypeError(`FOREIGN KEY ${key.name} compares different SQL value domains`);
|
|
319
|
+
}
|
|
320
|
+
if (key.onDelete === "set null" && !child.nullable) {
|
|
321
|
+
throw new TypeError(`FOREIGN KEY ${key.name} cannot SET NULL a NOT NULL column`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
261
325
|
const catalogRecordTextEncoder = new TextEncoder();
|
|
262
326
|
function durableRecordRetainedBytes(record, label) {
|
|
263
327
|
// Keep this byte accounting identical to the canonical record-wire JSON codec without
|
|
@@ -389,6 +453,11 @@ export function validateTableRecordBounds(record) {
|
|
|
389
453
|
throw new RangeError(`A table cannot exceed ${String(MAX_TABLE_CONSTRAINTS)} named constraints`);
|
|
390
454
|
}
|
|
391
455
|
const constraintNames = new Set();
|
|
456
|
+
for (const key of record.foreignKeys ?? []) {
|
|
457
|
+
if (key.enforced !== undefined && typeof key.enforced !== "boolean") {
|
|
458
|
+
throw new TypeError(`FOREIGN KEY enforcement is invalid: ${key.name}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
392
461
|
for (const constraintName of namedConstraints) {
|
|
393
462
|
validateCatalogName(constraintName, "Constraint name");
|
|
394
463
|
if (constraintNames.has(constraintName)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minnowdb/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|
|
@@ -149,6 +149,11 @@
|
|
|
149
149
|
"classification": "different",
|
|
150
150
|
"reason": "Minnow returns arrays as canonical JSON text at the JavaScript boundary while PostgreSQL clients commonly return native arrays."
|
|
151
151
|
},
|
|
152
|
+
{
|
|
153
|
+
"id": "type.date",
|
|
154
|
+
"classification": "different",
|
|
155
|
+
"reason": "Minnow returns zoneless DATE values as canonical YYYY-MM-DD text while PostgreSQL clients commonly materialize them as midnight Date objects."
|
|
156
|
+
},
|
|
152
157
|
{
|
|
153
158
|
"id": "json.query",
|
|
154
159
|
"classification": "different",
|
package/sql-feature-matrix.json
CHANGED
|
@@ -1161,6 +1161,12 @@
|
|
|
1161
1161
|
"example": "SELECT TIME '12:00:00' AS at",
|
|
1162
1162
|
"notes": "A time of day without a time zone, returned as canonical text."
|
|
1163
1163
|
},
|
|
1164
|
+
{
|
|
1165
|
+
"id": "type.date",
|
|
1166
|
+
"status": "supported",
|
|
1167
|
+
"example": "SELECT CAST('2026-08-26' AS DATE) AS day",
|
|
1168
|
+
"notes": "A calendar date without a time zone, returned as canonical YYYY-MM-DD text."
|
|
1169
|
+
},
|
|
1164
1170
|
{
|
|
1165
1171
|
"id": "ddl.sequence",
|
|
1166
1172
|
"status": "supported",
|