@crvouga/sqlite-mem 0.1.0 → 0.2.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 (146) hide show
  1. package/AGENTS.md +156 -0
  2. package/COMPATIBILITY-AUDIT.md +7 -9
  3. package/COMPATIBILITY.md +4 -3
  4. package/README.md +152 -14
  5. package/compat/coverage.json +797 -512
  6. package/compat/requirements.json +1 -1
  7. package/dist/api/database.d.ts +90 -12
  8. package/dist/api/statement.d.ts +74 -17
  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 +25 -1
  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 +9 -5
  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 +33 -12
  39. package/dist/index.js +900 -120
  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 +5 -4
  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/vtable/fts/options.d.ts +0 -1
  71. package/dist/vtable/fts/porter.d.ts +0 -1
  72. package/dist/vtable/fts/query.d.ts +0 -1
  73. package/dist/vtable/fts/table.d.ts +5 -6
  74. package/dist/vtable/fts/tokenize.d.ts +1 -2
  75. package/dist/vtable/fts5.d.ts +1 -2
  76. package/dist/vtable/index.d.ts +2 -3
  77. package/dist/vtable/modules.d.ts +2 -3
  78. package/package.json +14 -7
  79. package/compat/gate-report.json +0 -424
  80. package/dist/api/database.d.ts.map +0 -1
  81. package/dist/api/statement.d.ts.map +0 -1
  82. package/dist/ast/nodes.d.ts.map +0 -1
  83. package/dist/constraints/check.d.ts.map +0 -1
  84. package/dist/errors/index.d.ts.map +0 -1
  85. package/dist/executor/attach.d.ts.map +0 -1
  86. package/dist/executor/ddl.d.ts.map +0 -1
  87. package/dist/executor/dml.d.ts.map +0 -1
  88. package/dist/executor/env.d.ts.map +0 -1
  89. package/dist/executor/execute.d.ts.map +0 -1
  90. package/dist/executor/pragma.d.ts.map +0 -1
  91. package/dist/executor/result.d.ts.map +0 -1
  92. package/dist/executor/select.d.ts.map +0 -1
  93. package/dist/executor/simple-select.d.ts.map +0 -1
  94. package/dist/executor/triggers.d.ts.map +0 -1
  95. package/dist/executor/vtable.d.ts.map +0 -1
  96. package/dist/expressions/context.d.ts.map +0 -1
  97. package/dist/expressions/eval.d.ts.map +0 -1
  98. package/dist/expressions/index.d.ts.map +0 -1
  99. package/dist/expressions/like.d.ts.map +0 -1
  100. package/dist/functions/aggregate.d.ts.map +0 -1
  101. package/dist/functions/datetime.d.ts.map +0 -1
  102. package/dist/functions/extensions.d.ts.map +0 -1
  103. package/dist/functions/index.d.ts.map +0 -1
  104. package/dist/functions/json.d.ts.map +0 -1
  105. package/dist/functions/math.d.ts.map +0 -1
  106. package/dist/functions/registry.d.ts.map +0 -1
  107. package/dist/functions/scalar.d.ts.map +0 -1
  108. package/dist/functions/table-valued.d.ts.map +0 -1
  109. package/dist/functions/window.d.ts.map +0 -1
  110. package/dist/index.d.ts.map +0 -1
  111. package/dist/indexes/index.d.ts.map +0 -1
  112. package/dist/json/index.d.ts.map +0 -1
  113. package/dist/json/jsonb.d.ts.map +0 -1
  114. package/dist/json/ops.d.ts.map +0 -1
  115. package/dist/json/parse.d.ts.map +0 -1
  116. package/dist/json/path.d.ts.map +0 -1
  117. package/dist/json/stringify.d.ts.map +0 -1
  118. package/dist/json/tvf.d.ts.map +0 -1
  119. package/dist/json/types.d.ts.map +0 -1
  120. package/dist/lexer/tokenize.d.ts.map +0 -1
  121. package/dist/parser/index.d.ts.map +0 -1
  122. package/dist/parser/parser.d.ts.map +0 -1
  123. package/dist/planner/access.d.ts.map +0 -1
  124. package/dist/planner/index.d.ts.map +0 -1
  125. package/dist/runtime/clock.d.ts.map +0 -1
  126. package/dist/runtime/index.d.ts.map +0 -1
  127. package/dist/runtime/options.d.ts.map +0 -1
  128. package/dist/runtime/prng.d.ts.map +0 -1
  129. package/dist/schema/catalog.d.ts.map +0 -1
  130. package/dist/serialization/codec.d.ts.map +0 -1
  131. package/dist/serialization/index.d.ts.map +0 -1
  132. package/dist/storage/database-state.d.ts.map +0 -1
  133. package/dist/storage/index.d.ts.map +0 -1
  134. package/dist/storage/row.d.ts.map +0 -1
  135. package/dist/storage/table.d.ts.map +0 -1
  136. package/dist/transactions/manager.d.ts.map +0 -1
  137. package/dist/types/collation.d.ts.map +0 -1
  138. package/dist/types/value.d.ts.map +0 -1
  139. package/dist/vtable/fts/options.d.ts.map +0 -1
  140. package/dist/vtable/fts/porter.d.ts.map +0 -1
  141. package/dist/vtable/fts/query.d.ts.map +0 -1
  142. package/dist/vtable/fts/table.d.ts.map +0 -1
  143. package/dist/vtable/fts/tokenize.d.ts.map +0 -1
  144. package/dist/vtable/fts5.d.ts.map +0 -1
  145. package/dist/vtable/index.d.ts.map +0 -1
  146. package/dist/vtable/modules.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,14 @@
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
+ /** Optional SQLite result-code name such as `SQLITE_CONSTRAINT`. */
4
6
  sqliteCode;
7
+ /**
8
+ * @param message - Human-readable error text.
9
+ * @param category - Coarse class; defaults to `"other"`.
10
+ * @param sqliteCode - Optional SQLite result-code name.
11
+ */
5
12
  constructor(message, category = "other", sqliteCode) {
6
13
  super(message);
7
14
  this.name = "SqliteError";
@@ -1176,10 +1183,14 @@ var Parser = class {
1176
1183
  if (!this.match("ON")) return null;
1177
1184
  this.expect("CONFLICT");
1178
1185
  let targetColumns = null;
1186
+ let targetExprs = null;
1179
1187
  if (this.match("LPAREN")) {
1180
1188
  targetColumns = [];
1189
+ targetExprs = [];
1181
1190
  do {
1182
- targetColumns.push(this.parseIdent());
1191
+ const expr = this.parseExprPrec(PREC.ADD);
1192
+ targetExprs.push(expr);
1193
+ targetColumns.push(expr.type === "column" ? expr.name : "");
1183
1194
  } while (this.match("COMMA"));
1184
1195
  this.expect("RPAREN");
1185
1196
  }
@@ -1187,14 +1198,14 @@ var Parser = class {
1187
1198
  if (this.match("WHERE")) targetWhere = this.parseExpr();
1188
1199
  this.expect("DO");
1189
1200
  if (this.match("NOTHING")) {
1190
- return { targetColumns, targetWhere, action: "nothing" };
1201
+ return { targetColumns, targetExprs, targetWhere, action: "nothing" };
1191
1202
  }
1192
1203
  this.expect("UPDATE");
1193
1204
  this.expect("SET");
1194
1205
  const set = this.parseSetItems();
1195
1206
  let where = null;
1196
1207
  if (this.match("WHERE")) where = this.parseExpr();
1197
- return { targetColumns, targetWhere, action: { set, where } };
1208
+ return { targetColumns, targetExprs, targetWhere, action: { set, where } };
1198
1209
  }
1199
1210
  parseSetItems() {
1200
1211
  const items = [];
@@ -1361,7 +1372,8 @@ var Parser = class {
1361
1372
  columns: [],
1362
1373
  constraints: [],
1363
1374
  asSelect,
1364
- withoutRowid: false
1375
+ withoutRowid: false,
1376
+ strict: false
1365
1377
  };
1366
1378
  }
1367
1379
  this.expect("LPAREN");
@@ -1376,12 +1388,30 @@ var Parser = class {
1376
1388
  } while (this.match("COMMA"));
1377
1389
  this.expect("RPAREN");
1378
1390
  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;
1391
+ let strict = false;
1392
+ while (!this.at("SEMI") && !this.at("EOF")) {
1393
+ if (this.match("WITHOUT")) {
1394
+ const word = this.parseIdent();
1395
+ if (word.toUpperCase() !== "ROWID") this.syntaxError("expected ROWID after WITHOUT");
1396
+ withoutRowid = true;
1397
+ } else {
1398
+ const word = this.parseIdent();
1399
+ if (word.toUpperCase() !== "STRICT") this.syntaxError(`unexpected table option ${word}`);
1400
+ strict = true;
1401
+ }
1402
+ this.match("COMMA");
1383
1403
  }
1384
- return { type: "create_table", ifNotExists, temp, name, columns, constraints, asSelect: null, withoutRowid };
1404
+ return {
1405
+ type: "create_table",
1406
+ ifNotExists,
1407
+ temp,
1408
+ name,
1409
+ columns,
1410
+ constraints,
1411
+ asSelect: null,
1412
+ withoutRowid,
1413
+ strict
1414
+ };
1385
1415
  }
1386
1416
  parseColumnDef() {
1387
1417
  const name = this.parseIdent();
@@ -1507,7 +1537,8 @@ var Parser = class {
1507
1537
  this.expect("RPAREN");
1508
1538
  }
1509
1539
  const { onDelete, onUpdate } = this.parseFkActions();
1510
- return { type: "references", table, columns, onDelete, onUpdate };
1540
+ const { deferrable, initiallyDeferred } = this.parseDeferrable();
1541
+ return { type: "references", table, columns, onDelete, onUpdate, deferrable, initiallyDeferred };
1511
1542
  }
1512
1543
  parseTableConstraint() {
1513
1544
  let name = null;
@@ -1554,20 +1585,40 @@ var Parser = class {
1554
1585
  this.expect("RPAREN");
1555
1586
  }
1556
1587
  const { onDelete, onUpdate } = this.parseFkActions();
1557
- return { type: "foreign_key", columns, refTable, refColumns, onDelete, onUpdate, name };
1588
+ const { deferrable, initiallyDeferred } = this.parseDeferrable();
1589
+ return {
1590
+ type: "foreign_key",
1591
+ columns,
1592
+ refTable,
1593
+ refColumns,
1594
+ onDelete,
1595
+ onUpdate,
1596
+ name,
1597
+ deferrable,
1598
+ initiallyDeferred
1599
+ };
1558
1600
  }
1559
1601
  this.syntaxError("expected table constraint");
1560
1602
  }
1561
1603
  parseIndexedColumns() {
1562
1604
  const cols = [];
1563
1605
  do {
1564
- const name = this.parseIdent();
1606
+ const expr = this.parseExprPrec(PREC.ADD);
1565
1607
  let collate = null;
1608
+ let inner = expr;
1609
+ if (inner.type === "collate") {
1610
+ collate = inner.collation;
1611
+ inner = inner.expr;
1612
+ }
1566
1613
  if (this.match("COLLATE")) collate = this.parseIdent();
1567
1614
  let order = null;
1568
1615
  if (this.match("ASC")) order = "ASC";
1569
1616
  else if (this.match("DESC")) order = "DESC";
1570
- cols.push({ name, collate, order });
1617
+ if (inner.type === "column" && inner.table === null) {
1618
+ cols.push({ name: inner.name, collate, order });
1619
+ } else {
1620
+ cols.push({ name: "", collate, order, expr: inner });
1621
+ }
1571
1622
  } while (this.match("COMMA"));
1572
1623
  return cols;
1573
1624
  }
@@ -1610,6 +1661,23 @@ var Parser = class {
1610
1661
  }
1611
1662
  return { onDelete, onUpdate };
1612
1663
  }
