@minnowdb/core 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/artifact-cache.d.ts +5 -0
- package/dist/engine/artifact-cache.js +64 -13
- package/dist/engine/client.d.ts +2 -2
- package/dist/engine/client.js +6 -2
- package/dist/engine/database.d.ts +11 -1
- package/dist/engine/database.js +291 -9
- package/dist/engine/point-read.d.ts +57 -0
- package/dist/engine/point-read.js +189 -0
- package/dist/engine/sql-driver.d.ts +7 -2
- package/dist/engine/worker-host.js +17 -2
- package/dist/storage/toolkit/record-core.js +104 -75
- package/dist/storage/types.js +34 -6
- package/dist/transactions/index.js +9 -1
- package/package.json +1 -1
- package/sql-feature-matrix.json +52 -2
package/dist/storage/types.js
CHANGED
|
@@ -12,6 +12,34 @@ export const storeNames = [
|
|
|
12
12
|
"gc",
|
|
13
13
|
];
|
|
14
14
|
export const MAX_MANIFEST_CHANGED_TABLE_IDS = 1_024;
|
|
15
|
+
/**
|
|
16
|
+
* A deep copy for the plain record shapes this module validates — objects, arrays, and
|
|
17
|
+
* primitives, bigint included. Records cross this boundary on every catalog read and every
|
|
18
|
+
* compaction-job advance, and `structuredClone` here was a sixth of a settle phase's CPU; any
|
|
19
|
+
* value outside the plain shape falls back to `structuredClone` for that value, so the copy
|
|
20
|
+
* stays exact whatever arrives.
|
|
21
|
+
*/
|
|
22
|
+
function clonePlainRecord(value) {
|
|
23
|
+
return clonePlainValue(value);
|
|
24
|
+
}
|
|
25
|
+
function clonePlainValue(value) {
|
|
26
|
+
if (typeof value !== "object" || value === null)
|
|
27
|
+
return value;
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
const copy = new Array(value.length);
|
|
30
|
+
for (let index = 0; index < value.length; index += 1)
|
|
31
|
+
copy[index] = clonePlainValue(value[index]);
|
|
32
|
+
return copy;
|
|
33
|
+
}
|
|
34
|
+
const prototype = Object.getPrototypeOf(value);
|
|
35
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
36
|
+
return structuredClone(value);
|
|
37
|
+
const copy = {};
|
|
38
|
+
for (const key of Object.keys(value)) {
|
|
39
|
+
copy[key] = clonePlainValue(value[key]);
|
|
40
|
+
}
|
|
41
|
+
return copy;
|
|
42
|
+
}
|
|
15
43
|
export function canonicalManifestChangedTableIds(ids) {
|
|
16
44
|
if (ids.length > MAX_MANIFEST_CHANGED_TABLE_IDS) {
|
|
17
45
|
throw new RangeError(`Manifest changed-table IDs cannot exceed ${String(MAX_MANIFEST_CHANGED_TABLE_IDS)}`);
|
|
@@ -51,7 +79,7 @@ export function validateSqlDomain(domain, context) {
|
|
|
51
79
|
}
|
|
52
80
|
validateEnumValues(domain.values, domain.name);
|
|
53
81
|
}
|
|
54
|
-
return
|
|
82
|
+
return clonePlainRecord(domain);
|
|
55
83
|
}
|
|
56
84
|
export const MAX_TABLE_COLUMNS = 1_024;
|
|
57
85
|
export const MAX_ENUM_VALUES = 4_096;
|
|
@@ -1789,7 +1817,7 @@ export function normalizeSegmentRecord(record) {
|
|
|
1789
1817
|
if (record.partitionOrdinal === undefined) {
|
|
1790
1818
|
if (level === 2)
|
|
1791
1819
|
throw new TypeError("A level-two segment requires a partition ordinal");
|
|
1792
|
-
return
|
|
1820
|
+
return clonePlainRecord(record);
|
|
1793
1821
|
}
|
|
1794
1822
|
const partitionOrdinal = nonNegativeWholeNumber(record.partitionOrdinal, "Segment partition ordinal");
|
|
1795
1823
|
if (record.level !== 2) {
|
|
@@ -1812,7 +1840,7 @@ export function normalizeSegmentRecord(record) {
|
|
|
1812
1840
|
record.rowIdEndExclusive !== record.rowIdStart + BigInt(rowCount)) {
|
|
1813
1841
|
throw new RangeError("A partitioned segment must have a contiguous positive row ID envelope");
|
|
1814
1842
|
}
|
|
1815
|
-
return
|
|
1843
|
+
return clonePlainRecord({ ...record, partitionOrdinal });
|
|
1816
1844
|
}
|
|
1817
1845
|
// Keyed multi-range partition: a merged full-row base whose live rows keep their original
|
|
1818
1846
|
// ids, described by positive, sorted, non-overlapping spans that sum to the row count.
|
|
@@ -1837,7 +1865,7 @@ export function normalizeSegmentRecord(record) {
|
|
|
1837
1865
|
if (spanRows !== rowCount) {
|
|
1838
1866
|
throw new RangeError("Partitioned segment spans must cover exactly the row count");
|
|
1839
1867
|
}
|
|
1840
|
-
return
|
|
1868
|
+
return clonePlainRecord({ ...record, partitionOrdinal });
|
|
1841
1869
|
}
|
|
1842
1870
|
export function updateTransactionRecord(record, update) {
|
|
1843
1871
|
const updated = {
|
|
@@ -2144,7 +2172,7 @@ export function normalizeGarbageCollectionJobRecord(record) {
|
|
|
2144
2172
|
cursor.transactionIndex !== 0)) {
|
|
2145
2173
|
throw new TypeError("A planned garbage collection job cannot contain progress");
|
|
2146
2174
|
}
|
|
2147
|
-
return
|
|
2175
|
+
return clonePlainRecord(normalized);
|
|
2148
2176
|
}
|
|
2149
2177
|
export function advanceGarbageCollectionJobRecord(record, accounting) {
|
|
2150
2178
|
const current = normalizeGarbageCollectionJobRecord(record);
|
|
@@ -2355,7 +2383,7 @@ export function normalizeCompactionJobRecord(record) {
|
|
|
2355
2383
|
}
|
|
2356
2384
|
validateCompactionRewrite(normalized);
|
|
2357
2385
|
validateCompactionJobState(normalized);
|
|
2358
|
-
return
|
|
2386
|
+
return clonePlainRecord(normalized);
|
|
2359
2387
|
}
|
|
2360
2388
|
export function updateCompactionJobRecord(record, update) {
|
|
2361
2389
|
const current = normalizeCompactionJobRecord(record);
|
|
@@ -35,6 +35,9 @@ export class LeasedSnapshot extends Snapshot {
|
|
|
35
35
|
now;
|
|
36
36
|
#record;
|
|
37
37
|
#released = false;
|
|
38
|
+
/** The record's ISO expiry, parsed once: expiry is checked before every batched block read. */
|
|
39
|
+
#expiresAtIso;
|
|
40
|
+
#expiresAtMs = Number.NaN;
|
|
38
41
|
constructor(store, version, record, now) {
|
|
39
42
|
super(store, version);
|
|
40
43
|
this.now = now;
|
|
@@ -46,7 +49,12 @@ export class LeasedSnapshot extends Snapshot {
|
|
|
46
49
|
return this.#record.id;
|
|
47
50
|
}
|
|
48
51
|
get expiresAt() {
|
|
49
|
-
|
|
52
|
+
const iso = this.#record.expiresAt;
|
|
53
|
+
if (iso !== this.#expiresAtIso) {
|
|
54
|
+
this.#expiresAtIso = iso;
|
|
55
|
+
this.#expiresAtMs = Date.parse(iso);
|
|
56
|
+
}
|
|
57
|
+
return new Date(this.#expiresAtMs);
|
|
50
58
|
}
|
|
51
59
|
async getBlock(id) {
|
|
52
60
|
this.#assertOpen();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minnowdb/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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",
|
package/sql-feature-matrix.json
CHANGED
|
@@ -554,11 +554,46 @@
|
|
|
554
554
|
"example": "SELECT amount, SUM(amount) OVER (ORDER BY amount, joined ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS windowed FROM rows",
|
|
555
555
|
"notes": "ROWS frames take row-distance bounds; RANGE frames take UNBOUNDED and CURRENT ROW bounds, where CURRENT ROW spans the ordering peer group."
|
|
556
556
|
},
|
|
557
|
+
{
|
|
558
|
+
"id": "window.frame-range-offset",
|
|
559
|
+
"status": "unsupported",
|
|
560
|
+
"example": "SELECT amount, SUM(amount) OVER (ORDER BY amount RANGE BETWEEN 1 PRECEDING AND CURRENT ROW) AS windowed FROM rows",
|
|
561
|
+
"error": "RANGE frames take only UNBOUNDED and CURRENT ROW bounds; use ROWS",
|
|
562
|
+
"notes": "A numeric RANGE offset bounds the frame by ordering-value distance, which the engine does not implement. ROWS frames take numeric offsets; RANGE frames take UNBOUNDED and CURRENT ROW bounds only."
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"id": "window.distinct-aggregate",
|
|
566
|
+
"status": "unsupported",
|
|
567
|
+
"example": "SELECT SUM(DISTINCT amount) OVER (PARTITION BY region) AS total FROM rows",
|
|
568
|
+
"error": "DISTINCT window aggregates are not supported",
|
|
569
|
+
"notes": "An aggregate used as a window function cannot take DISTINCT; PostgreSQL rejects this form too. DISTINCT aggregates work in grouped aggregation, so aggregate in a grouped block and window over that."
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"id": "window.outside-select",
|
|
573
|
+
"status": "unsupported",
|
|
574
|
+
"example": "SELECT amount FROM rows ORDER BY ROW_NUMBER() OVER (ORDER BY amount)",
|
|
575
|
+
"error": "Window functions are only allowed in the select list",
|
|
576
|
+
"notes": "PostgreSQL also evaluates window functions in ORDER BY; Minnow evaluates them only as select items. Alias the window in the select list and order by the alias."
|
|
577
|
+
},
|
|
557
578
|
{
|
|
558
579
|
"id": "join.right",
|
|
559
580
|
"status": "supported",
|
|
560
581
|
"example": "SELECT r.region FROM rows r RIGHT JOIN dims d ON d.region = r.region",
|
|
561
|
-
"notes": "Desugars to the mirrored LEFT JOIN; supported as the sole join of a block
|
|
582
|
+
"notes": "Desugars to the mirrored LEFT JOIN; supported as the sole join of a block, and not beside SELECT *."
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
"id": "join.right-multi",
|
|
586
|
+
"status": "unsupported",
|
|
587
|
+
"example": "SELECT r.region FROM rows r JOIN dims d ON d.region = r.region RIGHT JOIN dims e ON e.region = r.region",
|
|
588
|
+
"error": "RIGHT JOIN is only supported as the sole join",
|
|
589
|
+
"notes": "RIGHT JOIN desugars by swapping the two sides of a LEFT JOIN, which needs the block to hold exactly one join. Rewrite the block so the preserved side is on the left of a LEFT JOIN."
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
"id": "join.right-wildcard",
|
|
593
|
+
"status": "unsupported",
|
|
594
|
+
"example": "SELECT * FROM rows r RIGHT JOIN dims d ON d.region = r.region",
|
|
595
|
+
"error": "RIGHT JOIN cannot be combined with SELECT *",
|
|
596
|
+
"notes": "The desugaring swaps the two sources, which would reorder a wildcard's output columns. Name the output columns explicitly, or write the mirrored LEFT JOIN."
|
|
562
597
|
},
|
|
563
598
|
{
|
|
564
599
|
"id": "join.non-equi",
|
|
@@ -758,7 +793,14 @@
|
|
|
758
793
|
"id": "join.full",
|
|
759
794
|
"status": "supported",
|
|
760
795
|
"example": "SELECT r.amount AS amount, d.label AS label FROM rows r FULL JOIN dims d ON d.region = r.region",
|
|
761
|
-
"notes": "Desugars into a union of two left joins, so it must be the sole join, with an equality ON and no grouping or
|
|
796
|
+
"notes": "Desugars into a union of two left joins, so it must be the sole join, with an equality ON, named output columns rather than SELECT *, and no grouping, DISTINCT, or window functions yet."
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
"id": "join.full-grouped",
|
|
800
|
+
"status": "unsupported",
|
|
801
|
+
"example": "SELECT r.region AS region, COUNT(*) AS matched FROM rows r FULL JOIN dims d ON d.region = r.region GROUP BY r.region",
|
|
802
|
+
"error": "FULL JOIN cannot be combined with grouping, DISTINCT, or window functions yet",
|
|
803
|
+
"notes": "FULL JOIN desugars into a union of two left joins, and grouping, DISTINCT, and window functions do not distribute over that union yet. Put the FULL JOIN in a derived table and group, deduplicate, or window in the outer block."
|
|
762
804
|
},
|
|
763
805
|
{
|
|
764
806
|
"id": "order-by.ordinal",
|
|
@@ -1272,6 +1314,14 @@
|
|
|
1272
1314
|
"setup": ["CREATE TABLE parents (id INTEGER PRIMARY KEY, label TEXT NOT NULL)"],
|
|
1273
1315
|
"example": "CREATE TABLE children (id INTEGER PRIMARY KEY, parent INTEGER REFERENCES parents(id) ON DELETE CASCADE)",
|
|
1274
1316
|
"notes": "Scalar and composite references target the parent's matching primary-key columns. Writes validate against their transaction; NULL references are satisfied. ON DELETE supports RESTRICT, CASCADE, and SET NULL atomically. Primary keys are immutable, so ON UPDATE has no action."
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
"id": "ddl.foreign-key-set-default",
|
|
1320
|
+
"status": "unsupported",
|
|
1321
|
+
"setup": ["CREATE TABLE default_parents (id INTEGER PRIMARY KEY, label TEXT NOT NULL)"],
|
|
1322
|
+
"example": "CREATE TABLE default_children (id INTEGER PRIMARY KEY, parent INTEGER REFERENCES default_parents(id) ON DELETE SET DEFAULT)",
|
|
1323
|
+
"error": "SET DEFAULT is not supported; use SET NULL or CASCADE",
|
|
1324
|
+
"notes": "SET DEFAULT would rewrite orphaned references to the column's default value at delete time; Minnow implements RESTRICT, CASCADE, and SET NULL. The statement is rejected at parse, before touching the catalog."
|
|
1275
1325
|
}
|
|
1276
1326
|
]
|
|
1277
1327
|
}
|