@minnowdb/core 0.5.0 → 0.6.1

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.
Files changed (42) hide show
  1. package/README.md +3 -2
  2. package/dist/engine/cancellation.d.ts +2 -0
  3. package/dist/engine/cancellation.js +4 -0
  4. package/dist/engine/catalog.d.ts +3 -1
  5. package/dist/engine/catalog.js +1 -0
  6. package/dist/engine/client.d.ts +32 -4
  7. package/dist/engine/client.js +82 -15
  8. package/dist/engine/database.d.ts +23 -14
  9. package/dist/engine/database.js +528 -79
  10. package/dist/engine/defaults.js +11 -0
  11. package/dist/engine/errors.d.ts +13 -0
  12. package/dist/engine/errors.js +22 -0
  13. package/dist/engine/fts.d.ts +2 -15
  14. package/dist/engine/live.d.ts +1 -7
  15. package/dist/engine/live.js +2 -12
  16. package/dist/engine/optimizer.d.ts +7 -0
  17. package/dist/engine/optimizer.js +1349 -76
  18. package/dist/engine/query.d.ts +11 -278
  19. package/dist/engine/query.js +178 -49
  20. package/dist/engine/schema-wire.d.ts +7 -1
  21. package/dist/engine/schema-wire.js +4 -0
  22. package/dist/engine/schema.d.ts +67 -33
  23. package/dist/engine/schema.js +138 -7
  24. package/dist/engine/sql-domains.d.ts +8 -0
  25. package/dist/engine/sql-domains.js +25 -0
  26. package/dist/engine/sql-json.js +22 -3
  27. package/dist/engine/vector.d.ts +2 -2
  28. package/dist/engine/vector.js +369 -43
  29. package/dist/engine/worker-host.js +119 -44
  30. package/dist/plan/index.d.ts +5 -4
  31. package/dist/plan/index.js +3 -3
  32. package/dist/plan/model.d.ts +224 -0
  33. package/dist/plan/model.js +1 -0
  34. package/dist/storage/types.d.ts +7 -0
  35. package/dist/storage/types.js +16 -0
  36. package/dist/transactions/index.d.ts +5 -3
  37. package/dist/transactions/index.js +58 -8
  38. package/dist/worker-protocol/index.d.ts +6 -1
  39. package/dist/worker-protocol/index.js +5 -2
  40. package/package.json +1 -1
  41. package/postgres-feature-profile.json +5 -0
  42. package/sql-feature-matrix.json +89 -21
@@ -1,4 +1,4 @@
1
- export declare const protocolVersion: 2;
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
4
  export type WorkerOperation = "benchmark" | "cancelBenchmark" | "memorySample" | "datasetList" | "datasetCreate" | "datasetDelete" | "runQuery" | "suiteReference" | "suiteWrite" | "suiteFeatureMatrix" | "suiteLive";
@@ -46,6 +46,11 @@ export type RpcRequest = {
46
46
  handleId: string | null;
47
47
  method: string;
48
48
  args: unknown[];
49
+ } | {
50
+ version: typeof protocolVersion;
51
+ /** The request whose work should stop. Cancellation has no response frame of its own. */
52
+ requestId: string;
53
+ kind: "rpc-cancel";
49
54
  };
