querylog: the aside file's name says why, so a schema change is not called corrupt
This commit is contained in:
@@ -138,7 +138,7 @@ pub fn open(io: std.Io, dir: std.Io.Dir, path: [:0]const u8) Error!OpenResult {
|
||||
const aside: ?[]const u8 = if (cause == .missing)
|
||||
null
|
||||
else
|
||||
try renameAside(io, dir, path, &aside_buf);
|
||||
try renameAside(io, dir, path, cause, &aside_buf);
|
||||
|
||||
// Not optional: a stale WAL left beside the renamed database would be
|
||||
// replayed into the freshly created file and corrupt it immediately. Any
|
||||
@@ -178,20 +178,37 @@ fn quickCheck(database: *db.Db) db.Error!bool {
|
||||
return std.ascii.eqlIgnoreCase(stmt.columnText(0), "ok");
|
||||
}
|
||||
|
||||
/// What the aside file's name calls the reason it was set aside.
|
||||
///
|
||||
/// The name is the only account of the reason an operator gets: the log line
|
||||
/// naming it scrolls away, the file stays for months. `fingerprint_mismatch` is
|
||||
/// a database with nothing wrong with it — this build's DDL moved — so calling
|
||||
/// its file "corrupt" invites the operator to delete evidence of a healthy file.
|
||||
fn asideTag(reason: RecreateReason) []const u8 {
|
||||
return switch (reason) {
|
||||
.missing => unreachable, // there is no file to rename
|
||||
.corrupt => "corrupt",
|
||||
.not_a_database => "not-a-database",
|
||||
.quick_check_failed => "quick-check-failed",
|
||||
.fingerprint_mismatch => "schema-changed",
|
||||
};
|
||||
}
|
||||
|
||||
/// Renames the unusable file out of the way and returns the name it now has.
|
||||
///
|
||||
/// `renamePreserve` is `RENAME_NOREPLACE`: it returns `error.PathAlreadyExists`
|
||||
/// instead of overwriting. A previously saved corrupt file must never be
|
||||
/// destroyed by the next recreate, and two recreates in the same second are not
|
||||
/// hypothetical on a boot loop — hence the uniquifying retries.
|
||||
fn renameAside(io: std.Io, dir: std.Io.Dir, path: []const u8, buf: []u8) Error![]const u8 {
|
||||
/// instead of overwriting. A previously saved file must never be destroyed by
|
||||
/// the next recreate, and two recreates in the same second are not hypothetical
|
||||
/// on a boot loop — hence the uniquifying retries.
|
||||
fn renameAside(io: std.Io, dir: std.Io.Dir, path: []const u8, reason: RecreateReason, buf: []u8) Error![]const u8 {
|
||||
const tag = asideTag(reason);
|
||||
const seconds = std.Io.Clock.real.now(io).toSeconds();
|
||||
var attempt: u32 = 0;
|
||||
while (attempt < 100) : (attempt += 1) {
|
||||
const aside = if (attempt == 0)
|
||||
std.fmt.bufPrint(buf, "{s}.corrupt-{d}", .{ path, seconds }) catch return error.NameTooLong
|
||||
std.fmt.bufPrint(buf, "{s}.{s}-{d}", .{ path, tag, seconds }) catch return error.NameTooLong
|
||||
else
|
||||
std.fmt.bufPrint(buf, "{s}.corrupt-{d}-{d}", .{ path, seconds, attempt }) catch return error.NameTooLong;
|
||||
std.fmt.bufPrint(buf, "{s}.{s}-{d}-{d}", .{ path, tag, seconds, attempt }) catch return error.NameTooLong;
|
||||
|
||||
dir.renamePreserve(path, dir, aside, io) catch |e| switch (e) {
|
||||
error.PathAlreadyExists => continue,
|
||||
@@ -287,6 +304,13 @@ test "recreatable is a whitelist and never selects a resource error" {
|
||||
}
|
||||
}
|
||||
|
||||
test "the aside name says why, and a healthy file is never called corrupt" {
|
||||
try testing.expectEqualStrings("corrupt", asideTag(.corrupt));
|
||||
try testing.expectEqualStrings("not-a-database", asideTag(.not_a_database));
|
||||
try testing.expectEqualStrings("quick-check-failed", asideTag(.quick_check_failed));
|
||||
try testing.expectEqualStrings("schema-changed", asideTag(.fingerprint_mismatch));
|
||||
}
|
||||
|
||||
test "recreatable selects exactly two of db.Error's members" {
|
||||
// Exhaustive over the whole set, so a variant added to `db.Error` later
|
||||
// defaults to propagate. The list above only proves the named errors are
|
||||
|
||||
Reference in New Issue
Block a user