@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.
@@ -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 structuredClone(domain);
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 structuredClone(record);
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 structuredClone({ ...record, partitionOrdinal });
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 structuredClone({ ...record, partitionOrdinal });
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 structuredClone(normalized);
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 structuredClone(normalized);
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
- return new Date(this.#record.expiresAt);
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.1",
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",
@@ -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 DISTINCT yet."
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
  }