Skip to documentation
SLOP

tiny.sql.history.resolver

Reference tiny.sql history resolver

Defined in history.

API (16)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallsNo direct callshistory.resolver.Workspaceactivatetest sourcelib.sql.src.history.resolvertest: history resolver keeps the firs...test sourcelib.sql.src.history.resolvertest: history resolver typed keys agr...history.resolver.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callershistory.resolver.Workspaceeachprivate sourcelib.sql.src.history.resolver.Workspacerequireprivate sourcelib.sql.src.history.resolverreadExactversionsamehistory.resolver.Resolverread
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsNo direct callstest sourcelib.sql.src.history.resolvertest: history resolver keeps the firs...test sourcelib.sql.src.history.resolvertest: history resolver typed keys agr...history.resolver.Scannerinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.sql.src.history.resolvertest: history resolver keeps the firs...test sourcelib.sql.src.history.resolvertest: history resolver typed keys agr...private sourcelib.sql.src.history.resolver.Workspacerequireprivate sourcelib.sql.src.history.resolverreadExactversionsamehistory.resolver.Scannernext
Static calls · unresolved targets: 2 · external targets: 2.
Called byCallstest sourcelib.sql.src.history.resolvertest: history resolver keeps the firs...test sourcelib.sql.src.history.resolvertest: history resolver typed keys agr...history.resolver.Capacityderivehistory.resolver.Workspaceactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallshistory.resolver.Resolverreadhistory.identityeachprivate sourcelib.sql.src.history.resolver.Workspacerequirehistory.resolver.Workspacescratchhistory.resolver.Workspaceeach
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallsNo direct callshistory.resolver.Workspaceeachhistory.resolver.Workspacescratch
Static calls · unresolved targets: 0 · external targets: 0.

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