1664
+ parseDeferrable() {
1665
+ let deferrable = false;
1666
+ let initiallyDeferred = false;
1667
+ if (this.match("NOT")) {
1668
+ this.expect("DEFERRABLE");
1669
+ } else if (this.match("DEFERRABLE")) {
1670
+ deferrable = true;
1671
+ }
1672
+ if (this.match("INITIALLY")) {
1673
+ if (this.match("DEFERRED")) initiallyDeferred = true;
1674
+ else {
1675
+ this.expect("IMMEDIATE");
1676
+ initiallyDeferred = false;
1677
+ }
1678
+ }
1679
+ return { deferrable, initiallyDeferred: deferrable && initiallyDeferred };
1680
+ }
1613
1681
  parseOrConflict() {
1614
1682
  if (!this.match("OR")) return null;
1615
1683
  return this.parseConflictAction();
@@ -2214,13 +2282,19 @@ var Parser = class {
2214
2282
  start = this.parseFrameBound(true);
2215
2283
  end = { kind: "current_row" };
2216
2284
  }
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 };
2285
+ let exclude = null;
2286
+ if (this.match("EXCLUDE")) {
2287
+ if (this.match("NO")) {
2288
+ this.expect("OTHERS");
2289
+ exclude = "no_others";
2290
+ } else if (this.match("CURRENT")) {
2291
+ this.expect("ROW");
2292
+ exclude = "current_row";
2293
+ } else if (this.match("GROUP")) exclude = "group";
2294
+ else if (this.match("TIES")) exclude = "ties";
2295
+ else this.syntaxError("expected EXCLUDE CURRENT ROW, GROUP, TIES, or NO OTHERS");
2296
+ }
2297
+ return { type, start, end, exclude };
2224
2298
  }
