import { test } from "node:test";
import assert from "node:assert/strict";
import { open, exec, query, close, exec_batch, exec_script } from "./sqlite-wasm-bridge.js";
import { fnv1aHash } from "./fnv1a.js";
async function withMemoryDb(fn) {
const handle = await open("bridge-test", true);
try {
return await fn(handle);
} finally {
await close(handle);
}
}
test("the bridge exports the open/exec/query/close/exec_batch/exec_script contract", () => {
for (const fn of [open, exec, query, close, exec_batch, exec_script]) {
assert.equal(typeof fn, "function");
}
});
test("an ephemeral open reports memory mode", async () => {
await withMemoryDb((handle) => {
assert.equal(handle.persistenceMode, "memory");
});
});
test("happy path: positional binds round-trip as column arrays in SELECT order", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, "CREATE TABLE t (pk TEXT, sk TEXT, n INTEGER)", []);
await exec(handle, "INSERT INTO t (pk, sk, n) VALUES (?, ?, ?)", ["u#1", "a", 10]);
await exec(handle, "INSERT INTO t (pk, sk, n) VALUES (?, ?, ?)", ["u#1", "b", 20]);
const rows = await query(handle, "SELECT pk, sk, n FROM t ORDER BY sk", []);
assert.deepEqual(rows, [
["u#1", "a", 10],
["u#1", "b", 20],
]);
});
});
test("a parameterised query binds and filters", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, "CREATE TABLE t (pk TEXT, sk TEXT)", []);
for (const [pk, sk] of [["u#1", "a"], ["u#1", "b"], ["u#2", "c"]]) {
await exec(handle, "INSERT INTO t (pk, sk) VALUES (?, ?)", [pk, sk]);
}
const rows = await query(handle, "SELECT sk FROM t WHERE pk = ? ORDER BY sk", ["u#1"]);
assert.deepEqual(rows, [["a"], ["b"]]);
});
});
test("multi-statement exec applies every statement (no binds)", async () => {
await withMemoryDb(async (handle) => {
await exec(
handle,
"CREATE TABLE a (x TEXT); CREATE TABLE b (y TEXT); CREATE INDEX b_y ON b (y);",
[],
);
const names = (
await query(
handle,
"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name",
[],
)
).map((row) => row[0]);
assert.deepEqual(names, ["a", "b"]);
});
});
test("integer round-trip > 2^53 is bit-identical to the stored i64 (hard gate)", async () => {
await withMemoryDb(async (handle) => {
const exact = 9007199254740993n; await exec(handle, "CREATE TABLE big (id INTEGER)", []);
await exec(handle, "INSERT INTO big (id) VALUES (?)", [exact]);
const [[readBack]] = await query(handle, "SELECT id FROM big", []);
assert.equal(typeof readBack, "bigint", "an i64 past 2^53 must return as BigInt, not a lossy number");
assert.equal(readBack, exact, "the read value must equal the exact i64 stored");
});
});
test("a small integer round-trips as a plain JS number", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, "CREATE TABLE small (id INTEGER)", []);
await exec(handle, "INSERT INTO small (id) VALUES (?)", [42]);
const [[readBack]] = await query(handle, "SELECT id FROM small", []);
assert.equal(typeof readBack, "number");
assert.equal(readBack, 42);
});
});
test("fnv1a_hash scalar matches js/fnv1a.js byte-for-byte", async () => {
await withMemoryDb(async (handle) => {
for (const input of ["", "a", "u#1", "artist#42", "café", "tenant#9007199254740993"]) {
const [[hashed]] = await query(handle, "SELECT fnv1a_hash(?)", [input]);
assert.equal(
Number(hashed),
fnv1aHash(input),
`fnv1a_hash(${JSON.stringify(input)})`,
);
}
});
});
const GSI_DDL =
"CREATE TABLE g (gsi_pk TEXT, gsi_sk TEXT, table_pk TEXT, table_sk TEXT, item_json TEXT, " +
"PRIMARY KEY (gsi_pk, gsi_sk, table_pk, table_sk))";
const GSI_INSERT =
"INSERT OR REPLACE INTO g (gsi_pk, gsi_sk, table_pk, table_sk, item_json) VALUES (?1, ?2, ?3, ?4, ?5)";
test("exec_batch inserts every row in one call", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
const rows = [];
for (let i = 0; i < 5; i += 1) rows.push([`g${i}`, `s${i}`, `p${i}`, `t${i}`, `{"n":${i}}`]);
await exec_batch(handle, GSI_INSERT, rows);
const read = await query(handle, "SELECT gsi_pk, gsi_sk, item_json FROM g ORDER BY gsi_pk", []);
assert.equal(read.length, 5);
assert.deepEqual(read[0], ["g0", "s0", '{"n":0}']);
assert.deepEqual(read[4], ["g4", "s4", '{"n":4}']);
});
});
test("exec_batch keeps each row's own value, including an empty sort key", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec_batch(handle, GSI_INSERT, [
["g", "sk1", "p1", "t1", "{}"],
["g", "", "p2", "t2", "{}"],
["g", "sk3", "p3", "t3", "{}"],
]);
const sks = (await query(handle, "SELECT gsi_sk FROM g ORDER BY table_pk", [])).map((r) => r[0]);
assert.deepEqual(sks, ["sk1", "", "sk3"]);
});
});
test("exec_batch rejects a row of the wrong arity, naming the row index", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await assert.rejects(
() =>
exec_batch(handle, GSI_INSERT, [
["g0", "s0", "p0", "t0", "{}"],
["g1", "s1", "p1", "t1"], ]),
(e) => {
assert.match(e.message, /row 1/);
assert.match(e.message, /expected 5 parameters, got 4/);
return true;
},
);
});
});
test("exec_batch is a no-op on an empty batch", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec_batch(handle, GSI_INSERT, []);
const [[count]] = await query(handle, "SELECT count(*) FROM g", []);
assert.equal(Number(count), 0);
});
});
test("exec_batch runs inside the caller's open transaction", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec(handle, "BEGIN IMMEDIATE", []);
await exec_batch(handle, GSI_INSERT, [
["g0", "s0", "p0", "t0", "{}"],
["g1", "s1", "p1", "t1", "{}"],
]);
await exec(handle, "COMMIT", []);
const [[count]] = await query(handle, "SELECT count(*) FROM g", []);
assert.equal(Number(count), 2);
});
});
test("a mid-batch failure names the row and the caller's rollback undoes the batch", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT NOT NULL)", []);
await exec(handle, "BEGIN IMMEDIATE", []);
await assert.rejects(
() =>
exec_batch(handle, "INSERT INTO t (id, v) VALUES (?1, ?2)", [
[1, "ok"],
[2, null], ]),
(e) => {
assert.match(e.message, /row 1/);
return true;
},
);
await exec(handle, "ROLLBACK", []);
const [[count]] = await query(handle, "SELECT count(*) FROM t", []);
assert.equal(Number(count), 0); });
});
const GSI_DELETE = "DELETE FROM g WHERE table_pk = ?1 AND table_sk = ?2";
test("exec_script applies an ordered delete-then-insert in one call", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec(handle, GSI_INSERT, ["old", "s", "p1", "t1", '{"v":"old"}']);
await exec_script(handle, [
{ sql: GSI_DELETE, params: ["p1", "t1"] },
{ sql: GSI_INSERT, params: ["new", "s", "p1", "t1", '{"v":"new"}'] },
]);
const rows = await query(handle, "SELECT gsi_pk, item_json FROM g", []);
assert.deepEqual(rows, [["new", '{"v":"new"}']]);
});
});
test("exec_script runs several distinct statements against different tables", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec(handle, "CREATE TABLE l (pk TEXT, sk TEXT, base_pk TEXT, base_sk TEXT, item_json TEXT)", []);
await exec_script(handle, [
{ sql: GSI_INSERT, params: ["g", "s", "p", "t", "{}"] },
{ sql: "INSERT INTO l (pk, sk, base_pk, base_sk, item_json) VALUES (?1, ?2, ?3, ?4, ?5)", params: ["p", "ls", "p", "t", "{}"] },
]);
const [[g]] = await query(handle, "SELECT count(*) FROM g", []);
const [[l]] = await query(handle, "SELECT count(*) FROM l", []);
assert.equal(Number(g), 1);
assert.equal(Number(l), 1);
});
});
test("exec_script is a no-op on an empty list", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec_script(handle, []);
const [[count]] = await query(handle, "SELECT count(*) FROM g", []);
assert.equal(Number(count), 0);
});
});
test("exec_script runs inside the caller's open transaction", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, GSI_DDL, []);
await exec(handle, "BEGIN IMMEDIATE", []);
await exec_script(handle, [
{ sql: GSI_INSERT, params: ["g0", "s0", "p0", "t0", "{}"] },
{ sql: GSI_INSERT, params: ["g1", "s1", "p1", "t1", "{}"] },
]);
await exec(handle, "COMMIT", []);
const [[count]] = await query(handle, "SELECT count(*) FROM g", []);
assert.equal(Number(count), 2);
});
});
test("a mid-script failure names the statement and the caller's rollback undoes it", async () => {
await withMemoryDb(async (handle) => {
await exec(handle, "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT NOT NULL)", []);
await exec(handle, "BEGIN IMMEDIATE", []);
await assert.rejects(
() =>
exec_script(handle, [
{ sql: "INSERT INTO t (id, v) VALUES (?1, ?2)", params: [1, "ok"] },
{ sql: "INSERT INTO t (id, v) VALUES (?1, ?2)", params: [2, null] }, ]),
(e) => {
assert.match(e.message, /statement 1/);
return true;
},
);
await exec(handle, "ROLLBACK", []);
const [[count]] = await query(handle, "SELECT count(*) FROM t", []);
assert.equal(Number(count), 0); });
});