Verify SQLite backups and restores
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
export interface DatabaseVerification {
|
||||
integrityCheck: "ok";
|
||||
foreignKeyViolations: number;
|
||||
}
|
||||
|
||||
function resolveDatabasePath(filename: string): string {
|
||||
return path.resolve(filename);
|
||||
}
|
||||
|
||||
export function verifyDatabaseFile(filename: string): DatabaseVerification {
|
||||
const databasePath = resolveDatabasePath(filename);
|
||||
const sqlite = new Database(databasePath, {
|
||||
readonly: true,
|
||||
fileMustExist: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const integrityCheck = sqlite.pragma("integrity_check", {
|
||||
simple: true,
|
||||
});
|
||||
if (integrityCheck !== "ok") {
|
||||
throw new Error(
|
||||
`SQLite integrity check failed for ${databasePath}: ${String(integrityCheck)}`
|
||||
);
|
||||
}
|
||||
|
||||
const foreignKeyCheck = sqlite.pragma("foreign_key_check") as unknown[];
|
||||
const foreignKeyViolations = foreignKeyCheck.length;
|
||||
if (foreignKeyViolations > 0) {
|
||||
throw new Error(
|
||||
`SQLite foreign-key check failed for ${databasePath}: ${foreignKeyViolations} violation(s)`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
integrityCheck: "ok",
|
||||
foreignKeyViolations,
|
||||
};
|
||||
} finally {
|
||||
sqlite.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function createVerifiedDatabaseBackup(
|
||||
sourceFilename: string,
|
||||
destinationFilename: string
|
||||
): Promise<DatabaseVerification> {
|
||||
const sourcePath = resolveDatabasePath(sourceFilename);
|
||||
const destinationPath = resolveDatabasePath(destinationFilename);
|
||||
|
||||
if (sourcePath === destinationPath) {
|
||||
throw new Error("Backup source and destination must be different files.");
|
||||
}
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
throw new Error(`Database file not found: ${sourcePath}`);
|
||||
}
|
||||
if (fs.existsSync(destinationPath)) {
|
||||
throw new Error(`Backup destination already exists: ${destinationPath}`);
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
||||
|
||||
const source = new Database(sourcePath, {
|
||||
readonly: true,
|
||||
fileMustExist: true,
|
||||
});
|
||||
try {
|
||||
await source.backup(destinationPath);
|
||||
} finally {
|
||||
source.close();
|
||||
}
|
||||
|
||||
return verifyDatabaseFile(destinationPath);
|
||||
}
|
||||
Reference in New Issue
Block a user