@crvouga/sqlite-mem 0.1.0 → 1.0.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.
Files changed (149) hide show
  1. package/AGENTS.md +154 -0
  2. package/COMPATIBILITY-AUDIT.md +7 -9
  3. package/COMPATIBILITY.md +5 -4
  4. package/README.md +166 -16
  5. package/compat/coverage.json +797 -512
  6. package/compat/requirements.json +1 -1
  7. package/dist/api/database.d.ts +97 -12
  8. package/dist/api/statement.d.ts +65 -18
  9. package/dist/ast/nodes.d.ts +11 -2
  10. package/dist/constraints/check.d.ts +5 -6
  11. package/dist/errors/index.d.ts +36 -3
  12. package/dist/executor/attach.d.ts +3 -4
  13. package/dist/executor/ddl.d.ts +3 -4
  14. package/dist/executor/dml.d.ts +4 -4
  15. package/dist/executor/env.d.ts +9 -11
  16. package/dist/executor/execute.d.ts +3 -4
  17. package/dist/executor/pragma.d.ts +3 -4
  18. package/dist/executor/result.d.ts +10 -6
  19. package/dist/executor/select.d.ts +4 -5
  20. package/dist/executor/simple-select.d.ts +3 -4
  21. package/dist/executor/triggers.d.ts +6 -7
  22. package/dist/executor/vtable.d.ts +5 -6
  23. package/dist/expressions/context.d.ts +5 -5
  24. package/dist/expressions/equals.d.ts +3 -0
  25. package/dist/expressions/eval.d.ts +9 -4
  26. package/dist/expressions/index.d.ts +3 -4
  27. package/dist/expressions/like.d.ts +13 -3
  28. package/dist/functions/aggregate.d.ts +1 -2
  29. package/dist/functions/datetime.d.ts +1 -2
  30. package/dist/functions/extensions.d.ts +1 -2
  31. package/dist/functions/index.d.ts +8 -9
  32. package/dist/functions/json.d.ts +3 -4
  33. package/dist/functions/math.d.ts +1 -2
  34. package/dist/functions/registry.d.ts +5 -6
  35. package/dist/functions/scalar.d.ts +2 -3
  36. package/dist/functions/table-valued.d.ts +3 -4
  37. package/dist/functions/window.d.ts +1 -2
  38. package/dist/index.d.ts +27 -12
  39. package/dist/index.js +986 -203
  40. package/dist/index.js.map +4 -4
  41. package/dist/indexes/index.d.ts +17 -4
  42. package/dist/json/index.d.ts +7 -8
  43. package/dist/json/jsonb.d.ts +1 -2
  44. package/dist/json/ops.d.ts +8 -3
  45. package/dist/json/parse.d.ts +2 -3
  46. package/dist/json/path.d.ts +1 -2
  47. package/dist/json/stringify.d.ts +1 -2
  48. package/dist/json/tvf.d.ts +1 -2
  49. package/dist/json/types.d.ts +0 -1
  50. package/dist/lexer/tokenize.d.ts +7 -1
  51. package/dist/parser/index.d.ts +11 -5
  52. package/dist/parser/parser.d.ts +3 -3
  53. package/dist/planner/access.d.ts +13 -6
  54. package/dist/planner/index.d.ts +1 -2
  55. package/dist/runtime/clock.d.ts +8 -2
  56. package/dist/runtime/index.d.ts +3 -4
  57. package/dist/runtime/options.d.ts +4 -6
  58. package/dist/runtime/prng.d.ts +12 -1
  59. package/dist/schema/catalog.d.ts +2 -3
  60. package/dist/serialization/codec.d.ts +17 -2
  61. package/dist/serialization/index.d.ts +1 -2
  62. package/dist/storage/database-state.d.ts +16 -8
  63. package/dist/storage/index.d.ts +6 -7
  64. package/dist/storage/row.d.ts +1 -2
  65. package/dist/storage/table.d.ts +8 -4
  66. package/dist/transactions/manager.d.ts +2 -3
  67. package/dist/types/collation.d.ts +1 -2
  68. package/dist/types/strict.d.ts +6 -0
  69. package/dist/types/value.d.ts +30 -2
  70. package/dist/unstable.d.ts +20 -0
  71. package/dist/unstable.js +8734 -0
  72. package/dist/unstable.js.map +7 -0
  73. package/dist/vtable/fts/options.d.ts +0 -1
  74. package/dist/vtable/fts/porter.d.ts +0 -1
  75. package/dist/vtable/fts/query.d.ts +0 -1
  76. package/dist/vtable/fts/table.d.ts +5 -6
  77. package/dist/vtable/fts/tokenize.d.ts +1 -2
  78. package/dist/vtable/fts5.d.ts +1 -2
  79. package/dist/vtable/index.d.ts +2 -3
  80. package/dist/vtable/modules.d.ts +2 -3
  81. package/package.json +19 -10
  82. package/compat/gate-report.json +0 -424
  83. package/dist/api/database.d.ts.map +0 -1
  84. package/dist/api/statement.d.ts.map +0 -1
  85. package/dist/ast/nodes.d.ts.map +0 -1
  86. package/dist/constraints/check.d.ts.map +0 -1
  87. package/dist/errors/index.d.ts.map +0 -1
  88. package/dist/executor/attach.d.ts.map +0 -1
  89. package/dist/executor/ddl.d.ts.map +0 -1
  90. package/dist/executor/dml.d.ts.map +0 -1
  91. package/dist/executor/env.d.ts.map +0 -1
  92. package/dist/executor/execute.d.ts.map +0 -1
  93. package/dist/executor/pragma.d.ts.map +0 -1
  94. package/dist/executor/result.d.ts.map +0 -1
  95. package/dist/executor/select.d.ts.map +0 -1
  96. package/dist/executor/simple-select.d.ts.map +0 -1
  97. package/dist/executor/triggers.d.ts.map +0 -1
  98. package/dist/executor/vtable.d.ts.map +0 -1
  99. package/dist/expressions/context.d.ts.map +0 -1
  100. package/dist/expressions/eval.d.ts.map +0 -1
  101. package/dist/expressions/index.d.ts.map +0 -1
  102. package/dist/expressions/like.d.ts.map +0 -1
  103. package/dist/functions/aggregate.d.ts.map +0 -1
  104. package/dist/functions/datetime.d.ts.map +0 -1
  105. package/dist/functions/extensions.d.ts.map +0 -1
  106. package/dist/functions/index.d.ts.map +0 -1
  107. package/dist/functions/json.d.ts.map +0 -1
  108. package/dist/functions/math.d.ts.map +0 -1
  109. package/dist/functions/registry.d.ts.map +0 -1
  110. package/dist/functions/scalar.d.ts.map +0 -1
  111. package/dist/functions/table-valued.d.ts.map +0 -1
  112. package/dist/functions/window.d.ts.map +0 -1
  113. package/dist/index.d.ts.map +0 -1
  114. package/dist/indexes/index.d.ts.map +0 -1
  115. package/dist/json/index.d.ts.map +0 -1
  116. package/dist/json/jsonb.d.ts.map +0 -1
  117. package/dist/json/ops.d.ts.map +0 -1
  118. package/dist/json/parse.d.ts.map +0 -1
  119. package/dist/json/path.d.ts.map +0 -1
  120. package/dist/json/stringify.d.ts.map +0 -1
  121. package/dist/json/tvf.d.ts.map +0 -1
  122. package/dist/json/types.d.ts.map +0 -1
  123. package/dist/lexer/tokenize.d.ts.map +0 -1
  124. package/dist/parser/index.d.ts.map +0 -1
  125. package/dist/parser/parser.d.ts.map +0 -1
  126. package/dist/planner/access.d.ts.map +0 -1
  127. package/dist/planner/index.d.ts.map +0 -1
  128. package/dist/runtime/clock.d.ts.map +0 -1
  129. package/dist/runtime/index.d.ts.map +0 -1
  130. package/dist/runtime/options.d.ts.map +0 -1
  131. package/dist/runtime/prng.d.ts.map +0 -1
  132. package/dist/schema/catalog.d.ts.map +0 -1
  133. package/dist/serialization/codec.d.ts.map +0 -1
  134. package/dist/serialization/index.d.ts.map +0 -1
  135. package/dist/storage/database-state.d.ts.map +0 -1
  136. package/dist/storage/index.d.ts.map +0 -1
  137. package/dist/storage/row.d.ts.map +0 -1
  138. package/dist/storage/table.d.ts.map +0 -1
  139. package/dist/transactions/manager.d.ts.map +0 -1
  140. package/dist/types/collation.d.ts.map +0 -1
  141. package/dist/types/value.d.ts.map +0 -1
  142. package/dist/vtable/fts/options.d.ts.map +0 -1
  143. package/dist/vtable/fts/porter.d.ts.map +0 -1
  144. package/dist/vtable/fts/query.d.ts.map +0 -1
  145. package/dist/vtable/fts/table.d.ts.map +0 -1
  146. package/dist/vtable/fts/tokenize.d.ts.map +0 -1
  147. package/dist/vtable/fts5.d.ts.map +0 -1
  148. package/dist/vtable/index.d.ts.map +0 -1
  149. package/dist/vtable/modules.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,12 +1,22 @@
1
1
  // src/errors/index.ts
