@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.
- package/compat/scenarios.ts +10 -0
- package/compat/sqllogictest-skip.json +5 -0
- package/dist/index.js +102 -27
- package/dist/index.js.map +3 -3
- package/dist/unstable.js +54 -15
- package/dist/unstable.js.map +3 -3
- package/package.json +6 -1
package/compat/scenarios.ts
CHANGED
|
@@ -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],
|
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
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
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)
|
|
4356
|
-
|
|
4357
|
-
|
|
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
|
-
|
|
4392
|
+
const date = parseDate(source, context);
|
|
4360
4393
|
if (!date) return null;
|
|
4361
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
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
|
|
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
|
|
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");
|