Initialize cold WAL bookkeeping for subprocess snapshots
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { Database } from "bun:sqlite";
|
||||||
import { databasePath } from "../src/db/config";
|
import { databasePath } from "../src/db/config";
|
||||||
import { backupConfig, createBackup } from "../src/ops/backups";
|
import { backupConfig, createBackup } from "../src/ops/backups";
|
||||||
const db = new Database(databasePath, { readonly: true, strict: true });
|
// SQLite needs writable WAL bookkeeping when the first snapshot opens a cold database.
|
||||||
|
// Only snapshot SQL runs here; never create a missing source file.
|
||||||
|
const db = new Database(databasePath, { readwrite: true, create: false, strict: true });
|
||||||
try { console.log(createBackup(db, backupConfig(databasePath))); } finally { db.close(); }
|
try { console.log(createBackup(db, backupConfig(databasePath))); } finally { db.close(); }
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ const config = JSON.parse(process.argv[3]) as BackupConfig;
|
|||||||
const latest = latestBackupTime(config);
|
const latest = latestBackupTime(config);
|
||||||
if (latest && Date.now() - latest < config.intervalMs) console.log(JSON.stringify({ timestamp: latest, created: false }));
|
if (latest && Date.now() - latest < config.intervalMs) console.log(JSON.stringify({ timestamp: latest, created: false }));
|
||||||
else {
|
else {
|
||||||
const db = new Database(path, { readonly: true, strict: true });
|
// SQLite needs writable WAL bookkeeping when the first snapshot opens a cold database.
|
||||||
|
// Only snapshot SQL runs here; never create a missing source file.
|
||||||
|
const db = new Database(path, { readwrite: true, create: false, strict: true });
|
||||||
try {
|
try {
|
||||||
db.exec('PRAGMA busy_timeout=5000');
|
db.exec('PRAGMA busy_timeout=5000');
|
||||||
createBackup(db, config);
|
createBackup(db, config);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from "bun:test";
|
import { expect, test } from "bun:test";
|
||||||
import { Database } from "bun:sqlite";
|
import { Database } from "bun:sqlite";
|
||||||
import { mkdtempSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
import { existsSync, mkdtempSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { fixture } from "../habits/test-fixture";
|
import { fixture } from "../habits/test-fixture";
|
||||||
@@ -116,3 +116,34 @@ test('scheduled backup ticks coalesce, expose failure and recovery, and stop adm
|
|||||||
const disabled = startBackups('/tmp/test-only.sqlite', { ...config, enabled: false }, run);
|
const disabled = startBackups('/tmp/test-only.sqlite', { ...config, enabled: false }, run);
|
||||||
await disabled.ready; expect(calls).toBe(2); disabled.stop();
|
await disabled.ready; expect(calls).toBe(2); disabled.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('the first scheduled snapshot initializes cold WAL bookkeeping without changing source rows', async () => {
|
||||||
|
const directory = mkdtempSync(join(tmpdir(), 'minabot-cold-wal-'));
|
||||||
|
const path = join(directory, 'live.sqlite'), f = fixture(path);
|
||||||
|
f.sqlite.exec('PRAGMA journal_mode=DELETE'); f.close();
|
||||||
|
const db = new Database(path, { readwrite: true, create: false, strict: true });
|
||||||
|
let runner: ReturnType<typeof startBackups> | undefined;
|
||||||
|
try {
|
||||||
|
db.exec('PRAGMA journal_mode=WAL');
|
||||||
|
expect(existsSync(`${path}-shm`)).toBe(false);
|
||||||
|
runner = startBackups(db, backupConfig(path, {}));
|
||||||
|
await runner.ready;
|
||||||
|
expect(runner.state.failed).toBe(false);
|
||||||
|
expect(runner.state.lastSuccess).toBeGreaterThan(0);
|
||||||
|
expect(db.query('SELECT count(*) AS n FROM users').get()).toEqual({ n: 2 });
|
||||||
|
expect(db.query('SELECT count(*) AS n FROM sessions').get()).toEqual({ n: 2 });
|
||||||
|
expect(db.query('SELECT total_changes() AS n').get()).toEqual({ n: 0 });
|
||||||
|
} finally { runner?.stop(); db.close(); rmSync(directory, { recursive: true, force: true }); }
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the scheduled snapshot never creates a missing source database', async () => {
|
||||||
|
const directory = mkdtempSync(join(tmpdir(), 'minabot-missing-source-'));
|
||||||
|
const path = join(directory, 'missing.sqlite');
|
||||||
|
const runner = startBackups(path, backupConfig(path, {}));
|
||||||
|
try {
|
||||||
|
await runner.ready;
|
||||||
|
expect(runner.state.failed).toBe(true);
|
||||||
|
expect(existsSync(path)).toBe(false);
|
||||||
|
} finally { runner.stop(); rmSync(directory, { recursive: true, force: true }); }
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user