2225
2299
  parseFrameBound(precedingSide) {
2226
2300
  if (this.match("UNBOUNDED")) {
@@ -2268,6 +2342,9 @@ var DEFAULT_DATABASE_SEED = 1;
2268
2342
  // src/runtime/prng.ts
2269
2343
  var Prng = class _Prng {
2270
2344
  state;
2345
+ /**
2346
+ * @param seed - Engine seed. `0` is replaced with a non-zero constant.
2347
+ */
2271
2348
  constructor(seed = 1) {
2272
2349
  let s = typeof seed === "bigint" ? seed : BigInt(seed | 0);
2273
2350
  if (s === 0n) s = 0x9e3779b97f4a7c15n;
@@ -2289,10 +2366,16 @@ var Prng = class _Prng {
2289
2366
  if (signed === -(1n << 63n)) signed += 1n;
2290
2367
  return signed;
2291
2368
  }
2369
+ /** Next float in `[0, 1)` from 53 bits of {@link nextU64}. */
2292
2370
  nextFloat() {
2293
2371
  const bits = this.nextU64();
2294
2372
  return Number(bits >> 11n) / Number(1n << 53n);
2295
2373
  }
2374
+ /**
2375
+ * Next integer in `[min, max]` (inclusive).
2376
+ *
2377
+ * @throws {RangeError} If `max < min`.
2378
+ */
2296
2379
  nextInt(min, max) {
2297
2380
  if (max < min) throw new RangeError("max < min");
2298
2381
  const span = max - min + 1;
@@ -2302,9 +2385,11 @@ var Prng = class _Prng {
2302
2385
  getState() {
2303
2386
  return this.state;
2304
2387
  }
2388
+ /** Restore unsigned 64-bit engine state from {@link getState}. */
2305
2389
  setState(state) {
2306
2390
  this.state = BigInt.asUintN(64, state);
2307
2391
  }
2392
+ /** Independent copy with the same engine state. */
2308
2393
  clone() {
2309
2394
  const copy = new _Prng(1);
2310
2395
  copy.state = this.state;
@@ -2325,6 +2410,7 @@ function deriveSeed(...parts) {
2325
2410
 
2326
2411
  // src/types/value.ts
2327
2412
  var SqlReal = class {
2413
+ /** IEEE float payload (`-0` already canonicalized to `+0`). */
2328
2414
  value;
2329
2415
  constructor(value) {
2330
2416
  this.value = canonicalizeNumber(value);
@@ -3458,6 +3544,23 @@ function jsonArrowPath(right) {
3458
3544
  function extractOne(root, path) {
3459
3545
  return pathGet(root, parseJsonPath(path));
3460
3546
  }
3547
+ function arrayInsertJson(root, path, value) {
3548
+ const steps = parseJsonPath(path);
3549
+ const last = steps[steps.length - 1];
3550
+ if (!last || last.kind === "root" || last.kind === "key") {
3551
+ throw new SqliteError("JSON path error", "other");
3552
+ }
3553
+ const result = cloneJson(root);
3554
+ const parentInfo = pathParent(result, steps);
3555
+ if (parentInfo?.parent.kind !== "array") return result;
3556
+ const parent = parentInfo.parent;
3557
+ let idx = parentInfo.index;
3558
+ if (last.kind === "fromEnd") idx = parent.elements.length - last.n;
3559
+ if (last.kind === "append") idx = parent.elements.length;
3560
+ if (idx < 0 || idx > parent.elements.length) return result;
3561
+ parent.elements.splice(idx, 0, cloneJson(value));
3562
+ return result;
3563
+ }
3461
3564
  function mutateJson(root, path, value, mode) {
3462
3565
  const steps = parseJsonPath(path);
3463
3566
  if (steps.length <= 1) {
@@ -3678,6 +3781,26 @@ function textOf(value) {
3678
3781
  if (isSqlJsonText(value)) return value.value;
3679
3782
  return String(value);
3680
3783
  }
3784
+ function arrayInsertArgs(args, asJsonb) {
3785
+ if (args.length === 0) {
3786
+ throw new SqliteError("wrong number of arguments to function json_array_insert()", "misuse");
3787
+ }
3788
+ if (args.length % 2 === 0) {
3789
+ throw new SqliteError("json_array_insert() requires an odd number of arguments", "misuse");
3790
+ }
3791
+ if (args.some((arg) => arg === null)) return null;
3792
+ try {
3793
+ let node = ensureJson(args[0]);
3794
+ for (let i = 1; i < args.length; i += 2) {
3795
+ const path = textOf(args[i]);
3796
+ const value = sqlValueToJsonNode(args[i + 1]);
3797
+ node = arrayInsertJson(node, path, value);
3798
+ }
3799
+ return asJsonb ? jsonNodeToSql(node, "jsonb") : jsonNodeToSql(node, "json");
3800
+ } catch (e) {
3801
+ wrapJsonError(e);
3802
+ }
3803
+ }
3681
3804
  function mutateArgs(args, mode, asJsonb) {
3682
3805
  if (args.length === 0) throw new SqliteError(`wrong number of arguments to function json_${mode}()`, "misuse");
3683
3806
  if (args.length % 2 === 0) {
@@ -3816,6 +3939,12 @@ var jsonScalarFunctions = {
3816
3939
  jsonb_insert(args) {
3817
3940
  return mutateArgs(args, "insert", true);
3818
3941
  },
3942
+ json_array_insert(args) {
3943
+ return arrayInsertArgs(args, false);
3944
+ },
3945
+ jsonb_array_insert(args) {
3946
+ return arrayInsertArgs(args, true);
3947
+ },
3819
3948
  json_replace(args) {
3820
3949
  return mutateArgs(args, "replace", false);
3821
3950
  },
@@ -4859,6 +4988,10 @@ var scalarFunctions = {
4859
4988
  requireArgs3("sqlite_log", args, 2);
4860
4989
  return null;
4861
4990
  },
4991
+ load_extension(args) {
4992
+ requireArgs3("load_extension", args, 1, 2);
4993
+ throw new SqliteError("not authorized", "misuse");
4994
+ },
4862
4995
  like(args) {
4863
4996
  requireArgs3("like", args, 2, 3);
4864
4997
  if (args[0] === null || args[1] === null || args[2] === null) return null;
@@ -5599,12 +5732,20 @@ function raiseActionName(expr) {
5599
5732
  var IndexStore = class _IndexStore {
5600
5733
  name;
5601
5734
  entries;
5602
- constructor(name = "index", entries) {
5735
+ keyValues;
5736
+ sortedKeys;
5737
+ frozen = false;
5738
+ constructor(name = "index", entries, keyValues) {
5603
5739
  this.name = name;
5604
5740
  this.entries = /* @__PURE__ */ new Map();
5741
+ this.keyValues = /* @__PURE__ */ new Map();
5742
+ this.sortedKeys = null;
5605
5743
  if (entries) {
5606
5744
  for (const [key, rowids] of entries) this.entries.set(key, [...rowids]);
5607
5745
  }
5746
+ if (keyValues) {
5747
+ for (const [key, values] of keyValues) this.keyValues.set(key, [...values]);
5748
+ }
5608
5749
  }
5609
5750
  checkUnique(values, rowid) {
5610
5751
  const key = serializeIndexKey(values);
@@ -5622,12 +5763,15 @@ var IndexStore = class _IndexStore {
5622
5763
  }
5623
5764
  }
5624
5765
  insert(values, rowid, unique = true) {
5766
+ this.assertMutable();
5625
5767
  const key = serializeIndexKey(values);
5626
5768
  if (key === null) return;
5627
5769
  if (unique) this.checkUnique(values, rowid);
5628
5770
  const existing = this.entries.get(key);
5629
5771
  if (!existing) {
5630
5772
  this.entries.set(key, [rowid]);
5773
+ this.keyValues.set(key, [...values]);
5774
+ this.sortedKeys = null;
5631
5775
  return;
5632
5776
  }
5633
5777
  for (const id of existing) if (sameRowid(id, rowid)) return;
@@ -5638,30 +5782,112 @@ var IndexStore = class _IndexStore {
5638
5782
  if (key === null) return [];
5639
5783
  return this.entries.get(key) ?? [];
5640
5784
  }
5785
+ /** Leftmost prefix match: INDEX(a,b) used for WHERE a = ?. */
5786
+ lookupPrefix(values) {
5787
+ if (values.length === 0) return [];
5788
+ const prefix = serializeIndexKey(values);
5789
+ if (prefix === null) return [];
5790
+ const exact = this.entries.get(prefix);
5791
+ const needle = `${prefix}|`;
5792
+ const rowids = exact ? [...exact] : [];
5793
+ for (const [key, ids] of this.entries) {
5794
+ if (key.startsWith(needle)) rowids.push(...ids);
5795
+ }
5796
+ return rowids;
5797
+ }
5798
+ /**
5799
+ * Ordered scan of rowids for a single-column comparison.
5800
+ * `op` applies to the first key component.
5801
+ */
5802
+ rangeLookup(op, bound, bound2) {
5803
+ const rowids = [];
5804
+ const keys = this.orderedKeys();
5805
+ for (const key of keys) {
5806
+ const values = this.keyValues.get(key);
5807
+ if (!values || values[0] === void 0) continue;
5808
+ const cmp = compareSerializedOrder(values[0], bound);
5809
+ let ok = false;
5810
+ if (op === ">") ok = cmp > 0;
5811
+ else if (op === ">=") ok = cmp >= 0;
5812
+ else if (op === "<") ok = cmp < 0;
5813
+ else if (op === "<=") ok = cmp <= 0;
5814
+ else if (op === "between" && bound2 !== void 0) {
5815
+ ok = cmp >= 0 && compareSerializedOrder(values[0], bound2) <= 0;
5816
+ }
5817
+ if (ok) {
5818
+ const ids = this.entries.get(key);
5819
+ if (ids) rowids.push(...ids);
5820
+ }
5821
+ }
5822
+ return rowids;
5823
+ }
5824
+ orderedRowids(desc = false) {
5825
+ const keys = this.orderedKeys();
5826
+ const rowids = [];
5827
+ const seq = desc ? [...keys].reverse() : keys;
5828
+ for (const key of seq) {
5829
+ const ids = this.entries.get(key);
5830
+ if (ids) rowids.push(...ids);
5831
+ }
5832
+ return rowids;
5833
+ }
5641
5834
  remove(values, rowid) {
5835
+ this.assertMutable();
5642
5836
  const key = serializeIndexKey(values);
5643
5837
  if (key === null) return false;
5644
5838
  const existing = this.entries.get(key);
5645
5839
  if (!existing) return false;
5646
5840
  if (rowid === void 0) {
5647
5841
  this.entries.delete(key);
5842
+ this.keyValues.delete(key);
5843
+ this.sortedKeys = null;
5648
5844
  return true;
5649
5845
  }
5650
5846
  const index = existing.findIndex((id) => sameRowid(id, rowid));
5651
5847
  if (index < 0) return false;
5652
5848
  existing.splice(index, 1);
5653
- if (existing.length === 0) this.entries.delete(key);
5849
+ if (existing.length === 0) {
5850
+ this.entries.delete(key);
5851
+ this.keyValues.delete(key);
5852
+ this.sortedKeys = null;
5853
+ }
5654
5854
  return true;
5655
5855
  }
5656
5856
  clear() {
5857
+ this.assertMutable();
5657
5858
  this.entries.clear();
5859
+ this.keyValues.clear();
5860
+ this.sortedKeys = null;
5658
5861
  }
5659
5862
  clone() {
5660
- return new _IndexStore(this.name, this.entries);
5863
+ return new _IndexStore(this.name, this.entries, this.keyValues);
5864
+ }
5865
+ freeze() {
5866
+ this.frozen = true;
5661
5867
  }
5662
5868
  get size() {
5663
5869
  return this.entries.size;
5664
5870
  }
5871
+ orderedKeys() {
5872
+ if (this.sortedKeys) return this.sortedKeys;
5873
+ const keys = [...this.entries.keys()];
5874
+ keys.sort((a, b) => {
5875
+ const left = this.keyValues.get(a);
5876
+ const right = this.keyValues.get(b);
5877
+ if (!left || !right) return a < b ? -1 : a > b ? 1 : 0;
5878
+ const n = Math.min(left.length, right.length);
5879
+ for (let i = 0; i < n; i++) {
5880
+ const cmp = compareSql(left[i] ?? null, right[i] ?? null);
5881
+ if (cmp !== 0) return cmp ?? 0;
5882
+ }
5883
+ return left.length - right.length;
5884
+ });
5885
+ this.sortedKeys = keys;
5886
+ return keys;
5887
+ }
5888
+ assertMutable() {
5889
+ if (this.frozen) throw new SqliteError("internal: cannot mutate a frozen index", "other");
5890
+ }
5665
5891
  };
5666
5892
  function serializeIndexKey(values) {
5667
5893
  if (values.some((value) => value === null)) return null;
@@ -5689,10 +5915,70 @@ function serializeValue(value) {
5689
5915
  for (const byte of value) hex += byte.toString(16).padStart(2, "0");
5690
5916
  return `b:${hex}`;
5691
5917
  }
5918
+ function compareSerializedOrder(left, right) {
5919
+ return compareSql(left, right) ?? 0;
5920
+ }
5692
5921
  function sameRowid(left, right) {
5693
5922
  return typeof left === "bigint" || typeof right === "bigint" ? BigInt(left) === BigInt(right) : left === right;
5694
5923
  }
5695
5924
 
5925
+ // src/types/strict.ts
5926
+ var STRICT_TYPES = /* @__PURE__ */ new Set(["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"]);
5927
+ function isStrictTypeName(typeName) {
5928
+ if (!typeName) return false;
5929
+ return STRICT_TYPES.has(typeName.trim().toUpperCase());
5930
+ }
5931
+ function applyStrictValue(value, typeName, table, column) {
5932
+ if (value === null) return null;
5933
+ const declared = typeName.trim().toUpperCase();
5934
+ const storage = storageClassOf(value);
5935
+ if (declared === "ANY") return value;
5936
+ if (declared === "TEXT") {
5937
+ if (storage === "text") return typeof value === "string" ? value : String(value.value);
5938
+ if (storage === "integer" || storage === "real") return String(isSqlReal(value) ? value.value : value);
5939
+ throw cannotStore(storage, declared, table, column);
5940
+ }
5941
+ if (declared === "INT" || declared === "INTEGER") {
5942
+ if (storage === "integer") return value;
5943
+ if (storage === "real") {
5944
+ const n = isSqlReal(value) ? value.value : Number(value);
5945
+ if (Number.isInteger(n)) return n;
5946
+ throw cannotStore(storage, declared, table, column);
5947
+ }
5948
+ if (storage === "text") {
5949
+ const n = coerceToNumber(value);
5950
+ if (n !== null && Number.isInteger(n) && String(value).trim() === String(n)) return n;
5951
+ throw cannotStore(storage, declared, table, column);
5952
+ }
5953
+ throw cannotStore(storage, declared, table, column);
5954
+ }
5955
+ if (declared === "REAL") {
5956
+ if (storage === "real") return isSqlReal(value) ? value : asSqlReal(Number(value));
5957
+ if (storage === "integer") return asSqlReal(typeof value === "bigint" ? Number(value) : Number(value));
5958
+ if (storage === "text") {
5959
+ const n = coerceToNumber(value);
5960
+ if (n !== null) return asSqlReal(n);
5961
+ }
5962
+ throw cannotStore(storage, declared, table, column);
5963
+ }
5964
+ if (declared === "BLOB") {
5965
+ if (storage === "blob") return value;
5966
+ throw cannotStore(storage, declared, table, column);
5967
+ }
5968
+ throw new SqliteError(`unknown datatype for ${table}.${column}: "${typeName}"`, "other");
5969
+ }
5970
+ function cannotStore(storage, declared, table, column) {
5971
+ const shown = storage === "integer" ? "INT" : storage.toUpperCase();
5972
+ throw new SqliteError(
5973
+ `cannot store ${shown} value in ${declared} column ${table}.${column}`,
5974
+ "datatype_mismatch",
5975
+ "SQLITE_CONSTRAINT_DATATYPE"
5976
+ );
5977
+ }
5978
+ function unknownStrictType(table, column, typeName) {
5979
+ throw new SqliteError(`unknown datatype for ${table}.${column}: "${typeName}"`, "other");
5980
+ }
5981
+
5696
5982
  // src/vtable/fts/options.ts
5697
5983
  var OPTION_NAMES = /* @__PURE__ */ new Set([
5698
5984
  "tokenize",
@@ -7235,11 +7521,13 @@ var Table = class _Table {
7235
7521
  indexes;
7236
7522
  originalSql;
7237
7523
  withoutRowid;
7524
+ strict;
7238
7525
  /** Clustered PK key string → row for WITHOUT ROWID tables. */
7239
7526
  clusteredRows;
7240
7527
  scanCache = null;
7241
7528
  /** Lazy covering hashes: column nameLower → serializeIndexKey → rowids. */
7242
7529
  equalityHashes = null;
7530
+ frozen = false;
7243
7531
  constructor(name, columns, options = {}) {
7244
7532
  this.name = name;
7245
7533
  this.columns = columns.map(cloneColumn);
@@ -7249,6 +7537,7 @@ var Table = class _Table {
7249
7537
  this.indexes = [...options.indexes ?? []];
7250
7538
  this.originalSql = options.originalSql ?? null;
7251
7539
  this.withoutRowid = options.withoutRowid ?? false;
7540
+ this.strict = options.strict ?? false;
7252
7541
  this.clusteredRows = /* @__PURE__ */ new Map();
7253
7542
  this.scanCache = null;
7254
7543
  }
@@ -7284,7 +7573,7 @@ var Table = class _Table {
7284
7573
  }
7285
7574
  /** True when the table has no user constraints that DML must enforce beyond hidden rowid. */
7286
7575
  isUnconstrained() {
7287
- if (this.withoutRowid || this.indexes.length > 0 || this.constraints.length > 0) return false;
7576
+ if (this.withoutRowid || this.strict || this.indexes.length > 0 || this.constraints.length > 0) return false;
7288
7577
  for (const column of this.columns) {
7289
7578
  if (column.notNull || column.primaryKey || column.unique || column.generated || column.defaultExpr) return false;
7290
7579
  }
@@ -7311,7 +7600,7 @@ var Table = class _Table {
7311
7600
  return rows;
7312
7601
  }
7313
7602
  ensureEqualityHash(columnLower) {
7314
- if (this.rows.size < EQUALITY_HASH_MIN_ROWS) return false;
7603
+ if (this.frozen || this.rows.size < EQUALITY_HASH_MIN_ROWS) return false;
7315
7604
  const column = this.columns.find((item) => item.nameLower === columnLower);
7316
7605
  if (!column) return false;
7317
7606
  this.equalityHashes ??= /* @__PURE__ */ new Map();
@@ -7361,6 +7650,7 @@ var Table = class _Table {
7361
7650
  }
7362
7651
  }
7363
7652
  insert(input, options) {
7653
+ this.assertMutable();
7364
7654
  const supplied = isInsertRow(input) ? input : { values: input };
7365
7655
  const values = options?.prepared ? supplied.values instanceof Map ? supplied.values : rowValues(supplied.values) : this.prepareValues(supplied.values);
7366
7656
  if (this.withoutRowid) {
@@ -7403,6 +7693,7 @@ var Table = class _Table {
7403
7693
  return rowid;
7404
7694
  }
7405
7695
  update(rowid, updates) {
7696
+ this.assertMutable();
7406
7697
  const key = canonicalRowid(rowid);
7407
7698
  const existing = this.rows.get(key);
7408
7699
  if (!existing) return void 0;
@@ -7445,6 +7736,7 @@ var Table = class _Table {
7445
7736
  return candidate;
7446
7737
  }
7447
7738
  delete(rowid) {
7739
+ this.assertMutable();
7448
7740
  const key = canonicalRowid(rowid);
7449
7741
  const existing = this.rows.get(key);
7450
7742
  if (!existing) return false;
@@ -7468,13 +7760,20 @@ var Table = class _Table {
7468
7760
  constraints: this.constraints,
7469
7761
  indexes: this.indexes,
7470
7762
  originalSql: this.originalSql,
7471
- withoutRowid: this.withoutRowid
7763
+ withoutRowid: this.withoutRowid,
7764
+ strict: this.strict
7472
7765
  });
7473
7766
  copy.nextRowid = this.nextRowid;
7474
7767
  for (const [rowid, row] of this.rows) copy.rows.set(rowid, cloneRow(row));
7475
7768
  for (const [clusterKey, row] of this.clusteredRows) copy.clusteredRows.set(clusterKey, cloneRow(row));
7476
7769
  return copy;
7477
7770
  }
7771
+ freeze() {
7772
+ this.frozen = true;
7773
+ }
7774
+ assertMutable() {
7775
+ if (this.frozen) throw new SqliteError("internal: cannot mutate a frozen table", "other");
7776
+ }
7478
7777
  /** Rebuild clustered storage after snapshot decode or bulk load. */
7479
7778
  rebuildClusteredRows() {
7480
7779
  if (!this.withoutRowid) return;
@@ -7491,7 +7790,10 @@ var Table = class _Table {
7491
7790
  const result = /* @__PURE__ */ new Map();
7492
7791
  for (const column of this.columns) {
7493
7792
  const key = normalizeColumnName(column.name);
7494
- result.set(key, applyAffinity(cloneSqlValue(supplied.get(key) ?? null), column.affinity));
7793
+ result.set(
7794
+ key,
7795
+ this.strict ? applyStrictValue(cloneSqlValue(supplied.get(key) ?? null), column.typeName ?? "", this.name, column.name) : applyAffinity(cloneSqlValue(supplied.get(key) ?? null), column.affinity)
7796
+ );
7495
7797
  }
7496
7798
  return result;
7497
7799
  }
@@ -7833,6 +8135,12 @@ var DatabaseState = class _DatabaseState {
7833
8135
  if (stmt.columns.length === 0) {
7834
8136
  throw new SqliteError("table must have at least one column", "syntax");
7835
8137
  }
8138
+ if (stmt.strict) {
8139
+ for (const definition of stmt.columns) {
8140
+ const typeName = definition.typeName?.trim() ?? "";
8141
+ if (!isStrictTypeName(typeName)) unknownStrictType(bare, definition.name, typeName || "null");
8142
+ }
8143
+ }
7836
8144
  const normalizedConstraints = [...stmt.constraints];
7837
8145
  for (const definition of stmt.columns) {
7838
8146
  for (const constraint of definition.constraints) {
@@ -7846,7 +8154,9 @@ var DatabaseState = class _DatabaseState {
7846
8154
  refColumns: constraint.columns,
7847
8155
  onDelete: constraint.onDelete,
7848
8156
  onUpdate: constraint.onUpdate,
7849
- name: null
8157
+ name: null,
8158
+ deferrable: constraint.deferrable,
8159
+ initiallyDeferred: constraint.initiallyDeferred
7850
8160
  });
7851
8161
  }
7852
8162
  }
@@ -7854,7 +8164,8 @@ var DatabaseState = class _DatabaseState {
7854
8164
  const table = new Table(bare, columns, {
7855
8165
  constraints: normalizedConstraints,
7856
8166
  originalSql,
7857
- withoutRowid: stmt.withoutRowid
8167
+ withoutRowid: stmt.withoutRowid,
8168
+ strict: stmt.strict
7858
8169
  });
7859
8170
  if (stmt.withoutRowid) {
7860
8171
  const pk = columns.filter((column) => column.primaryKey);
@@ -7937,7 +8248,7 @@ var DatabaseState = class _DatabaseState {
7937
8248
  return this.databaseForSchema(oldSchema, oldName).renameTable(oldBare, splitQualifiedName(newName).bare);
7938
8249
  }
7939
8250
  const oldKey = keyOf(oldBare);
7940
- const table = this.getTable(oldName);
8251
+ const table = this.getWritableTable(oldName);
7941
8252
  const { schema: newSchema, bare: newBare } = splitQualifiedName(newName);
7942
8253
  if (newSchema !== null) {
7943
8254
  throw new SqliteError(`no such table: ${newName}`, "no_such_table");
@@ -7946,11 +8257,13 @@ var DatabaseState = class _DatabaseState {
7946
8257
  this.tables.delete(oldKey);
7947
8258
  table.name = newBare;
7948
8259
  this.tables.set(keyOf(newBare), table);
7949
- for (const index of this.indexes.values()) {
7950
- if (keyOf(index.tableName) === oldKey) index.tableName = newBare;
8260
+ for (const index of [...this.indexes.values()]) {
8261
+ if (keyOf(index.tableName) !== oldKey) continue;
8262
+ this.ensureWritableIndex(index).tableName = newBare;
7951
8263
  }
7952
- for (const trigger of this.triggers.values()) {
7953
- if (keyOf(trigger.tableName) === oldKey) trigger.tableName = newBare;
8264
+ for (const [key, trigger] of this.triggers) {
8265
+ if (keyOf(trigger.tableName) !== oldKey) continue;
8266
+ this.triggers.set(key, { ...trigger, tableName: newBare });
7954
8267
  }
7955
8268
  this.schemaVersion++;
7956
8269
  return table;
@@ -7985,7 +8298,7 @@ var DatabaseState = class _DatabaseState {
7985
8298
  return deleted;
7986
8299
  }
7987
8300
  createIndex(stmt, originalSql = null) {
7988
- const table = this.getTable(stmt.table);
8301
+ const table = this.getWritableTable(stmt.table);
7989
8302
  const { schema, bare } = splitQualifiedName(stmt.name);
7990
8303
  const target = schema !== null ? this.databaseForSchema(schema, stmt.name) : this.databaseForTable(table);
7991
8304
  const key = keyOf(bare);
@@ -7993,6 +8306,7 @@ var DatabaseState = class _DatabaseState {
7993
8306
  if (existing && stmt.ifNotExists) return existing;
7994
8307
  target.assertSchemaNameAvailable(bare);
7995
8308
  for (const indexed of stmt.columns) {
8309
+ if (indexed.expr) continue;
7996
8310
  if (!table.columns.some((column) => keyOf(column.name) === keyOf(indexed.name))) {
7997
8311
  throw new SqliteError(`no such column: ${indexed.name}`, "no_such_column");
7998
8312
  }
@@ -8022,7 +8336,10 @@ var DatabaseState = class _DatabaseState {
8022
8336
  }
8023
8337
  target.indexes.delete(key);
8024
8338
  const table = target.tables.get(keyOf(index.tableName));
8025
- if (table) table.indexes = table.indexes.filter((item) => keyOf(item) !== key);
8339
+ if (table) {
8340
+ const writable = target.ensureWritableTable(table);
8341
+ writable.indexes = writable.indexes.filter((item) => keyOf(item) !== key);
8342
+ }
8026
8343
  target.schemaVersion++;
8027
8344
  return true;
8028
8345
  }
@@ -8106,6 +8423,71 @@ var DatabaseState = class _DatabaseState {
8106
8423
  copy.totalChanges = this.totalChanges;
8107
8424
  copy.foreignKeysEnabled = this.foreignKeysEnabled;
8108
8425
  copy.schemaVersion = this.schemaVersion;
8426
+ copy.userVersion = this.userVersion;
8427
+ return copy;
8428
+ }
8429
+ /** Share table/index objects with the live state. Callers must freeze first. */
8430
+ cloneShallow() {
8431
+ const copy = new _DatabaseState();
8432
+ copy.tables = new Map(this.tables);
8433
+ for (const [key, table] of this.virtualTables) copy.virtualTables.set(key, table.clone());
8434
+ copy.views = new Map(this.views);
8435
+ copy.indexes = new Map(this.indexes);
8436
+ copy.triggers = new Map(this.triggers);
8437
+ for (const [name, attached] of this.attached) {
8438
+ copy.attached.set(name, { state: attached.state.cloneShallow(), filename: attached.filename });
8439
+ }
8440
+ copy.lastInsertRowid = this.lastInsertRowid;
8441
+ copy.changes = this.changes;
8442
+ copy.totalChanges = this.totalChanges;
8443
+ copy.foreignKeysEnabled = this.foreignKeysEnabled;
8444
+ copy.schemaVersion = this.schemaVersion;
8445
+ copy.userVersion = this.userVersion;
8446
+ return copy;
8447
+ }
8448
+ freezeShared() {
8449
+ for (const table of this.tables.values()) table.freeze();
8450
+ for (const index of this.indexes.values()) index.store.freeze();
8451
+ for (const attached of this.attached.values()) attached.state.freezeShared();
8452
+ }
8453
+ thawShared() {
8454
+ for (const table of this.tables.values()) table.frozen = false;
8455
+ for (const index of this.indexes.values()) index.store.frozen = false;
8456
+ for (const attached of this.attached.values()) attached.state.thawShared();
8457
+ }
8458
+ getWritableTable(name) {
8459
+ const { schema, bare } = splitQualifiedName(name);
8460
+ if (schema !== null) {
8461
+ return this.databaseForSchema(schema, name).getWritableTable(bare);
8462
+ }
8463
+ const table = this.getTable(bare);
8464
+ return this.ensureWritableTable(table);
8465
+ }
8466
+ ensureWritableTable(table) {
8467
+ if (!table.frozen) return table;
8468
+ const key = keyOf(table.name);
8469
+ const copy = table.clone();
8470
+ this.tables.set(key, copy);
8471
+ return copy;
8472
+ }
8473
+ getWritableIndex(name) {
8474
+ const key = keyOf(name);
8475
+ const index = this.indexes.get(key);
8476
+ if (!index) throw new SqliteError(`no such index: ${name}`, "other");
8477
+ return this.ensureWritableIndex(index);
8478
+ }
8479
+ ensureWritableIndex(index) {
8480
+ if (!index.store.frozen) return index;
8481
+ const copy = {
8482
+ name: index.name,
8483
+ tableName: index.tableName,
8484
+ unique: index.unique,
8485
+ columns: structuredClone(index.columns),
8486
+ where: index.where ? structuredClone(index.where) : null,
8487
+ originalSql: index.originalSql,
8488
+ store: index.store.clone()
8489
+ };
8490
+ this.indexes.set(keyOf(index.name), copy);
8109
8491
  return copy;
8110
8492
  }
8111
8493
  replaceWith(state, options) {
@@ -8121,6 +8503,7 @@ var DatabaseState = class _DatabaseState {
8121
8503
  this.totalChanges = copy.totalChanges;
8122
8504
  this.foreignKeysEnabled = copy.foreignKeysEnabled;
8123
8505
  this.schemaVersion = copy.schemaVersion;
8506
+ this.userVersion = copy.userVersion;
8124
8507
  }
8125
8508
  assertSchemaNameAvailable(name) {
8126
8509
  const key = keyOf(name);
@@ -8312,7 +8695,8 @@ function encodeDatabaseState(state, runtime) {
8312
8695
  constraints: table.constraints,
8313
8696
  indexes: [...table.indexes].sort(compareNames),
8314
8697
  originalSql: table.originalSql,
8315
- withoutRowid: table.withoutRowid || void 0
8698
+ withoutRowid: table.withoutRowid || void 0,
8699
+ strict: table.strict || void 0
8316
8700
  });
8317
8701
  writer.value(table.nextRowid);
8318
8702
  const rows = [...table.rows.values()].sort((a, b) => compareRowids2(a.rowid, b.rowid));
@@ -8364,7 +8748,8 @@ function decodeDatabaseState(snapshot) {
8364
8748
  constraints: meta.constraints,
8365
8749
  indexes: meta.indexes,
8366
8750
  originalSql: meta.originalSql,
8367
- withoutRowid: meta.withoutRowid ?? false
8751
+ withoutRowid: meta.withoutRowid ?? false,
8752
+ strict: meta.strict ?? false
8368
8753
  });
8369
8754
  table.nextRowid = asRowid2(reader.value());
8370
8755
  const rowCount = reader.u32();
@@ -8478,7 +8863,8 @@ var TransactionManager = class {
8478
8863
  if (this.inTransaction) {
8479
8864
  throw new SqliteError("cannot start a transaction within a transaction", "transaction", "SQLITE_ERROR");
8480
8865
  }
8481
- this.transactionSnapshot = this.state.clone();
8866
+ this.state.freezeShared();
8867
+ this.transactionSnapshot = this.state.cloneShallow();
8482
8868
  this.transactionPrngState = this.prng.getState();
8483
8869
  this.savepoints = [];
8484
8870
  this.startedBySavepoint = false;
@@ -8489,6 +8875,7 @@ var TransactionManager = class {
8489
8875
  this.transactionPrngState = null;
8490
8876
  this.savepoints = [];
8491
8877
  this.startedBySavepoint = false;
8878
+ this.state.thawShared();
8492
8879
  }
8493
8880
  rollback(savepoint) {
8494
8881
  this.requireTransaction("cannot rollback - no transaction is active");
@@ -8496,7 +8883,7 @@ var TransactionManager = class {
8496
8883
  const index = this.findSavepoint(savepoint);
8497
8884
  const snapshot2 = this.savepoints[index];
8498
8885
  if (!snapshot2) throw new SqliteError(`no such savepoint: ${savepoint}`, "transaction", "SQLITE_ERROR");
8499
- this.state.replaceWith(snapshot2.state);
8886
+ this.state.replaceWith(snapshot2.state, { adopt: true });
8500
8887
  this.prng.setState(snapshot2.prngState);
8501
8888
  this.savepoints.splice(index + 1);
8502
8889
  return;
@@ -8506,22 +8893,26 @@ var TransactionManager = class {
8506
8893
  if (!snapshot || prngState === null) {
8507
8894
  throw new SqliteError("cannot rollback - no transaction is active", "transaction", "SQLITE_ERROR");
8508
8895
  }
8509
- this.state.replaceWith(snapshot);
8896
+ this.state.replaceWith(snapshot, { adopt: true });
8510
8897
  this.prng.setState(prngState);
8511
8898
  this.transactionSnapshot = null;
8512
8899
  this.transactionPrngState = null;
8513
8900
  this.savepoints = [];
8514
8901
  this.startedBySavepoint = false;
8902
+ this.state.thawShared();
8515
8903
  }
8516
8904
  savepoint(name) {
8517
8905
  if (!this.inTransaction) {
8518
- this.transactionSnapshot = this.state.clone();
8906
+ this.state.freezeShared();
8907
+ this.transactionSnapshot = this.state.cloneShallow();
8519
8908
  this.transactionPrngState = this.prng.getState();
8520
8909
  this.startedBySavepoint = true;
8910
+ } else {
8911
+ this.state.freezeShared();
8521
8912
  }
8522
8913
  this.savepoints.push({
8523
8914
  name,
8524
- state: this.state.clone(),
8915
+ state: this.state.cloneShallow(),
8525
8916
  prngState: this.prng.getState()
8526
8917
  });
8527
8918
  }
@@ -8533,6 +8924,7 @@ var TransactionManager = class {
8533
8924
  this.transactionSnapshot = null;
8534
8925
  this.transactionPrngState = null;
8535
8926
  this.startedBySavepoint = false;
8927
+ this.state.thawShared();
8536
8928
  }
8537
8929
  }
8538
8930
  findSavepoint(name) {
@@ -8721,6 +9113,7 @@ function toSqlValue(value) {
8721
9113
  return canonicalizeNumber(value);
8722
9114
  }
8723
9115
  if (typeof value === "boolean") return value ? 1 : 0;
9116
+ if (value instanceof SqlReal || value instanceof SqlJsonText) return value;
8724
9117
  if (value instanceof Uint8Array) return new Uint8Array(value);
8725
9118
  if (value instanceof ArrayBuffer) return new Uint8Array(value);
8726
9119
  throw new SqliteError(`unsupported bind value: ${typeof value}`, "datatype_mismatch");
@@ -8935,6 +9328,44 @@ function buildSqliteMaster(state) {
8935
9328
  return buildSchemaCatalog(state, "sqlite_master");
8936
9329
  }
8937
9330
 
9331
+ // src/expressions/equals.ts
9332
+ function exprEquals(left, right) {
9333
+ if (left.type !== right.type) return false;
9334
+ switch (left.type) {
9335
+ case "null":
9336
+ return true;
9337
+ case "literal":
9338
+ return right.type === "literal" && valuesEqual(left.value, right.value);
9339
+ case "column":
9340
+ return right.type === "column" && (left.table ?? "").toLowerCase() === (right.table ?? "").toLowerCase() && left.name.toLowerCase() === right.name.toLowerCase();
9341
+ case "unary":
9342
+ return right.type === "unary" && left.op === right.op && exprEquals(left.expr, right.expr);
9343
+ case "binary":
9344
+ return right.type === "binary" && left.op === right.op && exprEquals(left.left, right.left) && exprEquals(left.right, right.right);
9345
+ case "function":
9346
+ return right.type === "function" && left.name.toLowerCase() === right.name.toLowerCase() && left.distinct === right.distinct && argsEqual(left.args, right.args);
9347
+ case "collate":
9348
+ return right.type === "collate" && left.collation.toLowerCase() === right.collation.toLowerCase() && exprEquals(left.expr, right.expr);
9349
+ case "cast":
9350
+ return right.type === "cast" && (left.typeName ?? "").toUpperCase() === (right.typeName ?? "").toUpperCase() && exprEquals(left.expr, right.expr);
9351
+ default:
9352
+ return JSON.stringify(left) === JSON.stringify(right);
9353
+ }
9354
+ }
9355
+ function argsEqual(left, right) {
9356
+ if (left === "*" || right === "*") return left === right;
9357
+ if (left.length !== right.length) return false;
9358
+ return left.every((expr, index) => exprEquals(expr, right[index]));
9359
+ }
9360
+ function valuesEqual(left, right) {
9361
+ if (left === right) return true;
9362
+ if (left instanceof Uint8Array && right instanceof Uint8Array) {
9363
+ if (left.length !== right.length) return false;
9364
+ return left.every((byte, index) => byte === right[index]);
9365
+ }
9366
+ return false;
9367
+ }
9368
+
8938
9369
  // src/planner/access.ts
8939
9370
  function conjunctions(expr) {
8940
9371
  if (expr.type === "binary" && expr.op === "AND") {
@@ -8955,6 +9386,16 @@ function equalityAgainstConst(expr) {
8955
9386
  }
8956
9387
  return null;
8957
9388
  }
9389
+ function equalityAgainstExpr(expr) {
9390
+ if (expr.type !== "binary" || expr.op !== "=" && expr.op !== "==") return null;
9391
+ if (expr.left.type !== "column" && isRowIndependentExpr(expr.right)) {
9392
+ return { expr: expr.left, valueExpr: expr.right };
9393
+ }
9394
+ if (expr.right.type !== "column" && isRowIndependentExpr(expr.left)) {
9395
+ return { expr: expr.right, valueExpr: expr.left };
9396
+ }
9397
+ return null;
9398
+ }
8958
9399
  function whereFullyCovered(where, coveredColumns, alias, tableName) {
8959
9400
  for (const part of conjunctions(where)) {
8960
9401
  const eq = equalityAgainstConst(part);
@@ -8991,8 +9432,8 @@ function lookupTableRows(from, where, env) {
8991
9432
  } catch {
8992
9433
  return null;
8993
9434
  }
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;
9435
+ const parts = conjunctions(where);
9436
+ const equalities = parts.map(equalityAgainstConst).filter((item) => item !== null).filter((item) => matchesTable(item.table, alias, table.name));
8996
9437
  const evalConst = (expr) => {
8997
9438
  try {
8998
9439
  return evalExpr(expr, env.createEvalContext(null));
@@ -9006,7 +9447,6 @@ function lookupTableRows(from, where, env) {
9006
9447
  if (value === void 0) continue;
9007
9448
  resolved.push({ table: eq.table, column: eq.column, value });
9008
9449
  }
9009
- if (resolved.length === 0) return null;
9010
9450
  const pk = table.integerPkColumn();
9011
9451
  for (const eq of resolved) {
9012
9452
  const col = eq.column.toLowerCase();
@@ -9026,17 +9466,40 @@ function lookupTableRows(from, where, env) {
9026
9466
  let best = null;
9027
9467
  for (const indexName of table.indexes) {
9028
9468
  const index = db.indexes.get(indexName.toLowerCase());
9029
- if (!index || index.where) continue;
9469
+ if (!index) continue;
9470
+ if (index.where && !parts.some((part) => exprEquals(part, index.where))) continue;
9471
+ if (index.columns.some((column) => column.expr)) {
9472
+ const prefix2 = [];
9473
+ for (const column of index.columns) {
9474
+ if (!column.expr) {
9475
+ const value2 = byColumn.get(column.name.toLowerCase());
9476
+ if (value2 === void 0) break;
9477
+ prefix2.push(normalizeForCollation(value2, column.collate ?? "BINARY"));
9478
+ continue;
9479
+ }
9480
+ const match = parts.map(equalityAgainstExpr).find((item) => item && exprEquals(item.expr, column.expr));
9481
+ if (!match) break;
9482
+ const value = evalConst(match.valueExpr);
9483
+ if (value === void 0) break;
9484
+ prefix2.push(normalizeForCollation(value, column.collate ?? "BINARY"));
9485
+ }
9486
+ if (prefix2.length === 0) continue;
9487
+ const rowids2 = prefix2.length === index.columns.length ? index.store.lookup(prefix2) : index.store.lookupPrefix(prefix2);
9488
+ best = { rowids: rowids2, columns: index.columns.map((column) => column.name.toLowerCase()) };
9489
+ if (index.unique || rowids2.length <= 1) break;
9490
+ continue;
9491
+ }
9030
9492
  const prefix = [];
9031
9493
  for (const column of index.columns) {
9032
9494
  const value = byColumn.get(column.name.toLowerCase());
9033
9495
  if (value === void 0) break;
9034
9496
  prefix.push(normalizeForCollation(value, column.collate ?? "BINARY"));
9035
9497
  }
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;
9498
+ if (prefix.length === 0) continue;
9499
+ const rowids = prefix.length === index.columns.length ? index.store.lookup(prefix) : index.store.lookupPrefix(prefix);
9500
+ best = { rowids, columns: index.columns.slice(0, prefix.length).map((column) => column.name.toLowerCase()) };
9501
+ if (index.unique && prefix.length === index.columns.length) break;
9502
+ if (rowids.length <= 1) break;
9040
9503
  }
9041
9504
  if (best) {
9042
9505
  const rows = [];
@@ -9055,6 +9518,77 @@ function lookupTableRows(from, where, env) {
9055
9518
  hashed.sort((a, b) => compareRowids3(a.rowid, b.rowid));
9056
9519
  return { table, alias, rows: hashed, coveredColumns: /* @__PURE__ */ new Set([col]) };
9057
9520
  }
9521
+ const range = rangeAgainstConst(where) ?? rangeFromBetween(where);
9522
+ if (range && matchesTable(range.table, alias, table.name)) {
9523
+ const value = evalConst(range.valueExpr);
9524
+ const value2 = range.valueExpr2 ? evalConst(range.valueExpr2) : void 0;
9525
+ if (value !== void 0) {
9526
+ for (const indexName of table.indexes) {
9527
+ const index = db.indexes.get(indexName.toLowerCase());
9528
+ if (!index || index.where || index.columns.length !== 1) continue;
9529
+ if (index.columns[0].expr) continue;
9530
+ if (index.columns[0].name.toLowerCase() !== range.column.toLowerCase()) continue;
9531
+ const bound = normalizeForCollation(value, index.columns[0].collate ?? "BINARY");
9532
+ const bound2 = value2 !== void 0 ? normalizeForCollation(value2, index.columns[0].collate ?? "BINARY") : void 0;
9533
+ const rowids = index.store.rangeLookup(range.op, bound, bound2);
9534
+ const rows = [];
9535
+ for (const rowid of rowids) {
9536
+ const row = table.get(rowid);
9537
+ if (row) rows.push(row);
9538
+ }
9539
+ return { table, alias, rows, coveredColumns: /* @__PURE__ */ new Set([range.column.toLowerCase()]) };
9540
+ }
9541
+ }
9542
+ }
9543
+ return null;
9544
+ }
9545
+ function rangeAgainstConst(expr) {
9546
+ if (expr.type !== "binary") return null;
9547
+ const op = expr.op;
9548
+ if (op !== ">" && op !== ">=" && op !== "<" && op !== "<=") return null;
9549
+ if (expr.left.type === "column" && isRowIndependentExpr(expr.right)) {
9550
+ return { table: expr.left.table, column: expr.left.name, op, valueExpr: expr.right };
9551
+ }
9552
+ if (expr.right.type === "column" && isRowIndependentExpr(expr.left)) {
9553
+ const flipped = op === ">" ? "<" : op === ">=" ? "<=" : op === "<" ? ">" : ">=";
9554
+ return { table: expr.right.table, column: expr.right.name, op: flipped, valueExpr: expr.left };
9555
+ }
9556
+ return null;
9557
+ }
9558
+ function rangeFromBetween(expr) {
9559
+ if (expr.type !== "between" || expr.not || expr.expr.type !== "column") return null;
9560
+ return {
9561
+ table: expr.expr.table,
9562
+ column: expr.expr.name,
9563
+ op: "between",
9564
+ valueExpr: expr.lower,
9565
+ valueExpr2: expr.upper
9566
+ };
9567
+ }
9568
+ function tryIndexedOrder(from, order, env, limit) {
9569
+ if (from.type !== "table" || order.expr.type !== "column") return null;
9570
+ const alias = from.alias ?? from.name;
9571
+ const db = env.state.databaseForSchema(from.schema, from.schema ? `${from.schema}.${from.name}` : from.name);
9572
+ let table;
9573
+ try {
9574
+ table = db.getTable(from.name);
9575
+ } catch {
9576
+ return null;
9577
+ }
9578
+ const col = order.expr.name.toLowerCase();
9579
+ for (const indexName of table.indexes) {
9580
+ const index = db.indexes.get(indexName.toLowerCase());
9581
+ if (!index || index.where || index.columns.length !== 1 || index.columns[0].expr) continue;
9582
+ if (index.columns[0].name.toLowerCase() !== col) continue;
9583
+ const desc = order.dir === "DESC";
9584
+ const rows = [];
9585
+ for (const rowid of index.store.orderedRowids(desc)) {
9586
+ const row = table.get(rowid);
9587
+ if (row) rows.push(row);
9588
+ if (limit !== void 0 && rows.length >= limit) break;
9589
+ }
9590
+ return { table, alias, rows, coveredColumns: /* @__PURE__ */ new Set([col]) };
9591
+ }
9058
9592
  return null;
9059
9593
  }
9060
9594
  function compareRowids3(left, right) {
@@ -9499,6 +10033,7 @@ function executeWith(stmt, env, parent) {
9499
10033
  function executeSelectCore(stmt, env, parent) {
9500
10034
  let scopes;
9501
10035
  let skipWhere = false;
10036
+ let skipOrder = false;
9502
10037
  if (stmt.from && stmt.where) {
9503
10038
  const indexed = tryIndexedTableRows(stmt.from, stmt.where, env);
9504
10039
  if (indexed) {
@@ -9507,6 +10042,28 @@ function executeSelectCore(stmt, env, parent) {
9507
10042
  } else {
9508
10043
  scopes = scanFrom(stmt.from, env, parent);
9509
10044
  }
10045
+ } else if (stmt.from && stmt.orderBy.length === 1 && stmt.orderBy[0]) {
10046
+ let take;
10047
+ if (stmt.limit) {
10048
+ const ctx = env.createEvalContext(null, parent);
10049
+ const limitValue = toInteger(evalExpr(stmt.limit.limit, ctx));
10050
+ const offsetValue = stmt.limit.offset ? toInteger(evalExpr(stmt.limit.offset, ctx)) : 0;
10051
+ if (limitValue !== null && offsetValue !== null) {
10052
+ const offset = Math.max(0, Number(offsetValue));
10053
+ const limit = Number(limitValue);
10054
+ take = limit < 0 ? void 0 : offset + limit;
10055
+ }
10056
+ }
10057
+ const ordered = tryIndexedOrder(
10058
+ stmt.from,
10059
+ { expr: stmt.orderBy[0].expr, dir: stmt.orderBy[0].dir ?? "ASC" },
10060
+ env,
10061
+ take
10062
+ );
10063
+ if (ordered) {
10064
+ scopes = scopesFromTableRows(ordered.table, ordered.rows, ordered.alias, env, parent);
10065
+ skipOrder = true;
10066
+ } else scopes = scanFrom(stmt.from, env, parent);
9510
10067
  } else {
9511
10068
  scopes = stmt.from ? scanFrom(stmt.from, env, parent) : [{ cells: [] }];
9512
10069
  }
@@ -9580,7 +10137,7 @@ function executeSelectCore(stmt, env, parent) {
9580
10137
  return true;
9581
10138
  });
9582
10139
  }
9583
- if (stmt.orderBy.length > 0) {
10140
+ if (stmt.orderBy.length > 0 && !skipOrder) {
9584
10141
  output.sort((left, right) => compareOutput(left, right, stmt.orderBy, columns, env, parent));
9585
10142
  }
9586
10143
  if (stmt.limit) {
@@ -10033,8 +10590,9 @@ function windowValue(expr, current, rows, env, parent, namedWindows) {
10033
10590
  }
10034
10591
  }
10035
10592
  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);
10593
+ const [start2, end2] = frameBounds(spec, index, partition.length, currentCtx, defaultFrameEnd, orderKeys);
10594
+ const framed2 = frameRows(partition, start2, end2, index, orderKeys, spec.frame?.exclude ?? null);
10595
+ return aggregateValue(expr.func, framed2, env, parent);
10038
10596
  }
10039
10597
  const name = expr.func.name.toLowerCase();
10040
10598
  const args = expr.func.args === "*" ? [] : expr.func.args;
@@ -10056,12 +10614,14 @@ function windowValue(expr, current, rows, env, parent, namedWindows) {
10056
10614
  const target = name === "lag" ? index - offset : index + offset;
10057
10615
  return values[target]?.[0] ?? evaluated[2] ?? null;
10058
10616
  }
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;
10617
+ const [start, end] = frameBounds(spec, index, partition.length, currentCtx, defaultFrameEnd, orderKeys);
10618
+ const framed = frameRows(partition, start, end, index, orderKeys, spec.frame?.exclude ?? null);
10619
+ const framedValues = framed.map((row) => args.map((arg) => evalExpr(arg, env.createEvalContext(row, parent))));
10620
+ if (name === "first_value") return framedValues[0]?.[0] ?? null;
10621
+ if (name === "last_value") return framedValues[framedValues.length - 1]?.[0] ?? null;
10062
10622
  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;
10623
+ const target = Number(toInteger(evaluated[1] ?? null) ?? 0) - 1;
10624
+ return target >= 0 && target < framedValues.length ? framedValues[target]?.[0] ?? null : null;
10065
10625
  }
10066
10626
  if (name === "ntile") {
10067
10627
  const buckets = Math.max(1, Number(toInteger(evaluated[0] ?? 1) ?? 1));
@@ -10108,15 +10668,21 @@ function resolveWindow(spec, namedWindows, seen = /* @__PURE__ */ new Set()) {
10108
10668
  ref: null
10109
10669
  };
10110
10670
  }
10111
- function frameBounds(spec, index, length, ctx, defaultFrameEnd) {
10671
+ function frameBounds(spec, index, length, ctx, defaultFrameEnd, orderKeys) {
10112
10672
  if (!spec.frame) return [0, spec.orderBy.length > 0 ? defaultFrameEnd : Math.max(0, length - 1)];
10113
- const bound = (item, _start) => {
10673
+ const isRangeLike = spec.frame.type === "RANGE" || spec.frame.type === "GROUPS";
10674
+ let peerFirst = index;
10675
+ if (isRangeLike && spec.orderBy.length > 0) {
10676
+ while (peerFirst > 0 && rowsEqual(orderKeys[peerFirst], orderKeys[peerFirst - 1])) peerFirst--;
10677
+ }
10678
+ const bound = (item, isStart) => {
10114
10679
  switch (item.kind) {
10115
10680
  case "unbounded_preceding":
10116
10681
  return 0;
10117
10682
  case "unbounded_following":
10118
10683
  return Math.max(0, length - 1);
10119
10684
  case "current_row":
10685
+ if (isRangeLike) return isStart ? peerFirst : defaultFrameEnd;
10120
10686
  return index;
10121
10687
  case "preceding":
10122
10688
  return Math.max(0, index - Number(toInteger(evalExpr(item.expr, ctx)) ?? 0));
@@ -10126,6 +10692,20 @@ function frameBounds(spec, index, length, ctx, defaultFrameEnd) {
10126
10692
  };
10127
10693
  return [bound(spec.frame.start, true), bound(spec.frame.end, false)];
10128
10694
  }
10695
+ function frameRows(partition, start, end, index, orderKeys, exclude) {
10696
+ let peerFirst = index;
10697
+ let peerLast = index;
10698
+ while (peerFirst > 0 && rowsEqual(orderKeys[peerFirst], orderKeys[peerFirst - 1])) peerFirst--;
10699
+ while (peerLast + 1 < partition.length && rowsEqual(orderKeys[peerLast], orderKeys[peerLast + 1])) peerLast++;
10700
+ const rows = [];
10701
+ for (let i = Math.max(0, start); i <= end && i < partition.length; i++) {
10702
+ if (exclude === "current_row" && i === index) continue;
10703
+ if (exclude === "group" && i >= peerFirst && i <= peerLast) continue;
10704
+ if (exclude === "ties" && i >= peerFirst && i <= peerLast && i !== index) continue;
10705
+ rows.push(partition[i]);
10706
+ }
10707
+ return rows;
10708
+ }
10129
10709
  function compareScopes(a, b, order, env, parent) {
10130
10710
  for (const item of order) {
10131
10711
  const left = evalExpr(item.expr, env.createEvalContext(a, parent));
@@ -10475,11 +11055,12 @@ function executeCreateIndex(stmt, env) {
10475
11055
  value: row.values.get(normalizeColumnName(column.name)) ?? null
10476
11056
  }))
10477
11057
  });
10478
- if (stmt.where && !evalExpr(stmt.where, ctx)) continue;
11058
+ if (stmt.where && isTruthySql(evalExpr(stmt.where, ctx)) !== true) continue;
10479
11059
  index.store.insert(
10480
- stmt.columns.map(
10481
- (column) => normalizeForCollation(row.values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
10482
- ),
11060
+ stmt.columns.map((column) => {
11061
+ const raw = column.expr ? evalExpr(column.expr, ctx) : row.values.get(normalizeColumnName(column.name)) ?? null;
11062
+ return normalizeForCollation(raw, column.collate ?? "BINARY");
11063
+ }),
10483
11064
  row.rowid,
10484
11065
  stmt.unique
10485
11066
  );
@@ -10491,7 +11072,7 @@ function executeCreateIndex(stmt, env) {
10491
11072
  return emptyResult(0, env.state.lastInsertRowid);
10492
11073
  }
10493
11074
  function executeAlterTable(stmt, env) {
10494
- const table = env.state.getTable(stmt.table);
11075
+ const table = env.state.getWritableTable(stmt.table);
10495
11076
  const action = stmt.action;
10496
11077
  if (action.kind === "rename_table") {
10497
11078
  env.state.renameTable(stmt.table, action.newName);
@@ -10510,7 +11091,7 @@ function executeAlterTable(stmt, env) {
10510
11091
  }
10511
11092
  for (const constraint of table.constraints) renameConstraintColumn(constraint, action.oldName, action.newName);
10512
11093
  for (const name of table.indexes) {
10513
- const index = env.state.indexes.get(name.toLowerCase());
11094
+ const index = env.state.getWritableIndex(name);
10514
11095
  if (index) {
10515
11096
  for (const indexed of index.columns)
10516
11097
  if (indexed.name.toLowerCase() === action.oldName.toLowerCase()) indexed.name = action.newName;
@@ -10830,13 +11411,17 @@ function executeFtsDelete(table, rowids) {
10830
11411
  }
10831
11412
 
10832
11413
  // src/executor/dml.ts
11414
+ function storeColumnValue(table, column, value) {
11415
+ if (table.strict) return applyStrictValue(value, column.typeName ?? "", table.name, column.name);
11416
+ return applyAffinity(value, column.affinity);
11417
+ }
10833
11418
  function executeInsert(stmt, env) {
10834
11419
  if (env.state.isVirtualTable(stmt.table)) {
10835
11420
  return executeVirtualInsert(stmt, env);
10836
11421
  }
10837
11422
  const fast = tryFastInsert(stmt, env);
10838
11423
  if (fast) return fast;
10839
- const table = env.state.getTable(stmt.table);
11424
+ const table = env.state.getWritableTable(stmt.table);
10840
11425
  const columnNames = stmt.columns ?? table.columns.map((column) => column.name);
10841
11426
  const rowidIndexes = columnNames.map((name, index) => isRowidName3(name) ? index : -1).filter((index) => index >= 0);
10842
11427
  if (rowidIndexes.length > 1) throw new SqliteError("duplicate column name: rowid", "other");
@@ -10861,7 +11446,7 @@ function executeInsert(stmt, env) {
10861
11446
  const column = table.columns[index];
10862
11447
  const suppliedIndex = suppliedIndexes[index];
10863
11448
  const value = suppliedIndex >= 0 ? source[suppliedIndex] ?? null : null;
10864
- values.set(column.nameLower ?? column.name.toLowerCase(), applyAffinity(value, column.affinity));
11449
+ values.set(column.nameLower ?? column.name.toLowerCase(), storeColumnValue(table, column, value));
10865
11450
  }
10866
11451
  last = table.insert({ values }, { prepared: true, skipValidate: true });
10867
11452
  changes++;
@@ -10877,7 +11462,7 @@ function executeInsert(stmt, env) {
10877
11462
  if (column.generated) continue;
10878
11463
  const suppliedIndex = suppliedIndexes[table.columns.indexOf(column)] ?? -1;
10879
11464
  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));
11465
+ values.set(column.nameLower ?? column.name.toLowerCase(), storeColumnValue(table, column, value));
10881
11466
  }
10882
11467
  for (const column of table.columns) {
10883
11468
  if (!column.generated) continue;
@@ -10901,7 +11486,7 @@ function executeInsert(stmt, env) {
10901
11486
  })
10902
11487
  });
10903
11488
  const computed = evalExpr(column.generated.expr, genCtx);
10904
- values.set(normalizeColumnName(column.name), applyAffinity(computed, column.affinity));
11489
+ values.set(normalizeColumnName(column.name), storeColumnValue(table, column, computed));
10905
11490
  } else {
10906
11491
  values.set(normalizeColumnName(column.name), null);
10907
11492
  }
@@ -10910,7 +11495,7 @@ function executeInsert(stmt, env) {
10910
11495
  const conflicts = conflictingRows(table, values, env);
10911
11496
  if (conflicts.length > 0) {
10912
11497
  if (stmt.upsert) {
10913
- const targetConflicts = stmt.upsert.targetColumns ? conflictsMatchingTarget(table, values, env, stmt.upsert.targetColumns) : conflicts;
11498
+ const targetConflicts = conflictsForUpsert(table, values, env, stmt.upsert);
10914
11499
  if (targetConflicts.length === 0) {
10915
11500
  throw new SqliteError(
10916
11501
  `UNIQUE constraint failed: ${table.name}`,
@@ -11004,7 +11589,7 @@ function tryFastInsert(stmt, env) {
11004
11589
  }
11005
11590
  let table;
11006
11591
  try {
11007
- table = env.state.getTable(plan.tableName);
11592
+ table = env.state.getWritableTable(plan.tableName);
11008
11593
  } catch {
11009
11594
  return null;
11010
11595
  }
@@ -11055,7 +11640,7 @@ function executeUpdate(stmt, env) {
11055
11640
  if (env.state.isVirtualTable(stmt.table)) {
11056
11641
  return executeVirtualUpdate(stmt, env);
11057
11642
  }
11058
- const table = env.state.getTable(stmt.table);
11643
+ const table = env.state.getWritableTable(stmt.table);
11059
11644
  const alias = stmt.alias ?? stmt.table;
11060
11645
  const candidates = [];
11061
11646
  if (stmt.from) {
@@ -11114,7 +11699,7 @@ function executeDelete(stmt, env) {
11114
11699
  if (env.state.isVirtualTable(stmt.table)) {
11115
11700
  return executeVirtualDelete(stmt, env);
11116
11701
  }
11117
- const table = env.state.getTable(stmt.table);
11702
+ const table = env.state.getWritableTable(stmt.table);
11118
11703
  const selectedSource = stmt.where === null ? null : tryIndexedTableRows({ type: "table", schema: null, name: stmt.table, alias: stmt.alias }, stmt.where, env);
11119
11704
  const selected = [...selectedSource ? selectedSource.rows : table.scan()].filter((row) => {
11120
11705
  if (!stmt.where) return true;
@@ -11148,7 +11733,7 @@ function evaluateSet(items, table, ctx) {
11148
11733
  if (item.columns.length === 1) {
11149
11734
  const column = columnOf(table, item.columns[0]);
11150
11735
  if (column.generated) throw new SqliteError(`cannot UPDATE generated column "${column.name}"`, "misuse");
11151
- updates.set(normalizeColumnName(column.name), evalExpr(item.expr, ctx));
11736
+ updates.set(normalizeColumnName(column.name), storeColumnValue(table, column, evalExpr(item.expr, ctx)));
11152
11737
  continue;
11153
11738
  }
11154
11739
  if (item.expr.type !== "row" || item.expr.values.length !== item.columns.length) {
@@ -11162,7 +11747,7 @@ function evaluateSet(items, table, ctx) {
11162
11747
  if (column.generated) throw new SqliteError(`cannot UPDATE generated column "${column.name}"`, "misuse");
11163
11748
  updates.set(
11164
11749
  normalizeColumnName(column.name),
11165
- evalExpr(item.expr.type === "row" ? item.expr.values[index] : item.expr, ctx)
11750
+ storeColumnValue(table, column, evalExpr(item.expr.type === "row" ? item.expr.values[index] : item.expr, ctx))
11166
11751
  );
11167
11752
  });
11168
11753
  }
@@ -11189,7 +11774,7 @@ function updateOne(table, row, updates, env) {
11189
11774
  });
11190
11775
  merged.set(
11191
11776
  normalizeColumnName(column.name),
11192
- applyAffinity(evalExpr(column.generated.expr, genCtx), column.affinity)
11777
+ storeColumnValue(table, column, evalExpr(column.generated.expr, genCtx))
11193
11778
  );
11194
11779
  }
11195
11780
  after = table.update(row.rowid, merged);
@@ -11220,20 +11805,23 @@ function validateRow(table, row, env) {
11220
11805
  );
11221
11806
  }
11222
11807
  function addIndexes(table, row, env) {
11223
- const indexes = env.state.databaseForTable(table).indexes;
11808
+ const db = env.state.databaseForTable(table);
11224
11809
  for (const name of table.indexes) {
11225
- const index = indexes.get(name.toLowerCase());
11226
- if (!index) continue;
11810
+ const found = db.indexes.get(name.toLowerCase());
11811
+ if (!found) continue;
11812
+ const index = db.ensureWritableIndex(found);
11227
11813
  if (index.where && isTruthySql(evalExpr(index.where, env.createEvalContext(scopeFor(table, row, table.name, env)))) !== true)
11228
11814
  continue;
11229
- index.store.insert(indexValues(index.columns, row), row.rowid, index.unique);
11815
+ index.store.insert(indexValues(index.columns, row, env, table), row.rowid, index.unique);
11230
11816
  }
11231
11817
  }
11232
11818
  function removeIndexes(table, row, env) {
11233
- const indexes = env.state.databaseForTable(table).indexes;
11819
+ const db = env.state.databaseForTable(table);
11234
11820
  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);
11821
+ const found = db.indexes.get(name.toLowerCase());
11822
+ if (!found) continue;
11823
+ const index = db.ensureWritableIndex(found);
11824
+ index.store.remove(indexValues(index.columns, row, env, table), row.rowid);
11237
11825
  }
11238
11826
  }
11239
11827
  function removeOne(table, row, env) {
@@ -11266,6 +11854,27 @@ function conflictsMatchingTarget(table, values, env, targetColumns) {
11266
11854
  }
11267
11855
  return conflictsForSets(table, values, matchingSets, env);
11268
11856
  }
11857
+ function conflictsForUpsert(table, values, env, upsert) {
11858
+ if (!upsert.targetColumns) return conflictingRows(table, values, env);
11859
+ const exprs = upsert.targetExprs;
11860
+ const hasExpr = exprs?.some((expr) => expr.type !== "column") ?? false;
11861
+ if (hasExpr && exprs) {
11862
+ const db = env.state.databaseForTable(table);
11863
+ for (const indexName of table.indexes) {
11864
+ const index = db.indexes.get(indexName.toLowerCase());
11865
+ if (!index?.unique || index.columns.length !== exprs.length) continue;
11866
+ const matches = index.columns.every((column, i) => {
11867
+ const expr = exprs[i];
11868
+ if (column.expr) return exprEquals(column.expr, expr);
11869
+ return expr.type === "column" && expr.name.toLowerCase() === column.name.toLowerCase();
11870
+ });
11871
+ if (!matches) continue;
11872
+ if (upsert.targetWhere && index.where && !exprEquals(upsert.targetWhere, index.where)) continue;
11873
+ return conflictsForSets(table, values, [index.columns], env);
11874
+ }
11875
+ }
11876
+ return conflictsMatchingTarget(table, values, env, upsert.targetColumns.filter(Boolean));
11877
+ }
11269
11878
  function uniqueColumnSets(table, env) {
11270
11879
  const sets = [];
11271
11880
  const seen = /* @__PURE__ */ new Set();
@@ -11277,7 +11886,7 @@ function uniqueColumnSets(table, env) {
11277
11886
  };
11278
11887
  for (const indexName of table.indexes) {
11279
11888
  const index = env.state.databaseForTable(table).indexes.get(indexName.toLowerCase());
11280
- if (index?.unique && !index.where) remember(index.columns);
11889
+ if (index?.unique) remember(index.columns);
11281
11890
  }
11282
11891
  const pk = table.integerPkColumn();
11283
11892
  if (pk) remember([{ name: pk.name, collate: pk.collate, order: null }]);
@@ -11303,9 +11912,7 @@ function conflictsForSets(table, values, sets, env) {
11303
11912
  };
11304
11913
  const db = env.state.databaseForTable(table);
11305
11914
  for (const columns of sets) {
11306
- const desired = columns.map(
11307
- (column) => normalizeForCollation(values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
11308
- );
11915
+ const desired = indexValuesFromMap(columns, values, table, env);
11309
11916
  if (desired.some((value) => value === null)) continue;
11310
11917
  if (columns.length === 1) {
11311
11918
  const pk = table.integerPkColumn();
@@ -11318,12 +11925,13 @@ function conflictsForSets(table, values, sets, env) {
11318
11925
  let usedIndex = false;
11319
11926
  for (const indexName of table.indexes) {
11320
11927
  const index = db.indexes.get(indexName.toLowerCase());
11321
- if (!index?.unique || index.where) continue;
11928
+ if (!index?.unique) continue;
11322
11929
  if (index.columns.length !== columns.length) continue;
11323
11930
  if (!index.columns.every((column, i) => normalizeColumnName(column.name) === normalizeColumnName(columns[i].name))) {
11324
11931
  continue;
11325
11932
  }
11326
11933
  usedIndex = true;
11934
+ if (index.where && !indexWhereMatches(index.where, values, table, env)) continue;
11327
11935
  for (const rowid of index.store.lookup(desired)) {
11328
11936
  const row = table.get(rowid);
11329
11937
  if (row) push(row);
@@ -11332,36 +11940,63 @@ function conflictsForSets(table, values, sets, env) {
11332
11940
  }
11333
11941
  if (usedIndex) continue;
11334
11942
  for (const row of table.scan()) {
11335
- const existing = indexValues(columns, row);
11943
+ const existing = indexValues(columns, row, env, table);
11336
11944
  if (desired.every((value, index) => compareSql(value, existing[index] ?? null) === 0)) push(row);
11337
11945
  }
11338
11946
  }
11339
11947
  return found;
11340
11948
  }
11341
- function indexValues(columns, row) {
11342
- return columns.map(
11343
- (column) => normalizeForCollation(row.values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
11344
- );
11949
+ function indexValues(columns, row, env, table) {
11950
+ if (!env || !table) {
11951
+ return columns.map(
11952
+ (column) => normalizeForCollation(row.values.get(normalizeColumnName(column.name)) ?? null, column.collate ?? "BINARY")
11953
+ );
11954
+ }
11955
+ const ctx = env.createEvalContext(scopeFor(table, row, table.name, env));
11956
+ return columns.map((column) => {
11957
+ const raw = column.expr ? evalExpr(column.expr, ctx) : row.values.get(normalizeColumnName(column.name)) ?? null;
11958
+ return normalizeForCollation(raw, column.collate ?? "BINARY");
11959
+ });
11960
+ }
11961
+ function indexValuesFromMap(columns, values, table, env) {
11962
+ const fake = { rowid: 0, values };
11963
+ return indexValues(columns, fake, env, table);
11964
+ }
11965
+ function indexWhereMatches(where, values, table, env) {
11966
+ const fake = { rowid: 0, values };
11967
+ return isTruthySql(evalExpr(where, env.createEvalContext(scopeFor(table, fake, table.name, env)))) === true;
11345
11968
  }
11346
11969
  function checkForeignKeys(table, row, env) {
11347
11970
  if (!env.state.foreignKeysEnabled) return;
11348
- const references = [];
11349
11971
  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");
11972
+ if (constraint.type !== "foreign_key") continue;
11973
+ if (fkIsDeferred(constraint, env)) continue;
11974
+ assertForeignKeySatisfied(row, constraint, env);
11975
+ }
11976
+ }
11977
+ function fkIsDeferred(constraint, env) {
11978
+ return env.transactions.inTransaction && constraint.initiallyDeferred;
11979
+ }
11980
+ function assertForeignKeySatisfied(row, constraint, env) {
11981
+ const values = constraint.columns.map((name) => row.values.get(normalizeColumnName(name)) ?? null);
11982
+ if (values.some((value) => value === null)) return;
11983
+ const parent = env.state.getTable(constraint.refTable);
11984
+ const parentColumns = constraint.refColumns ?? parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
11985
+ if (![...parent.scan()].some(
11986
+ (candidate) => values.every((value, index) => {
11987
+ const parentColumn = parentColumns[index];
11988
+ return parentColumn !== void 0 && compareSql(value, candidate.values.get(normalizeColumnName(parentColumn)) ?? null) === 0;
11989
+ })
11990
+ )) {
11991
+ throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
11992
+ }
11993
+ }
11994
+ function checkDeferredForeignKeys(env) {
11995
+ if (!env.state.foreignKeysEnabled) return;
11996
+ for (const table of env.state.tables.values()) {
11997
+ for (const constraint of table.constraints) {
11998
+ if (constraint.type !== "foreign_key" || !constraint.initiallyDeferred) continue;
11999
+ for (const row of table.scan()) assertForeignKeySatisfied(row, constraint, env);
11365
12000
  }
11366
12001
  }
11367
12002
  }
@@ -11393,8 +12028,9 @@ function applyReferentialDelete(parent, row, env) {
11393
12028
  } else if (constraint.onDelete === "SET DEFAULT") {
11394
12029
  const updated = updateOne(child, candidate, defaultUpdates(child, constraint.columns, env), env);
11395
12030
  changes += 1 + updated.cascaded;
11396
- } else
12031
+ } else if (!fkIsDeferred(constraint, env)) {
11397
12032
  throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
12033
+ }
11398
12034
  }
11399
12035
  }
11400
12036
  }
@@ -11435,7 +12071,7 @@ function applyReferentialUpdate(parent, before, after, env) {
11435
12071
  } else if (constraint.onUpdate === "SET DEFAULT") {
11436
12072
  const updated = updateOne(child, candidate, defaultUpdates(child, constraint.columns, env), env);
11437
12073
  changes += 1 + updated.cascaded;
11438
- } else {
12074
+ } else if (!fkIsDeferred(constraint, env)) {
11439
12075
  throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
11440
12076
  }
11441
12077
  }
@@ -11901,6 +12537,7 @@ function executeStatement(stmt, env) {
11901
12537
  env.transactions.begin();
11902
12538
  return emptyResult(0, env.state.lastInsertRowid);
11903
12539
  case "commit":
12540
+ checkDeferredForeignKeys(env);
11904
12541
  env.transactions.commit();
11905
12542
  return emptyResult(0, env.state.lastInsertRowid);
11906
12543
  case "rollback":
@@ -11947,7 +12584,8 @@ function executeAnalyze(stmt, env) {
11947
12584
  ],
11948
12585
  constraints: [],
11949
12586
  asSelect: null,
11950
- withoutRowid: false
12587
+ withoutRowid: false,
12588
+ strict: false
11951
12589
  },
11952
12590
  "CREATE TABLE sqlite_stat1(tbl,idx,stat)"
11953
12591
  );
@@ -11990,38 +12628,85 @@ function executeAnalyze(stmt, env) {
11990
12628
  }
11991
12629
 
11992
12630
  // src/api/statement.ts
11993
- var Statement = class {
12631
+ var Statement = class _Statement {
11994
12632
  constructor(database, sql, statements) {
11995
12633
  this.database = database;
11996
12634
  this.sql = sql;
11997
12635
  this.statements = statements;
12636
+ this.schemaVersion = database.state.schemaVersion;
11998
12637
  }
11999
12638
  database;
12000
12639
  sql;
12001
- statements;
12002
12640
  bound = [];
12003
12641
  namedPlan = null;
12004
12642
  env = null;
12643
+ statements;
12644
+ schemaVersion;
12645
+ /**
12646
+ * Construct a {@link Statement} for {@link Database.prepare}.
12647
+ * @internal
12648
+ */
12649
+ static create(database, sql, statements) {
12650
+ return new _Statement(database, sql, statements);
12651
+ }
12652
+ /**
12653
+ * Store parameters for later {@link run} / {@link all} / {@link get} / {@link result}.
12654
+ *
12655
+ * Supports positional `?` / `?NNN` and named `:name` / `@name` / `$name` placeholders.
12656
+ *
12657
+ * @param params - Values to bind, in placeholder order.
12658
+ * @returns `this` for chaining.
12659
+ */
12005
12660
  bind(...params) {
12006
12661
  this.bound = [...params];
12007
12662
  return this;
12008
12663
  }
12664
+ /**
12665
+ * Execute for side effects (INSERT / UPDATE / DELETE / DDL).
12666
+ *
12667
+ * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
12668
+ * @returns Mutation counters for this execution.
12669
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12670
+ */
12009
12671
  run(...params) {
12010
12672
  const result = this.execute(params.length > 0 ? params : this.bound, { named: false });
12011
12673
  return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
12012
12674
  }
12675
+ /**
12676
+ * Execute and return every result row as an object keyed by column name.
12677
+ *
12678
+ * @typeParam T - Row shape. Defaults to {@link QueryRow}.
12679
+ * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
12680
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12681
+ */
12013
12682
  all(...params) {
12014
12683
  return this.execute(params.length > 0 ? params : this.bound, { named: true }).rows;
12015
12684
  }
12016
- /** Full result including column names (needed for empty result-set metadata). */
12685
+ /**
12686
+ * Execute and return the full {@link ResultSet}, including column names.
12687
+ *
12688
+ * Use this when you need metadata for an empty result (column names with zero rows).
12689
+ * {@link all} only returns row objects.
12690
+ *
12691
+ * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
12692
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12693
+ */
12017
12694
  result(...params) {
12018
12695
  return this.execute(params.length > 0 ? params : this.bound, { named: true });
12019
12696
  }
12697
+ /**
12698
+ * Execute and return the first row, or `undefined` if there are no rows.
12699
+ *
12700
+ * @typeParam T - Row shape. Defaults to {@link QueryRow}.
12701
+ * @param params - If provided, override the last {@link bind}; otherwise bound values are used.
12702
+ * @throws {SqliteError} If the database is closed, the statement is empty, or execution fails.
12703
+ */
12020
12704
  get(...params) {
12021
12705
  return this.execute(params.length > 0 ? params : this.bound, { named: true, maxRows: 1 }).rows[0];
12022
12706
  }
12023
12707
  execute(params, options) {
12024
12708
  this.database.assertOpen();
12709
+ this.reprepareIfSchemaChanged();
12025
12710
  if (this.statements.length === 0) throw new SqliteError("empty statement", "misuse");
12026
12711
  const env = this.obtainEnv(params);
12027
12712
  env.maxRows = options?.maxRows ?? Number.POSITIVE_INFINITY;
@@ -12051,6 +12736,13 @@ var Statement = class {
12051
12736
  });
12052
12737
  return this.env;
12053
12738
  }
12739
+ reprepareIfSchemaChanged() {
12740
+ if (this.schemaVersion === this.database.state.schemaVersion) return;
12741
+ this.statements = parse(this.sql);
12742
+ this.env = null;
12743
+ this.namedPlan = null;
12744
+ this.schemaVersion = this.database.state.schemaVersion;
12745
+ }
12054
12746
  bindNamed(env, params) {
12055
12747
  this.namedPlan ??= planNamedParameters(this.sql);
12056
12748
  for (const item of this.namedPlan.named) {
@@ -12082,31 +12774,81 @@ function planNamedParameters(sql) {
12082
12774
 
12083
12775
  // src/api/database.ts
12084
12776
  var Database = class {
12777
+ /** @internal Engine catalog, tables, and mutation counters. */
12085
12778
  state = new DatabaseState();
12779
+ /** Seed used to construct the PRNG when `options.prng` is omitted. */
12086
12780
  seed;
12781
+ /**
12782
+ * PRNG backing `random()` / `randomblob()` and related builtins.
12783
+ * Prefer passing `seed` or `prng` to the constructor.
12784
+ * @internal
12785
+ */
12087
12786
  prng;
12787
+ /**
12788
+ * Clock used by `date('now')` / `datetime('now')` / `CURRENT_TIMESTAMP`.
12789
+ * Prefer passing `now` to the constructor.
12790
+ * @internal
12791
+ */
12088
12792
  now;
12793
+ /** @internal Transaction / savepoint manager. */
12089
12794
  transactions;
12090
12795
  closed = false;
12091
12796
  transactionSequence = 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
12804
  this.prng = options.prng ?? new Prng(this.seed);
12095
12805
  this.now = resolveClock(options.now);
12096
12806
  this.transactions = new TransactionManager(this.state, this.prng);
12097
12807
  }
12808
+ /**
12809
+ * Execute SQL for its side effects (DDL/DML). Multiple statements are allowed.
12810
+ *
12811
+ * @param sql - SQL to run (semicolon-separated statements are ok).
12812
+ * @param params - Bound parameters for `?` / `:name` placeholders.
12813
+ * @throws {SqliteError} If the database is closed or the SQL fails.
12814
+ */
12098
12815
  exec(sql, params = []) {
12099
12816
  this.assertOpen();
12100
- new Statement(this, sql, parse(sql)).run(...params);
12817
+ Statement.create(this, sql, parse(sql)).run(...params);
12101
12818
  }
12819
+ /**
12820
+ * Execute a query and return all rows as objects keyed by column name.
12821
+ *
12822
+ * @typeParam T - Row shape. Defaults to {@link QueryRow}.
12823
+ * @param sql - SQL SELECT (or any statement that produces a result set).
12824
+ * @param params - Bound parameters for `?` / `:name` placeholders.
12825
+ * @returns All result rows.
12826
+ * @throws {SqliteError} If the database is closed or the SQL fails.
12827
+ */
12102
12828
  query(sql, params = []) {
12103
12829
  this.assertOpen();
12104
- return new Statement(this, sql, parse(sql)).all(...params);
12830
+ return Statement.create(this, sql, parse(sql)).all(...params);
12105
12831
  }
12832
+ /**
12833
+ * Compile `sql` into a reusable {@link Statement}.
12834
+ *
12835
+ * @param sql - SQL to prepare.
12836
+ * @throws {SqliteError} If the database is closed or `sql` cannot be parsed.
12837
+ */
12106
12838
  prepare(sql) {
12107
12839
  this.assertOpen();
12108
- return new Statement(this, sql, parse(sql));
12840
+ return Statement.create(this, sql, parse(sql));
12109
12841
  }
12842
+ /**
12843
+ * Run `fn` inside a transaction. Commits on success; rolls back if `fn` throws.
12844
+ *
12845
+ * Nested calls use SAVEPOINTs so an inner failure does not abort the outer
12846
+ * transaction.
12847
+ *
12848
+ * @param fn - Work to run while the transaction is open.
12849
+ * @returns The value returned by `fn`.
12850
+ * @throws {SqliteError} If the database is closed. Re-throws whatever `fn` throws after rollback.
12851
+ */
12110
12852
  transaction(fn) {
12111
12853
  this.assertOpen();
12112
12854
  if (!this.transactions.inTransaction) {
@@ -12132,6 +12874,13 @@ var Database = class {
12132
12874
  throw error;
12133
12875
  }
12134
12876
  }
12877
+ /**
12878
+ * Serialize schema, rows, PRNG state, and clock into a custom snapshot blob.
12879
+ *
12880
+ * This is not a `.sqlite` file. Restore it with {@link restore}.
12881
+ *
12882
+ * @throws {SqliteError} If the database is closed.
12883
+ */
12135
12884
  snapshot() {
12136
12885
  this.assertOpen();
12137
12886
  return encodeDatabaseState(this.state, {
@@ -12139,6 +12888,14 @@ var Database = class {
12139
12888
  nowMs: this.now().getTime()
12140
12889
  });
12141
12890
  }
12891
+ /**
12892
+ * Replace this database's contents with a blob from {@link snapshot}.
12893
+ *
12894
+ * Restores PRNG state and the clock when the snapshot includes them (v2).
12895
+ *
12896
+ * @param snapshot - Bytes previously returned by {@link snapshot}.
12897
+ * @throws {SqliteError} If the database is closed, a transaction is open, or the blob is invalid.
12898
+ */
12142
12899
  restore(snapshot) {
12143
12900
  this.assertOpen();
12144
12901
  if (this.transactions.inTransaction) throw new SqliteError("cannot restore during a transaction", "transaction");
@@ -12150,19 +12907,39 @@ var Database = class {
12150
12907
  this.now = fixedClock(new Date(decoded.runtime.nowMs));
12151
12908
  }
12152
12909
  }
12910
+ /**
12911
+ * Close the database. Further SQL throws {@link SqliteError}. Idempotent.
12912
+ *
12913
+ * Rolls back an open transaction, if any.
12914
+ */
12153
12915
  close() {
12154
12916
  if (this.closed) return;
12155
12917
  if (this.transactions.inTransaction) this.transactions.rollback();
12156
12918
  this.closed = true;
12157
12919
  }
12920
+ /**
12921
+ * Rows changed by the most recent INSERT / UPDATE / DELETE (SQLite `changes()`).
12922
+ *
12923
+ * @throws {SqliteError} If the database is closed.
12924
+ */
12158
12925
  get changes() {
12159
12926
  this.assertOpen();
12160
12927
  return this.state.changes;
12161
12928
  }
12929
+ /**
12930
+ * Rowid of the most recent INSERT (SQLite `last_insert_rowid()`).
12931
+ *
12932
+ * @throws {SqliteError} If the database is closed.
12933
+ */
12162
12934
  get lastInsertRowid() {
12163
12935
  this.assertOpen();
12164
12936
  return this.state.lastInsertRowid;
12165
12937
  }
12938
+ /**
12939
+ * Throw if {@link close} has already been called.
12940
+ * @internal
12941
+ * @throws {SqliteError} If the database is closed.
12942
+ */
12166
12943
  assertOpen() {
12167
12944
  if (this.closed) throw new SqliteError("Database is closed", "misuse");
12168
12945
  }
@@ -12172,11 +12949,13 @@ export {
12172
12949
  DEFAULT_NOW,
12173
12950
  Database,
12174
12951
  Prng,
12952
+ SqlJsonText,
12175
12953
  SqlReal,
12176
12954
  SqliteError,
12177
12955
  Statement,
12178
12956
  affinityFromTypeName,
12179
12957
  applyAffinity,
12958
+ asSqlJsonText,
12180
12959
  asSqlReal,
12181
12960
  canonicalizeNumber,
12182
12961
  cloneSqlValue,
@@ -12188,6 +12967,7 @@ export {
12188
12967
  evalExpr,
12189
12968
  fixedClock,
12190
12969
  globMatch,
12970
+ isSqlJsonText,
12191
12971
  isSqlReal,
12192
12972
  isTruthySql,
12193
12973
  likeMatch,