50
55
  export type RpcResponse = {
51
56
  version: typeof protocolVersion;
@@ -1,4 +1,4 @@
1
- export const protocolVersion = 2;
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
4
  export function parseRequest(value) {
@@ -64,8 +64,11 @@ export function parseRpcRequest(value) {
64
64
  if (typeof value !== "object" || value === null)
65
65
  return null;
66
66
  const candidate = value;
67
- if (candidate.kind !== "rpc-init" && candidate.kind !== "rpc-call")
67
+ if (candidate.kind !== "rpc-init" &&
68
+ candidate.kind !== "rpc-call" &&
69
+ candidate.kind !== "rpc-cancel") {
68
70
  return null;
71
+ }
69
72
  if (candidate.version !== protocolVersion)
70
73
  throw new Error("Unsupported protocol version");
71
74
  if (typeof candidate.requestId !== "string" || candidate.requestId.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minnowdb/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
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",
@@ -129,6 +129,11 @@
129
129
  "classification": "different",
130
130
  "reason": "Minnow returns JSON text; PostgreSQL returns a native JSON value, and member order is unspecified without aggregate-local ORDER BY."
131
131
  },
132
+ {
133
+ "id": "subquery.correlated-json-aggregate",
134
+ "classification": "different",
135
+ "reason": "Both engines accept the correlated JSON aggregate and agree on its JSON value, but Minnow returns JSON text while PostgreSQL returns a native JSON value."
136
+ },
132
137
  {
133
138
  "id": "type.exact-numeric",
134
139
  "classification": "different",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": 4,
2
+ "version": 5,
3
3
  "description": "Executable PostgreSQL compatibility examples for MinnowDatabase. postgres-feature-profile.json records differences, extensions, and embedded exclusions.",
4
4
  "features": [
5
5
  {
@@ -20,7 +20,8 @@
20
20
  {
21
21
  "id": "select.distinct",
22
22
  "status": "supported",
23
- "example": "SELECT DISTINCT region FROM rows"
23
+ "example": "SELECT DISTINCT region FROM rows",
24
+ "notes": "Plain projections and SELECT DISTINCT * are supported. DISTINCT combined with grouping, HAVING, or aggregate/window expressions is rejected."
24
25
  },
25
26
  {
26
27
  "id": "select.scalar-subquery",
@@ -127,7 +128,7 @@
127
128
  "id": "group-by.rollup",
128
129
  "status": "supported",
129
130
  "example": "SELECT region, SUM(amount) AS total FROM rows GROUP BY ROLLUP(region)",
130
- "notes": "ROLLUP/CUBE/GROUPING SETS desugar into a UNION ALL of grouped blocks. The GROUPING() marker is deliberately unsupported, so rollup NULLs and data NULLs are indistinguishable. SQLite itself has none of these."
131
+ "notes": "ROLLUP/CUBE/GROUPING SETS desugar into a UNION ALL of grouped blocks. GROUPING() distinguishes rolled-up columns from data NULLs. SQLite itself has none of these."
131
132
  },
132
133
  {
133
134
  "id": "group-by.grouping-sets",
@@ -245,7 +246,7 @@
245
246
  "id": "mutation.update-keyed",
246
247
  "status": "supported",
247
248
  "example": "UPDATE keyed SET score = score + 1 WHERE score > 0",
248
- "notes": "Requires a unique-key table; read-then-mutate, not serializable."
249
+ "notes": "Requires a unique-key table. A standalone statement reads and publishes inside one retryable write scope; an explicit transaction stages it with the transaction's other statements."
249
250
  },
250
251
  {
251
252
  "id": "mutation.delete-keyed",
@@ -256,8 +257,8 @@
256
257
  {
257
258
  "id": "mutation.returning",
258
259
  "status": "supported",
259
- "example": "DELETE FROM keyed WHERE name = 'x' RETURNING name, score",
260
- "notes": "RETURNING works on INSERT, UPDATE, and DELETE; inserts echo written values, updates return post-update values, deletes the rows as read."
260
+ "example": "DELETE FROM keyed WHERE name = 'x' RETURNING keyed.name, keyed.score",
261
+ "notes": "RETURNING works on INSERT, UPDATE, and DELETE; inserts echo written values, updates return post-update values, and deletes return the rows as read. Columns and target.* may be target-qualified."
261
262
  },
262
263
  {
263
264
  "id": "mutation.upsert",
@@ -316,7 +317,7 @@
316
317
  "id": "predicate.quantified",
317
318
  "status": "supported",
318
319
  "example": "SELECT region FROM rows WHERE amount > ALL (SELECT amount FROM dims)",
319
- "notes": "ANY/SOME/ALL with full three-valued logic; correlated forms are rejected. SQLite itself has no quantified comparisons."
320
+ "notes": "ANY/SOME/ALL use full three-valued logic, including when a correlated form is nested below OR, NOT, CASE, or a select expression. SQLite itself has no quantified comparisons."
320
321
  },
321
322
  {
322
323
  "id": "predicate.ilike",
@@ -387,30 +388,85 @@
387
388
  "id": "subquery.correlated",
388
389
  "status": "supported",
389
390
  "example": "SELECT region FROM rows r WHERE amount > (SELECT AVG(amount) FROM rows q WHERE q.region = r.region)",
390
- "notes": "Equality-correlated subqueries decorrelate into derived-table joins at compile time; both executors run plain joins."
391
+ "notes": "Correlated subqueries decorrelate into derived-table joins at compile time; both executors run plain joins."
391
392
  },
392
393
  {
393
394
  "id": "subquery.correlated-exists",
394
395
  "status": "supported",
395
396
  "example": "SELECT amount FROM rows r WHERE EXISTS (SELECT region FROM dims d WHERE d.region = r.region)",
396
- "notes": "EXISTS joins the subquery's distinct correlation keys; NOT EXISTS becomes a left join checked with IS NULL."
397
+ "notes": "Top-level EXISTS and NOT EXISTS lower to semi-joins and anti-joins. Nested boolean forms use hidden per-probe match flags."
397
398
  },
398
399
  {
399
400
  "id": "subquery.correlated-select",
400
401
  "status": "supported",
401
402
  "example": "SELECT r.region, (SELECT AVG(q.amount) FROM rows q WHERE q.region = r.region) AS regional FROM rows r",
402
- "notes": "Correlated scalar aggregates decorrelate in the select list too, outside grouped queries."
403
+ "notes": "Correlated scalar aggregates and single-row projections decorrelate in the select list. In a grouped query, their outer references must be GROUP BY columns and the scalar cannot sit inside an outer aggregate."
404
+ },
405
+ {
406
+ "id": "subquery.correlated-select-limit",
407
+ "status": "supported",
408
+ "example": "SELECT r.region, (SELECT q.amount FROM rows q WHERE q.region = r.region ORDER BY q.amount DESC LIMIT 1) AS peak FROM rows r",
409
+ "notes": "ORDER BY, LIMIT, and OFFSET apply independently to each distinct outer probe. Zero rows yield NULL and more than one unbounded row raises a scalar-cardinality error."
410
+ },
411
+ {
412
+ "id": "subquery.correlated-json-aggregate",
413
+ "status": "supported",
414
+ "example": "SELECT r.region, (SELECT JSON_ARRAYAGG(JSON_OBJECT('amount' VALUE q.amount) ORDER BY q.amount) FROM rows q WHERE q.region = r.region) AS amounts FROM rows r",
415
+ "notes": "JSON aggregate expressions use the same set-at-a-time decorrelation as numeric aggregates and preserve their JSON result domain."
416
+ },
417
+ {
418
+ "id": "subquery.correlated-select-grouped",
419
+ "status": "supported",
420
+ "example": "SELECT r.region, COUNT(*) AS c, (SELECT AVG(q.amount) FROM rows q WHERE q.region = r.region) AS regional FROM rows r GROUP BY r.region",
421
+ "notes": "The decorrelated value is functionally determined by the outer grouping keys and is carried as an internal group key."
422
+ },
423
+ {
424
+ "id": "subquery.correlated-scalar-non-equi",
425
+ "status": "supported",
426
+ "example": "SELECT r.amount, (SELECT COUNT(*) FROM rows q WHERE q.amount < r.amount) AS lower_count FROM rows r",
427
+ "notes": "Non-equality scalar aggregates group inner rows once per distinct outer probe tuple, then join the result back by equality."
428
+ },
429
+ {
430
+ "id": "subquery.correlated-exists-expression",
431
+ "status": "supported",
432
+ "example": "SELECT r.amount FROM rows r WHERE r.amount > 100 OR EXISTS (SELECT d.region FROM dims d WHERE d.region = r.region)",
433
+ "notes": "Correlated EXISTS and NOT EXISTS remain set-at-a-time below OR, NOT, or CASE and across deeper correlated EXISTS, IN, NOT IN, or scalar blocks. Generated aliases are unique across the complete plan tree."
403
434
  },
404
435
  {
405
436
  "id": "subquery.correlated-non-equi",
406
437
  "status": "supported",
407
438
  "example": "SELECT r.amount FROM rows r WHERE EXISTS (SELECT q.amount FROM rows q WHERE q.amount < r.amount)",
408
- "notes": "Non-equality EXISTS and NOT EXISTS correlations lower to semi-joins and anti-joins; correlated scalar aggregates and IN remain equality-only."
439
+ "notes": "Non-equality EXISTS and NOT EXISTS correlations lower to semi-joins and anti-joins. Scalar aggregates use distinct outer probes."
440
+ },
441
+ {
442
+ "id": "subquery.correlated-in-non-equi",
443
+ "status": "supported",
444
+ "example": "SELECT r.amount FROM rows r WHERE r.region IN (SELECT q.region FROM rows q WHERE q.amount < r.amount)",
445
+ "notes": "A range-correlated IN predicate lowers to a semi-join carrying both its correlation and membership comparisons."
409
446
  },
410
447
  {
411
448
  "id": "subquery.correlated-not-in",
412
449
  "status": "supported",
413
- "example": "SELECT region FROM rows r WHERE region NOT IN (SELECT d.region FROM dims d WHERE d.region = r.region)"
450
+ "example": "SELECT region FROM rows r WHERE region NOT IN (SELECT d.region FROM dims d WHERE d.region = r.region)",
451
+ "notes": "Correlated NOT IN preserves empty-set and NULL semantics rather than treating it as a simple anti-join."
452
+ },
453
+ {
454
+ "id": "subquery.correlated-membership-expression",
455
+ "status": "supported",
456
+ "example": "SELECT r.amount FROM rows r WHERE r.amount = 3 OR r.region NOT IN (SELECT q.region FROM rows q WHERE q.amount < r.amount)",
457
+ "notes": "Correlated IN and NOT IN retain true, false, and unknown results below OR, NOT, CASE, and in select expressions."
458
+ },
459
+ {
460
+ "id": "subquery.correlated-not-in-non-equi",
461
+ "status": "supported",
462
+ "example": "SELECT r.amount FROM rows r WHERE 'north' NOT IN (SELECT q.region FROM rows q WHERE q.amount < r.amount)",
463
+ "notes": "Range correlation uses an anti-join for exact matches plus per-probe total and non-NULL counts."
464
+ },
465
+ {
466
+ "id": "subquery.correlated-quantified",
467
+ "status": "supported",
468
+ "example": "SELECT r.amount FROM rows r WHERE r.amount = 3 OR r.amount > ALL (SELECT q.amount FROM rows q WHERE q.region = r.region)",
469
+ "notes": "Top-level WHERE uses semi/anti joins. Nested expressions group true, false, unknown, and empty-set counts per distinct outer probe tuple."
414
470
  },
415
471
  {
416
472
  "id": "cte.recursive",
@@ -456,13 +512,13 @@
456
512
  "id": "aggregate.filter",
457
513
  "status": "supported",
458
514
  "example": "SELECT region, COUNT(*) FILTER (WHERE amount > 5) AS big FROM rows GROUP BY region",
459
- "notes": "Desugars into a CASE inside the aggregate, so it works with every aggregate and DISTINCT."
515
+ "notes": "Desugars into a CASE inside COUNT/SUM/AVG/MIN/MAX and preserves DISTINCT. JSON_ARRAYAGG does not support FILTER."
460
516
  },
461
517
  {
462
518
  "id": "window.aggregate-over",
463
519
  "status": "supported",
464
520
  "example": "SELECT SUM(amount) OVER (PARTITION BY region) AS total FROM rows",
465
- "notes": "Default frame only: whole partition without OVER ordering, running with peers when ordered."
521
+ "notes": "Without explicit framing, the default is the whole partition when unordered and a peer-aware running frame when ordered. ROWS, RANGE, GROUPS, and exclusions are tracked separately below."
466
522
  },
467
523
  {
468
524
  "id": "window.in-expression",
@@ -569,7 +625,7 @@
569
625
  "id": "trigger.create-after",
570
626
  "status": "supported",
571
627
  "example": "CREATE TRIGGER keyed_audit AFTER INSERT ON keyed BEGIN INSERT INTO rows (region, amount) VALUES (NEW.name, NEW.score); END",
572
- "notes": "AFTER and BEFORE row triggers on INSERT/UPDATE/DELETE, executed atomically inside the triggering commit with NEW/OLD references. Bodies: INSERT ... VALUES into keyless tables; UPDATE/DELETE against keyed tables. One cascade level is allowed; deeper chains error at write time."
628
+ "notes": "AFTER and BEFORE row triggers on INSERT/UPDATE/DELETE execute atomically with NEW/OLD references. Bodies support parameter-free INSERT ... VALUES into keyless tables and UPDATE/DELETE against keyed tables; ON CONFLICT and RETURNING are rejected. One cascade level is allowed."
573
629
  },
574
630
  {
575
631
  "id": "trigger.create-before",
@@ -970,8 +1026,8 @@
970
1026
  {
971
1027
  "id": "json.object",
972
1028
  "status": "supported",
973
- "example": "SELECT JSON_OBJECT('a' VALUE 1, 'missing' VALUE NULL) AS document",
974
- "notes": "Defaults to NULL ON NULL and WITHOUT UNIQUE KEYS. NULL keys are rejected. The constructor returns JSON text; cast or store it as JSON/JSONB for domain validation and JSONB canonicalization."
1029
+ "example": "SELECT JSON_OBJECT('a' VALUE 1, 'detail' VALUE JSON_OBJECT('name' VALUE 'Acme')) AS document",
1030
+ "notes": "Defaults to NULL ON NULL and WITHOUT UNIQUE KEYS. NULL keys are rejected. JSON-producing arguments embed as documents rather than escaped strings; ordinary text remains a string. The constructor returns JSON text; cast or store it as JSON/JSONB for domain validation and JSONB canonicalization."
975
1031
  },
976
1032
  {
977
1033
  "id": "json.array",
@@ -990,6 +1046,12 @@
990
1046
  "example": "CREATE TABLE defaulted (id INTEGER PRIMARY KEY, tier TEXT DEFAULT 'basic')",
991
1047
  "notes": "DEFAULT accepts a variable-free scalar expression. Omission or SQL DEFAULT invokes it; explicit NULL follows the column's independent nullability."
992
1048
  },
1049
+ {
1050
+ "id": "ddl.generated-column",
1051
+ "status": "supported",
1052
+ "example": "CREATE TABLE generated_value (base INTEGER, doubled INTEGER GENERATED ALWAYS AS (base * 2) STORED)",
1053
+ "notes": "Stored generated columns recompute on every write and may be indexed, but cannot be caller-assigned or used as Minnow's row-addressing primary/unique key. Expressions are immutable and row-local."
1054
+ },
993
1055
  {
994
1056
  "id": "ddl.create-table-key-clause",
995
1057
  "status": "supported",
@@ -1117,7 +1179,13 @@
1117
1179
  "id": "from.lateral",
1118
1180
  "status": "supported",
1119
1181
  "example": "SELECT x.amount FROM rows r, LATERAL (SELECT amount FROM dims WHERE dims.region = r.region) x",
1120
- "notes": "Equality-correlated derived sources become set-at-a-time joins. Correlated grouping, ordering, and LIMIT remain unsupported."
1182
+ "notes": "Equality and range-correlated derived sources become set-at-a-time joins. Correlated grouping, ordering, and LIMIT remain unsupported."
1183
+ },
1184
+ {
1185
+ "id": "from.lateral-non-equi",
1186
+ "status": "supported",
1187
+ "example": "SELECT x.amount FROM rows r, LATERAL (SELECT q.amount FROM rows q WHERE q.amount < r.amount) x",
1188
+ "notes": "Non-equality correlation lowers to an ordinary general join rather than executing the derived source once per outer row."
1121
1189
  },
1122
1190
  {
1123
1191
  "id": "aggregate.string-agg",
@@ -1146,8 +1214,8 @@
1146
1214
  {
1147
1215
  "id": "aggregate.json",
1148
1216
  "status": "supported",
1149
- "example": "SELECT JSON_ARRAYAGG(region) AS regions FROM rows",
1150
- "notes": "Supports DISTINCT, includes SQL NULL as JSON null, and returns NULL for empty input. FILTER, window use, aggregate-local ORDER BY, and explicit NULL/ABSENT clauses are not supported; input order is otherwise unspecified."
1217
+ "example": "SELECT JSON_ARRAYAGG(JSON_OBJECT('region' VALUE region)) AS regions FROM rows",
1218
+ "notes": "Supports DISTINCT and aggregate-local ORDER BY, embeds JSON-producing inputs as documents, includes SQL NULL as JSON null, and returns NULL for empty input. FILTER, window use, and explicit NULL/ABSENT clauses are not supported; input order is unspecified without ORDER BY."
1151
1219
  },
1152
1220
  {
1153
1221
  "id": "type.array",
@@ -1184,7 +1252,7 @@
1184
1252
  "id": "ddl.create-view",
1185
1253
  "status": "supported",
1186
1254
  "example": "CREATE VIEW west AS SELECT region, amount FROM rows WHERE region = 'west'",
1187
- "notes": "The catalog stores the query text and the schema inferred from it, so a view answers the same questions a table does and reads expand it into the query it stands for — anywhere a read runs, including inside a write scope. A view is never a write target. CREATE OR REPLACE redefines one; a view stacked on it follows the new definition, and a cycle two redefinitions close is caught on the next read rather than recursed."
1255
+ "notes": "The catalog stores the query text and inferred schema, so reads expand a view anywhere a table can be read, including inside a write scope. A view is never a write target. CREATE OR REPLACE redefines one; dependent views follow it and cycles are rejected on read. CREATE VIEW column-name lists are not supported."
1188
1256
  },
1189
1257
  {
1190
1258
  "id": "ddl.drop-view",