Skip to documentation
SLOP

tiny.sql.history.verification

Reference tiny.sql history verification

Defined in history.

API (14)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callshistoryverification
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/sql/src/history/validate.zig:1042

zig
pub const Inspection = struct {    last_valid_boundary: usize,    bad: ?BadRecord = null,};
Called byCallsNo direct callshistory.verification.Workspaceactivatetest sourcelib.sql.src.history.verifytest: history verification reports a ...history.verification.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.sql.src.history.verifyrunWorkprivate sourcelib.sql.src.history.verifyverifySerialhistory.verification.Workersverify
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallstest sourcelib.sql.src.history.verifytest: history verification reports a ...history.verification.Capacityderivehistory.verification.Workspaceactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.sql.src.history.verifytest: history verification reports a ...history.verificationverifyFile
Static calls · unresolved targets: 0 · external targets: 3.

Source: lib/sql/src/history/root.zig:7

zig
pub const verification = @import("verify.zig");

Source: lib/sql/src/history/verify.zig

zig
const std = @import("std");const sys = @import("sys");const sql = @import("../root.zig");const record_mod = @import("record.zig");const batch_mod = @import("batch.zig");const validate_mod = @import("validate.zig");const version = sql.version;const max_lanes: usize = 4;/// A complete scan uses caller storage and never opens the history for write.pub const Limits = struct {    metadata_bytes_max: usize = 4 * 1024 * 1024,    dependencies_max: usize = 2 * 1024 * 1024,    conflict_bytes_max: usize = 4 * 1024 * 1024,    working_bytes_max: usize,};pub const Capacity = struct {    bytes: usize,    pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {        if (limits.metadata_bytes_max == 0 or limits.dependencies_max == 0 or            limits.conflict_bytes_max == 0 or limits.working_bytes_max == 0)        {            return error.CapacityOverflow;        }        if (limits.conflict_bytes_max > std.math.maxInt(u32)) {            return error.CapacityOverflow;        }        _ = std.math.add(            usize,            limits.metadata_bytes_max,            limits.conflict_bytes_max,        ) catch return error.CapacityOverflow;        return .{ .bytes = limits.working_bytes_max };    }};pub const Storage = struct {    bytes: []u8,};pub const Exhaustion = struct {    name: []const u8,    bytes_in_use: usize,    requested: usize,    available: usize,};pub const Workspace = struct {    storage: []u8,    fixed: std.heap.FixedBufferAllocator,    limits: Limits,    exhaustion: ?Exhaustion = null,    fn allocator(self: *Workspace) std.mem.Allocator {        return .{            .ptr = self,            .vtable = &.{                .alloc = alloc,                .resize = resize,                .remap = remap,                .free = free,            },        };    }    fn alloc(ctx: *anyopaque, len: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 {        const self: *Workspace = @ptrCast(@alignCast(ctx));        const underlying = self.fixed.allocator();        const result = underlying.vtable.alloc(underlying.ptr, len, alignment, ra);        if (result == null) self.noteExhaustion(len);        return result;    }    fn resize(        ctx: *anyopaque,        memory: []u8,        alignment: std.mem.Alignment,        len: usize,        ra: usize,    ) bool {        const self: *Workspace = @ptrCast(@alignCast(ctx));        const underlying = self.fixed.allocator();        return underlying.vtable.resize(underlying.ptr, memory, alignment, len, ra);    }    fn remap(        ctx: *anyopaque,        memory: []u8,        alignment: std.mem.Alignment,        len: usize,        ra: usize,    ) ?[*]u8 {        const self: *Workspace = @ptrCast(@alignCast(ctx));        const underlying = self.fixed.allocator();        return underlying.vtable.remap(underlying.ptr, memory, alignment, len, ra);    }    fn free(ctx: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ra: usize) void {        const self: *Workspace = @ptrCast(@alignCast(ctx));        const underlying = self.fixed.allocator();        underlying.vtable.free(underlying.ptr, memory, alignment, ra);    }    fn noteExhaustion(self: *Workspace, requested: usize) void {        const used = self.fixed.end_index;        self.exhaustion = .{            .name = "working_bytes_max",            .bytes_in_use = used,            .requested = requested,            .available = self.storage.len - used,        };    }    pub fn activate(limits: Limits, storage: Storage) error{        CapacityOverflow,        CapacityExceeded,    }!Workspace {        const capacity = try Capacity.derive(limits);        if (storage.bytes.len < capacity.bytes) return error.CapacityExceeded;        const bytes = storage.bytes[0..capacity.bytes];        return .{            .storage = bytes,            .fixed = std.heap.FixedBufferAllocator.init(bytes),            .limits = limits,        };    }    pub fn reset(self: *Workspace) void {        self.fixed.reset();        self.exhaustion = null;    }};pub const Report = validate_mod.Inspection;/// Reports the earliest bad record while leaving the file untouched.pub fn verifyFile(    workspace: *Workspace,    io: std.Io,    file: std.Io.File,    end: usize,    control: sql.wal.Control,) !Report {    workspace.reset();    return validate_mod.inspectWhole(        workspace.allocator(),        io,        file,        end,        .{            .metadata_bytes_max = workspace.limits.metadata_bytes_max,            .dependencies_max = workspace.limits.dependencies_max,            .conflict_bytes_max = workspace.limits.conflict_bytes_max,        },        control,    ) catch |err| switch (err) {        error.OutOfMemory => {            std.debug.assert(workspace.exhaustion != null);            return error.CapacityExceeded;        },        else => return err,    };}const Work = struct {    bytes: []const u8 = &.{},    records: []batch_mod.Record = &.{},};pub const Workers = struct {    handles: [max_lanes - 1]sys.thread.JoinHandle = undefined,    started: usize = 0,    mutex: sys.thread.Mutex = .{},    ready: sys.thread.Condition = .{},    done: sys.thread.Condition = .{},    generation: usize = 0,    completed: usize = 0,    shutdown: bool = false,    work: Work = .{},    next_index: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),    pub fn start(self: *Workers, requested_lanes: usize) void {        if (comptime !sys.thread.threadsSupported()) return;        const background = @min(requested_lanes -| 1, self.handles.len);        while (self.started < background) {            self.handles[self.started] = sys.thread.spawn(workerLoop, .{self}) catch return;            self.started += 1;        }    }    pub fn deinit(self: *Workers) void {        self.mutex.lock();        self.shutdown = true;        self.ready.broadcast();        self.mutex.unlock();        for (self.handles[0..self.started]) |handle| handle.join();        self.* = undefined;    }    pub fn verify(self: *Workers, bytes: []const u8, records: []batch_mod.Record) void {        if (records.len == 0) return;        if (self.started == 0 or records.len == 1) {            verifySerial(bytes, records);            return;        }        self.mutex.lock();        self.work = .{ .bytes = bytes, .records = records };        self.next_index.store(0, .monotonic);        self.completed = 0;        self.generation +%= 1;        self.ready.broadcast();        self.mutex.unlock();        runWork(self, self.work);        self.mutex.lock();        while (self.completed != self.started) self.done.wait(&self.mutex);        self.mutex.unlock();    }    fn workerLoop(self: *Workers) void {        var observed_generation: usize = 0;        self.mutex.lock();        while (true) {            while (!self.shutdown and self.generation == observed_generation) self.ready.wait(&self.mutex);            if (self.shutdown) {                self.mutex.unlock();                return;            }            observed_generation = self.generation;            const work = self.work;            self.mutex.unlock();            runWork(self, work);            self.mutex.lock();            self.completed += 1;            if (self.completed == self.started) self.done.signal();        }    }};fn runWork(workers: *Workers, work: Work) void {    while (true) {        const index = workers.next_index.fetchAdd(1, .monotonic);        if (index >= work.records.len) return;        verifyRecord(work.bytes, &work.records[index]);    }}fn verifySerial(bytes: []const u8, records: []batch_mod.Record) void {    for (records) |*record| verifyRecord(bytes, record);}fn verifyRecord(bytes: []const u8, record: *batch_mod.Record) void {    const payload = bytes[record.payload_start..][0..record.payload_len];    record.valid_hash = version.same(record.expected, record_mod.recordHash(record.kind_value, payload));}test "history verification reports a missing dependency without writing" {    const store_mod = @import("store.zig");    const allocator = std.testing.allocator;    const io = std.Options.debug_io;    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var file = try tmp.dir.createFile(io, "verify.history", .{ .read = true });    defer file.close(io);    var payload: std.ArrayList(u8) = .empty;    defer payload.deinit(allocator);    const root = version.emptyHash("verify-root");    try record_mod.appendHash(allocator, &payload, root);    try record_mod.appendU32(allocator, &payload, 0);    const boundary = try store_mod.writeTestingRecord(file, 0, .commit, payload.items);    payload.clearRetainingCapacity();    const missing = version.emptyHash("verify-missing");    try record_mod.appendHash(allocator, &payload, missing);    try record_mod.appendBytes(allocator, &payload, "main");    const end = try store_mod.writeTestingRecord(file, boundary, .ref, payload.items);    var before: [256]u8 = undefined;    try std.testing.expect(end <= before.len);    try std.testing.expectEqual(end, try file.readPositionalAll(io, before[0..end], 0));    const limits = Limits{        .metadata_bytes_max = 128,        .dependencies_max = 16,        .conflict_bytes_max = 128,        .working_bytes_max = 1024 * 1024,    };    const capacity = try Capacity.derive(limits);    const bytes = try allocator.alloc(u8, capacity.bytes);    defer allocator.free(bytes);    var workspace = try Workspace.activate(limits, .{ .bytes = bytes });    const report = try verifyFile(&workspace, io, file, end, .{});    try std.testing.expectEqual(boundary, report.last_valid_boundary);    try std.testing.expectEqual(boundary, report.bad.?.offset);    try std.testing.expectEqual(record_mod.RecordKind.ref, report.bad.?.kind.?);    try std.testing.expect(version.same(missing, report.bad.?.dependency.?.key));    var after: [256]u8 = undefined;    try std.testing.expectEqual(end, try file.readPositionalAll(io, after[0..end], 0));    try std.testing.expectEqualSlices(u8, before[0..end], after[0..end]);    after[boundary] ^= 0xff;    try file.writePositionalAll(io, after[boundary..][0..1], boundary);    const malformed = try verifyFile(&workspace, io, file, end, .{});    try std.testing.expectEqual(boundary, malformed.last_valid_boundary);    try std.testing.expectEqual(boundary, malformed.bad.?.offset);    try std.testing.expect(malformed.bad.?.kind == null);    var unchanged: [256]u8 = undefined;    try std.testing.expectEqual(end, try file.readPositionalAll(io, unchanged[0..end], 0));    try std.testing.expectEqualSlices(u8, after[0..end], unchanged[0..end]);    const small_limits = Limits{        .metadata_bytes_max = 128,        .dependencies_max = 16,        .conflict_bytes_max = 128,        .working_bytes_max = 64,    };    var small_storage: [64]u8 = undefined;    var small = try Workspace.activate(small_limits, .{ .bytes = &small_storage });    try std.testing.expectError(error.CapacityExceeded, verifyFile(&small, io, file, end, .{}));    const exhausted = small.exhaustion.?;    try std.testing.expectEqualStrings("working_bytes_max", exhausted.name);    try std.testing.expectEqual(small.fixed.end_index, exhausted.bytes_in_use);    try std.testing.expectEqual(small_storage.len - exhausted.bytes_in_use, exhausted.available);    try std.testing.expect(exhausted.requested > 0);}

Audit

Definitions15
Public names15
Members26
Version26.7.0
Revisiondaab053ee433