2
2
  var SqliteError = class extends Error {
3
+ /** Coarse error class (constraint vs syntax vs missing object, …). */
3
4
  category;
5
+ /** SQLite result-code name such as `SQLITE_CONSTRAINT_UNIQUE`. */
4
6
  sqliteCode;
5
- constructor(message, category = "other", sqliteCode) {
7
+ /** Same value as {@link sqliteCode} (Node `err.code` convention). */
8
+ code;
9
+ /**
10
+ * @param message - Human-readable error text.
11
+ * @param category - Coarse class; defaults to `"other"`.
12
+ * @param sqliteCode - SQLite result-code name; defaults to `"SQLITE_ERROR"`.
13
+ */
14
+ constructor(message, category = "other", sqliteCode = "SQLITE_ERROR") {
6
15
  super(message);
7
16
  this.name = "SqliteError";
8
17
  this.category = category;
9
18
  this.sqliteCode = sqliteCode;
19
+ this.code = sqliteCode;
10
20
  }
11
21
  };
12
22
  var TriggerRaiseError = class extends SqliteError {
@@ -1176,10 +1186,14 @@ var Parser = class {
1176
1186
  if (!this.match("ON")) return null;
1177
1187
  this.expect("CONFLICT");
1178
1188
  let targetColumns = null;
1189
+ let targetExprs = null;
1179
1190
  if (this.match("LPAREN")) {
1180
1191
  targetColumns = [];
1192
+ targetExprs = [];
1181
1193
  do {
1182
- targetColumns.push(this.parseIdent());
1194
+ const expr = this.parseExprPrec(PREC.ADD);
1195
+ targetExprs.push(expr);
1196
+ targetColumns.push(expr.type === "column" ? expr.name : "");
1183
1197
  } while (this.match("COMMA"));
1184
1198
  this.expect("RPAREN");
1185
1199
  }
@@ -1187,14 +1201,14 @@ var Parser = class {
1187
1201
  if (this.match("WHERE")) targetWhere = this.parseExpr();
1188
1202
  this.expect("DO");
1189
1203
  if (this.match("NOTHING")) {
1190
- return { targetColumns, targetWhere, action: "nothing" };
1204
+ return { targetColumns, targetExprs, targetWhere, action: "nothing" };
1191
1205
  }
1192
1206
  this.expect("UPDATE");
1193
1207
  this.expect("SET");
1194
1208
  const set = this.parseSetItems();
1195
1209
  let where = null;
1196
1210
  if (this.match("WHERE")) where = this.parseExpr();
1197
- return { targetColumns, targetWhere, action: { set, where } };
1211
+ return { targetColumns, targetExprs, targetWhere, action: { set, where } };
1198
1212
  }
1199
1213
  parseSetItems() {
1200
1214
  const items = [];
@@ -1361,7 +1375,8 @@ var Parser = class {
1361
1375
  columns: [],
1362
1376
  constraints: [],
1363
1377
  asSelect,
1364
- withoutRowid: false
1378
+ withoutRowid: false,
1379
+ strict: false
1365
1380
  };
1366
1381
  }
1367
1382
  this.expect("LPAREN");
@@ -1376,12 +1391,30 @@ var Parser = class {
1376
1391
  } while (this.match("COMMA"));
1377
1392
  this.expect("RPAREN");
1378
1393
  let withoutRowid = false;
1379
- if (this.match("WITHOUT")) {
1380
- const word = this.parseIdent();
1381
- if (word.toUpperCase() !== "ROWID") this.syntaxError("expected ROWID after WITHOUT");
1382
- withoutRowid = true;
1394
+ let strict = false;
1395
+ while (!this.at("SEMI") && !this.at("EOF")) {
1396
+ if (this.match("WITHOUT")) {
1397
+ const word = this.parseIdent();
1398
+ if (word.toUpperCase() !== "ROWID") this.syntaxError("expected ROWID after WITHOUT");
1399
+ withoutRowid = true;
1400
+ } else {
1401
+ const word = this.parseIdent();
1402
+ if (word.toUpperCase() !== "STRICT") this.syntaxError(`unexpected table option ${word}`);
1403
+ strict = true;
1404
+ }
1405
+ this.match("COMMA");
1383
1406
  }
1384
- return { type: "create_table", ifNotExists, temp, name, columns, constraints, asSelect: null, withoutRowid };
1407
+ return {
1408
+ type: "create_table",
1409
+ ifNotExists,
1410
+ temp,
1411
+ name,
1412
+ columns,
1413
+ constraints,
1414
+ asSelect: null,
1415
+ withoutRowid,
1416
+ strict
1417
+ };
1385
1418
  }
1386
1419
  parseColumnDef() {
1387
1420
  const name = this.parseIdent();
@@ -1507,7 +1540,8 @@ var Parser = class {
1507
1540
  this.expect("RPAREN");
1508
1541
  }
1509
1542
  const { onDelete, onUpdate } = this.parseFkActions();
1510
- return { type: "references", table, columns, onDelete, onUpdate };
1543
+ const { deferrable, initiallyDeferred } = this.parseDeferrable();
1544
+ return { type: "references", table, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
1511
1545
  }
1512
1546
  parseTableConstraint() {
1513
1547
  let name = null;
@@ -1554,20 +1588,40 @@ var Parser = class {
1554
1588
  this.expect("RPAREN");
1555
1589
  }
1556
1590
  const { onDelete, onUpdate } = this.parseFkActions();
1557
- return { type: "foreign_key", columns, refTable, refColumns, onDelete, onUpdate, name };
1591
+ const { deferrable, initiallyDeferred } = this.parseDeferrable();
1592
+ return {
1593
+ type: "foreign_key",
1594
+ columns,
1595
+ refTable,
1596
+ refColumns,
1597
+ onDelete,
1598
+ onUpdate,
1599
+ name,
1600
+ deferrable,
1601
+ initiallyDeferred
1602
+ };
1558
1603
  }
1559
1604
  this.syntaxError("expected table constraint");
1560
1605
  }
1561
1606
  parseIndexedColumns() {
1562
1607
  const cols = [];
1563
1608
  do {
1564
- const name = this.parseIdent();
1609
+ const expr = this.parseExprPrec(PREC.ADD);
1565
1610
  let collate = null;
1611
+ let inner = expr;
1612
+ if (inner.type === "collate") {
1613
+ collate = inner.collation;
1614
+ inner = inner.expr;
1615
+ }
1566
1616
  if (this.match("COLLATE")) collate = this.parseIdent();
1567
1617
  let order = null;
1568
1618
  if (this.match("ASC")) order = "ASC";
1569
1619
  else if (this.match("DESC")) order = "DESC";
1570
- cols.push({ name, collate, order });
1620
+ if (inner.type === "column" && inner.table === null) {
1621
+ cols.push({ name: inner.name, collate, order });
1622
+ } else {
1623
+ cols.push({ name: "", collate, order, expr: inner });
1624
+ }
1571
1625
  } while (this.match("COMMA"));
1572
1626
  return cols;
1573
1627
  }
@@ -1610,6 +1664,23 @@ var Parser = class {
1610
1664
  }
1611
1665
  return { onDelete, onUpdate };
1612
1666
  }
1667
+ parseDeferrable() {
1668
+ let deferrable = false;
1669
+ let initiallyDeferred = false;
1670
+ if (this.match("NOT")) {
1671
+ this.expect("DEFERRABLE");
1672
+ } else if (this.match("DEFERRABLE")) {
1673
+ deferrable = true;
1674
+ }
1675
+ if (this.match("INITIALLY")) {
1676
+ if (this.match("DEFERRED")) initiallyDeferred = true;
1677
+ else {
1678
+ this.expect("IMMEDIATE");
1679
+ initiallyDeferred = false;
1680
+ }
1681
+ }
1682
+ return { deferrable, initiallyDeferred: deferrable && initiallyDeferred };
1683
+ }
1613
1684
  parseOrConflict() {
1614
1685
  if (!this.match("OR")) return null;
1615
1686
  return this.parseConflictAction();
@@ -2214,13 +2285,19 @@ var Parser = class {
2214
2285
  start = this.parseFrameBound(true);
2215
2286
  end = { kind: "current_row" };
2216
2287
  }
2217
- this.match("EXCLUDE");
2218
- if (this.at("CURRENT")) this.match("CURRENT");
2219
- if (this.at("ROW")) this.match("ROW");
2220
- if (this.at("TIES")) this.match("TIES");
2221
- if (this.at("OTHERS")) this.match("OTHERS");
2222
- if (this.at("GROUP")) this.match("GROUP");
2223
- return { type, start, end };
2288
+ let exclude = null;
2289
+ if (this.match("EXCLUDE")) {
2290
+ if (this.match("NO")) {
2291
+ this.expect("OTHERS");
2292
+ exclude = "no_others";
2293
+ } else if (this.match("CURRENT")) {
2294
+ this.expect("ROW");
2295
+ exclude = "current_row";
2296
+ } else if (this.match("GROUP")) exclude = "group";
2297
+ else if (this.match("TIES")) exclude = "ties";
2298
+ else this.syntaxError("expected EXCLUDE CURRENT ROW, GROUP, TIES, or NO OTHERS");
2299
+ }
2300
+ return { type, start, end, exclude };
2224
2301
  }
2225
2302
  parseFrameBound(precedingSide) {
2226
2303
  if (this.match("UNBOUNDED")) {
@@ -2268,6 +2345,9 @@ var DEFAULT_DATABASE_SEED = 1;
2268
2345
  // src/runtime/prng.ts
2269
2346
  var Prng = class _Prng {
2270
2347
  state;
2348
+ /**
2349
+ * @param seed - Engine seed. `0` is replaced with a non-zero constant.
2350
+ */
2271
2351
  constructor(seed = 1) {
2272
2352
  let s = typeof seed === "bigint" ? seed : BigInt(seed | 0);
2273
2353
  if (s === 0n) s = 0x9e3779b97f4a7c15n;
@@ -2289,10 +2369,16 @@ var Prng = class _Prng {
2289
2369
  if (signed === -(1n << 63n)) signed += 1n;
2290
2370
  return signed;
2291
2371
  }
2372
+ /** Next float in `[0, 1)` from 53 bits of {@link nextU64}. */
2292
2373
  nextFloat() {
2293
2374
  const bits = this.nextU64();
2294
2375
  return Number(bits >> 11n) / Number(1n << 53n);
2295
2376
  }
2377
+ /**
2378
+ * Next integer in `[min, max]` (inclusive).
2379
+ *
2380
+ * @throws {RangeError} If `max < min`.
2381
+ */
2296
2382
  nextInt(min, max) {
2297
2383
  if (max < min) throw new RangeError("max < min");
2298
2384
  const span = max - min + 1;
@@ -2302,29 +2388,21 @@ var Prng = class _Prng {
2302
2388
  getState() {
2303
2389
  return this.state;
2304
2390
  }
2391
+ /** Restore unsigned 64-bit engine state from {@link getState}. */
2305
2392
  setState(state) {
2306
2393
  this.state = BigInt.asUintN(64, state);
2307
2394
  }
2395
+ /** Independent copy with the same engine state. */
2308
2396
  clone() {
2309
2397
  const copy = new _Prng(1);
2310
2398
  copy.state = this.state;
2311
2399
  return copy;
2312
2400
  }
2313
2401
  };
2314
- function deriveSeed(...parts) {
2315
- let hash = 2166136261;
2316
- for (const part of parts) {
2317
- const text2 = String(part);
2318
- for (let i = 0; i < text2.length; i++) {
2319
- hash ^= text2.charCodeAt(i);
2320
- hash = Math.imul(hash, 16777619);
2321
- }
2322
- }
2323
- return hash | 0;
2324
- }
2325
2402
 
2326
2403
  // src/types/value.ts
2327
2404
  var SqlReal = class {
2405
+ /** IEEE float payload (`-0` already canonicalized to `+0`). */
2328
2406
  value;
2329
2407
  constructor(value) {
2330
2408
  this.value = canonicalizeNumber(value);
@@ -3458,6 +3536,23 @@ function jsonArrowPath(right) {
3458
3536
  function extractOne(root, path) {
3459
3537
  return pathGet(root, parseJsonPath(path));
3460
3538
  }
3539
+ function arrayInsertJson(root, path, value) {
3540
+ const steps = parseJsonPath(path);
3541
+ const last = steps[steps.length - 1];
3542
+ if (!last || last.kind === "root" || last.kind === "key") {
3543
+ throw new SqliteError("JSON path error", "other");
3544
+ }
3545
+ const result = cloneJson(root);
3546
+ const parentInfo = pathParent(result, steps);
3547
+ if (parentInfo?.parent.kind !== "array") return result;
3548
+ const parent = parentInfo.parent;
3549
+ let idx = parentInfo.index;
3550
+ if (last.kind === "fromEnd") idx = parent.elements.length - last.n;
3551
+ if (last.kind === "append") idx = parent.elements.length;
3552
+ if (idx < 0 || idx > parent.elements.length) return result;
3553
+ parent.elements.splice(idx, 0, cloneJson(value));
3554
+ return result;
3555
+ }
3461
3556
  function mutateJson(root, path, value, mode) {
3462
3557
  const steps = parseJsonPath(path);
3463
3558
  if (steps.length <= 1) {
@@ -3678,6 +3773,26 @@ function textOf(value) {
3678
3773
  if (isSqlJsonText(value)) return value.value;
3679
3774
  return String(value);
3680
3775
  }
3776
+ function arrayInsertArgs(args, asJsonb) {
3777
+ if (args.length === 0) {
3778
+ throw new SqliteError("wrong number of arguments to function json_array_insert()", "misuse");
3779
+ }
3780
+ if (args.length % 2 === 0) {
3781
+ throw new SqliteError("json_array_insert() requires an odd number of arguments", "misuse");
3782
+ }
3783
+ if (args.some((arg) => arg === null)) return null;
3784
+ try {
3785
+ let node = ensureJson(args[0]);
3786
+ for (let i = 1; i < args.length; i += 2) {
3787
+ const path = textOf(args[i]);
3788
+ const value = sqlValueToJsonNode(args[i + 1]);
3789
+ node = arrayInsertJson(node, path, value);
3790
+ }
3791
+ return asJsonb ? jsonNodeToSql(node, "jsonb") : jsonNodeToSql(node, "json");
3792
+ } catch (e) {
3793
+ wrapJsonError(e);
3794
+ }
3795
+ }
3681
3796
  function mutateArgs(args, mode, asJsonb) {
3682
3797
  if (args.length === 0) throw new SqliteError(`wrong number of arguments to function json_${mode}()`, "misuse");
3683
3798
  if (args.length % 2 === 0) {
@@ -3816,6 +3931,12 @@ var jsonScalarFunctions = {
3816
3931
  jsonb_insert(args) {
3817
3932
  return mutateArgs(args, "insert", true);
3818
3933
  },
3934
+ json_array_insert(args) {
3935
+ return arrayInsertArgs(args, false);
3936
+ },
3937
+ jsonb_array_insert(args) {
3938
+ return arrayInsertArgs(args, true);
3939
+ },
3819
3940
  json_replace(args) {
3820
3941
  return mutateArgs(args, "replace", false);
3821
3942
  },
@@ -4859,6 +4980,10 @@ var scalarFunctions = {
4859
4980
  requireArgs3("sqlite_log", args, 2);
4860
4981
  return null;
4861
4982
  },
4983
+ load_extension(args) {
4984
+ requireArgs3("load_extension", args, 1, 2);
4985
+ throw new SqliteError("not authorized", "misuse");
4986
+ },
4862
4987
  like(args) {
4863
4988
  requireArgs3("like", args, 2, 3);
4864
4989
  if (args[0] === null || args[1] === null || args[2] === null) return null;
@@ -5599,12 +5724,20 @@ function raiseActionName(expr) {
5599
5724
  var IndexStore = class _IndexStore {
5600
5725
  name;
5601
5726
  entries;
5602
- constructor(name = "index", entries) {
5727
+ keyValues;
5728
+ sortedKeys;
5729
+ frozen = false;
5730
+ constructor(name = "index", entries, keyValues) {
5603
5731
  this.name = name;
5604
5732
  this.entries = /* @__PURE__ */ new Map();
5733
+ this.keyValues = /* @__PURE__ */ new Map();
5734
+ this.sortedKeys = null;
5605
5735
  if (entries) {
5606
5736
  for (const [key, rowids] of entries) this.entries.set(key, [...rowids]);
5607
5737
  }
5738
+ if (keyValues) {
5739
+ for (const [key, values] of keyValues) this.keyValues.set(key, [...values]);
5740
+ }
5608
5741
  }
5609
5742
  checkUnique(values, rowid) {
5610
5743
  const key = serializeIndexKey(values);
@@ -5622,12 +5755,15 @@ var IndexStore = class _IndexStore {
5622
5755
  }
5623
5756
  }
5624
5757
  insert(values, rowid, unique = true) {
5758
+ this.assertMutable();
5625
5759
  const key = serializeIndexKey(values);
5626
5760
  if (key === null) return;
5627
5761
  if (unique) this.checkUnique(values, rowid);
5628
5762
  const existing = this.entries.get(key);
5629
5763
  if (!existing) {
5630
5764
  this.entries.set(key, [rowid]);
5765
+ this.keyValues.set(key, [...values]);
5766
+ this.sortedKeys = null;
5631
5767
  return;
5632
5768
  }
5633
5769
  for (const id of existing) if (sameRowid(id, rowid)) return;
@@ -5638,30 +5774,112 @@ var IndexStore = class _IndexStore {
5638
5774
  if (key === null) return [];
5639
5775
  return this.entries.get(key) ?? [];
5640
5776
  }
5777
+ /** Leftmost prefix match: INDEX(a,b) used for WHERE a = ?. */
5778
+ lookupPrefix(values) {
5779
+ if (values.length === 0) return [];
5780
+ const prefix = serializeIndexKey(values);
5781
+ if (prefix === null) return [];
5782
+ const exact = this.entries.get(prefix);
5783
+ const needle = `${prefix}|`;
5784
+ const rowids = exact ? [...exact] : [];
5785
+ for (const [key, ids] of this.entries) {
5786
+ if (key.startsWith(needle)) rowids.push(...ids);
5787
+ }
5788
+ return rowids;
5789
+ }
5790
+ /**
5791
+ * Ordered scan of rowids for a single-column comparison.
5792
+ * `op` applies to the first key component.
5793
+ */
5794
+ rangeLookup(op, bound, bound2) {
5795
+ const rowids = [];
5796
+ const keys = this.orderedKeys();
5797
+ for (const key of keys) {
5798
+ const values = this.keyValues.get(key);
5799
+ if (!values || values[0] === void 0) continue;
5800
+ const cmp = compareSerializedOrder(values[0], bound);
5801
+ let ok = false;
5802
+ if (op === ">") ok = cmp > 0;
5803
+ else if (op === ">=") ok = cmp >= 0;
5804
+ else if (op === "<") ok = cmp < 0;
5805
+ else if (op === "<=") ok = cmp <= 0;
5806
+ else if (op === "between" && bound2 !== void 0) {
5807
+ ok = cmp >= 0 && compareSerializedOrder(values[0], bound2) <= 0;
5808
+ }
5809
+ if (ok) {
5810
+ const ids = this.entries.get(key);
5811
+ if (ids) rowids.push(...ids);
5812
+ }
5813
+ }
5814
+ return rowids;
5815
+ }
5816
+ orderedRowids(desc = false) {
5817
+ const keys = this.orderedKeys();
5818
+ const rowids = [];
5819
+ const seq = desc ? [...keys].reverse() : keys;
5820
+ for (const key of seq) {
5821
+ const ids = this.entries.get(key);
5822
+ if (ids) rowids.push(...ids);
5823
+ }
5824
+ return rowids;
5825
+ }
5641
5826
  remove(values, rowid) {
5827
+ this.assertMutable();
5642
5828
  const key = serializeIndexKey(values);
5643
5829
  if (key === null) return false;
5644
5830
  const existing = this.entries.get(key);
5645
5831
  if (!existing) return false;
5646
5832
  if (rowid === void 0) {
5647
5833
  this.entries.delete(key);
5834
+ this.keyValues.delete(key);
5835
+ this.sortedKeys = null;
5648
5836
  return true;
5649
5837
  }
5650
5838
  const index = existing.findIndex((id) => sameRowid(id, rowid));
5651
5839
  if (index < 0) return false;
5652
5840
  existing.splice(index, 1);
5653
- if (existing.length === 0) this.entries.delete(key);
5841
+ if (existing.length === 0) {
5842
+ this.entries.delete(key);
5843
+ this.keyValues.delete(key);
5844
+ this.sortedKeys = null;
5845
+ }
5654
5846
  return true;
5655
5847
  }
5656
5848
  clear() {
5849
+ this.assertMutable();
5657
5850
  this.entries.clear();
5851
+ this.keyValues.clear();
5852
+ this.sortedKeys = null;
5658
5853
  }
5659
5854
  clone() {
5660
- return new _IndexStore(this.name, this.entries);
5855
+ return new _IndexStore(this.name, this.entries, this.keyValues);
5856
+ }
5857
+ freeze() {
5858
+ this.frozen = true;
5661
5859
  }
5662
5860
  get size() {
5663
5861
  return this.entries.size;
5664
5862
  }
5863
+ orderedKeys() {
5864
+ if (this.sortedKeys) return this.sortedKeys;
5865
+ const keys = [...this.entries.keys()];
5866
+ keys.sort((a, b) => {
5867
+ const left = this.keyValues.get(a);
5868
+ const right = this.keyValues.get(b);
5869
+ if (!left || !right) return a < b ? -1 : a > b ? 1 : 0;
5870
+ const n = Math.min(left.length, right.length);
5871
+ for (let i = 0; i < n; i++) {
5872
+ const cmp = compareSql(left[i] ?? null, right[i] ?? null);
5873
+ if (cmp !== 0) return cmp ?? 0;
5874
+ }
5875
+ return left.length - right.length;
5876
+ });
5877
+ this.sortedKeys = keys;
5878
+ return keys;
5879
+ }
5880
+ assertMutable() {
5881
+ if (this.frozen) throw new SqliteError("internal: cannot mutate a frozen index", "other");
5882
+ }
5665
5883
  };
5666
5884
  function serializeIndexKey(values) {
5667
5885
  if (values.some((value) => value === null)) return null;
@@ -5689,10 +5907,70 @@ function serializeValue(value) {
5689
5907
  for (const byte of value) hex += byte.toString(16).padStart(2, "0");
5690
5908
  return `b:${hex}`;
5691
5909
  }
5910
+ function compareSerializedOrder(left, right) {
5911
+ return compareSql(left, right) ?? 0;
5912
+ }
5692
5913
  function sameRowid(left, right) {
5693
5914
  return typeof left === "bigint" || typeof right === "bigint" ? BigInt(left) === BigInt(right) : left === right;
5694
5915
  }
5695
5916
 
5917
+ // src/types/strict.ts
5918
+ var STRICT_TYPES = /* @__PURE__ */ new Set(["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"]);
5919
+ function isStrictTypeName(typeName) {
5920
+ if (!typeName) return false;
5921
+ return STRICT_TYPES.has(typeName.trim().toUpperCase());
5922
+ }
5923
+ function applyStrictValue(value, typeName, table, column) {
5924
+ if (value === null) return null;
5925
+ const declared = typeName.trim().toUpperCase();
5926
+ const storage = storageClassOf(value);
5927
+ if (declared === "ANY") return value;
5928
+ if (declared === "TEXT") {
5929
+ if (storage === "text") return typeof value === "string" ? value : String(value.value);
5930
+ if (storage === "integer" || storage === "real") return String(isSqlReal(value) ? value.value : value);
5931
+ throw cannotStore(storage, declared, table, column);
5932
+ }
5933
+ if (declared === "INT" || declared === "INTEGER") {
5934
+ if (storage === "integer") return value;
5935
+ if (storage === "real") {
5936
+ const n = isSqlReal(value) ? value.value : Number(value);
5937
+ if (Number.isInteger(n)) return n;
5938
+ throw cannotStore(storage, declared, table, column);
5939
+ }
5940
+ if (storage === "text") {
5941
+ const n = coerceToNumber(value);
5942
+ if (n !== null && Number.isInteger(n) && String(value).trim() === String(n)) return n;
5943
+ throw cannotStore(storage, declared, table, column);
5944
+ }
5945
+ throw cannotStore(storage, declared, table, column);
5946
+ }
5947
+ if (declared === "REAL") {
5948
+ if (storage === "real") return isSqlReal(value) ? value : asSqlReal(Number(value));
5949
+ if (storage === "integer") return asSqlReal(typeof value === "bigint" ? Number(value) : Number(value));
5950
+ if (storage === "text") {
5951
+ const n = coerceToNumber(value);
5952
+ if (n !== null) return asSqlReal(n);
5953
+ }
5954
+ throw cannotStore(storage, declared, table, column);
5955
+ }
5956
+ if (declared === "BLOB") {
5957
+ if (storage === "blob") return value;
5958
+ throw cannotStore(storage, declared, table, column);
5959
+ }
5960
+ throw new SqliteError(`unknown datatype for ${table}.${column}: "${typeName}"`, "other");
5961
+ }
5962
+ function cannotStore(storage, declared, table, column) {
5963
+ const shown = storage === "integer" ? "INT" : storage.toUpperCase();
5964
+ throw new SqliteError(
5965
+ `cannot store ${shown} value in ${declared} column ${table}.${column}`,
5966
+ "datatype_mismatch",
5967
+ "SQLITE_CONSTRAINT_DATATYPE"
5968
+ );
5969
+ }
5970
+ function unknownStrictType(table, column, typeName) {
5971
+ throw new SqliteError(`unknown datatype for ${table}.${column}: "${typeName}"`, "other");
5972
+ }
5973
+
5696
5974
  // src/vtable/fts/options.ts
5697
5975
  var OPTION_NAMES = /* @__PURE__ */ new Set([
5698
5976
  "tokenize",
@@ -7235,11 +7513,13 @@ var Table = class _Table {
7235
7513
  indexes;
7236
7514
  originalSql;
7237
7515
  withoutRowid;
7516
+ strict;
7238
7517
  /** Clustered PK key string → row for WITHOUT ROWID tables. */
7239
7518
  clusteredRows;
7240
7519
  scanCache = null;
7241
7520
  /** Lazy covering hashes: column nameLower → serializeIndexKey → rowids. */
7242
7521
  equalityHashes = null;
7522
+ frozen = false;
7243
7523
  constructor(name, columns, options = {}) {
7244
7524
  this.name = name;
7245
7525
  this.columns = columns.map(cloneColumn);
@@ -7249,6 +7529,7 @@ var Table = class _Table {
7249
7529
  this.indexes = [...options.indexes ?? []];
7250
7530
  this.originalSql = options.originalSql ?? null;
7251
7531
  this.withoutRowid = options.withoutRowid ?? false;
7532
+ this.strict = options.strict ?? false;
7252
7533
  this.clusteredRows = /* @__PURE__ */ new Map();
7253
7534
  this.scanCache = null;
7254
7535
  }
@@ -7284,7 +7565,7 @@ var Table = class _Table {
7284
7565
  }
7285
7566
  /** True when the table has no user constraints that DML must enforce beyond hidden rowid. */
7286
7567
  isUnconstrained() {
7287
- if (this.withoutRowid || this.indexes.length > 0 || this.constraints.length > 0) return false;
7568
+ if (this.withoutRowid || this.strict || this.indexes.length > 0 || this.constraints.length > 0) return false;
7288
7569
  for (const column of this.columns) {
7289
7570
  if (column.notNull || column.primaryKey || column.unique || column.generated || column.defaultExpr) return false;
7290
7571
  }
@@ -7311,7 +7592,7 @@ var Table = class _Table {
7311
7592
  return rows;
7312
7593
  }
7313
7594
  ensureEqualityHash(columnLower) {
7314
- if (this.rows.size < EQUALITY_HASH_MIN_ROWS) return false;
7595
+ if (this.frozen || this.rows.size < EQUALITY_HASH_MIN_ROWS) return false;
7315
7596
  const column = this.columns.find((item) => item.nameLower === columnLower);
7316
7597
  if (!column) return false;
7317
7598
  this.equalityHashes ??= /* @__PURE__ */ new Map();
@@ -7361,6 +7642,7 @@ var Table = class _Table {
7361
7642
  }
7362
7643
  }
7363
7644
  insert(input, options) {
7645
+ this.assertMutable();
7364
7646
  const supplied = isInsertRow(input) ? input : { values: input };
7365
7647
  const values = options?.prepared ? supplied.values instanceof Map ? supplied.values : rowValues(supplied.values) : this.prepareValues(supplied.values);
7366
7648
  if (this.withoutRowid) {
@@ -7403,6 +7685,7 @@ var Table = class _Table {
7403
7685
  return rowid;
7404
7686
  }
7405
7687
  update(rowid, updates) {
7688
+ this.assertMutable();
7406
7689
  const key = canonicalRowid(rowid);
7407
7690
  const existing = this.rows.get(key);
7408
7691
  if (!existing) return void 0;
@@ -7445,6 +7728,7 @@ var Table = class _Table {
7445
7728
  return candidate;
7446
7729
  }
7447
7730
  delete(rowid) {
7731
+ this.assertMutable();
7448
7732
  const key = canonicalRowid(rowid);
7449
7733
  const existing = this.rows.get(key);
7450
7734
  if (!existing) return false;
@@ -7468,13 +7752,20 @@ var Table = class _Table {
7468
7752
  constraints: this.constraints,
7469
7753
  indexes: this.indexes,
7470
7754
  originalSql: this.originalSql,
7471
- withoutRowid: this.withoutRowid
7755
+ withoutRowid: this.withoutRowid,
7756
+ strict: this.strict
7472
7757
  });
7473
7758
  copy.nextRowid = this.nextRowid;
7474
7759
  for (const [rowid, row] of this.rows) copy.rows.set(rowid, cloneRow(row));
7475
7760
  for (const [clusterKey, row] of this.clusteredRows) copy.clusteredRows.set(clusterKey, cloneRow(row));
7476
7761
  return copy;
7477
7762
  }
7763
+ freeze() {
7764
+ this.frozen = true;
7765
+ }
7766
+ assertMutable() {
7767
+ if (this.frozen) throw new SqliteError("internal: cannot mutate a frozen table", "other");
7768
+ }
7478
7769
  /** Rebuild clustered storage after snapshot decode or bulk load. */
7479
7770
  rebuildClusteredRows() {
7480
7771
  if (!this.withoutRowid) return;
@@ -7491,7 +7782,10 @@ var Table = class _Table {
7491
7782
  const result = /* @__PURE__ */ new Map();
7492
7783
  for (const column of this.columns) {
7493
7784
  const key = normalizeColumnName(column.name);
7494
- result.set(key, applyAffinity(cloneSqlValue(supplied.get(key) ?? null), column.affinity));
7785
+ result.set(
7786
+ key,
7787
+ this.strict ? applyStrictValue(cloneSqlValue(supplied.get(key) ?? null), column.typeName ?? "", this.name, column.name) : applyAffinity(cloneSqlValue(supplied.get(key) ?? null), column.affinity)
7788
+ );
7495
7789
  }
7496
7790
  return result;
7497
7791
  }
@@ -7833,6 +8127,12 @@ var DatabaseState = class _DatabaseState {
7833
8127
  if (stmt.columns.length === 0) {
7834
8128
  throw new SqliteError("table must have at least one column", "syntax");
7835
8129
  }
8130
+ if (stmt.strict) {
8131
+ for (const definition of stmt.columns) {
8132
+ const typeName = definition.typeName?.trim() ?? "";
8133
+ if (!isStrictTypeName(typeName)) unknownStrictType(bare, definition.name, typeName || "null");
8134
+ }
8135
+ }
7836
8136
  const normalizedConstraints = [...stmt.constraints];
7837
8137
  for (const definition of stmt.columns) {
7838
8138
  for (const constraint of definition.constraints) {
@@ -7846,7 +8146,9 @@ var DatabaseState = class _DatabaseState {
7846
8146
  refColumns: constraint.columns,
7847
8147
  onDelete: constraint.onDelete,
7848
8148
  onUpdate: constraint.onUpdate,
7849
- name: null
8149
+ name: null,
8150
+ deferrable: constraint.deferrable,
8151
+ initiallyDeferred: constraint.initiallyDeferred
7850
8152
  });
7851
8153
  }
7852
8154
  }
@@ -7854,7 +8156,8 @@ var DatabaseState = class _DatabaseState {
7854
8156
  const table = new Table(bare, columns, {
7855
8157
  constraints: normalizedConstraints,
7856
8158
  originalSql,
7857
- withoutRowid: stmt.withoutRowid
8159
+ withoutRowid: stmt.withoutRowid,
8160
+ strict: stmt.strict
7858
8161
  });
7859
8162
  if (stmt.withoutRowid) {
7860
8163
  const pk = columns.filter((column) => column.primaryKey);
@@ -7937,7 +8240,7 @@ var DatabaseState = class _DatabaseState {
7937
8240
  return this.databaseForSchema(oldSchema, oldName).renameTable(oldBare, splitQualifiedName(newName).bare);
7938
8241
  }
7939
8242
  const oldKey = keyOf(oldBare);
7940
- const table = this.getTable(oldName);
8243
+ const table = this.getWritableTable(oldName);
7941
8244
  const { schema: newSchema, bare: newBare } = splitQualifiedName(newName);
7942
8245
  if (newSchema !== null) {
7943
8246
  throw new SqliteError(`no such table: ${newName}`, "no_such_table");
@@ -7946,11 +8249,13 @@ var DatabaseState = class _DatabaseState {
7946
8249
  this.tables.delete(oldKey);
7947
8250
  table.name = newBare;
7948
8251
  this.tables.set(keyOf(newBare), table);
7949
- for (const index of this.indexes.values()) {
7950
- if (keyOf(index.tableName) === oldKey) index.tableName = newBare;
8252
+ for (const index of [...this.indexes.values()]) {
8253
+ if (keyOf(index.tableName) !== oldKey) continue;
8254
+ this.ensureWritableIndex(index).tableName = newBare;
7951
8255
  }
7952
- for (const trigger of this.triggers.values()) {
7953
- if (keyOf(trigger.tableName) === oldKey) trigger.tableName = newBare;
8256
+ for (const [key, trigger] of this.triggers) {
8257
+ if (keyOf(trigger.tableName) !== oldKey) continue;
8258
+ this.triggers.set(key, { ...trigger, tableName: newBare });
7954
8259
  }
7955
8260
  this.schemaVersion++;
7956
8261
  return table;
@@ -7985,7 +8290,7 @@ var DatabaseState = class _DatabaseState {
7985
8290
  return deleted;
7986
8291
  }
7987
8292
  createIndex(stmt, originalSql = null) {
7988
- const table = this.getTable(stmt.table);
8293
+ const table = this.getWritableTable(stmt.table);
7989
8294
  const { schema, bare } = splitQualifiedName(stmt.name);
7990
8295
  const target = schema !== null ? this.databaseForSchema(schema, stmt.name) : this.databaseForTable(table);
7991
8296
  const key = keyOf(bare);
@@ -7993,6 +8298,7 @@ var DatabaseState = class _DatabaseState {
7993
8298
  if (existing && stmt.ifNotExists) return existing;
7994
8299
  target.assertSchemaNameAvailable(bare);
7995
8300
  for (const indexed of stmt.columns) {
8301
+ if (indexed.expr) continue;
7996
8302
  if (!table.columns.some((column) => keyOf(column.name) === keyOf(indexed.name))) {
7997
8303
  throw new SqliteError(`no such column: ${indexed.name}`, "no_such_column");
7998
8304
  }
@@ -8022,7 +8328,10 @@ var DatabaseState = class _DatabaseState {
8022
8328
  }
8023
8329
  target.indexes.delete(key);
8024
8330
  const table = target.tables.get(keyOf(index.tableName));
8025
- if (table) table.indexes = table.indexes.filter((item) => keyOf(item) !== key);
8331
+ if (table) {
8332
+ const writable = target.ensureWritableTable(table);
8333
+ writable.indexes = writable.indexes.filter((item) => keyOf(item) !== key);
8334
+ }
8026
8335
  target.schemaVersion++;
8027
8336
  return true;
8028
8337
  }
@@ -8106,6 +8415,71 @@ var DatabaseState = class _DatabaseState {
8106
8415
  copy.totalChanges = this.totalChanges;
8107
8416
  copy.foreignKeysEnabled = this.foreignKeysEnabled;
8108
8417
  copy.schemaVersion = this.schemaVersion;
8418
+ copy.userVersion = this.userVersion;
8419
+ return copy;
8420
+ }
8421
+ /** Share table/index objects with the live state. Callers must freeze first. */
8422
+ cloneShallow() {
8423
+ const copy = new _DatabaseState();
8424
+ copy.tables = new Map(this.tables);
8425
+ for (const [key, table] of this.virtualTables) copy.virtualTables.set(key, table.clone());
8426
+ copy.views = new Map(this.views);
8427
+ copy.indexes = new Map(this.indexes);
8428
+ copy.triggers = new Map(this.triggers);
8429
+ for (const [name, attached] of this.attached) {
8430
+ copy.attached.set(name, { state: attached.state.cloneShallow(), filename: attached.filename });
8431
+ }
8432
+ copy.lastInsertRowid = this.lastInsertRowid;
8433
+ copy.changes = this.changes;
8434
+ copy.totalChanges = this.totalChanges;
8435
+ copy.foreignKeysEnabled = this.foreignKeysEnabled;
8436
+ copy.schemaVersion = this.schemaVersion;
8437
+ copy.userVersion = this.userVersion;
8438
+ return copy;
8439
+ }
8440
+ freezeShared() {
8441
+ for (const table of this.tables.values()) table.freeze();
8442
+ for (const index of this.indexes.values()) index.store.freeze();
8443
+ for (const attached of this.attached.values()) attached.state.freezeShared();
8444
+ }
8445
+ thawShared() {
8446
+ for (const table of this.tables.values()) table.frozen = false;
8447
+ for (const index of this.indexes.values()) index.store.frozen = false;
8448
+ for (const attached of this.attached.values()) attached.state.thawShared();
8449
+ }
8450
+ getWritableTable(name) {
8451
+ const { schema, bare } = splitQualifiedName(name);
8452
+ if (schema !== null) {
8453
+ return this.databaseForSchema(schema, name).getWritableTable(bare);
8454
+ }
8455
+ const table = this.getTable(bare);
8456
+ return this.ensureWritableTable(table);
8457
+ }
8458
+ ensureWritableTable(table) {
8459
+ if (!table.frozen) return table;
8460
+ const key = keyOf(table.name);
8461
+ const copy = table.clone();
8462
+ this.tables.set(key, copy);
8463
+ return copy;
8464
+ }
8465
+ getWritableIndex(name) {
8466
+ const key = keyOf(name);
8467
+ const index = this.indexes.get(key);
8468
+ if (!index) throw new SqliteError(`no such index: ${name}`, "other");
8469
+ return this.ensureWritableIndex(index);
8470
+ }
8471
+ ensureWritableIndex(index) {
8472
+ if (!index.store.frozen) return index;
8473
+ const copy = {
8474
+ name: index.name,
8475
+ tableName: index.tableName,
8476
+ unique: index.unique,
8477
+ columns: structuredClone(index.columns),
8478
+ where: index.where ? structuredClone(index.where) : null,
8479
+ originalSql: index.originalSql,
8480
+ store: index.store.clone()
8481
+ };
8482
+ this.indexes.set(keyOf(index.name), copy);
8109
8483
  return copy;
8110
8484
  }
8111
8485
  replaceWith(state, options) {
@@ -8121,6 +8495,7 @@ var DatabaseState = class _DatabaseState {
8121
8495
  this.totalChanges = copy.totalChanges;
8122
8496
  this.foreignKeysEnabled = copy.foreignKeysEnabled;
8123
8497
  this.schemaVersion = copy.schemaVersion;
8498
+ this.userVersion = copy.userVersion;
8124
8499
  }
8125
8500
  assertSchemaNameAvailable(name) {
8126
8501
  const key = keyOf(name);
@@ -8312,7 +8687,8 @@ function encodeDatabaseState(state, runtime) {
8312
8687
  constraints: table.constraints,
8313
8688
  indexes: [...table.indexes].sort(compareNames),
8314
8689
  originalSql: table.originalSql,
8315
- withoutRowid: table.withoutRowid || void 0
8690
+ withoutRowid: table.withoutRowid || void 0,
8691
+ strict: table.strict || void 0
8316
8692
  });
8317
8693
  writer.value(table.nextRowid);
8318
8694
  const rows = [...table.rows.values()].sort((a, b) => compareRowids2(a.rowid, b.rowid));
@@ -8349,7 +8725,7 @@ function decodeDatabaseState(snapshot) {
8349
8725
  throw new SqliteError("invalid sqlite-mem snapshot magic", "other");
8350
8726
  const version = reader.u32();
8351
8727
  if (version !== VERSION && version !== VERSION_V1) {
8352
- throw new SqliteError(`unsupported sqlite-mem snapshot version: ${version}`, "unsupported");
8728
+ throw new SqliteError(`unsupported sqlite-mem snapshot version: ${version}`, "snapshot_version", "SQLITE_FORMAT");
8353
8729
  }
8354
8730
  const state = new DatabaseState();
8355
8731
  state.foreignKeysEnabled = reader.u8() !== 0;
@@ -8364,7 +8740,8 @@ function decodeDatabaseState(snapshot) {
8364
8740
  constraints: meta.constraints,
8365
8741
  indexes: meta.indexes,
8366
8742
  originalSql: meta.originalSql,
8367
- withoutRowid: meta.withoutRowid ?? false
8743
+ withoutRowid: meta.withoutRowid ?? false,
8744
+ strict: meta.strict ?? false
8368
8745
  });
8369
8746
  table.nextRowid = asRowid2(reader.value());
8370
8747
  const rowCount = reader.u32();
@@ -8478,7 +8855,8 @@ var TransactionManager = class {
8478
8855
  if (this.inTransaction) {
8479
8856
  throw new SqliteError("cannot start a transaction within a transaction", "transaction", "SQLITE_ERROR");
8480
8857
  }
8481
- this.transactionSnapshot = this.state.clone();
8858
+ this.state.freezeShared();
8859
+ this.transactionSnapshot = this.state.cloneShallow();
8482
8860
  this.transactionPrngState = this.prng.getState();
8483
8861
  this.savepoints = [];
8484
8862
  this.startedBySavepoint = false;
@@ -8489,6 +8867,7 @@ var TransactionManager = class {
8489
8867
  this.transactionPrngState = null;
8490
8868
  this.savepoints = [];
8491
8869
  this.startedBySavepoint = false;
8870
+ this.state.thawShared();
8492
8871
  }
8493
8872
  rollback(savepoint) {
8494
8873
  this.requireTransaction("cannot rollback - no transaction is active");
@@ -8496,7 +8875,7 @@ var TransactionManager = class {
8496
8875
  const index = this.findSavepoint(savepoint);
8497
8876
  const snapshot2 = this.savepoints[index];
8498
8877
  if (!snapshot2) throw new SqliteError(`no such savepoint: ${savepoint}`, "transaction", "SQLITE_ERROR");
8499
- this.state.replaceWith(snapshot2.state);
8878
+ this.state.replaceWith(snapshot2.state, { adopt: true });
8500
8879
  this.prng.setState(snapshot2.prngState);
8501
8880
  this.savepoints.splice(index + 1);
8502
8881
  return;
@@ -8506,22 +8885,26 @@ var TransactionManager = class {
8506
8885
  if (!snapshot || prngState === null) {
8507
8886
  throw new SqliteError("cannot rollback - no transaction is active", "transaction", "SQLITE_ERROR");
8508
8887
  }
8509
- this.state.replaceWith(snapshot);
8888
+ this.state.replaceWith(snapshot, { adopt: true });
8510
8889
  this.prng.setState(prngState);
8511
8890
  this.transactionSnapshot = null;
8512
8891
  this.transactionPrngState = null;
8513
8892
  this.savepoints = [];
8514
8893
  this.startedBySavepoint = false;
8894
+ this.state.thawShared();
8515
8895
  }
8516
8896
  savepoint(name) {
8517
8897
  if (!this.inTransaction) {
8518
- this.transactionSnapshot = this.state.clone();
8898
+ this.state.freezeShared();
8899
+ this.transactionSnapshot = this.state.cloneShallow();
8519
8900
  this.transactionPrngState = this.prng.getState();
8520
8901
  this.startedBySavepoint = true;
8902
+ } else {
8903
+ this.state.freezeShared();
8521
8904
  }
8522
8905
  this.savepoints.push({
8523
8906
  name,
8524
- state: this.state.clone(),
8907
+ state: this.state.cloneShallow(),
8525
8908
  prngState: this.prng.getState()
8526
8909
  });
8527
8910
  }
@@ -8533,6 +8916,7 @@ var TransactionManager = class {
8533
8916
  this.transactionSnapshot = null;
8534
8917
  this.transactionPrngState = null;
8535
8918
  this.startedBySavepoint = false;
8919
+ this.state.thawShared();
8536
8920
  }
8537
8921
  }
8538
8922
  findSavepoint(name) {
@@ -8597,16 +8981,14 @@ var ExecutionEnv = class {
8597
8981
  }
8598
8982
  getBoundParameter(name) {
8599
8983
  if (typeof name === "number") {
8600
- if (name < 1 || name > this.positional.length)
8601
- throw new SqliteError(`binding parameter ${name} is not supplied`, "misuse");
8984
+ if (name < 1 || name > this.positional.length) return null;
8602
8985
  return this.positional[name - 1];
8603
8986
  }
8604
8987
  const key = name.toLowerCase();
8605
- const value = this.named.get(key);
8606
- if (value === void 0 && !this.named.has(key)) {
8607
- throw new SqliteError(`binding parameter :${name} is not supplied`, "misuse");
8988
+ if (!this.named.has(key)) {
8989
+ return null;
8608
8990
  }
8609
- return value ?? null;
8991
+ return this.named.get(key) ?? null;
8610
8992
  }
8611
8993
  setNamed(name, value) {
8612
8994
  this.named.set(name.toLowerCase(), toSqlValue(value));
@@ -8694,7 +9076,7 @@ var ExecutionEnv = class {
8694
9076
  const result = this.selectRunner(select, this, context);
8695
9077
  return {
8696
9078
  columns: result.columns,
8697
- rows: result.values?.map((row2) => [...row2]) ?? result.rows.map((record) => result.columns.map((column) => record[column] ?? null))
9079
+ rows: result.values.length > 0 ? result.values.map((row2) => [...row2]) : result.rows.map((record) => result.columns.map((column) => record[column] ?? null))
8698
9080
  };
8699
9081
  },
8700
9082
  matchFts: (table, column, query) => {
@@ -8721,14 +9103,30 @@ function toSqlValue(value) {
8721
9103
  return canonicalizeNumber(value);
8722
9104
  }
8723
9105
  if (typeof value === "boolean") return value ? 1 : 0;
8724
- if (value instanceof Uint8Array) return new Uint8Array(value);
9106
+ if (value instanceof SqlReal || value instanceof SqlJsonText) return value;
9107
+ if (value instanceof Uint8Array) {
9108
+ if (isSharedArrayBufferView(value)) {
9109
+ throw new SqliteError("cannot bind SharedArrayBuffer-backed buffers; copy into a Uint8Array first", "misuse");
9110
+ }
9111
+ return new Uint8Array(value);
9112
+ }
8725
9113
  if (value instanceof ArrayBuffer) return new Uint8Array(value);
9114
+ if (typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer) {
9115
+ throw new SqliteError("cannot bind SharedArrayBuffer; copy into an ArrayBuffer or Uint8Array first", "misuse");
9116
+ }
9117
+ if (ArrayBuffer.isView(value)) {
9118
+ const kind = Object.prototype.toString.call(value).slice(8, -1);
9119
+ throw new SqliteError(`cannot bind ${kind}; only Uint8Array and ArrayBuffer are accepted as BLOB values`, "misuse");
9120
+ }
8726
9121
  throw new SqliteError(`unsupported bind value: ${typeof value}`, "datatype_mismatch");
8727
9122
  }
9123
+ function isSharedArrayBufferView(view) {
9124
+ return typeof SharedArrayBuffer !== "undefined" && view.buffer instanceof SharedArrayBuffer;
9125
+ }
8728
9126
 
8729
9127
  // src/executor/result.ts
8730
9128
  function emptyResult(changes = 0, lastInsertRowid = 0) {
8731
- return { columns: [], rows: [], changes, lastInsertRowid };
9129
+ return { columns: [], rows: [], values: [], changes, lastInsertRowid };
8732
9130
  }
8733
9131
  function exportSqlValue(value) {
8734
9132
  if (isSqlReal(value)) return value.value;
@@ -8740,7 +9138,7 @@ function valuesToResult(columns, values, changes = 0, lastInsertRowid = 0, optio
8740
9138
  const keepValues = options?.keepValues !== false;
8741
9139
  return {
8742
9140
  columns,
8743
- values: keepValues ? values.map((row) => row.map(exportSqlValue)) : void 0,
9141
+ values: keepValues ? values.map((row) => row.map(exportSqlValue)) : [],
8744
9142
  rows: named ? values.map((row) => {
8745
9143
  const object = {};
8746
9144
  for (let index = 0; index < columns.length; index++) {
@@ -8753,7 +9151,10 @@ function valuesToResult(columns, values, changes = 0, lastInsertRowid = 0, optio
8753
9151
  };
8754
9152
  }
8755
9153
  function resultValues(result) {
8756
- return result.values?.map((row) => [...row]) ?? result.rows.map((row) => result.columns.map((column) => row[column] ?? null));
9154
+ if (result.values.length > 0 || result.rows.length === 0) {
9155
+ return result.values.map((row) => [...row]);
9156
+ }
9157
+ return result.rows.map((row) => result.columns.map((column) => row[column] ?? null));
8757
9158
  }
8758
9159
 
8759
9160
  // src/executor/attach.ts
@@ -8935,6 +9336,44 @@ function buildSqliteMaster(state) {
8935
9336
  return buildSchemaCatalog(state, "sqlite_master");
8936
9337
  }
8937
9338
 
9339
+ // src/expressions/equals.ts
9340
+ function exprEquals(left, right) {
9341
+ if (left.type !== right.type) return false;
9342
+ switch (left.type) {
9343
+ case "null":
9344
+ return true;
9345
+ case "literal":
9346
+ return right.type === "literal" && valuesEqual(left.value, right.value);
9347
+ case "column":
9348
+ return right.type === "column" && (left.table ?? "").toLowerCase() === (right.table ?? "").toLowerCase() && left.name.toLowerCase() === right.name.toLowerCase();
9349
+ case "unary":
9350
+ return right.type === "unary" && left.op === right.op && exprEquals(left.expr, right.expr);
9351
+ case "binary":
9352
+ return right.type === "binary" && left.op === right.op && exprEquals(left.left, right.left) && exprEquals(left.right, right.right);
9353
+ case "function":
9354
+ return right.type === "function" && left.name.toLowerCase() === right.name.toLowerCase() && left.distinct === right.distinct && argsEqual(left.args, right.args);
9355
+ case "collate":
9356
+ return right.type === "collate" && left.collation.toLowerCase() === right.collation.toLowerCase() && exprEquals(left.expr, right.expr);
9357
+ case "cast":
9358
+ return right.type === "cast" && (left.typeName ?? "").toUpperCase() === (right.typeName ?? "").toUpperCase() && exprEquals(left.expr, right.expr);
9359
+ default:
9360
+ return JSON.stringify(left) === JSON.stringify(right);
9361
+ }
9362
+ }
9363
+ function argsEqual(left, right) {
9364
+ if (left === "*" || right === "*") return left === right;
9365
+ if (left.length !== right.length) return false;
9366
+ return left.every((expr, index) => exprEquals(expr, right[index]));
9367
+ }
9368
+ function valuesEqual(left, right) {
9369
+ if (left === right) return true;
9370
+ if (left instanceof Uint8Array && right instanceof Uint8Array) {
9371
+ if (left.length !== right.length) return false;
9372
+ return left.every((byte, index) => byte === right[index]);
9373
+ }
9374
+ return false;
9375
+ }
9376
+
8938
9377
  // src/planner/access.ts
8939
9378
  function conjunctions(expr) {
8940
9379
  if (expr.type === "binary" && expr.op === "AND") {
@@ -8955,6 +9394,16 @@ function equalityAgainstConst(expr) {
8955
9394
  }
8956
9395
  return null;
8957
9396
  }
9397
+ function equalityAgainstExpr(expr) {
9398
+ if (expr.type !== "binary" || expr.op !== "=" && expr.op !== "==") return null;
9399
+ if (expr.left.type !== "column" && isRowIndependentExpr(expr.right)) {
9400
+ return { expr: expr.left, valueExpr: expr.right };
9401
+ }
9402
+ if (expr.right.type !== "column" && isRowIndependentExpr(expr.left)) {
9403
+ return { expr: expr.right, valueExpr: expr.left };
9404
+ }
9405
+ return null;
9406
+ }
8958
9407
  function whereFullyCovered(where, coveredColumns, alias, tableName) {
8959
9408
  for (const part of conjunctions(where)) {
8960
9409
  const eq = equalityAgainstConst(part);
@@ -8991,8 +9440,8 @@ function lookupTableRows(from, where, env) {
8991
9440
  } catch {
8992
9441
  return null;
8993
9442
  }
8994
- const equalities = conjunctions(where).map(equalityAgainstConst).filter((item) => item !== null).filter((item) => matchesTable(item.table, alias, table.name));
8995
- if (equalities.length === 0) return null;
9443
+ const parts = conjunctions(where);
9444
+ const equalities = parts.map(equalityAgainstConst).filter((item) => item !== null).filter((item) => matchesTable(item.table, alias, table.name));
8996
9445
  const evalConst = (expr) => {
8997
9446
  try {
8998
9447
  return evalExpr(expr, env.createEvalContext(null));
@@ -9006,7 +9455,6 @@ function lookupTableRows(from, where, env) {
9006
9455
  if (value === void 0) continue;
9007
9456
  resolved.push({ table: eq.table, column: eq.column, value });
9008
9457
  }
9009
- if (resolved.length === 0) return null;
9010
9458
  const pk = table.integerPkColumn();
9011
9459
  for (const eq of resolved) {
9012
9460
  const col = eq.column.toLowerCase();
@@ -9026,17 +9474,40 @@ function lookupTableRows(from, where, env) {
9026
9474
  let best = null;
9027
9475
  for (const indexName of table.indexes) {
9028
9476
  const index = db.indexes.get(indexName.toLowerCase());
9029
- if (!index || index.where) continue;
9477
+ if (!index) continue;
9478
+ if (index.where && !parts.some((part) => exprEquals(part, index.where))) continue;
9479
+ if (index.columns.some((column) => column.expr)) {
9480
+ const prefix2 = [];
9481
+ for (const column of index.columns) {
9482
+ if (!column.expr) {
9483
+ const value2 = byColumn.get(column.name.toLowerCase());
9484
+ if (value2 === void 0) break;
9485
+ prefix2.push(normalizeForCollation(value2, column.collate ?? "BINARY"));
9486
+ continue;
9487
+ }
9488
+ const match = parts.map(equalityAgainstExpr).find((item) => item && exprEquals(item.expr, column.expr));
9489
+ if (!match) break;
9490
+ const value = evalConst(match.valueExpr);
9491
+ if (value === void 0) break;
9492
+ prefix2.push(normalizeForCollation(value, column.collate ?? "BINARY"));
9493
+ }
9494
+ if (prefix2.length === 0) continue;
9495
+ const rowids2 = prefix2.length === index.columns.length ? index.store.lookup(prefix2) : index.store.lookupPrefix(prefix2);
9496
+ best = { rowids: rowids2, columns: index.columns.map((column) => column.name.toLowerCase()) };
9497
+ if (index.unique || rowids2.length <= 1) break;
9498
+ continue;
9499
+ }
9030
9500
  const prefix = [];
9031
9501
  for (const column of index.columns) {
9032
9502
  const value = byColumn.get(column.name.toLowerCase());
9033
9503
  if (value === void 0) break;
9034
9504
  prefix.push(normalizeForCollation(value, column.collate ?? "BINARY"));
9035
9505
  }
9036
- if (prefix.length !== index.columns.length) continue;
9037
- const rowids = index.store.lookup(prefix);
9038
- best = { rowids, columns: index.columns.map((column) => column.name.toLowerCase()) };
9039
- if (index.unique || rowids.length <= 1) break;
9506
+ if (prefix.length === 0) continue;
9507
+ const rowids = prefix.length === index.columns.length ? index.store.lookup(prefix) : index.store.lookupPrefix(prefix);
9508
+ best = { rowids, columns: index.columns.slice(0, prefix.length).map((column) => column.name.toLowerCase()) };
9509
+ if (index.unique && prefix.length === index.columns.length) break;
9510
+ if (rowids.length <= 1) break;
9040
9511
  }
9041
9512
  if (best) {
9042
9513
  const rows = [];
@@ -9055,6 +9526,77 @@ function lookupTableRows(from, where, env) {
9055
9526
  hashed.sort((a, b) => compareRowids3(a.rowid, b.rowid));
9056
9527
  return { table, alias, rows: hashed, coveredColumns: /* @__PURE__ */ new Set([col]) };
9057
9528
  }
9529
+ const range = rangeAgainstConst(where) ?? rangeFromBetween(where);
9530
+ if (range && matchesTable(range.table, alias, table.name)) {
9531
+ const value = evalConst(range.valueExpr);
9532
+ const value2 = range.valueExpr2 ? evalConst(range.valueExpr2) : void 0;
9533
+ if (value !== void 0) {
9534
+ for (const indexName of table.indexes) {
9535
+ const index = db.indexes.get(indexName.toLowerCase());
9536
+ if (!index || index.where || index.columns.length !== 1) continue;
9537
+ if (index.columns[0].expr) continue;
9538
+ if (index.columns[0].name.toLowerCase() !== range.column.toLowerCase()) continue;
9539
+ const bound = normalizeForCollation(value, index.columns[0].collate ?? "BINARY");
9540
+ const bound2 = value2 !== void 0 ? normalizeForCollation(value2, index.columns[0].collate ?? "BINARY") : void 0;
9541
+ const rowids = index.store.rangeLookup(range.op, bound, bound2);
9542
+ const rows = [];
9543
+ for (const rowid of rowids) {
9544
+ const row = table.get(rowid);
9545
+ if (row) rows.push(row);
9546
+ }
9547
+ return { table, alias, rows, coveredColumns: /* @__PURE__ */ new Set([range.column.toLowerCase()]) };
9548
+ }
9549
+ }
9550
+ }
9551
+ return null;
9552
+ }
9553
+ function rangeAgainstConst(expr) {
9554
+ if (expr.type !== "binary") return null;
9555
+ const op = expr.op;
9556
+ if (op !== ">" && op !== ">=" && op !== "<" && op !== "<=") return null;
9557
+ if (expr.left.type === "column" && isRowIndependentExpr(expr.right)) {
9558
+ return { table: expr.left.table, column: expr.left.name, op, valueExpr: expr.right };
9559
+ }
9560
+ if (expr.right.type === "column" && isRowIndependentExpr(expr.left)) {
9561
+ const flipped = op === ">" ? "<" : op === ">=" ? "<=" : op === "<" ? ">" : ">=";
9562
+ return { table: expr.right.table, column: expr.right.name, op: flipped, valueExpr: expr.left };
9563
+ }
9564
+ return null;
9565
+ }
9566
+ function rangeFromBetween(expr) {
9567
+ if (expr.type !== "between" || expr.not || expr.expr.type !== "column") return null;
9568
+ return {
9569
+ table: expr.expr.table,
9570
+ column: expr.expr.name,
9571
+ op: "between",
9572
+ valueExpr: expr.lower,
9573
+ valueExpr2: expr.upper
9574
+ };
9575
+ }
9576
+ function tryIndexedOrder(from, order, env, limit) {
9577
+ if (from.type !== "table" || order.expr.type !== "column") return null;
9578
+ const alias = from.alias ?? from.name;
9579
+ const db = env.state.databaseForSchema(from.schema, from.schema ? `${from.schema}.${from.name}` : from.name);
9580
+ let table;
9581
+ try {
9582
+ table = db.getTable(from.name);
9583
+ } catch {
9584
+ return null;
9585
+ }
9586
+ const col = order.expr.name.toLowerCase();
9587
+ for (const indexName of table.indexes) {
9588
+ const index = db.indexes.get(indexName.toLowerCase());
9589
+ if (!index || index.where || index.columns.length !== 1 || index.columns[0].expr) continue;
9590
+ if (index.columns[0].name.toLowerCase() !== col) continue;
9591
+ const desc = order.dir === "DESC";
9592
+ const rows = [];
9593
+ for (const rowid of index.store.orderedRowids(desc)) {
9594
+ const row = table.get(rowid);
9595
+ if (row) rows.push(row);
9596
+ if (limit !== void 0 && rows.length >= limit) break;
9597
+ }
9598
+ return { table, alias, rows, coveredColumns: /* @__PURE__ */ new Set([col]) };
9599
+ }
9058
9600
  return null;
9059
9601
  }
9060
9602
  function compareRowids3(left, right) {
@@ -9499,6 +10041,7 @@ function executeWith(stmt, env, parent) {
9499
10041
  function executeSelectCore(stmt, env, parent) {
9500
10042
  let scopes;
9501
10043
  let skipWhere = false;
10044
+ let skipOrder = false;
9502
10045
  if (stmt.from && stmt.where) {
9503
10046
  const indexed = tryIndexedTableRows(stmt.from, stmt.where, env);
9504
10047
  if (indexed) {
@@ -9507,6 +10050,28 @@ function executeSelectCore(stmt, env, parent) {
9507
10050
  } else {
9508
10051
  scopes = scanFrom(stmt.from, env, parent);
9509
10052
  }
10053
+ } else if (stmt.from && stmt.orderBy.length === 1 && stmt.orderBy[0]) {
10054
+ let take;
10055
+ if (stmt.limit) {
10056
+ const ctx = env.createEvalContext(null, parent);
10057
+ const limitValue = toInteger(evalExpr(stmt.limit.limit, ctx));
10058
+ const offsetValue = stmt.limit.offset ? toInteger(evalExpr(stmt.limit.offset, ctx)) : 0;
10059
+ if (limitValue !== null && offsetValue !== null) {
10060
+ const offset = Math.max(0, Number(offsetValue));
10061
+ const limit = Number(limitValue);
10062
+ take = limit < 0 ? void 0 : offset + limit;
10063
+ }
10064
+ }
10065
+ const ordered = tryIndexedOrder(
10066
+ stmt.from,
10067
+ { expr: stmt.orderBy[0].expr, dir: stmt.orderBy[0].dir ?? "ASC" },
10068
+ env,
10069
+ take
10070
+ );
10071
+ if (ordered) {
10072
+ scopes = scopesFromTableRows(ordered.table, ordered.rows, ordered.alias, env, parent);
10073
+ skipOrder = true;
10074
+ } else scopes = scanFrom(stmt.from, env, parent);
9510
10075
  } else {
9511
10076
  scopes = stmt.from ? scanFrom(stmt.from, env, parent) : [{ cells: [] }];
9512
10077
  }
@@ -9580,7 +10145,7 @@ function executeSelectCore(stmt, env, parent) {
9580
10145
  return true;
9581
10146
  });
9582
10147
  }
9583
- if (stmt.orderBy.length > 0) {
10148
+ if (stmt.orderBy.length > 0 && !skipOrder) {
9584
10149
  output.sort((left, right) => compareOutput(left, right, stmt.orderBy, columns, env, parent));
9585
10150
  }
9586
10151
  if (stmt.limit) {
@@ -10033,8 +10598,9 @@ function windowValue(expr, current, rows, env, parent, namedWindows) {
10033
10598
  }
10034
10599
  }
10035
10600
  if (expr.func.type === "aggregate") {
10036
- const [start2, end2] = frameBounds(spec, index, partition.length, currentCtx, defaultFrameEnd);
10037
- return aggregateValue(expr.func, partition.slice(start2, end2 + 1), env, parent);
10601
+ const [start2, end2] = frameBounds(spec, index, partition.length, currentCtx, defaultFrameEnd, orderKeys);
10602
+ const framed2 = frameRows(partition, start2, end2, index, orderKeys, spec.frame?.exclude ?? null);
10603
+ return aggregateValue(expr.func, framed2, env, parent);
10038
10604
  }
10039
10605
  const name = expr.func.name.toLowerCase();
10040
10606
  const args = expr.func.args === "*" ? [] : expr.func.args;
@@ -10056,12 +10622,14 @@ function windowValue(expr, current, rows, env, parent, namedWindows) {
10056
10622
  const target = name === "lag" ? index - offset : index + offset;
10057
10623
  return values[target]?.[0] ?? evaluated[2] ?? null;
10058
10624
  }
10059
- const [start, end] = frameBounds(spec, index, partition.length, currentCtx, defaultFrameEnd);
10060
- if (name === "first_value") return values[start]?.[0] ?? null;
10061
- if (name === "last_value") return values[end]?.[0] ?? null;
10625
+ const [start, end] = frameBounds(spec, index, partition.length, currentCtx, defaultFrameEnd, orderKeys);
10626
+ const framed = frameRows(partition, start, end, index, orderKeys, spec.frame?.exclude ?? null);
10627
+ const framedValues = framed.map((row) => args.map((arg) => evalExpr(arg, env.createEvalContext(row, parent))));
10628
+ if (name === "first_value") return framedValues[0]?.[0] ?? null;
10629
+ if (name === "last_value") return framedValues[framedValues.length - 1]?.[0] ?? null;
10062
10630
  if (name === "nth_value") {
10063
- const target = start + Number(toInteger(evaluated[1] ?? null) ?? 0) - 1;
10064
- return target >= start && target <= end ? values[target]?.[0] ?? null : null;
10631
+ const target = Number(toInteger(evaluated[1] ?? null) ?? 0) - 1;
10632
+ return target >= 0 && target < framedValues.length ? framedValues[target]?.[0] ?? null : null;
10065
10633
  }
10066
10634
  if (name === "ntile") {
10067
10635
  const buckets = Math.max(1, Number(toInteger(evaluated[0] ?? 1) ?? 1));
@@ -10108,15 +10676,21 @@ function resolveWindow(spec, namedWindows, seen = /* @__PURE__ */ new Set()) {
10108
10676
  ref: null
10109
10677
  };
10110
10678
  }
10111
- function frameBounds(spec, index, length, ctx, defaultFrameEnd) {
10679
+ function frameBounds(spec, index, length, ctx, defaultFrameEnd, orderKeys) {
10112
10680
  if (!spec.frame) return [0, spec.orderBy.length > 0 ? defaultFrameEnd : Math.max(0, length - 1)];
10113
- const bound = (item, _start) => {
10681
+ const isRangeLike = spec.frame.type === "RANGE" || spec.frame.type === "GROUPS";
10682
+ let peerFirst = index;
10683
+ if (isRangeLike && spec.orderBy.length > 0) {
10684
+ while (peerFirst > 0 && rowsEqual(orderKeys[peerFirst], orderKeys[peerFirst - 1])) peerFirst--;
10685
+ }
10686
+ const bound = (item, isStart) => {
10114
10687
  switch (item.kind) {
10115
10688
  case "unbounded_preceding":
10116
10689
  return 0;
10117
10690
  case "unbounded_following":
10118
10691
  return Math.max(0, length - 1);
10119
10692
  case "current_row":
10693
+ if (isRangeLike) return isStart ? peerFirst : defaultFrameEnd;
10120
10694
  return index;
10121
10695
  case "preceding":
10122
10696
  return Math.max(0, index - Number(toInteger(evalExpr(item.expr, ctx)) ?? 0));
@@ -10126,6 +10700,20 @@ function frameBounds(spec, index, length, ctx, defaultFrameEnd) {
10126
10700
  };
10127
10701
  return [bound(spec.frame.start, true), bound(spec.frame.end, false)];
10128
10702
  }
10703
+ function frameRows(partition, start, end, index, orderKeys, exclude) {
10704
+ let peerFirst = index;
10705
+ let peerLast = index;
10706
+ while (peerFirst > 0 && rowsEqual(orderKeys[peerFirst], orderKeys[peerFirst - 1])) peerFirst--;
10707
+ while (peerLast + 1 < partition.length && rowsEqual(orderKeys[peerLast], orderKeys[peerLast + 1])) peerLast++;
10708
+ const rows = [];
10709
+ for (let i = Math.max(0, start); i <= end && i < partition.length; i++) {
10710
+ if (exclude === "current_row" && i === index) continue;
10711
+ if (exclude === "group" && i >= peerFirst && i <= peerLast) continue;
10712
+ if (exclude === "ties" && i >= peerFirst && i <= peerLast && i !== index) continue;
10713
+ rows.push(partition[i]);
10714
+ }
10715
+ return rows;
10716
+ }
10129
10717
  function compareScopes(a, b, order, env, parent) {
10130
10718
  for (const item of order) {
10131
10719
  const left = evalExpr(item.expr, env.createEvalContext(a, parent));
@@ -10475,11 +11063,12 @@ function executeCreateIndex(stmt, env) {
10475
11063
  value: row.values.get(normalizeColumnName(column.name)) ?? null
10476
11064
  }))
10477
11065
  });
10478
- if (stmt.where && !evalExpr(stmt.where, ctx)) continue;
11066
+ if (stmt.where && isTruthySql(evalExpr(stmt.where, ctx)) !== true) continue;
10479
11067
  index.store.insert(
10480
- stmt.columns.map(
10481
- (column) => normalizeForCollation(row.values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
10482
- ),
11068
+ stmt.columns.map((column) => {
11069
+ const raw = column.expr ? evalExpr(column.expr, ctx) : row.values.get(normalizeColumnName(column.name)) ?? null;
11070
+ return normalizeForCollation(raw, column.collate ?? "BINARY");
11071
+ }),
10483
11072
  row.rowid,
10484
11073
  stmt.unique
10485
11074
  );
@@ -10491,7 +11080,7 @@ function executeCreateIndex(stmt, env) {
10491
11080
  return emptyResult(0, env.state.lastInsertRowid);
10492
11081
  }
10493
11082
  function executeAlterTable(stmt, env) {
10494
- const table = env.state.getTable(stmt.table);
11083
+ const table = env.state.getWritableTable(stmt.table);
10495
11084
  const action = stmt.action;
10496
11085
  if (action.kind === "rename_table") {
10497
11086
  env.state.renameTable(stmt.table, action.newName);
@@ -10510,7 +11099,7 @@ function executeAlterTable(stmt, env) {
10510
11099
  }
10511
11100
  for (const constraint of table.constraints) renameConstraintColumn(constraint, action.oldName, action.newName);
10512
11101
  for (const name of table.indexes) {
10513
- const index = env.state.indexes.get(name.toLowerCase());
11102
+ const index = env.state.getWritableIndex(name);
10514
11103
  if (index) {
10515
11104
  for (const indexed of index.columns)
10516
11105
  if (indexed.name.toLowerCase() === action.oldName.toLowerCase()) indexed.name = action.newName;
@@ -10830,13 +11419,17 @@ function executeFtsDelete(table, rowids) {
10830
11419
  }
10831
11420
 
10832
11421
  // src/executor/dml.ts
11422
+ function storeColumnValue(table, column, value) {
11423
+ if (table.strict) return applyStrictValue(value, column.typeName ?? "", table.name, column.name);
11424
+ return applyAffinity(value, column.affinity);
11425
+ }
10833
11426
  function executeInsert(stmt, env) {
10834
11427
  if (env.state.isVirtualTable(stmt.table)) {
10835
11428
  return executeVirtualInsert(stmt, env);
10836
11429
  }
10837
11430
  const fast = tryFastInsert(stmt, env);
10838
11431
  if (fast) return fast;
10839
- const table = env.state.getTable(stmt.table);
11432
+ const table = env.state.getWritableTable(stmt.table);
10840
11433
  const columnNames = stmt.columns ?? table.columns.map((column) => column.name);
10841
11434
  const rowidIndexes = columnNames.map((name, index) => isRowidName3(name) ? index : -1).filter((index) => index >= 0);
10842
11435
  if (rowidIndexes.length > 1) throw new SqliteError("duplicate column name: rowid", "other");
@@ -10861,7 +11454,7 @@ function executeInsert(stmt, env) {
10861
11454
  const column = table.columns[index];
10862
11455
  const suppliedIndex = suppliedIndexes[index];
10863
11456
  const value = suppliedIndex >= 0 ? source[suppliedIndex] ?? null : null;
10864
- values.set(column.nameLower ?? column.name.toLowerCase(), applyAffinity(value, column.affinity));
11457
+ values.set(column.nameLower ?? column.name.toLowerCase(), storeColumnValue(table, column, value));
10865
11458
  }
10866
11459
  last = table.insert({ values }, { prepared: true, skipValidate: true });
10867
11460
  changes++;
@@ -10877,7 +11470,7 @@ function executeInsert(stmt, env) {
10877
11470
  if (column.generated) continue;
10878
11471
  const suppliedIndex = suppliedIndexes[table.columns.indexOf(column)] ?? -1;
10879
11472
  const value = suppliedIndex >= 0 ? source[suppliedIndex] ?? null : column.defaultExpr ? evalExpr(column.defaultExpr, env.createEvalContext()) : null;
10880
- values.set(column.nameLower ?? column.name.toLowerCase(), applyAffinity(value, column.affinity));
11473
+ values.set(column.nameLower ?? column.name.toLowerCase(), storeColumnValue(table, column, value));
10881
11474
  }
10882
11475
  for (const column of table.columns) {
10883
11476
  if (!column.generated) continue;
@@ -10901,7 +11494,7 @@ function executeInsert(stmt, env) {
10901
11494
  })
10902
11495
  });
10903
11496
  const computed = evalExpr(column.generated.expr, genCtx);
10904
- values.set(normalizeColumnName(column.name), applyAffinity(computed, column.affinity));
11497
+ values.set(normalizeColumnName(column.name), storeColumnValue(table, column, computed));
10905
11498
  } else {
10906
11499
  values.set(normalizeColumnName(column.name), null);
10907
11500
  }
@@ -10910,7 +11503,7 @@ function executeInsert(stmt, env) {
10910
11503
  const conflicts = conflictingRows(table, values, env);
10911
11504
  if (conflicts.length > 0) {
10912
11505
  if (stmt.upsert) {
10913
- const targetConflicts = stmt.upsert.targetColumns ? conflictsMatchingTarget(table, values, env, stmt.upsert.targetColumns) : conflicts;
11506
+ const targetConflicts = conflictsForUpsert(table, values, env, stmt.upsert);
10914
11507
  if (targetConflicts.length === 0) {
10915
11508
  throw new SqliteError(
10916
11509
  `UNIQUE constraint failed: ${table.name}`,
@@ -11004,7 +11597,7 @@ function tryFastInsert(stmt, env) {
11004
11597
  }
11005
11598
  let table;
11006
11599
  try {
11007
- table = env.state.getTable(plan.tableName);
11600
+ table = env.state.getWritableTable(plan.tableName);
11008
11601
  } catch {
11009
11602
  return null;
11010
11603
  }
@@ -11055,7 +11648,7 @@ function executeUpdate(stmt, env) {
11055
11648
  if (env.state.isVirtualTable(stmt.table)) {
11056
11649
  return executeVirtualUpdate(stmt, env);
11057
11650
  }
11058
- const table = env.state.getTable(stmt.table);
11651
+ const table = env.state.getWritableTable(stmt.table);
11059
11652
  const alias = stmt.alias ?? stmt.table;
11060
11653
  const candidates = [];
11061
11654
  if (stmt.from) {
@@ -11114,7 +11707,7 @@ function executeDelete(stmt, env) {
11114
11707
  if (env.state.isVirtualTable(stmt.table)) {
11115
11708
  return executeVirtualDelete(stmt, env);
11116
11709
  }
11117
- const table = env.state.getTable(stmt.table);
11710
+ const table = env.state.getWritableTable(stmt.table);
11118
11711
  const selectedSource = stmt.where === null ? null : tryIndexedTableRows({ type: "table", schema: null, name: stmt.table, alias: stmt.alias }, stmt.where, env);
11119
11712
  const selected = [...selectedSource ? selectedSource.rows : table.scan()].filter((row) => {
11120
11713
  if (!stmt.where) return true;
@@ -11148,7 +11741,7 @@ function evaluateSet(items, table, ctx) {
11148
11741
  if (item.columns.length === 1) {
11149
11742
  const column = columnOf(table, item.columns[0]);
11150
11743
  if (column.generated) throw new SqliteError(`cannot UPDATE generated column "${column.name}"`, "misuse");
11151
- updates.set(normalizeColumnName(column.name), evalExpr(item.expr, ctx));
11744
+ updates.set(normalizeColumnName(column.name), storeColumnValue(table, column, evalExpr(item.expr, ctx)));
11152
11745
  continue;
11153
11746
  }
11154
11747
  if (item.expr.type !== "row" || item.expr.values.length !== item.columns.length) {
@@ -11162,7 +11755,7 @@ function evaluateSet(items, table, ctx) {
11162
11755
  if (column.generated) throw new SqliteError(`cannot UPDATE generated column "${column.name}"`, "misuse");
11163
11756
  updates.set(
11164
11757
  normalizeColumnName(column.name),
11165
- evalExpr(item.expr.type === "row" ? item.expr.values[index] : item.expr, ctx)
11758
+ storeColumnValue(table, column, evalExpr(item.expr.type === "row" ? item.expr.values[index] : item.expr, ctx))
11166
11759
  );
11167
11760
  });
11168
11761
  }
@@ -11189,7 +11782,7 @@ function updateOne(table, row, updates, env) {
11189
11782
  });
11190
11783
  merged.set(
11191
11784
  normalizeColumnName(column.name),
11192
- applyAffinity(evalExpr(column.generated.expr, genCtx), column.affinity)
11785
+ storeColumnValue(table, column, evalExpr(column.generated.expr, genCtx))
11193
11786
  );
11194
11787
  }
11195
11788
  after = table.update(row.rowid, merged);
@@ -11220,20 +11813,23 @@ function validateRow(table, row, env) {
11220
11813
  );
11221
11814
  }
11222
11815
  function addIndexes(table, row, env) {
11223
- const indexes = env.state.databaseForTable(table).indexes;
11816
+ const db = env.state.databaseForTable(table);
11224
11817
  for (const name of table.indexes) {
11225
- const index = indexes.get(name.toLowerCase());
11226
- if (!index) continue;
11818
+ const found = db.indexes.get(name.toLowerCase());
11819
+ if (!found) continue;
11820
+ const index = db.ensureWritableIndex(found);
11227
11821
  if (index.where && isTruthySql(evalExpr(index.where, env.createEvalContext(scopeFor(table, row, table.name, env)))) !== true)
11228
11822
  continue;
11229
- index.store.insert(indexValues(index.columns, row), row.rowid, index.unique);
11823
+ index.store.insert(indexValues(index.columns, row, env, table), row.rowid, index.unique);
11230
11824
  }
11231
11825
  }
11232
11826
  function removeIndexes(table, row, env) {
11233
- const indexes = env.state.databaseForTable(table).indexes;
11827
+ const db = env.state.databaseForTable(table);
11234
11828
  for (const name of table.indexes) {
11235
- const index = indexes.get(name.toLowerCase());
11236
- if (index) index.store.remove(indexValues(index.columns, row), row.rowid);
11829
+ const found = db.indexes.get(name.toLowerCase());
11830
+ if (!found) continue;
11831
+ const index = db.ensureWritableIndex(found);
11832
+ index.store.remove(indexValues(index.columns, row, env, table), row.rowid);
11237
11833
  }
11238
11834
  }
11239
11835
  function removeOne(table, row, env) {
@@ -11266,6 +11862,27 @@ function conflictsMatchingTarget(table, values, env, targetColumns) {
11266
11862
  }
11267
11863
  return conflictsForSets(table, values, matchingSets, env);
11268
11864
  }
11865
+ function conflictsForUpsert(table, values, env, upsert) {
11866
+ if (!upsert.targetColumns) return conflictingRows(table, values, env);
11867
+ const exprs = upsert.targetExprs;
11868
+ const hasExpr = exprs?.some((expr) => expr.type !== "column") ?? false;
11869
+ if (hasExpr && exprs) {
11870
+ const db = env.state.databaseForTable(table);
11871
+ for (const indexName of table.indexes) {
11872
+ const index = db.indexes.get(indexName.toLowerCase());
11873
+ if (!index?.unique || index.columns.length !== exprs.length) continue;
11874
+ const matches = index.columns.every((column, i) => {
11875
+ const expr = exprs[i];
11876
+ if (column.expr) return exprEquals(column.expr, expr);
11877
+ return expr.type === "column" && expr.name.toLowerCase() === column.name.toLowerCase();
11878
+ });
11879
+ if (!matches) continue;
11880
+ if (upsert.targetWhere && index.where && !exprEquals(upsert.targetWhere, index.where)) continue;
11881
+ return conflictsForSets(table, values, [index.columns], env);
11882
+ }
11883
+ }
11884
+ return conflictsMatchingTarget(table, values, env, upsert.targetColumns.filter(Boolean));
11885
+ }
11269
11886
  function uniqueColumnSets(table, env) {
11270
11887
  const sets = [];
11271
11888
  const seen = /* @__PURE__ */ new Set();
@@ -11277,7 +11894,7 @@ function uniqueColumnSets(table, env) {
11277
11894
  };
11278
11895
  for (const indexName of table.indexes) {
11279
11896
  const index = env.state.databaseForTable(table).indexes.get(indexName.toLowerCase());
11280
- if (index?.unique && !index.where) remember(index.columns);
11897
+ if (index?.unique) remember(index.columns);
11281
11898
  }
11282
11899
  const pk = table.integerPkColumn();
11283
11900
  if (pk) remember([{ name: pk.name, collate: pk.collate, order: null }]);
@@ -11303,9 +11920,7 @@ function conflictsForSets(table, values, sets, env) {
11303
11920
  };
11304
11921
  const db = env.state.databaseForTable(table);
11305
11922
  for (const columns of sets) {
11306
- const desired = columns.map(
11307
- (column) => normalizeForCollation(values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
11308
- );
11923
+ const desired = indexValuesFromMap(columns, values, table, env);
11309
11924
  if (desired.some((value) => value === null)) continue;
11310
11925
  if (columns.length === 1) {
11311
11926
  const pk = table.integerPkColumn();
@@ -11318,12 +11933,13 @@ function conflictsForSets(table, values, sets, env) {
11318
11933
  let usedIndex = false;
11319
11934
  for (const indexName of table.indexes) {
11320
11935
  const index = db.indexes.get(indexName.toLowerCase());
11321
- if (!index?.unique || index.where) continue;
11936
+ if (!index?.unique) continue;
11322
11937
  if (index.columns.length !== columns.length) continue;
11323
11938
  if (!index.columns.every((column, i) => normalizeColumnName(column.name) === normalizeColumnName(columns[i].name))) {
11324
11939
  continue;
11325
11940
  }
11326
11941
  usedIndex = true;
11942
+ if (index.where && !indexWhereMatches(index.where, values, table, env)) continue;
11327
11943
  for (const rowid of index.store.lookup(desired)) {
11328
11944
  const row = table.get(rowid);
11329
11945
  if (row) push(row);
@@ -11332,36 +11948,63 @@ function conflictsForSets(table, values, sets, env) {
11332
11948
  }
11333
11949
  if (usedIndex) continue;
11334
11950
  for (const row of table.scan()) {
11335
- const existing = indexValues(columns, row);
11951
+ const existing = indexValues(columns, row, env, table);
11336
11952
  if (desired.every((value, index) => compareSql(value, existing[index] ?? null) === 0)) push(row);
11337
11953
  }
11338
11954
  }
11339
11955
  return found;
11340
11956
  }
11341
- function indexValues(columns, row) {
11342
- return columns.map(
11343
- (column) => normalizeForCollation(row.values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
11344
- );
11957
+ function indexValues(columns, row, env, table) {
11958
+ if (!env || !table) {
11959
+ return columns.map(
11960
+ (column) => normalizeForCollation(row.values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
11961
+ );
11962
+ }
11963
+ const ctx = env.createEvalContext(scopeFor(table, row, table.name, env));
11964
+ return columns.map((column) => {
11965
+ const raw = column.expr ? evalExpr(column.expr, ctx) : row.values.get(normalizeColumnName(column.name)) ?? null;
11966
+ return normalizeForCollation(raw, column.collate ?? "BINARY");
11967
+ });
11968
+ }
11969
+ function indexValuesFromMap(columns, values, table, env) {
11970
+ const fake = { rowid: 0, values };
11971
+ return indexValues(columns, fake, env, table);
11972
+ }
11973
+ function indexWhereMatches(where, values, table, env) {
11974
+ const fake = { rowid: 0, values };
11975
+ return isTruthySql(evalExpr(where, env.createEvalContext(scopeFor(table, fake, table.name, env)))) === true;
11345
11976
  }
11346
11977
  function checkForeignKeys(table, row, env) {
11347
11978
  if (!env.state.foreignKeysEnabled) return;
11348
- const references = [];
11349
11979
  for (const constraint of table.constraints) {
11350
- if (constraint.type === "foreign_key")
11351
- references.push({ columns: constraint.columns, table: constraint.refTable, refColumns: constraint.refColumns });
11352
- }
11353
- for (const reference of references) {
11354
- const values = reference.columns.map((name) => row.values.get(normalizeColumnName(name)) ?? null);
11355
- if (values.some((value) => value === null)) continue;
11356
- const parent = env.state.getTable(reference.table);
11357
- const parentColumns = reference.refColumns ?? parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
11358
- if (![...parent.scan()].some(
11359
- (candidate) => values.every((value, index) => {
11360
- const parentColumn = parentColumns[index];
11361
- return parentColumn !== void 0 && compareSql(value, candidate.values.get(normalizeColumnName(parentColumn)) ?? null) === 0;
11362
- })
11363
- )) {
11364
- throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
11980
+ if (constraint.type !== "foreign_key") continue;
11981
+ if (fkIsDeferred(constraint, env)) continue;
11982
+ assertForeignKeySatisfied(row, constraint, env);
11983
+ }
11984
+ }
11985
+ function fkIsDeferred(constraint, env) {
11986
+ return env.transactions.inTransaction && constraint.initiallyDeferred;
11987
+ }
11988
+ function assertForeignKeySatisfied(row, constraint, env) {
11989
+ const values = constraint.columns.map((name) => row.values.get(normalizeColumnName(name)) ?? null);
11990
+ if (values.some((value) => value === null)) return;
11991
+ const parent = env.state.getTable(constraint.refTable);
11992
+ const parentColumns = constraint.refColumns ?? parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
11993
+ if (![...parent.scan()].some(
11994
+ (candidate) => values.every((value, index) => {
11995
+ const parentColumn = parentColumns[index];
11996
+ return parentColumn !== void 0 && compareSql(value, candidate.values.get(normalizeColumnName(parentColumn)) ?? null) === 0;
11997
+ })
11998
+ )) {
11999
+ throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
12000
+ }
12001
+ }
12002
+ function checkDeferredForeignKeys(env) {
12003
+ if (!env.state.foreignKeysEnabled) return;
12004
+ for (const table of env.state.tables.values()) {
12005
+ for (const constraint of table.constraints) {
12006
+ if (constraint.type !== "foreign_key" || !constraint.initiallyDeferred) continue;
12007
+ for (const row of table.scan()) assertForeignKeySatisfied(row, constraint, env);
11365
12008
  }
11366
12009
  }
11367
12010
  }
@@ -11393,8 +12036,9 @@ function applyReferentialDelete(parent, row, env) {
11393
12036
  } else if (constraint.onDelete === "SET DEFAULT") {
11394
12037
  const updated = updateOne(child, candidate, defaultUpdates(child, constraint.columns, env), env);
11395
12038
  changes += 1 + updated.cascaded;
11396
- } else
12039
+ } else if (!fkIsDeferred(constraint, env)) {
11397
12040
  throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
12041
+ }
11398
12042
  }
11399
12043
  }
11400
12044
  }
@@ -11435,7 +12079,7 @@ function applyReferentialUpdate(parent, before, after, env) {
11435
12079
  } else if (constraint.onUpdate === "SET DEFAULT") {
11436
12080
  const updated = updateOne(child, candidate, defaultUpdates(child, constraint.columns, env), env);
11437
12081
  changes += 1 + updated.cascaded;
11438
- } else {
12082
+ } else if (!fkIsDeferred(constraint, env)) {
11439
12083
  throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
11440
12084
  }
11441
12085
  }
@@ -11901,6 +12545,7 @@ function executeStatement(stmt, env) {
11901
12545
  env.transactions.begin();
11902
12546
  return emptyResult(0, env.state.lastInsertRowid);
11903
12547
  case "commit":
12548
+ checkDeferredForeignKeys(env);
11904
12549
  env.transactions.commit();
11905
12550
  return emptyResult(0, env.state.lastInsertRowid);
11906
12551
  case "rollback":
@@ -11947,7 +12592,8 @@ function executeAnalyze(stmt, env) {
11947
12592
  ],
11948
12593
  constraints: [],
11949
12594
  asSelect: null,
11950
- withoutRowid: false
12595
+ withoutRowid: false,
12596
+ strict: false
11951
12597
  },
11952
12598
  "CREATE TABLE sqlite_stat1(tbl,idx,stat)"
11953
12599
  );
@@ -11990,51 +12636,88 @@ function executeAnalyze(stmt, env) {
11990
12636
  }
11991
12637
 
11992
12638
  // src/api/statement.ts
11993
- var Statement = class {
12639
+ var Statement = class _Statement {
11994
12640
  constructor(database, sql, statements) {
11995
12641
  this.database = database;
11996
12642
  this.sql = sql;
11997
12643
  this.statements = statements;
12644
+ this.schemaVersion = database.state.schemaVersion;
11998
12645
  }
11999
12646
  database;
12000
12647
  sql;
12001
- statements;
12002
- bound = [];
12003
12648
  namedPlan = null;
12004
12649
  env = null;
12005
- bind(...params) {
12006
- this.bound = [...params];
12007
- return this;
12650
+ statements;
12651
+ schemaVersion;
12652
+ /**
12653
+ * Construct a {@link Statement} for {@link Database.prepare} / {@link Database.exec}.
12654
+ * @internal
12655
+ */
12656
+ static create(database, sql, statements) {
12657
+ return new _Statement(database, sql, statements);
12008
12658
  }
12659
+ /**
12660
+ * Execute for side effects (INSERT / UPDATE / DELETE / DDL).
12661
+ *
12662
+ * @param params - Bind values for this call only.
12663
+ * @returns Mutation counters for this execution.
12664
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12665
+ */
12009
12666
  run(...params) {
12010
- const result = this.execute(params.length > 0 ? params : this.bound, { named: false });
12667
+ const result = this.execute(params, { named: false });
12011
12668
  return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
12012
12669
  }
12670
+ /**
12671
+ * Execute and return every result row as an object keyed by column name.
12672
+ *
12673
+ * @typeParam T - Row shape. Defaults to {@link QueryRow}.
12674
+ * @param params - Bind values for this call only.
12675
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12676
+ */
12013
12677
  all(...params) {
12014
- return this.execute(params.length > 0 ? params : this.bound, { named: true }).rows;
12678
+ return this.execute(params, { named: true }).rows;
12015
12679
  }
12016
- /** Full result including column names (needed for empty result-set metadata). */
12680
+ /**
12681
+ * Execute and return the full {@link ResultSet}, including column names.
12682
+ *
12683
+ * Use this when you need metadata for an empty result (column names with zero rows).
12684
+ * {@link all} only returns row objects.
12685
+ *
12686
+ * @param params - Bind values for this call only.
12687
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12688
+ */
12017
12689
  result(...params) {
12018
- return this.execute(params.length > 0 ? params : this.bound, { named: true });
12690
+ return this.execute(params, { named: true });
12019
12691
  }
12692
+ /**
12693
+ * Execute and return the first row, or `undefined` if there are no rows.
12694
+ *
12695
+ * @typeParam T - Row shape. Defaults to {@link QueryRow}.
12696
+ * @param params - Bind values for this call only.
12697
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12698
+ */
12020
12699
  get(...params) {
12021
- return this.execute(params.length > 0 ? params : this.bound, { named: true, maxRows: 1 }).rows[0];
12700
+ return this.execute(params, { named: true, maxRows: 1 }).rows[0];
12022
12701
  }
12023
12702
  execute(params, options) {
12024
12703
  this.database.assertOpen();
12704
+ this.reprepareIfSchemaChanged();
12025
12705
  if (this.statements.length === 0) throw new SqliteError("empty statement", "misuse");
12706
+ this.namedPlan ??= planNamedParameters(this.sql);
12707
+ const expected = this.namedPlan.expectedCount;
12708
+ if (params.length > 0 && params.length !== expected) {
12709
+ throw new SqliteError(`SQLite query expected ${expected} values, received ${params.length}`, "misuse");
12710
+ }
12026
12711
  const env = this.obtainEnv(params);
12027
12712
  env.maxRows = options?.maxRows ?? Number.POSITIVE_INFINITY;
12028
12713
  env.includeNamedRows = options?.named !== false;
12029
12714
  env.includeValues = true;
12030
12715
  this.bindNamed(env, params);
12031
12716
  let result;
12032
- let lastQuery;
12033
12717
  for (const statement of this.statements) {
12034
12718
  result = executeStatement(statement, env);
12035
- if (result.columns.length > 0) lastQuery = result;
12036
12719
  }
12037
- return lastQuery ?? result;
12720
+ return result;
12038
12721
  }
12039
12722
  obtainEnv(params) {
12040
12723
  if (this.env) {
@@ -12051,6 +12734,13 @@ var Statement = class {
12051
12734
  });
12052
12735
  return this.env;
12053
12736
  }
12737
+ reprepareIfSchemaChanged() {
12738
+ if (this.schemaVersion === this.database.state.schemaVersion) return;
12739
+ this.statements = parse(this.sql);
12740
+ this.env = null;
12741
+ this.namedPlan = null;
12742
+ this.schemaVersion = this.database.state.schemaVersion;
12743
+ }
12054
12744
  bindNamed(env, params) {
12055
12745
  this.namedPlan ??= planNamedParameters(this.sql);
12056
12746
  for (const item of this.namedPlan.named) {
@@ -12077,61 +12767,129 @@ function planNamedParameters(sql) {
12077
12767
  }
12078
12768
  }
12079
12769
  }
12080
- return { named };
12770
+ return { named, expectedCount: nextSlot - 1 };
12081
12771
  }
12082
12772
 
12083
12773
  // src/api/database.ts
12084
12774
  var Database = class {
12775
+ /** @internal Engine catalog, tables, and mutation counters. */
12085
12776
  state = new DatabaseState();
12777
+ /** Seed used to construct the PRNG. */
12086
12778
  seed;
12779
+ /**
12780
+ * PRNG backing `random()` / `randomblob()` and related builtins.
12781
+ * Prefer passing `seed` to the constructor.
12782
+ * @internal
12783
+ */
12087
12784
  prng;
12785
+ /**
12786
+ * Clock used by `date('now')` / `datetime('now')` / `CURRENT_TIMESTAMP`.
12787
+ * Prefer passing `now` to the constructor.
12788
+ * @internal
12789
+ */
12088
12790
  now;
12791
+ /** @internal Transaction / savepoint manager. */
12089
12792
  transactions;
12090
12793
  closed = false;
12091
12794
  transactionSequence = 0;
12795
+ /** Depth of active {@link transaction} callbacks (not SQL BEGIN). */
12796
+ apiTransactionDepth = 0;
12797
+ /**
12798
+ * Create an empty in-memory database.
12799
+ *
12800
+ * @param options - Determinism knobs. See {@link DatabaseOptions}.
12801
+ */
12092
12802
  constructor(options = {}) {
12093
12803
  this.seed = options.seed ?? DEFAULT_DATABASE_SEED;
12094
- this.prng = options.prng ?? new Prng(this.seed);
12804
+ this.prng = new Prng(this.seed);
12095
12805
  this.now = resolveClock(options.now);
12096
12806
  this.transactions = new TransactionManager(this.state, this.prng);
12097
12807
  }
12098
- exec(sql, params = []) {
12808
+ /**
12809
+ * Execute SQL for its side effects (DDL/DML). Multiple statements are allowed.
12810
+ *
12811
+ * Does not accept bind parameters — use {@link prepare} or {@link query}.
12812
+ *
12813
+ * @param sql - SQL to run (semicolon-separated statements are ok).
12814
+ * @throws {SqliteError} If the database is closed, extra arguments are passed, or the SQL fails.
12815
+ */
12816
+ exec(sql) {
12099
12817
  this.assertOpen();
12100
- new Statement(this, sql, parse(sql)).run(...params);
12818
+ if (arguments.length > 1) {
12819
+ throw new SqliteError("exec() does not accept parameters; use prepare() or query()", "misuse");
12820
+ }
12821
+ Statement.create(this, sql, parse(sql)).run();
12101
12822
  }
12823
+ /**
12824
+ * Execute a single-statement query and return all rows as objects keyed by column name.
12825
+ *
12826
+ * @typeParam T - Row shape. Defaults to {@link QueryRow}.
12827
+ * @param sql - A single SQL statement (trailing `;` is fine).
12828
+ * @param params - Bound parameters for `?` / `:name` placeholders.
12829
+ * @returns All result rows.
12830
+ * @throws {SqliteError} If the database is closed, `sql` is not a single statement, or execution fails.
12831
+ */
12102
12832
  query(sql, params = []) {
12103
12833
  this.assertOpen();
12104
- return new Statement(this, sql, parse(sql)).all(...params);
12834
+ return this.prepareSingle(sql).all(...params);
12105
12835
  }
12836
+ /**
12837
+ * Compile a single SQL statement into a reusable {@link Statement}.
12838
+ *
12839
+ * @param sql - A single SQL statement (trailing `;` is fine). Multi-statement scripts are rejected.
12840
+ * @throws {SqliteError} If the database is closed or `sql` cannot be prepared as one statement.
12841
+ */
12106
12842
  prepare(sql) {
12107
12843
  this.assertOpen();
12108
- return new Statement(this, sql, parse(sql));
12844
+ return this.prepareSingle(sql);
12109
12845
  }
12846
+ /**
12847
+ * Run `fn` inside a transaction. Commits on success; rolls back if `fn` throws.
12848
+ *
12849
+ * Nested calls use SAVEPOINTs so an inner failure does not abort the outer
12850
+ * transaction. Calling {@link close} from inside `fn` throws `misuse`.
12851
+ *
12852
+ * @param fn - Work to run while the transaction is open.
12853
+ * @returns The value returned by `fn`.
12854
+ * @throws {SqliteError} If the database is closed. Re-throws whatever `fn` throws after rollback.
12855
+ */
12110
12856
  transaction(fn) {
12111
12857
  this.assertOpen();
12112
- if (!this.transactions.inTransaction) {
12113
- this.transactions.begin();
12858
+ this.apiTransactionDepth++;
12859
+ try {
12860
+ if (!this.transactions.inTransaction) {
12861
+ this.transactions.begin();
12862
+ try {
12863
+ const value = fn();
12864
+ this.transactions.commit();
12865
+ return value;
12866
+ } catch (error) {
12867
+ this.transactions.rollback();
12868
+ throw error;
12869
+ }
12870
+ }
12871
+ const name = `__api_transaction_${++this.transactionSequence}`;
12872
+ this.transactions.savepoint(name);
12114
12873
  try {
12115
12874
  const value = fn();
12116
- this.transactions.commit();
12875
+ this.transactions.release(name);
12117
12876
  return value;
12118
12877
  } catch (error) {
12119
- this.transactions.rollback();
12878
+ this.transactions.rollback(name);
12879
+ this.transactions.release(name);
12120
12880
  throw error;
12121
12881
  }
12122
- }
12123
- const name = `__api_transaction_${++this.transactionSequence}`;
12124
- this.transactions.savepoint(name);
12125
- try {
12126
- const value = fn();
12127
- this.transactions.release(name);
12128
- return value;
12129
- } catch (error) {
12130
- this.transactions.rollback(name);
12131
- this.transactions.release(name);
12132
- throw error;
12882
+ } finally {
12883
+ this.apiTransactionDepth--;
12133
12884
  }
12134
12885
  }
12886
+ /**
12887
+ * Serialize schema, rows, PRNG state, and clock into a custom snapshot blob.
12888
+ *
12889
+ * This is not a `.sqlite` file. Restore it with {@link restore}.
12890
+ *
12891
+ * @throws {SqliteError} If the database is closed.
12892
+ */
12135
12893
  snapshot() {
12136
12894
  this.assertOpen();
12137
12895
  return encodeDatabaseState(this.state, {
@@ -12139,6 +12897,16 @@ var Database = class {
12139
12897
  nowMs: this.now().getTime()
12140
12898
  });
12141
12899
  }
12900
+ /**
12901
+ * Replace this database's contents with a blob from {@link snapshot}.
12902
+ *
12903
+ * Restores PRNG state and the clock when the snapshot includes them (v2).
12904
+ * Newer library versions can restore older snapshots; older libraries cannot
12905
+ * restore newer format versions.
12906
+ *
12907
+ * @param snapshot - Bytes previously returned by {@link snapshot}.
12908
+ * @throws {SqliteError} If the database is closed, a transaction is open, or the blob is invalid.
12909
+ */
12142
12910
  restore(snapshot) {
12143
12911
  this.assertOpen();
12144
12912
  if (this.transactions.inTransaction) throw new SqliteError("cannot restore during a transaction", "transaction");
@@ -12150,55 +12918,70 @@ var Database = class {
12150
12918
  this.now = fixedClock(new Date(decoded.runtime.nowMs));
12151
12919
  }
12152
12920
  }
12921
+ /**
12922
+ * Close the database. Further SQL throws {@link SqliteError}. Idempotent.
12923
+ *
12924
+ * Rolls back an open SQL transaction, if any. Throws if called from inside
12925
+ * a {@link transaction} callback.
12926
+ */
12153
12927
  close() {
12154
12928
  if (this.closed) return;
12929
+ if (this.apiTransactionDepth > 0) {
12930
+ throw new SqliteError("cannot close database inside transaction()", "misuse");
12931
+ }
12155
12932
  if (this.transactions.inTransaction) this.transactions.rollback();
12156
12933
  this.closed = true;
12157
12934
  }
12935
+ /**
12936
+ * Rows changed by the most recent INSERT / UPDATE / DELETE (SQLite `changes()`).
12937
+ *
12938
+ * @throws {SqliteError} If the database is closed.
12939
+ */
12158
12940
  get changes() {
12159
12941
  this.assertOpen();
12160
12942
  return this.state.changes;
12161
12943
  }
12944
+ /**
12945
+ * Rowid of the most recent INSERT (SQLite `last_insert_rowid()`).
12946
+ *
12947
+ * @throws {SqliteError} If the database is closed.
12948
+ */
12162
12949
  get lastInsertRowid() {
12163
12950
  this.assertOpen();
12164
12951
  return this.state.lastInsertRowid;
12165
12952
  }
12953
+ /**
12954
+ * Throw if {@link close} has already been called.
12955
+ * @internal
12956
+ * @throws {SqliteError} If the database is closed.
12957
+ */
12166
12958
  assertOpen() {
12167
12959
  if (this.closed) throw new SqliteError("Database is closed", "misuse");
12168
12960
  }
12961
+ prepareSingle(sql) {
12962
+ const statements = parse(sql);
12963
+ if (statements.length === 0) {
12964
+ throw new SqliteError("empty statement", "misuse");
12965
+ }
12966
+ if (statements.length > 1) {
12967
+ throw new SqliteError("query()/prepare() accept a single statement only; use exec() for scripts", "misuse");
12968
+ }
12969
+ return Statement.create(this, sql, statements);
12970
+ }
12169
12971
  };
12972
+ var disposeKey = Symbol.dispose;
12973
+ if (typeof disposeKey === "symbol") {
12974
+ Object.defineProperty(Database.prototype, disposeKey, {
12975
+ value: function() {
12976
+ this.close();
12977
+ },
12978
+ writable: true,
12979
+ configurable: true
12980
+ });
12981
+ }
12170
12982
  export {
12171
- DEFAULT_DATABASE_SEED,
12172
- DEFAULT_NOW,
12173
12983
  Database,
12174
- Prng,
12175
- SqlReal,
12176
12984
  SqliteError,
12177
- Statement,
12178
- affinityFromTypeName,
12179
- applyAffinity,
12180
- asSqlReal,
12181
- canonicalizeNumber,
12182
- cloneSqlValue,
12183
- coerceToNumber,
12184
- compareSql,
12185
- decodeDatabaseState,
12186
- deriveSeed,
12187
- encodeDatabaseState,
12188
- evalExpr,
12189
- fixedClock,
12190
- globMatch,
12191
- isSqlReal,
12192
- isTruthySql,
12193
- likeMatch,
12194
- parse,
12195
- resolveClock,
12196
- sqlValueEquals,
12197
- storageClassOf,
12198
- toInteger,
12199
- tokenize,
12200
- typeofSql,
12201
- utf8Decode,
12202
- utf8Encode
12985
+ Statement
12203
12986
  };
12204
12987
  //# sourceMappingURL=index.js.map