Spaces:
Sleeping
Sleeping
File size: 3,937 Bytes
ad2afbf b01d539 ad2afbf b01d539 ad2afbf b01d539 ad2afbf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import { SqliteSystemDatabase } from "./sqlite-system-database.js";
import { PersistenceBusyError } from "./database.js";
import { OperationalMetrics } from "../observability/operational-metrics.js";
const temporaryDirectories: string[] = [];
afterEach(async () => {
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { recursive: true, force: true })),
);
});
describe("SQLite transaction runner", () => {
it("rolls back a write when the synchronous callback throws", async () => {
const directory = await mkdtemp(join(tmpdir(), "oloka-transaction-"));
temporaryDirectories.push(directory);
const database = await SqliteSystemDatabase.connect(
pathToFileURL(join(directory, "database.sqlite")).href,
);
await database.migrate();
const pragmas = database.transactions.run(
"read",
({ database: connection }) => ({
foreignKeys: connection.prepare("PRAGMA foreign_keys").get(),
journalMode: connection.prepare("PRAGMA journal_mode").get(),
synchronous: connection.prepare("PRAGMA synchronous").get(),
busyTimeout: connection.prepare("PRAGMA busy_timeout").get(),
}),
);
expect(pragmas).toEqual({
foreignKeys: { foreign_keys: 1 },
journalMode: { journal_mode: "wal" },
synchronous: { synchronous: 2 },
busyTimeout: { timeout: 5000 },
});
expect(() =>
database.transactions.run("immediate", ({ database: connection }) => {
connection
.prepare(
"INSERT INTO system_metadata (key, value_json, updated_at, version) VALUES (?, ?, ?, 1)",
)
.run("kernel.schema", "{}", 1);
throw new Error("stop");
}),
).toThrow("stop");
const count = database.transactions.run(
"read",
({ database: connection }) =>
connection
.prepare("SELECT COUNT(*) AS count FROM system_metadata")
.get(),
);
expect(count).toEqual({ count: 0 });
await database.close();
});
it("rejects thenables and nested write transactions at runtime", async () => {
const directory = await mkdtemp(join(tmpdir(), "oloka-transaction-"));
temporaryDirectories.push(directory);
const database = await SqliteSystemDatabase.connect(
pathToFileURL(join(directory, "database.sqlite")).href,
);
await database.migrate();
expect(() =>
database.transactions.run("immediate", () => Promise.resolve("invalid")),
).toThrow(/synchronous/i);
expect(() =>
database.transactions.run("deferred", () =>
database.transactions.run("immediate", () => "invalid"),
),
).toThrow(/nested write/i);
await database.close();
});
it("maps a SQLite write-lock conflict to PersistenceBusyError", async () => {
const directory = await mkdtemp(join(tmpdir(), "oloka-transaction-"));
temporaryDirectories.push(directory);
const databaseUrl = pathToFileURL(join(directory, "database.sqlite")).href;
const first = await SqliteSystemDatabase.connect(databaseUrl);
await first.migrate();
const metrics = new OperationalMetrics();
const second = await SqliteSystemDatabase.connect(databaseUrl, { metrics });
second.transactions.run("read", ({ database }) =>
database.exec("PRAGMA busy_timeout=1"),
);
expect(() =>
first.transactions.run("immediate", () =>
second.transactions.run("immediate", () => undefined),
),
).toThrow(PersistenceBusyError);
expect(metrics.snapshot().sqliteBusyCount).toBe(1);
expect(metrics.snapshot().timings.transaction?.count).toBeGreaterThan(0);
await Promise.all([first.close(), second.close()]);
});
});
|