zig
const std = @import("std");const sql = @import("../root.zig");const identity = @import("identity.zig");const record_mod = @import("record.zig");const version = sql.version;pub const Error = std.Io.File.ReadPositionalError || error{    CapacityExceeded,    CapacityOverflow,    InvalidHistory,    WrongObject,};pub const Limits = struct {    metadata_bytes_max: usize = 4 * 1024 * 1024,    stream_bytes: usize = 64 * 1024,    relation_entries_max: usize = 1024,    conflict_entries_max: usize = 1024,};pub const Capacity = struct {    bytes: usize,    relation_entries: usize,    conflict_entries: usize,    pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {        if (limits.stream_bytes < version.hash_bytes) return error.CapacityOverflow;        if (limits.metadata_bytes_max < version.hash_bytes) return error.CapacityOverflow;        if (limits.metadata_bytes_max > std.math.maxInt(u32)) {            return error.CapacityOverflow;        }        const bytes = std.math.add(            usize,            limits.metadata_bytes_max,            limits.stream_bytes,        ) catch return error.CapacityOverflow;        _ = std.math.mul(            usize,            limits.relation_entries_max,            @sizeOf(version.RelationEntry),        ) catch return error.CapacityOverflow;        _ = std.math.mul(            usize,            limits.conflict_entries_max,            @sizeOf(version.ConflictEntry),        ) catch return error.CapacityOverflow;        return .{            .bytes = bytes,            .relation_entries = limits.relation_entries_max,            .conflict_entries = limits.conflict_entries_max,        };    }};pub const Storage = struct {    bytes: []u8,    relations: []version.RelationEntry,    conflicts: []version.ConflictEntry,};pub const Exhaustion = struct {    name: []const u8,    requested: usize,    available: usize,};pub const Workspace = struct {    metadata: []u8,    stream: []u8,    relations: []version.RelationEntry,    conflicts: []version.ConflictEntry,    exhaustion: ?Exhaustion = null,    /// Activation borrows all storage before any record is read.    pub fn activate(limits: Limits, storage: Storage) Error!Workspace {        const capacity = try Capacity.derive(limits);        if (storage.bytes.len < capacity.bytes) return error.CapacityExceeded;        if (storage.relations.len < capacity.relation_entries) return error.CapacityExceeded;        if (storage.conflicts.len < capacity.conflict_entries) return error.CapacityExceeded;        return .{            .metadata = storage.bytes[0..limits.metadata_bytes_max],            .stream = storage.bytes[limits.metadata_bytes_max..capacity.bytes],            .relations = storage.relations[0..limits.relation_entries_max],            .conflicts = storage.conflicts[0..limits.conflict_entries_max],        };    }    pub fn scratch(self: *Workspace) identity.Scratch {        return .{ .relations = self.relations, .conflicts = self.conflicts };    }    pub fn each(        self: *Workspace,        kind: record_mod.RecordKind,        payload: []const u8,        location: identity.Location,        context: anytype,        comptime accept: fn (@TypeOf(context), identity.Entry) anyerror!void,    ) anyerror!void {        identity.each(kind, payload, location, self.scratch(), context, accept) catch |err| {            if (err != error.KeyScratchExhausted) return err;            const count_offset: usize = switch (kind) {                .database_root => version.hash_bytes,                .conflict_root => 0,                else => unreachable,            };            if (payload.len -| count_offset < 4) return error.InvalidHistory;            const requested = record_mod.readIntU32(payload[count_offset..][0..4]);            const available = switch (kind) {                .database_root => self.relations.len,                .conflict_root => self.conflicts.len,                else => unreachable,            };            try self.require("key_entries_max", requested, available);            unreachable;        };    }    fn require(self: *Workspace, name: []const u8, requested: usize, available: usize) Error!void {        if (requested <= available) return;        self.exhaustion = .{            .name = name,            .requested = requested,            .available = available,        };        return error.CapacityExceeded;    }};pub const Scanned = struct {    kind: record_mod.RecordKind,    location: identity.Location,    payload: []const u8,    end: u64,};/// A file-order scanner retains only one metadata record or stream chunk.pub const Scanner = struct {    io: std.Io,    file: std.Io.File,    workspace: *Workspace,    offset: u64,    end: u64,    chunk_key: version.Hash = undefined,    pub fn init(        io: std.Io,        file: std.Io.File,        workspace: *Workspace,        start: u64,        end: u64,    ) Error!Scanner {        if (start > end) return error.InvalidHistory;        return .{ .io = io, .file = file, .workspace = workspace, .offset = start, .end = end };    }    pub fn next(self: *Scanner) Error!?Scanned {        if (self.offset == self.end) return null;        if (self.end - self.offset < record_mod.record_header_size) return error.InvalidHistory;        var header_bytes: [record_mod.record_header_size]u8 = undefined;        try readExact(self.io, self.file, &header_bytes, self.offset);        const header = try record_mod.Header.decode(&header_bytes);        const payload_start = std.math.add(            u64,            self.offset,            record_mod.record_header_size,        ) catch return error.InvalidHistory;        const record_end = std.math.add(            u64,            payload_start,            header.payload_len,        ) catch return error.InvalidHistory;        if (record_end > self.end) return error.InvalidHistory;        const location = identity.Location{            .record_offset = self.offset,            .payload_len = header.payload_len,            .envelope_hash = header.envelope_hash,        };        var hasher = record_mod.envelopeHasher(            @backingInt(header.kind),            header.payload_len,        );        const payload: []const u8 = if (header.kind == .row_chunk) chunk: {            var cursor = payload_start;            var copied: usize = 0;            while (cursor < record_end) {                const count: usize = @intCast(@min(self.workspace.stream.len, record_end - cursor));                try readExact(self.io, self.file, self.workspace.stream[0..count], cursor);                if (copied < version.hash_bytes) {                    const take = @min(version.hash_bytes - copied, count);                    @memcpy(self.chunk_key[copied..][0..take], self.workspace.stream[0..take]);                    copied += take;                }                hasher.update(self.workspace.stream[0..count]);                cursor += count;            }            break :chunk &self.chunk_key;        } else metadata: {            try self.workspace.require(                "metadata_bytes_max",                header.payload_len,                self.workspace.metadata.len,            );            const bytes = self.workspace.metadata[0..header.payload_len];            try readExact(self.io, self.file, bytes, payload_start);            hasher.update(bytes);            break :metadata bytes;        };        var actual: version.Hash = undefined;        hasher.final(&actual);        if (!version.same(actual, header.envelope_hash)) return error.InvalidHistory;        const result = Scanned{            .kind = header.kind,            .location = location,            .payload = payload,            .end = record_end,        };        self.offset = record_end;        return result;    }};pub const Resolver = struct {    io: std.Io,    file: std.Io.File,    indexed_eof: u64,    workspace: *Workspace,    /// The caller owns payload storage and keeps the returned slice alive.    pub fn read(        self: *Resolver,        key: identity.Key,        location: identity.Location,        output: []u8,    ) Error![]const u8 {        const end = std.math.add(            u64,            location.record_offset,            location.encodedLen(),        ) catch return error.InvalidHistory;        if (end > self.indexed_eof) return error.InvalidHistory;        try self.workspace.require("record_payload", location.payload_len, output.len);        var header_bytes: [record_mod.record_header_size]u8 = undefined;        try readExact(self.io, self.file, &header_bytes, location.record_offset);        const header = try record_mod.Header.decode(&header_bytes);        if (header.payload_len != location.payload_len) return error.InvalidHistory;        if (!version.same(header.envelope_hash, location.envelope_hash)) {            return error.InvalidHistory;        }        try readExact(            self.io,            self.file,            output[0..location.payload_len],            location.record_offset + record_mod.record_header_size,        );        const payload = output[0..location.payload_len];        if (!version.same(            record_mod.recordHash(@backingInt(header.kind), payload),            header.envelope_hash,        )) return error.InvalidHistory;        var found = false;        const Context = struct {            wanted: identity.Key,            position: u32,            found: *bool,            fn accept(context: *@This(), entry: identity.Entry) Error!void {                if (entry.key.kind != context.wanted.kind) return;                if (entry.location.node_position != context.position) return;                context.found.* = version.same(entry.key.bytes, context.wanted.bytes);            }        };        var context = Context{            .wanted = key,            .position = location.node_position,            .found = &found,        };        self.workspace.each(            header.kind,            payload,            location,            &context,            Context.accept,        ) catch |err| switch (err) {            error.CapacityExceeded => return error.CapacityExceeded,            else => return error.InvalidHistory,        };        if (!found) return error.WrongObject;        return payload;    }};fn readExact(io: std.Io, file: std.Io.File, output: []u8, offset: u64) Error!void {    var filled: usize = 0;    while (filled < output.len) {        const position = std.math.add(u64, offset, filled) catch return error.InvalidHistory;        const count = try file.readPositionalAll(io, output[filled..], position);        if (count == 0) return error.InvalidHistory;        filled += count;    }}test "history resolver keeps the first typed commit location" {    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, "objects.history", .{ .read = true });    defer file.close(io);    var payload: std.ArrayList(u8) = .empty;    defer payload.deinit(allocator);    const root = version.emptyHash("resolver-commit");    try record_mod.appendHash(allocator, &payload, root);    try record_mod.appendU32(allocator, &payload, 0);    const first_end = try store_mod.writeTestingRecord(file, 0, .commit, payload.items);    const end = try store_mod.writeTestingRecord(file, first_end, .commit, payload.items);    const limits = Limits{        .metadata_bytes_max = 128,        .stream_bytes = 64,        .relation_entries_max = 2,        .conflict_entries_max = 2,    };    const capacity = try Capacity.derive(limits);    const bytes = try allocator.alloc(u8, capacity.bytes);    defer allocator.free(bytes);    const relations = try allocator.alloc(version.RelationEntry, capacity.relation_entries);    defer allocator.free(relations);    const conflicts = try allocator.alloc(version.ConflictEntry, capacity.conflict_entries);    defer allocator.free(conflicts);    var workspace = try Workspace.activate(limits, .{        .bytes = bytes,        .relations = relations,        .conflicts = conflicts,    });    var scanner = try Scanner.init(io, file, &workspace, 0, end);    const first = (try scanner.next()).?;    try std.testing.expectEqual(@as(u64, 0), first.location.record_offset);    const expected = version.Commit.init(root, &.{}).hash;    var captured: ?identity.Entry = null;    const Capture = struct {        fn accept(target: *?identity.Entry, entry: identity.Entry) !void {            target.* = entry;        }    };    try workspace.each(        first.kind,        first.payload,        first.location,        &captured,        Capture.accept,    );    try std.testing.expect(version.same(expected, captured.?.key.bytes));    const second = (try scanner.next()).?;    try std.testing.expectEqual(@as(u64, first_end), second.location.record_offset);    try std.testing.expect((try scanner.next()) == null);    var resolver = Resolver{ .io = io, .file = file, .indexed_eof = end, .workspace = &workspace };    var output: [64]u8 = undefined;    const read = try resolver.read(captured.?.key, captured.?.location, &output);    try std.testing.expectEqualSlices(u8, payload.items, read);}test "history resolver typed keys agree with replay across object kinds" {    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();    const path = "typed.history";    var relation = try store_mod.testingMerkleRelationRoot(        allocator,        .{ .branches = 1, .leaves = 2 },        null,        "typed",    );    defer relation.deinit();    const rows = try store_mod.testingChunkRows(allocator, 300, 42);    defer version.freeRelationRows(allocator, rows);    const artifact = version.ConflictArtifact.init("metrics", 7, "old", "ours", "theirs");    var database_root: version.DatabaseRoot = undefined;    var commit: version.Commit = undefined;    {        var history = try store_mod.History.open(allocator, tmp.dir, .{            .path = path,            .recovery = .reject,        });        defer history.deinit();        try history.putRelationRoot(relation);        try history.putRelationRows(relation.hash, rows);        try history.putConflict(artifact);        const conflicts = try history.putConflictRoot(&.{artifact.entry()});        database_root = try version.DatabaseRoot.initSorted(allocator, &.{            .{ .name = "metrics", .hash = relation.hash },        }, conflicts);        try history.putDatabaseRoot(database_root);        commit = version.Commit.init(database_root.hash, &.{});        try history.putCommit(commit);        try history.putRef(.{ .name = "main", .target = commit.hash });        try history.putRef(.{ .name = "temporary", .target = commit.hash });        try history.deleteRef("temporary");        const chunk = history.row_chunks.items[0].payload;        const duplicate = try allocator.alloc(u8, chunk.len);        defer allocator.free(duplicate);        try std.testing.expectEqual(            chunk.len,            try history.file.?.readPositionalAll(io, duplicate, chunk.offset),        );        try history.appendRecord(.row_chunk, duplicate);        try history.flushSync();    }    defer database_root.deinit();    var replay = try store_mod.History.open(allocator, tmp.dir, .{        .path = path,        .recovery = .reject,        .read_only = true,    });    defer replay.deinit();    const verify_mod = @import("verify.zig");    const verify_limits = verify_mod.Limits{        .metadata_bytes_max = 128 * 1024,        .dependencies_max = 4096,        .conflict_bytes_max = 4096,        .working_bytes_max = 8 * 1024 * 1024,    };    const verify_capacity = try verify_mod.Capacity.derive(verify_limits);    const verify_bytes = try allocator.alloc(u8, verify_capacity.bytes);    defer allocator.free(verify_bytes);    var verify_workspace = try verify_mod.Workspace.activate(        verify_limits,        .{ .bytes = verify_bytes },    );    const report = try verify_mod.verifyFile(        &verify_workspace,        io,        replay.file.?,        replay.len(),        .{},    );    try std.testing.expect(report.bad == null);    try std.testing.expectEqual(replay.len(), report.last_valid_boundary);    const limits = Limits{        .metadata_bytes_max = 128 * 1024,        .stream_bytes = 4096,        .relation_entries_max = 16,        .conflict_entries_max = 16,    };    const capacity = try Capacity.derive(limits);    const bytes = try allocator.alloc(u8, capacity.bytes);    defer allocator.free(bytes);    const relations = try allocator.alloc(version.RelationEntry, capacity.relation_entries);    defer allocator.free(relations);    const conflicts = try allocator.alloc(version.ConflictEntry, capacity.conflict_entries);    defer allocator.free(conflicts);    var workspace = try Workspace.activate(limits, .{        .bytes = bytes,        .relations = relations,        .conflicts = conflicts,    });    var entries: std.ArrayList(identity.Entry) = .empty;    defer entries.deinit(allocator);    var duplicates: usize = 0;    const Collect = struct {        allocator: std.mem.Allocator,        entries: *std.ArrayList(identity.Entry),        duplicates: *usize,        fn accept(context: *@This(), entry: identity.Entry) !void {            for (context.entries.items) |existing| {                if (existing.key.kind != entry.key.kind) continue;                if (!version.same(existing.key.bytes, entry.key.bytes)) continue;                context.duplicates.* += 1;                return;            }            try context.entries.append(context.allocator, entry);        }    };    var collect = Collect{        .allocator = allocator,        .entries = &entries,        .duplicates = &duplicates,    };    var scanner = try Scanner.init(io, replay.file.?, &workspace, 0, replay.len());    while (try scanner.next()) |record| {        try workspace.each(            record.kind,            record.payload,            record.location,            &collect,            Collect.accept,        );    }    try std.testing.expectEqual(@as(usize, 1), duplicates);    const replay_count = replay.commits.items.len + replay.conflicts.items.len +        replay.database_roots.items.len + replay.relation_roots.items.len +        replay.relation_rows.items.len + replay.conflict_roots.items.len +        replay.row_chunks.items.len + replay.index_pages.items.len +        replay.tree_nodes.items.len + replay.relation_spans.items.len;    try std.testing.expectEqual(replay_count, entries.items.len);    var resolver = Resolver{        .io = io,        .file = replay.file.?,        .indexed_eof = replay.len(),        .workspace = &workspace,    };    const output = try allocator.alloc(u8, 128 * 1024);    defer allocator.free(output);    for (entries.items) |entry| {        const key = entry.key.bytes;        const present = switch (entry.key.kind) {            .commit => replay.findCommit(key) != null,            .conflict => replay.findConflict(key) != null,            .database_root => replay.findDatabaseRoot(key) != null,            .relation_root => replay.findRelationRoot(key) != null,            .relation_rows => replay.hasRelationRows(key),            .conflict_root => replay.findConflictRoot(key) != null,            .row_chunk => replay.findRowChunk(key) != null,            .chunk_index_page => replay.hasIndexPage(key),            .tree_node => replay.findTreeNode(key) != null,            .relation_spans => replay.lookup.relation_spans.get(key) != null,        };        try std.testing.expect(present);        _ = try resolver.read(entry.key, entry.location, output);    }}

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

zig
pub const resolver = @import("resolver.zig");

Audit

Definitions17
Public names17
Members32
Version26.7.0
Revisiondaab053ee433