@crvouga/sqlite-mem 1.4.0 → 1.6.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.
@@ -818,6 +818,16 @@ const TAIL: CatalogSection[] = [
818
818
  ["win-01", "window frame fuzz", F],
819
819
  ["up-01", "UPSERT fuzz", F],
820
820
  ["date-01", "date modifier fuzz", F],
821
+ ["trig-01", "BEFORE/AFTER trigger + RAISE fuzz", F],
822
+ ["genc-01", "STORED/VIRTUAL generated column fuzz", F],
823
+ ["notin-01", "NOT IN / IN NULL + scalar cardinality fuzz", F],
824
+ ["cnt-01", "changes/total_changes/last_insert_rowid fuzz", F],
825
+ ["fkedge-01", "FK SET NULL/DEFAULT/MATCH edge fuzz", F],
826
+ ["tlp-01", "ternary logic partitioning metamorphic", F],
827
+ ["norec-01", "NoREC metamorphic rewrite", F],
828
+ ["robust-01", "SqliteError-only / timeout / SQLM bit-flip", F],
829
+ ["slt-01", "sqllogictest vendor corpus differential", F],
830
+ ["dst-01", "mixed dump-after-each DST engine", F],
821
831
  ["prop-01", "snapshot restore idempotence", P],
822
832
  ["prop-02", "insert then delete snapshot bytes", P],
823
833
  ["prop-03", "index-added vs index-free equivalence", P],
@@ -0,0 +1,5 @@
1
+ {
2
+ "files": [],
3
+ "prefixes": [],
4
+ "notes": "Add vendor/sqllogictest/test/<file> or <file>#<line> entries when a record is intentionally unsupported."
5
+ }
package/dist/index.js CHANGED
@@ -1677,10 +1677,19 @@ var Parser = class {
1677
1677
  parseFkActions() {
1678
1678
  let onDelete = null;
1679
1679
  let onUpdate = null;
1680
+ while (this.match("MATCH")) {
1681
+ this.parseIdent();
1682
+ }
1680
1683
  while (this.at("ON")) {
1681
1684
  if (this.peek().kind === "DELETE") onDelete = this.parseFkAction("DELETE");
1682
1685
  else if (this.peek().kind === "UPDATE") onUpdate = this.parseFkAction("UPDATE");
1683
1686
  else this.syntaxError("expected ON DELETE or ON UPDATE");
1687
+ while (this.match("MATCH")) {
1688
+ this.parseIdent();
1689
+ }
1690
+ }
1691
+ while (this.match("MATCH")) {
1692
+ this.parseIdent();
1684
1693
  }
1685
1694
  return { onDelete, onUpdate };
1686
1695
  }
@@ -4341,6 +4350,21 @@ function applyModifier(date, modifier, flags) {
4341
4350
  }
4342
4351
  return result;
4343
4352
  }
4353
+ function applyModifiers(date, modifiers) {
4354
+ const flags = { subsec: false };
4355
+ let current = date;
4356
+ for (const modifier of modifiers) {
4357
+ if (typeof modifier !== "string") return null;
4358
+ const next = applyModifier(current, modifier, flags);
4359
+ if (!next) return null;
4360
+ current = next;
4361
+ }
4362
+ return { date: current, subsec: flags.subsec };
4363
+ }
4364
+ function fromUnixSeconds(seconds) {
4365
+ const date = new Date(seconds * 1e3);
4366
+ return Number.isNaN(date.getTime()) ? null : date;
4367
+ }
4344
4368
  function resolveDate(args, context) {
4345
4369
  let source = args[0];
4346
4370
  let modifiers = args.slice(1);
@@ -4348,23 +4372,26 @@ function resolveDate(args, context) {
4348
4372
  if (modifiers[0] === "unixepoch") {
4349
4373
  const seconds = coerceToNumber(source);
4350
4374
  if (seconds === null) return null;
4351
- source = seconds / 86400 + JULIAN_UNIX_EPOCH;
4352
- modifiers = modifiers.slice(1);
4353
- } else if (typeof modifiers[0] === "string" && modifiers[0].trim().toLowerCase() === "auto") {
4375
+ const date2 = fromUnixSeconds(seconds);
4376
+ if (!date2) return null;
4377
+ return applyModifiers(date2, modifiers.slice(1));
4378
+ }
4379
+ if (typeof modifiers[0] === "string" && modifiers[0].trim().toLowerCase() === "auto") {
4354
4380
  const number = coerceToNumber(source);
4355
- if (number === null) return null;
4356
- source = number >= 0 && number < 53734845e-1 ? number : number / 86400 + JULIAN_UNIX_EPOCH;
4357
- modifiers = modifiers.slice(1);
4381
+ if (number === null) {
4382
+ modifiers = modifiers.slice(1);
4383
+ } else if (number >= 0 && number < 53734845e-1) {
4384
+ source = number;
4385
+ modifiers = modifiers.slice(1);
4386
+ } else {
4387
+ const date2 = fromUnixSeconds(number);
4388
+ if (!date2) return null;
4389
+ return applyModifiers(date2, modifiers.slice(1));
4390
+ }
4358
4391
  }
4359
- let date = parseDate(source, context);
4392
+ const date = parseDate(source, context);
4360
4393
  if (!date) return null;
4361
- const flags = { subsec: false };
4362
- for (const modifier of modifiers) {
4363
- if (typeof modifier !== "string") return null;
4364
- date = applyModifier(date, modifier, flags);
4365
- if (!date) return null;
4366
- }
4367
- return { date, subsec: flags.subsec };
4394
+ return applyModifiers(date, modifiers);
4368
4395
  }
4369
4396
  function pad(value, length = 2) {
4370
4397
  return String(value).padStart(length, "0");
@@ -8873,7 +8900,11 @@ var Reader = class {
8873
8900
  return utf8Decode(this.raw(this.u32()));
8874
8901
  }
8875
8902
  json() {
8876
- return JSON.parse(this.text(), jsonReviver);
8903
+ try {
8904
+ return JSON.parse(this.text(), jsonReviver);
8905
+ } catch {
8906
+ throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
8907
+ }
8877
8908
  }
8878
8909
  value() {
8879
8910
  const tag = this.u8();
@@ -8951,6 +8982,14 @@ function encodeDatabaseState(state, runtime) {
8951
8982
  return writer.finish();
8952
8983
  }
8953
8984
  function decodeDatabaseState(snapshot) {
8985
+ try {
8986
+ return decodeDatabaseStateInner(snapshot);
8987
+ } catch (error) {
8988
+ if (error instanceof SqliteError) throw error;
8989
+ throw new SqliteError("invalid or truncated sqlite-mem snapshot", "other");
8990
+ }
8991
+ }
8992
+ function decodeDatabaseStateInner(snapshot) {
8954
8993
  const reader = new Reader(snapshot);
8955
8994
  const magic = reader.raw(4);
8956
8995
  if (!magic.every((byte, index) => byte === MAGIC[index]))
@@ -9103,12 +9142,20 @@ var TransactionManager = class {
9103
9142
  }
9104
9143
  rollback(savepoint) {
9105
9144
  this.requireTransaction("cannot rollback - no transaction is active");
9145
+ const preserved = {
9146
+ totalChanges: this.state.totalChanges,
9147
+ changes: this.state.changes,
9148
+ lastInsertRowid: this.state.lastInsertRowid
9149
+ };
9106
9150
  if (savepoint !== void 0) {
9107
9151
  const index = this.findSavepoint(savepoint);
9108
9152
  const snapshot2 = this.savepoints[index];
9109
9153
  if (!snapshot2) throw new SqliteError(`no such savepoint: ${savepoint}`, "transaction", "SQLITE_ERROR");
9110
9154
  this.state.replaceWith(snapshot2.state, { adopt: true });
9111
9155
  this.prng.setState(snapshot2.prngState);
9156
+ this.state.totalChanges = preserved.totalChanges;
9157
+ this.state.changes = preserved.changes;
9158
+ this.state.lastInsertRowid = preserved.lastInsertRowid;
9112
9159
  this.savepoints.splice(index + 1);
9113
9160
  return;
9114
9161
  }
@@ -9119,6 +9166,9 @@ var TransactionManager = class {
9119
9166
  }
9120
9167
  this.state.replaceWith(snapshot, { adopt: true });
9121
9168
  this.prng.setState(prngState);
9169
+ this.state.totalChanges = preserved.totalChanges;
9170
+ this.state.changes = preserved.changes;
9171
+ this.state.lastInsertRowid = preserved.lastInsertRowid;
9122
9172
  this.transactionSnapshot = null;
9123
9173
  this.transactionPrngState = null;
9124
9174
  this.savepoints = [];
@@ -9858,11 +9908,13 @@ function pragmaTableInfo(args, env, xinfo) {
9858
9908
  const tableName = requireNameArg(args, "table_info");
9859
9909
  const table = env.state.tables.get(tableName.toLowerCase());
9860
9910
  if (!table) return { columns: xinfoColumns(xinfo), rows: [] };
9861
- const rows = table.columns.map((column, cid) => {
9911
+ const visible = xinfo ? table.columns : table.columns.filter((column) => !column.generated);
9912
+ const rows = visible.map((column, cid) => {
9862
9913
  const pkIndex = table.columns.filter((c) => c.primaryKey).findIndex((c) => c.name === column.name);
9863
9914
  const pk = column.primaryKey ? pkIndex >= 0 ? pkIndex + 1 : 1 : 0;
9864
9915
  const dflt = column.defaultExpr ? defaultLiteral(column.defaultExpr) : null;
9865
- const base = [cid, column.name, column.typeName ?? "", column.notNull ? 1 : 0, dflt, pk];
9916
+ const notNull = column.notNull || table.withoutRowid && column.primaryKey ? 1 : 0;
9917
+ const base = [cid, column.name, column.typeName ?? "", notNull, dflt, pk];
9866
9918
  if (xinfo) {
9867
9919
  let hidden = 0;
9868
9920
  if (column.generated && !column.generated.stored) hidden = 2;
@@ -11321,10 +11373,20 @@ function scanFrom(item, env, parent) {
11321
11373
  if (item.joinType === "RIGHT" || item.joinType === "FULL") {
11322
11374
  right.forEach((rhs, rightIndex) => {
11323
11375
  if (matchedRight.has(rightIndex)) return;
11324
- const rightCells = using ? rhs.cells.map(
11325
- (cell) => using.some((name) => name.toLowerCase() === cell.name.toLowerCase()) ? { ...cell, hiddenByUsing: true } : cell
11326
- ) : rhs.cells;
11327
- result.push({ cells: [...nullLeft, ...rightCells] });
11376
+ if (using) {
11377
+ const leftCells = nullLeft.map((cell) => {
11378
+ const shared = using.some((name) => name.toLowerCase() === cell.name.toLowerCase());
11379
+ if (!shared) return cell;
11380
+ const fromRight = rhs.cells.find((c) => c.name.toLowerCase() === cell.name.toLowerCase());
11381
+ return { ...cell, value: fromRight?.value ?? null, hiddenByUsing: false };
11382
+ });
11383
+ const rightOnly = rhs.cells.filter(
11384
+ (cell) => !using.some((name) => name.toLowerCase() === cell.name.toLowerCase())
11385
+ );
11386
+ result.push({ cells: [...leftCells, ...rightOnly] });
11387
+ } else {
11388
+ result.push({ cells: [...nullLeft, ...rhs.cells] });
11389
+ }
11328
11390
  });
11329
11391
  }
11330
11392
  return result;
@@ -13225,17 +13287,28 @@ function checkForeignKeys(table, row, env) {
13225
13287
  function fkIsDeferred(constraint, env) {
13226
13288
  return env.transactions.inTransaction && constraint.initiallyDeferred;
13227
13289
  }
13228
- function assertForeignKeySatisfied(row, constraint, env) {
13290
+ function assertForeignKeySatisfied(row, constraint, env, excludeParent = null) {
13229
13291
  const values = constraint.columns.map((name) => row.values.get(normalizeColumnName(name)) ?? null);
13292
+ assertForeignKeyValues(values, constraint, env, excludeParent);
13293
+ }
13294
+ function assertForeignKeySatisfiedWithUpdates(row, constraint, updates, env, excludeParent) {
13295
+ const values = constraint.columns.map((name) => {
13296
+ const key = normalizeColumnName(name);
13297
+ return updates.has(key) ? updates.get(key) ?? null : row.values.get(key) ?? null;
13298
+ });
13299
+ assertForeignKeyValues(values, constraint, env, excludeParent);
13300
+ }
13301
+ function assertForeignKeyValues(values, constraint, env, excludeParent) {
13230
13302
  if (values.some((value) => value === null)) return;
13231
13303
  const parent = env.state.getTable(constraint.refTable);
13232
13304
  const parentColumns = constraint.refColumns ?? parent.columns.filter((column) => column.primaryKey).map((column) => column.name);
13233
- if (![...parent.scan()].some(
13234
- (candidate) => values.every((value, index) => {
13305
+ if (![...parent.scan()].some((candidate) => {
13306
+ if (excludeParent && candidate.rowid === excludeParent.rowid) return false;
13307
+ return values.every((value, index) => {
13235
13308
  const parentColumn = parentColumns[index];
13236
13309
  return parentColumn !== void 0 && compareSql(value, candidate.values.get(normalizeColumnName(parentColumn)) ?? null) === 0;
13237
- })
13238
- )) {
13310
+ });
13311
+ })) {
13239
13312
  throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");
13240
13313
  }
13241
13314
  }
@@ -13276,7 +13349,9 @@ function applyReferentialDelete(parent, row, env) {
13276
13349
  );
13277
13350
  changes += 1 + updated.cascaded;
13278
13351
  } else if (constraint.onDelete === "SET DEFAULT") {
13279
- const updated = updateOne(child, candidate, defaultUpdates(child, constraint.columns, env), env);
13352
+ const updates = defaultUpdates(child, constraint.columns, env);
13353
+ assertForeignKeySatisfiedWithUpdates(candidate, constraint, updates, env, row);
13354
+ const updated = updateOne(child, candidate, updates, env);
13280
13355
  changes += 1 + updated.cascaded;
13281
13356
  } else if (constraint.onDelete === "RESTRICT" || !fkIsDeferred(constraint, env)) {
13282
13357
  throw new SqliteError("FOREIGN KEY constraint failed", "constraint_foreign", "SQLITE_CONSTRAINT_FOREIGNKEY");