Skip to documentation
SLOP

tiny.sql.version

Reference tiny.sql version

Defined in tiny.sql.

API (47)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callsprivate sourcelib.sql.src.syncrelationRowsRelationValuecloneversioncloneRelationRows
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsprivate sourcelib.sql.src.session.database.DatabaseSessionflushStagedRelationsDatabaseRootinitSortedversiondatabaseRootReplacingEntries
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsConnectionadoptRebuiltHistoryConnectioncreateConnectionmaterializedWorkingValuetest sourcelib.sql.src.versiontest: database value accepts replacem...test sourcelib.sql.src.versiontest: relation value applies row edit...versiondatabaseValueReplacingRelationsversiondatabaseValue
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsHistorydatabaseValueprivate sourcelib.sql.src.mergedatabaseValueForRelationsmergemergeDatabaseversiondatabaseValueReplacingRelationsDatabaseRootinitSortedversiondatabaseValueFromOwnedRelations
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsversiondatabaseValuetest sourcelib.sql.src.versiontest: database value accepts replacem...versiondatabaseValueFromOwnedRelationsversionrelationValueprivate sourcelib.sql.src.versionreplacementRelationtiny.tldrformats.elf.liveness.statedeinitversiondatabaseValueReplacingRelations
Static calls · unresolved targets: 0 · external targets: 8.
Called byCallstest sourcelib.sql.src.branchtest: branch ancestry rejects missing...test sourcelib.sql.src.branchtest: branch checkout keeps head and ...test sourcelib.sql.src.branchtest: branch fast-forward follows com...test sourcelib.sql.src.branchtest: branch merge base chooses neare...private sourcelib.sql.src.connectionrelationRootHasStats+72 moreprivate sourcelib.sql.src.version.Builderfinishprivate sourcelib.sql.src.version.BuilderinitversionemptyHash
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsblamerelationprivate sourcelib.sql.src.blamesnapshotCommitprivate sourcelib.sql.src.chunkbuildRowstest sourcelib.sql.src.chunktest: chunk boundaries cover rows con...test sourcelib.sql.src.chunktest: chunk boundaries stable under s...+28 moreversionfreeRelationRows
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsversionrelationRootversionmapRoot
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsLifecycleSparseReadSessionbeginLifecycleSparseReadSessionfinishprivate sourcelib.sql.src.lifecycleopenReadOnlyFromSnapshotprivate sourcelib.sql.src.versiondatabaseRootMaintainedFromversionreadDatabaseRootMaintained
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.sql.src.versionexpectKeyMatchesRootprivate sourcelib.sql.src.versionrelationRootMaintainedFromversionreadRelationRootMaintained
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.sql.src.connection.ConnectionpublishStagedValueprivate sourcelib.sql.src.connection.ConnectionverifyMaterializedDatabaseRoottest sourcelib.sql.src.connectiontest: connection materialized root ve...PreparedRelationcurrentCacheKeyprivate sourcelib.sql.src.session.database.StagedRelationflushRoot+2 moreprivate sourcelib.sql.src.version.Builderbytesprivate sourcelib.sql.src.version.Builderfinishprivate sourcelib.sql.src.version.Builderhashprivate sourcelib.sql.src.version.Builderinitprivate sourcelib.sql.src.version.BuilderwriteU32+6 moreversionrelationKey
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallsprivate sourcelib.sql.src.connection.DatabaseMaterializationvalidateRelationIdentityversionrelationValueApplyingMaterializedEditstest sourcelib.sql.src.versiontest: relation root from materialized...private sourcelib.sql.src.versionrelationRootFromRowsWithOptionalStatsversionrelationRootFromRows
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.sql.src.connection.DatabaseMaterializationvalidateRelationIdentityprivate sourcelib.sql.src.versionrelationRootFromRowsWithOptionalStatsversionrelationRootFromRowsWithStats
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.sql.src.connection.ConnectionpublishStagedValuePreparedRelationrefreshprivate sourcelib.sql.src.planprepareRelationWithValidationRelationSessionopenRelationSessionrefresh+2 moreprivate sourcelib.sql.src.versionrelationRootMaintainedFromversionrelationRootMaintained
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.sql.src.blamesnapshotCommittest sourcelib.sql.src.history.storetest: history merkle relation roots p...versionrelationValueFromRoottest sourcelib.sql.src.versiontest: relation root from materialized...versionrelationRows
Static calls · unresolved targets: 3 · external targets: 5.
Called byCallstest sourcelib.sql.src.mergetest: database merge applies clean re...test sourcelib.sql.src.mergetest: database merge contains cross-s...test sourcelib.sql.src.mergetest: database merge matches relation...test sourcelib.sql.src.mergetest: database merge preserves a thei...test sourcelib.sql.src.mergetest: database merge records relation...+2 moreversionrelationRootMaintainedversionrelationValueFromRootversionrelationValue
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate sourcelib.sql.src.mergerelationValueApplyingMergetest sourcelib.sql.src.versiontest: relation value applies row edit...versionfreeRelationRowsversionrelationRootFromRowsprivate sourcelib.sql.src.versionrelationRowsApplyingEditsversionrelationValueApplyingMaterializedEdits
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsversionrelationValueversionfreeRelationRowsversionrelationRowsversionrelationValueFromRoot
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsblamerelationtest sourcelib.sql.src.blametest: blame follows side branches to ...private sourcelib.sql.src.branchcontainsprivate sourcelib.sql.src.branchcontainsAncestorprivate sourcelib.sql.src.branchfind+202 moreversionsame
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.sql.src.history.storetestingMerkleRelationRootprivate sourcelib.sql.src.history.storetestingRelationRootprivate sourcelib.sql.src.planshapeKeyversionrelationKeyversionrelationRoot+3 moreprivate sourcelib.sql.src.version.Builderbytesprivate sourcelib.sql.src.version.Builderfinishprivate sourcelib.sql.src.version.Builderinitprivate sourcelib.sql.src.version.BuilderrowValueprivate sourcelib.sql.src.version.BuilderwriteU64private sourcelib.sql.src.version.BuilderwriteU8versionschemaHash
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/sql/src/root.zig:49

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

Source: lib/sql/src/version.zig

zig
const std = @import("std");const simd = @import("simd");const catalog_mod = @import("catalog.zig");const key = @import("key.zig");const page = @import("page.zig");const relation_mod = @import("relation.zig");const row = @import("row.zig");const tree = @import("tree.zig");const wal = @import("wal.zig");const Bytes = simd.ScalableTag(u8);const Allocator = std.mem.Allocator;pub const hash_bytes = 32;pub const format_version: u32 = 1;pub const Hash = [hash_bytes]u8;pub const Error = Allocator.Error || tree.Error || row.Error || key.Error || relation_mod.Error || catalog_mod.Error;pub const MapRoot = tree.Root;pub const IndexRoot = struct {    fields: Hash,    map: MapRoot,    stats: Hash,    hash: Hash,    pub fn deinit(self: *IndexRoot) void {        self.map.deinit();        self.* = undefined;    }    pub fn clone(self: *const IndexRoot, allocator: Allocator) Allocator.Error!IndexRoot {        return .{            .fields = self.fields,            .map = try self.map.clone(allocator),            .stats = self.stats,            .hash = self.hash,        };    }};pub const StatsRoot = struct {    table: ?tree.Summary = null,    indexes: usize = 0,    hash: Hash,};pub const RelationSchema = struct {    allocator: Allocator,    columns: []catalog_mod.ColumnDefinition,    indexes: []catalog_mod.IndexDefinition,    pub fn init(allocator: Allocator, columns: []const catalog_mod.ColumnDefinition, indexes: []const catalog_mod.IndexDefinition) Allocator.Error!RelationSchema {        const owned_columns = try allocator.alloc(catalog_mod.ColumnDefinition, columns.len);        var column_count: usize = 0;        errdefer {            for (owned_columns[0..column_count]) |*column| deinitColumn(allocator, column);            allocator.free(owned_columns);        }        for (columns, owned_columns) |column, *target| {            target.* = try cloneColumn(allocator, column);            column_count += 1;        }        const owned_indexes = try allocator.alloc(catalog_mod.IndexDefinition, indexes.len);        var index_count: usize = 0;        errdefer {            for (owned_indexes[0..index_count]) |*index| deinitIndex(allocator, index);            allocator.free(owned_indexes);        }        for (indexes, owned_indexes) |index, *target| {            target.* = try cloneIndex(allocator, index);            index_count += 1;        }        return .{            .allocator = allocator,            .columns = owned_columns,            .indexes = owned_indexes,        };    }    pub fn deinit(self: *RelationSchema) void {        for (self.columns) |*column| deinitColumn(self.allocator, column);        for (self.indexes) |*index| deinitIndex(self.allocator, index);        self.allocator.free(self.columns);        self.allocator.free(self.indexes);        self.* = undefined;    }    pub fn clone(self: *const RelationSchema, allocator: Allocator) Allocator.Error!RelationSchema {        return try RelationSchema.init(allocator, self.columns, self.indexes);    }};pub const RelationRoot = struct {    allocator: Allocator,    format: u32 = format_version,    name: []u8,    catalog: catalog_mod.Schema,    schema: Hash,    schema_descriptor: RelationSchema,    table: MapRoot,    indexes: []IndexRoot,    stats: StatsRoot,    hash: Hash,    pub fn deinit(self: *RelationRoot) void {        self.table.deinit();        for (self.indexes) |*index| index.deinit();        self.allocator.free(self.indexes);        self.schema_descriptor.deinit();        self.allocator.free(self.name);        self.* = undefined;    }    pub fn clone(self: *const RelationRoot, allocator: Allocator) Allocator.Error!RelationRoot {        const name = try allocator.dupe(u8, self.name);        errdefer allocator.free(name);        var table = try self.table.clone(allocator);        errdefer table.deinit();        const indexes = try allocator.alloc(IndexRoot, self.indexes.len);        var index_count: usize = 0;        errdefer {            for (indexes[0..index_count]) |*index| index.deinit();            allocator.free(indexes);        }        for (self.indexes, indexes) |index, *target| {            target.* = try index.clone(allocator);            index_count += 1;        }        var schema_descriptor = try self.schema_descriptor.clone(allocator);        errdefer schema_descriptor.deinit();        return .{            .allocator = allocator,            .format = self.format,            .name = name,            .catalog = self.catalog,            .schema = self.schema,            .schema_descriptor = schema_descriptor,            .table = table,            .indexes = indexes,            .stats = self.stats,            .hash = self.hash,        };    }};pub const RelationEntry = struct {    name: []const u8,    hash: Hash,};pub const RelationRow = struct {    rowid: i64,    bytes: []u8,};pub const RelationValue = struct {    root: RelationRoot,    rows: []RelationRow,    pub fn deinit(self: *RelationValue, allocator: Allocator) void {        self.root.deinit();        freeRelationRows(allocator, self.rows);        self.* = undefined;    }    pub fn clone(self: *const RelationValue, allocator: Allocator) Allocator.Error!RelationValue {        var root = try self.root.clone(allocator);        errdefer root.deinit();        const rows = try cloneRelationRows(allocator, self.rows);        return .{            .root = root,            .rows = rows,        };    }};pub const ConflictKind = enum(u8) {    row,    relation,};pub const ConflictValue = union(ConflictKind) {    row: []const u8,    relation: Hash,};pub const ConflictEntry = struct {    kind: ConflictKind = .row,    relation: []const u8,    rowid: i64 = 0,    hash: Hash,    pub fn eql(left: ConflictEntry, right: ConflictEntry) bool {        return left.kind == right.kind and            std.mem.eql(u8, left.relation, right.relation) and            (left.kind == .relation or left.rowid == right.rowid) and            same(left.hash, right.hash);    }    pub fn sameSlot(left: ConflictEntry, right: ConflictEntry) bool {        return left.kind == right.kind and            std.mem.eql(u8, left.relation, right.relation) and            (left.kind == .relation or left.rowid == right.rowid);    }    pub fn lessThan(_: void, left: ConflictEntry, right: ConflictEntry) bool {        const relation_order = simd.order(Bytes, left.relation, right.relation);        if (relation_order != .eq) return relation_order == .lt;        if (left.kind != right.kind) return @backingInt(left.kind) < @backingInt(right.kind);        if (left.kind == .row and left.rowid != right.rowid) return left.rowid < right.rowid;        return simd.order(Bytes, left.hash[0..], right.hash[0..]) == .lt;    }};pub const ConflictRoot = struct {    hash: Hash,    count: usize = 0,    pub fn empty() ConflictRoot {        return .{            .hash = emptyHash("sql.conflicts.empty"),        };    }    pub fn init(entries: []const ConflictEntry) ConflictRoot {        var builder = Builder.init("sql.conflicts");        builder.writeU64(entries.len);        for (entries) |entry| {            builder.writeU8(@backingInt(entry.kind));            builder.bytes(entry.relation);            if (entry.kind == .row) builder.writeI64(entry.rowid);            builder.hash(entry.hash);        }        return .{            .hash = builder.finish(),            .count = entries.len,        };    }    pub fn initSorted(allocator: Allocator, entries: []const ConflictEntry) Allocator.Error!ConflictRoot {        const sorted = try allocator.dupe(ConflictEntry, entries);        defer allocator.free(sorted);        std.mem.sort(ConflictEntry, sorted, {}, ConflictEntry.lessThan);        return init(sorted);    }};pub const ConflictArtifact = struct {    kind: ConflictKind = .row,    relation: []const u8,    rowid: i64 = 0,    base: ?ConflictValue = null,    ours: ?ConflictValue = null,    theirs: ?ConflictValue = null,    hash: Hash,    pub fn init(relation: []const u8, rowid: i64, base: ?[]const u8, ours: ?[]const u8, theirs: ?[]const u8) ConflictArtifact {        return initRow(relation, rowid, base, ours, theirs);    }    pub fn initRow(relation: []const u8, rowid: i64, base: ?[]const u8, ours: ?[]const u8, theirs: ?[]const u8) ConflictArtifact {        var builder = Builder.init("sql.conflict");        builder.writeU8(@backingInt(ConflictKind.row));        builder.bytes(relation);        builder.writeI64(rowid);        builder.optionalConflictValue(.row, if (base) |value| ConflictValue{ .row = value } else null);        builder.optionalConflictValue(.row, if (ours) |value| ConflictValue{ .row = value } else null);        builder.optionalConflictValue(.row, if (theirs) |value| ConflictValue{ .row = value } else null);        return .{            .kind = .row,            .relation = relation,            .rowid = rowid,            .base = if (base) |value| .{ .row = value } else null,            .ours = if (ours) |value| .{ .row = value } else null,            .theirs = if (theirs) |value| .{ .row = value } else null,            .hash = builder.finish(),        };    }    pub fn initRelation(relation: []const u8, base: ?Hash, ours: ?Hash, theirs: ?Hash) ConflictArtifact {        var builder = Builder.init("sql.conflict");        builder.writeU8(@backingInt(ConflictKind.relation));        builder.bytes(relation);        builder.optionalConflictValue(.relation, if (base) |value| ConflictValue{ .relation = value } else null);        builder.optionalConflictValue(.relation, if (ours) |value| ConflictValue{ .relation = value } else null);        builder.optionalConflictValue(.relation, if (theirs) |value| ConflictValue{ .relation = value } else null);        return .{            .kind = .relation,            .relation = relation,            .base = if (base) |value| .{ .relation = value } else null,            .ours = if (ours) |value| .{ .relation = value } else null,            .theirs = if (theirs) |value| .{ .relation = value } else null,            .hash = builder.finish(),        };    }    pub fn entry(self: ConflictArtifact) ConflictEntry {        return .{            .kind = self.kind,            .relation = self.relation,            .rowid = self.rowid,            .hash = self.hash,        };    }};pub const DatabaseRoot = struct {    allocator: ?Allocator = null,    format: u32 = format_version,    feature: u32 = 0,    entries: []const RelationEntry = &.{},    conflicts: Hash,    hash: Hash,    pub fn init(entries: []const RelationEntry, conflicts: ConflictRoot) DatabaseRoot {        var builder = Builder.init("sql.database");        builder.writeU32(format_version);        builder.writeU32(0);        builder.hash(conflicts.hash);        builder.writeU64(entries.len);        for (entries) |entry| {            builder.bytes(entry.name);            builder.hash(entry.hash);        }        return .{            .entries = entries,            .conflicts = conflicts.hash,            .hash = builder.finish(),        };    }    pub fn initSorted(allocator: Allocator, entries: []const RelationEntry, conflicts: ConflictRoot) Allocator.Error!DatabaseRoot {        const sorted = try allocator.alloc(RelationEntry, entries.len);        var copied: usize = 0;        errdefer {            for (sorted[0..copied]) |entry| allocator.free(entry.name);            allocator.free(sorted);        }        for (entries, sorted) |entry, *target| {            target.* = .{                .name = try allocator.dupe(u8, entry.name),                .hash = entry.hash,            };            copied += 1;        }        std.mem.sort(RelationEntry, sorted, {}, relationEntryLessThan);        var root = init(sorted, conflicts);        root.allocator = allocator;        return root;    }    pub fn clone(self: *const DatabaseRoot, allocator: Allocator) Allocator.Error!DatabaseRoot {        return try self.withConflicts(allocator, self.conflicts);    }    pub fn withConflicts(        self: *const DatabaseRoot,        allocator: Allocator,        conflicts: Hash,    ) Allocator.Error!DatabaseRoot {        return try initSorted(allocator, self.entries, .{ .hash = conflicts });    }    pub fn deinit(self: *DatabaseRoot) void {        if (self.allocator) |allocator| {            for (self.entries) |entry| allocator.free(entry.name);            allocator.free(self.entries);        }        self.* = undefined;    }};pub const DatabaseValue = struct {    allocator: Allocator,    root: DatabaseRoot,    relations: []RelationValue,    pub fn deinit(self: *DatabaseValue) void {        self.root.deinit();        for (self.relations) |*relation| relation.deinit(self.allocator);        if (self.relations.len != 0) self.allocator.free(self.relations);        self.* = undefined;    }    pub fn intoRoot(self: *DatabaseValue) DatabaseRoot {        const root = self.root;        for (self.relations) |*relation| relation.deinit(self.allocator);        if (self.relations.len != 0) self.allocator.free(self.relations);        self.* = undefined;        return root;    }    pub fn clone(self: *const DatabaseValue, allocator: Allocator) Allocator.Error!DatabaseValue {        var root = try self.root.clone(allocator);        errdefer root.deinit();        const relations = try allocator.alloc(RelationValue, self.relations.len);        var relation_count: usize = 0;        errdefer {            for (relations[0..relation_count]) |*relation| relation.deinit(allocator);            if (relations.len != 0) allocator.free(relations);        }        for (self.relations, relations) |relation, *target| {            target.* = try relation.clone(allocator);            relation_count += 1;        }        return .{            .allocator = allocator,            .root = root,            .relations = relations,        };    }    pub fn findRelation(self: *const DatabaseValue, name: []const u8) ?*const RelationValue {        for (self.relations) |*relation_value| {            if (std.mem.eql(u8, relation_value.root.name, name)) return relation_value;        }        return null;    }};pub const Commit = struct {    root: Hash,    parents: []const Hash = &.{},    hash: Hash,    pub fn init(root: Hash, parents: []const Hash) Commit {        var builder = Builder.init("sql.commit");        builder.hash(root);        builder.writeU64(parents.len);        for (parents) |parent| builder.hash(parent);        return .{            .root = root,            .parents = parents,            .hash = builder.finish(),        };    }};pub const Ref = struct {    name: []const u8,    target: Hash,};pub const WorkingSet = struct {    base: Hash,    working: Hash,    staged: Hash,    pub fn init(base: Hash) WorkingSet {        return .{            .base = base,            .working = base,            .staged = base,        };    }    pub fn withWorking(self: WorkingSet, working: Hash) WorkingSet {        return .{            .base = self.base,            .working = working,            .staged = self.staged,        };    }    pub fn stage(self: WorkingSet) WorkingSet {        return .{            .base = self.base,            .working = self.working,            .staged = self.working,        };    }    pub fn advance(self: WorkingSet, base: Hash) WorkingSet {        _ = self;        return WorkingSet.init(base);    }    pub fn dirty(self: WorkingSet) bool {        return !same(self.base, self.working);    }    pub fn hasStaged(self: WorkingSet) bool {        return !same(self.base, self.staged);    }};pub fn emptyHash(kind: []const u8) Hash {    var builder = Builder.init(kind);    return builder.finish();}pub fn same(left: Hash, right: Hash) bool {    return std.mem.eql(u8, left[0..], right[0..]);}pub fn relationRoot(allocator: Allocator, name: []const u8, catalog_schema: catalog_mod.Schema, handle: *const catalog_mod.RelationHandle, stats: ?*const catalog_mod.RelationStats) Error!RelationRoot {    const owned_name = try allocator.dupe(u8, name);    errdefer allocator.free(owned_name);    var table_root = try mapRoot(allocator, &handle.relation.table.rows);    errdefer table_root.deinit();    var schema_descriptor = try RelationSchema.init(allocator, handle.definitions, handle.index_definitions);    errdefer schema_descriptor.deinit();    const schema_hash = schemaHash(schema_descriptor.columns, schema_descriptor.indexes);    const stats_root = relationStatsRoot(stats);    const indexes = try allocator.alloc(IndexRoot, handle.specs.len);    var index_count: usize = 0;    errdefer {        for (indexes[0..index_count]) |*index| index.deinit();        allocator.free(indexes);    }    for (handle.specs, indexes) |spec, *target| {        var index_tree = try handle.relation.space.tree(spec.root_page);        var map = try mapRoot(allocator, &index_tree);        var map_assigned = false;        errdefer if (!map_assigned) map.deinit();        const index_stats = findIndexStats(stats, spec.root_page);        const index_stats_hash = indexStatsHash(index_stats);        target.* = .{            .fields = fieldsHash(spec.fields, spec.columns),            .map = map,            .stats = index_stats_hash,            .hash = indexHash(spec.fields, spec.columns, map.hash, index_stats_hash),        };        map_assigned = true;        index_count += 1;    }    var builder = Builder.init("sql.relation");    builder.writeU32(format_version);    builder.bytes(name);    builder.hash(schema_hash);    builder.hash(table_root.hash);    builder.hash(stats_root.hash);    builder.writeU64(indexes.len);    for (indexes) |index| builder.hash(index.hash);    return .{        .allocator = allocator,        .name = owned_name,        .catalog = .{            .format = catalog_schema.format,            .version = catalog_schema.version,        },        .schema = schema_hash,        .schema_descriptor = schema_descriptor,        .table = table_root,        .indexes = indexes,        .stats = stats_root,        .hash = builder.finish(),    };}pub fn relationRootMaintained(    allocator: Allocator,    name: []const u8,    catalog_schema: catalog_mod.Schema,    handle: *const catalog_mod.RelationHandle,    stats: ?*const catalog_mod.RelationStats,) Error!RelationRoot {    return try relationRootMaintainedFrom(allocator, name, catalog_schema, handle, stats);}pub fn readRelationRootMaintained(    allocator: Allocator,    name: []const u8,    catalog_schema: catalog_mod.Schema,    handle: *const catalog_mod.ReadRelationHandle,    stats: ?*const catalog_mod.RelationStats,) Error!RelationRoot {    return try relationRootMaintainedFrom(allocator, name, catalog_schema, handle, stats);}fn relationRootMaintainedFrom(    allocator: Allocator,    name: []const u8,    catalog_schema: catalog_mod.Schema,    handle: anytype,    stats: ?*const catalog_mod.RelationStats,) Error!RelationRoot {    const owned_name = try allocator.dupe(u8, name);    errdefer allocator.free(owned_name);    var table_root = try mapRootFromIdentity(allocator, &handle.relation.table.rows);    errdefer table_root.deinit();    var schema_descriptor = try RelationSchema.init(allocator, handle.definitions, handle.index_definitions);    errdefer schema_descriptor.deinit();    const schema_hash = schemaHash(schema_descriptor.columns, schema_descriptor.indexes);    const stats_root = relationStatsRoot(stats);    const indexes = try allocator.alloc(IndexRoot, handle.specs.len);    var index_count: usize = 0;    errdefer {        for (indexes[0..index_count]) |*index| index.deinit();        allocator.free(indexes);    }    for (handle.specs, indexes) |spec, *target| {        var index_tree = try handle.relation.space.tree(spec.root_page);        var map = try mapRootFromIdentity(allocator, &index_tree);        var map_assigned = false;        errdefer if (!map_assigned) map.deinit();        const index_stats = findIndexStats(stats, spec.root_page);        const index_stats_hash = indexStatsHash(index_stats);        target.* = .{            .fields = fieldsHash(spec.fields, spec.columns),            .map = map,            .stats = index_stats_hash,            .hash = indexHash(spec.fields, spec.columns, map.hash, index_stats_hash),        };        map_assigned = true;        index_count += 1;    }    var builder = Builder.init("sql.relation");    builder.writeU32(format_version);    builder.bytes(name);    builder.hash(schema_hash);    builder.hash(table_root.hash);    builder.hash(stats_root.hash);    builder.writeU64(indexes.len);    for (indexes) |index| builder.hash(index.hash);    return .{        .allocator = allocator,        .name = owned_name,        .catalog = .{            .format = catalog_schema.format,            .version = catalog_schema.version,        },        .schema = schema_hash,        .schema_descriptor = schema_descriptor,        .table = table_root,        .indexes = indexes,        .stats = stats_root,        .hash = builder.finish(),    };}pub const RelationKey = struct {    schema: Hash,    table: Hash,    stats: Hash,    hash: Hash,};pub fn relationKey(    name: []const u8,    handle: *const catalog_mod.RelationHandle,    stats: ?*const catalog_mod.RelationStats,) Error!RelationKey {    const schema_hash = schemaHash(handle.definitions, handle.index_definitions);    const table_identity = try handle.relation.table.rows.identity();    const table_hash = handle.relation.table.rows.digestIdentity(&table_identity);    const stats_hash = relationStatsRoot(stats).hash;    var builder = Builder.init("sql.relation");    builder.writeU32(format_version);    builder.bytes(name);    builder.hash(schema_hash);    builder.hash(table_hash);    builder.hash(stats_hash);    builder.writeU64(handle.specs.len);    for (handle.specs) |spec| {        var index_tree = try handle.relation.space.tree(spec.root_page);        const index_identity = try index_tree.identity();        const map_hash = index_tree.digestIdentity(&index_identity);        const index_stats_hash = indexStatsHash(findIndexStats(stats, spec.root_page));        builder.hash(indexHash(spec.fields, spec.columns, map_hash, index_stats_hash));    }    return .{        .schema = schema_hash,        .table = table_hash,        .stats = stats_hash,        .hash = builder.finish(),    };}fn mapRootFromIdentity(allocator: Allocator, stored: anytype) Error!MapRoot {    const identity = try stored.identity();    const nodes = try allocator.alloc(tree.Node, 0);    errdefer allocator.free(nodes);    const edges = try allocator.alloc(usize, 0);    errdefer allocator.free(edges);    return .{        .allocator = allocator,        .summary = .{            .entries = @intCast(identity.entries),            .key_bytes = @intCast(identity.key_bytes),            .record_bytes = @intCast(identity.value_bytes),            .value_bytes = @intCast(identity.value_bytes),        },        .hash = stored.digestIdentity(&identity),        .subtree = std.mem.zeroes(Hash),        .nodes = nodes,        .edges = edges,    };}pub fn relationRows(allocator: Allocator, handle: *const catalog_mod.RelationHandle) Error![]RelationRow {    var rows: std.ArrayList(RelationRow) = .empty;    errdefer {        for (rows.items) |row_value| allocator.free(row_value.bytes);        rows.deinit(allocator);    }    var scan: relation_mod.Scan = undefined;    try handle.relation.scan(&scan, allocator, null, null);    defer scan.deinit();    while (try scan.next()) |entry| {        const bytes = try allocator.dupe(u8, entry.bytes);        errdefer allocator.free(bytes);        try rows.append(allocator, .{            .rowid = entry.rowid,            .bytes = bytes,        });    }    return try rows.toOwnedSlice(allocator);}pub fn cloneRelationRows(allocator: Allocator, rows: []const RelationRow) Allocator.Error![]RelationRow {    const cloned = try allocator.alloc(RelationRow, rows.len);    var count: usize = 0;    errdefer {        for (cloned[0..count]) |row_value| allocator.free(row_value.bytes);        allocator.free(cloned);    }    for (rows, cloned) |row_value, *target| {        target.* = .{            .rowid = row_value.rowid,            .bytes = try allocator.dupe(u8, row_value.bytes),        };        count += 1;    }    std.mem.sort(RelationRow, cloned, {}, relationRowLessThan);    return cloned;}pub fn freeRelationRows(allocator: Allocator, rows: []RelationRow) void {    for (rows) |row_value| allocator.free(row_value.bytes);    if (rows.len != 0) allocator.free(rows);}pub fn relationValue(allocator: Allocator, name: []const u8, catalog_schema: catalog_mod.Schema, handle: *const catalog_mod.RelationHandle, stats: ?*const catalog_mod.RelationStats) Error!RelationValue {    var root = try relationRootMaintained(allocator, name, catalog_schema, handle, stats);    defer root.deinit();    return try relationValueFromRoot(allocator, &root, handle);}pub fn relationValueFromRoot(allocator: Allocator, root: *const RelationRoot, handle: *const catalog_mod.RelationHandle) Error!RelationValue {    var owned_root = try root.clone(allocator);    errdefer owned_root.deinit();    const rows = try relationRows(allocator, handle);    errdefer freeRelationRows(allocator, rows);    return .{        .root = owned_root,        .rows = rows,    };}pub fn relationRootFromRows(allocator: Allocator, source: *const RelationRoot, rows: []const RelationRow) Error!RelationRoot {    return try relationRootFromRowsWithOptionalStats(allocator, source, rows, null);}pub fn relationRootFromRowsWithStats(    allocator: Allocator,    source: *const RelationRoot,    rows: []const RelationRow,    stats: *const catalog_mod.RelationStats,) Error!RelationRoot {    return try relationRootFromRowsWithOptionalStats(allocator, source, rows, stats);}fn relationRootFromRowsWithOptionalStats(    allocator: Allocator,    source: *const RelationRoot,    rows: []const RelationRow,    stats: ?*const catalog_mod.RelationStats,) Error!RelationRoot {    const owned_name = try allocator.dupe(u8, source.name);    errdefer allocator.free(owned_name);    var table_root = try tableRootFromRows(allocator, rows);    errdefer table_root.deinit();    var schema_descriptor = try source.schema_descriptor.clone(allocator);    errdefer schema_descriptor.deinit();    const schema_hash = schemaHash(schema_descriptor.columns, schema_descriptor.indexes);    if (stats) |relation_stats| {        if (relation_stats.indexes.len != schema_descriptor.indexes.len) {            return error.CatalogCorrupt;        }        if (!logicalSummaryEqual(table_root.summary, relation_stats.table)) {            return error.CatalogCorrupt;        }    }    const stats_root = relationStatsRoot(stats);    const indexes = try relationIndexesFromRows(        allocator,        rows,        schema_descriptor.indexes,        stats,    );    errdefer freeIndexRoots(allocator, indexes);    var builder = Builder.init("sql.relation");    builder.writeU32(format_version);    builder.bytes(source.name);    builder.hash(schema_hash);    builder.hash(table_root.hash);    builder.hash(stats_root.hash);    builder.writeU64(indexes.len);    for (indexes) |index| builder.hash(index.hash);    return .{        .allocator = allocator,        .name = owned_name,        .catalog = source.catalog,        .schema = schema_hash,        .schema_descriptor = schema_descriptor,        .table = table_root,        .indexes = indexes,        .stats = stats_root,        .hash = builder.finish(),    };}fn relationIndexesFromRows(    allocator: Allocator,    rows: []const RelationRow,    definitions: []const catalog_mod.IndexDefinition,    stats: ?*const catalog_mod.RelationStats,) Error![]IndexRoot {    const indexes = try allocator.alloc(IndexRoot, definitions.len);    var index_count: usize = 0;    errdefer {        for (indexes[0..index_count]) |*index| index.deinit();        allocator.free(indexes);    }    for (definitions, indexes, 0..) |definition, *target, index_offset| {        var map = try indexRootFromRows(            allocator,            rows,            definition.fields,            definition.columns,        );        var map_assigned = false;        errdefer if (!map_assigned) map.deinit();        const index_stats = if (stats) |relation_stats|            &relation_stats.indexes[index_offset]        else            null;        if (index_stats) |prepared| {            if (!std.mem.eql(u8, prepared.name, definition.name)) {                return error.CatalogCorrupt;            }            if (!logicalSummaryEqual(map.summary, prepared.summary)) {                return error.CatalogCorrupt;            }        }        const stats_hash = indexStatsHash(index_stats);        target.* = .{            .fields = fieldsHash(definition.fields, definition.columns),            .map = map,            .stats = stats_hash,            .hash = indexHash(                definition.fields,                definition.columns,                map.hash,                stats_hash,            ),        };        map_assigned = true;        index_count += 1;    }    return indexes;}fn freeIndexRoots(allocator: Allocator, indexes: []IndexRoot) void {    for (indexes) |*index| index.deinit();    allocator.free(indexes);}pub fn relationValueApplyingMaterializedEdits(allocator: Allocator, base: *const RelationValue, edits: []const relation_mod.Edit) Error!RelationValue {    const rows = try relationRowsApplyingEdits(allocator, base.rows, edits);    errdefer freeRelationRows(allocator, rows);    var root = try relationRootFromRows(allocator, &base.root, rows);    errdefer root.deinit();    return .{        .root = root,        .rows = rows,    };}pub fn databaseValue(    allocator: Allocator,    catalog: *const catalog_mod.Catalog,    conflicts: Hash,) Error!DatabaseValue {    return try databaseValueReplacingRelations(allocator, catalog, &.{}, conflicts);}pub fn databaseValueReplacingRelations(    allocator: Allocator,    catalog: *const catalog_mod.Catalog,    replacements: []const RelationValue,    conflicts: Hash,) Error!DatabaseValue {    var names = try catalog.relationNames(allocator);    defer names.deinit();    const relations = try allocator.alloc(RelationValue, names.names.len);    var relation_count: usize = 0;    var relations_owned = true;    errdefer {        if (relations_owned) {            for (relations[0..relation_count]) |*relation| relation.deinit(allocator);            if (relations.len != 0) allocator.free(relations);        }    }    for (names.names, relations) |name, *relation| {        if (replacementRelation(replacements, name)) |replacement| {            relation.* = try replacement.clone(allocator);        } else {            var state = try catalog.readRelation(allocator, name);            defer state.deinit();            relation.* = try relationValue(                allocator,                name,                state.schema,                &state.handle,                state.relationStats(),            );        }        relation_count += 1;    }    const value = try databaseValueFromOwnedRelations(allocator, relations, conflicts);    relations_owned = false;    return value;}pub fn databaseValueFromOwnedRelations(allocator: Allocator, relations: []RelationValue, conflicts: Hash) Allocator.Error!DatabaseValue {    const entries = try allocator.alloc(RelationEntry, relations.len);    defer allocator.free(entries);    for (relations, entries) |relation, *entry| {        entry.* = .{            .name = relation.root.name,            .hash = relation.root.hash,        };    }    var root = try DatabaseRoot.initSorted(allocator, entries, .{ .hash = conflicts });    errdefer root.deinit();    return .{        .allocator = allocator,        .root = root,        .relations = relations,    };}fn replacementRelation(replacements: []const RelationValue, name: []const u8) ?*const RelationValue {    for (replacements) |*replacement| {        if (std.mem.eql(u8, replacement.root.name, name)) return replacement;    }    return null;}pub fn databaseRoot(    allocator: Allocator,    catalog: *const catalog_mod.Catalog,    conflicts: Hash,) Error!DatabaseRoot {    var names = try catalog.relationNames(allocator);    defer names.deinit();    const entries = try allocator.alloc(RelationEntry, names.names.len);    defer allocator.free(entries);    for (names.names, entries) |name, *entry| {        var state = try catalog.readRelation(allocator, name);        defer state.deinit();        var root = try relationRoot(            allocator,            name,            state.schema,            &state.handle,            state.relationStats(),        );        defer root.deinit();        entry.* = .{            .name = name,            .hash = root.hash,        };    }    return try DatabaseRoot.initSorted(allocator, entries, .{ .hash = conflicts });}pub fn databaseRootMaintained(    allocator: Allocator,    catalog: *const catalog_mod.Catalog,    conflicts: Hash,) Error!DatabaseRoot {    return try databaseRootMaintainedFrom(allocator, catalog, conflicts);}pub fn readDatabaseRootMaintained(    allocator: Allocator,    catalog: *const catalog_mod.Reader,    conflicts: Hash,) Error!DatabaseRoot {    return try databaseRootMaintainedFrom(allocator, catalog, conflicts);}fn databaseRootMaintainedFrom(    allocator: Allocator,    catalog: anytype,    conflicts: Hash,) Error!DatabaseRoot {    var names = try catalog.relationNames(allocator);    defer names.deinit();    const entries = try allocator.alloc(RelationEntry, names.names.len);    defer allocator.free(entries);    for (names.names, entries) |name, *entry| {        var state = try catalog.readRelation(allocator, name);        defer state.deinit();        var root = try relationRootMaintainedFrom(            allocator,            name,            state.schema,            &state.handle,            state.relationStats(),        );        defer root.deinit();        entry.* = .{            .name = name,            .hash = root.hash,        };    }    return try DatabaseRoot.initSorted(allocator, entries, .{ .hash = conflicts });}pub fn databaseRootReplacingEntries(allocator: Allocator, base: *const DatabaseRoot, replacements: []const RelationEntry) Allocator.Error!DatabaseRoot {    const entries = try allocator.alloc(RelationEntry, base.entries.len);    defer allocator.free(entries);    for (base.entries, entries) |entry, *target| {        target.* = entry;        for (replacements) |replacement| {            if (std.mem.eql(u8, replacement.name, entry.name)) {                target.hash = replacement.hash;                break;            }        }    }    return try DatabaseRoot.initSorted(allocator, entries, .{ .hash = base.conflicts });}pub fn mapRoot(allocator: Allocator, source: *const tree.Tree) Error!MapRoot {    return try source.root(allocator);}fn tableRootFromRows(allocator: Allocator, rows: []const RelationRow) Error!MapRoot {    const keys = try allocator.alloc([key.rowid_size]u8, rows.len);    defer allocator.free(keys);    const entries = try allocator.alloc(tree.RootEntry, rows.len);    defer allocator.free(entries);    for (rows, entries, 0..) |row_value, *entry, offset| {        _ = try row.View.init(row_value.bytes);        entry.* = .{            .key = try key.encodeRowId(keys[offset][0..], row_value.rowid),            .value = row_value.bytes,        };    }    std.mem.sort(tree.RootEntry, entries, {}, rootEntryLessThan);    return try tree.rootFromSortedEntries(allocator, entries);}fn indexRootFromRows(allocator: Allocator, rows: []const RelationRow, fields: []const usize, columns: []const row.Column) Error!MapRoot {    if (fields.len > relation_mod.max_index_fields) return error.TooManyIndexFields;    var key_bytes: usize = 0;    for (rows) |row_value| {        const view = try row.View.init(row_value.bytes);        var projected: [relation_mod.max_index_fields]row.Value = undefined;        const values = try view.project(fields, projected[0..]);        var buffer: [page.size]u8 = undefined;        const encoded = try key.encodeIndex(&buffer, values, columns, row_value.rowid);        key_bytes = std.math.add(usize, key_bytes, encoded.len) catch            return error.OutputTooSmall;    }    const keys = if (key_bytes == 0)        @as([]u8, &.{})    else        try allocator.alloc(u8, key_bytes);    defer if (keys.len != 0) allocator.free(keys);    const entries = try allocator.alloc(tree.RootEntry, rows.len);    defer allocator.free(entries);    var cursor: usize = 0;    for (rows, entries) |row_value, *entry| {        const view = try row.View.init(row_value.bytes);        var projected: [relation_mod.max_index_fields]row.Value = undefined;        const values = try view.project(fields, projected[0..]);        const encoded = try key.encodeIndex(            keys[cursor..],            values,            columns,            row_value.rowid,        );        entry.* = .{            .key = encoded,            .value = "",        };        cursor += encoded.len;    }    std.debug.assert(cursor == keys.len);    std.mem.sort(tree.RootEntry, entries, {}, rootEntryLessThan);    return try tree.rootFromSortedEntries(allocator, entries);}pub fn schemaHash(definitions: []const catalog_mod.ColumnDefinition, indexes: []const catalog_mod.IndexDefinition) Hash {    var builder = Builder.init("sql.schema");    builder.writeU64(definitions.len);    for (definitions) |definition| {        builder.bytes(definition.name);        builder.writeU8(@backingInt(definition.column.collation));        builder.rowValue(definition.default);    }    builder.writeU64(indexes.len);    for (indexes) |index| {        builder.bytes(index.name);        builder.writeU64(index.fields.len);        for (index.fields) |field| builder.writeU64(field);        builder.writeU64(index.columns.len);        for (index.columns) |column| builder.writeU8(@backingInt(column.collation));    }    return builder.finish();}fn cloneColumn(allocator: Allocator, column: catalog_mod.ColumnDefinition) Allocator.Error!catalog_mod.ColumnDefinition {    const name = try allocator.dupe(u8, column.name);    errdefer allocator.free(name);    const default = try cloneValue(allocator, column.default);    errdefer deinitValue(allocator, default);    return .{        .name = name,        .column = column.column,        .default = default,    };}fn deinitColumn(allocator: Allocator, column: *catalog_mod.ColumnDefinition) void {    allocator.free(column.name);    deinitValue(allocator, column.default);    column.* = undefined;}fn cloneIndex(allocator: Allocator, index: catalog_mod.IndexDefinition) Allocator.Error!catalog_mod.IndexDefinition {    const name = try allocator.dupe(u8, index.name);    errdefer allocator.free(name);    const fields = try allocator.dupe(usize, index.fields);    errdefer allocator.free(fields);    const columns = try allocator.dupe(row.Column, index.columns);    errdefer allocator.free(columns);    return .{        .name = name,        .fields = fields,        .columns = columns,    };}fn deinitIndex(allocator: Allocator, index: *catalog_mod.IndexDefinition) void {    allocator.free(index.name);    allocator.free(index.fields);    allocator.free(index.columns);    index.* = undefined;}fn cloneValue(allocator: Allocator, value: row.Value) Allocator.Error!row.Value {    return switch (value) {        .nil => .nil,        .integer => |integer| .{ .integer = integer },        .text => |text| .{ .text = try allocator.dupe(u8, text) },        .blob => |blob| .{ .blob = try allocator.dupe(u8, blob) },    };}fn deinitValue(allocator: Allocator, value: row.Value) void {    switch (value) {        .nil, .integer => {},        .text => |text| allocator.free(text),        .blob => |blob| allocator.free(blob),    }}fn indexHash(fields: []const usize, columns: []const row.Column, map_hash: Hash, stats_hash: Hash) Hash {    var builder = Builder.init("sql.index");    builder.hash(fieldsHash(fields, columns));    builder.hash(map_hash);    builder.hash(stats_hash);    return builder.finish();}fn fieldsHash(fields: []const usize, columns: []const row.Column) Hash {    var builder = Builder.init("sql.index.fields");    builder.writeU64(fields.len);    for (fields) |field| builder.writeU64(field);    builder.writeU64(columns.len);    for (columns) |column| builder.writeU8(@backingInt(column.collation));    return builder.finish();}fn relationStatsRoot(stats: ?*const catalog_mod.RelationStats) StatsRoot {    const relation_stats = stats orelse return .{        .hash = emptyHash("sql.stats.none"),    };    var builder = Builder.init("sql.stats.relation");    builder.logicalSummary(relation_stats.table);    builder.writeU64(relation_stats.indexes.len);    for (relation_stats.indexes) |index| builder.hash(indexStatsHash(&index));    return .{        .table = relation_stats.table,        .indexes = relation_stats.indexes.len,        .hash = builder.finish(),    };}fn logicalSummaryEqual(left: tree.Summary, right: tree.Summary) bool {    return left.entries == right.entries and        left.key_bytes == right.key_bytes and        left.value_bytes == right.value_bytes;}fn findIndexStats(stats: ?*const catalog_mod.RelationStats, root_page: u32) ?*const catalog_mod.IndexStats {    const relation_stats = stats orelse return null;    for (relation_stats.indexes) |*index_stats| {        if (index_stats.root_page == root_page) return index_stats;    }    return null;}fn indexStatsHash(stats: ?*const catalog_mod.IndexStats) Hash {    const index_stats = stats orelse return emptyHash("sql.stats.index.none");    var builder = Builder.init("sql.stats.index");    builder.bytes(index_stats.name);    builder.logicalSummary(index_stats.summary);    builder.distribution(index_stats.distribution);    return builder.finish();}fn relationEntryLessThan(_: void, left: RelationEntry, right: RelationEntry) bool {    return simd.order(Bytes, left.name, right.name) == .lt;}fn rootEntryLessThan(_: void, left: tree.RootEntry, right: tree.RootEntry) bool {    return simd.order(Bytes, left.key, right.key) == .lt;}pub fn relationRowLessThan(_: void, left: RelationRow, right: RelationRow) bool {    return left.rowid < right.rowid;}fn relationRowsApplyingEdits(allocator: Allocator, source: []const RelationRow, edits: []const relation_mod.Edit) Error![]RelationRow {    var rows: std.ArrayList(RelationRow) = .empty;    errdefer {        for (rows.items) |row_value| allocator.free(row_value.bytes);        rows.deinit(allocator);    }    for (source) |row_value| {        const bytes = try allocator.dupe(u8, row_value.bytes);        errdefer allocator.free(bytes);        try rows.append(allocator, .{            .rowid = row_value.rowid,            .bytes = bytes,        });    }    for (edits) |edit| try applyRelationRowEdit(allocator, &rows, edit);    return try rows.toOwnedSlice(allocator);}fn applyRelationRowEdit(allocator: Allocator, rows: *std.ArrayList(RelationRow), edit: relation_mod.Edit) Error!void {    switch (edit) {        .put => |put_edit| {            _ = try row.View.init(put_edit.bytes);            const bytes = try allocator.dupe(u8, put_edit.bytes);            errdefer allocator.free(bytes);            const target = relationRowPosition(rows.items, put_edit.rowid);            if (target.found) {                allocator.free(rows.items[target.index].bytes);                rows.items[target.index].bytes = bytes;            } else {                try rows.insert(allocator, target.index, .{                    .rowid = put_edit.rowid,                    .bytes = bytes,                });            }        },        .update => |update_edit| {            const target = relationRowPosition(rows.items, update_edit.rowid);            if (!target.found) return error.KeyNotFound;            const merged = try relation_mod.applyUpdate(allocator, rows.items[target.index].bytes, update_edit.assignments);            allocator.free(rows.items[target.index].bytes);            rows.items[target.index].bytes = merged;        },        .delete => |rowid| {            const target = relationRowPosition(rows.items, rowid);            if (!target.found) return error.KeyNotFound;            const removed = rows.orderedRemove(target.index);            allocator.free(removed.bytes);        },    }}const RelationRowPosition = struct {    index: usize,    found: bool,};fn relationRowPosition(rows: []const RelationRow, rowid: i64) RelationRowPosition {    var low: usize = 0;    var high: usize = rows.len;    while (low < high) {        const mid = low + (high - low) / 2;        if (rows[mid].rowid < rowid) {            low = mid + 1;        } else {            high = mid;        }    }    return .{        .index = low,        .found = low < rows.len and rows[low].rowid == rowid,    };}const Builder = struct {    hasher: std.crypto.hash.sha2.Sha256,    fn init(tag: []const u8) Builder {        var builder = Builder{ .hasher = std.crypto.hash.sha2.Sha256.init(.{}) };        builder.bytes(tag);        return builder;    }    fn finish(self: *Builder) Hash {        var digest: Hash = undefined;        self.hasher.final(&digest);        return digest;    }    fn hash(self: *Builder, value: Hash) void {        self.hasher.update(value[0..]);    }    fn bytes(self: *Builder, value: []const u8) void {        self.writeU64(value.len);        self.hasher.update(value);    }    fn optionalBytes(self: *Builder, value: ?[]const u8) void {        if (value) |bytes_value| {            self.writeU8(1);            self.bytes(bytes_value);        } else {            self.writeU8(0);        }    }    fn optionalConflictValue(self: *Builder, kind: ConflictKind, value: ?ConflictValue) void {        if (value) |conflict_value| {            self.writeU8(1);            switch (conflict_value) {                .row => |bytes_value| {                    std.debug.assert(kind == .row);                    self.bytes(bytes_value);                },                .relation => |hash_value| {                    std.debug.assert(kind == .relation);                    self.hash(hash_value);                },            }        } else {            self.writeU8(0);        }    }    fn rowValue(self: *Builder, row_value: row.Value) void {        self.writeU8(@backingInt(std.meta.activeTag(row_value)));        switch (row_value) {            .nil => {},            .integer => |integer| self.writeI64(integer),            .text => |text| self.bytes(text),            .blob => |blob| self.bytes(blob),        }    }    fn distribution(self: *Builder, value: catalog_mod.IndexDistribution) void {        self.writeU64(value.distinct_values);        self.writeU64(value.max_equal);        self.writeU64(value.samples.len);        for (value.samples) |entry_sample| self.sample(entry_sample);        self.bytes(value.sample_keys);        self.writeU64(value.prefixes.len);        for (value.prefixes) |prefix| {            self.writeU64(prefix.field_count);            self.writeU64(prefix.distinct_values);            self.writeU64(prefix.max_equal);            self.writeU64(prefix.samples.len);            for (prefix.samples) |entry_sample| self.sample(entry_sample);            self.bytes(prefix.sample_keys);        }    }    fn sample(self: *Builder, value: catalog_mod.IndexSample) void {        self.bytes(value.key);        self.writeU64(value.less_than);        self.writeU64(value.equal_count);        self.writeU64(value.less_distinct);    }    fn logicalSummary(self: *Builder, value: tree.Summary) void {        self.writeU64(value.entries);        self.writeU64(value.key_bytes);        self.writeU64(value.value_bytes);    }    fn writeU8(self: *Builder, value: u8) void {        self.hasher.update(&.{value});    }    fn writeU32(self: *Builder, value: u32) void {        var encoded: [4]u8 = undefined;        std.mem.writeInt(u32, encoded[0..], value, .big);        self.hasher.update(&encoded);    }    fn writeU64(self: *Builder, value: anytype) void {        var encoded: [8]u8 = undefined;        std.mem.writeInt(u64, encoded[0..], @intCast(value), .big);        self.hasher.update(&encoded);    }    fn writeI64(self: *Builder, value: i64) void {        var encoded: [8]u8 = undefined;        std.mem.writeInt(i64, encoded[0..], value, .big);        self.hasher.update(&encoded);    }};test "database root hash depends on relation root hashes and names" {    const empty = emptyHash("empty");    var changed = empty;    changed[0] +%= 1;    const conflicts = ConflictRoot.empty();    const left = DatabaseRoot.init(&.{        .{ .name = "items", .hash = empty },    }, conflicts);    const same_left = DatabaseRoot.init(&.{        .{ .name = "items", .hash = empty },    }, conflicts);    const renamed = DatabaseRoot.init(&.{        .{ .name = "users", .hash = empty },    }, conflicts);    const modified = DatabaseRoot.init(&.{        .{ .name = "items", .hash = changed },    }, conflicts);    try std.testing.expect(same(left.hash, same_left.hash));    try std.testing.expect(!same(left.hash, renamed.hash));    try std.testing.expect(!same(left.hash, modified.hash));}test "database root sorted hash is independent of relation entry order" {    const items = emptyHash("items");    const users = emptyHash("users");    const conflicts = ConflictRoot.empty();    var first = try DatabaseRoot.initSorted(std.testing.allocator, &.{        .{ .name = "items", .hash = items },        .{ .name = "users", .hash = users },    }, conflicts);    defer first.deinit();    var second = try DatabaseRoot.initSorted(std.testing.allocator, &.{        .{ .name = "users", .hash = users },        .{ .name = "items", .hash = items },    }, conflicts);    defer second.deinit();    try std.testing.expect(same(first.hash, second.hash));    try std.testing.expectEqualStrings("items", first.entries[0].name);    try std.testing.expectEqualStrings("users", first.entries[1].name);}test "database root hash depends on conflict root" {    const relation = emptyHash("relation");    const artifact = ConflictArtifact.init("items", 7, "base", "ours", "theirs");    const conflicts = ConflictRoot.init(&.{artifact.entry()});    const clean = DatabaseRoot.init(&.{.{ .name = "items", .hash = relation }}, ConflictRoot.empty());    const conflicted = DatabaseRoot.init(&.{.{ .name = "items", .hash = relation }}, conflicts);    try std.testing.expect(!same(clean.hash, conflicted.hash));    try std.testing.expect(same(conflicts.hash, conflicted.conflicts));}test "conflict artifact hash distinguishes row and relation values" {    const ours = emptyHash("conflict.ours");    const theirs = emptyHash("conflict.theirs");    const row_artifact = ConflictArtifact.init("items", 0, null, "ours", "theirs");    const relation_artifact = ConflictArtifact.initRelation("items", null, ours, theirs);    try std.testing.expectEqual(ConflictKind.row, row_artifact.kind);    try std.testing.expectEqual(ConflictKind.relation, relation_artifact.kind);    try std.testing.expect(!same(row_artifact.hash, relation_artifact.hash));    try std.testing.expect(same(ours, relation_artifact.ours.?.relation));    try std.testing.expect(same(theirs, relation_artifact.theirs.?.relation));    const conflicts = ConflictRoot.init(&.{relation_artifact.entry()});    try std.testing.expectEqual(@as(usize, 1), conflicts.count);}test "schema hash includes index descriptor names" {    const columns = [_]catalog_mod.ColumnDefinition{.{ .name = "value" }};    const fields = [_]usize{0};    const index_columns = [_]row.Column{.{}};    const first = [_]catalog_mod.IndexDefinition{.{        .name = "items_value",        .fields = fields[0..],        .columns = index_columns[0..],    }};    const second = [_]catalog_mod.IndexDefinition{.{        .name = "items_value_alt",        .fields = fields[0..],        .columns = index_columns[0..],    }};    try std.testing.expect(!same(schemaHash(columns[0..], first[0..]), schemaHash(columns[0..], second[0..])));}test "conflict root sorted hash is independent of artifact order" {    const first = ConflictArtifact.init("items", 1, "base", "ours", "theirs");    const second = ConflictArtifact.init("users", 2, null, "ours", "theirs");    const left = try ConflictRoot.initSorted(std.testing.allocator, &.{ first.entry(), second.entry() });    const right = try ConflictRoot.initSorted(std.testing.allocator, &.{ second.entry(), first.entry() });    try std.testing.expect(same(left.hash, right.hash));    try std.testing.expectEqual(@as(usize, 2), left.count);}test "relation stats hash ignores physical table shape" {    var no_indexes = [_]catalog_mod.IndexStats{};    const left = catalog_mod.RelationStats{        .allocator = std.testing.allocator,        .table_root_page = 17,        .table = .{            .branch_pages = 1,            .leaf_pages = 2,            .overflow_pages = 3,            .entries = 11,            .inline_records = 9,            .overflow_records = 2,            .max_depth = 2,            .key_bytes = 44,            .record_bytes = 99,            .value_bytes = 180,        },        .indexes = no_indexes[0..],    };    const same_logical = catalog_mod.RelationStats{        .allocator = std.testing.allocator,        .table_root_page = 91,        .table = .{            .branch_pages = 8,            .leaf_pages = 13,            .overflow_pages = 21,            .entries = 11,            .inline_records = 4,            .overflow_records = 7,            .max_depth = 5,            .key_bytes = 44,            .record_bytes = 24,            .value_bytes = 180,        },        .indexes = no_indexes[0..],    };    const different_logical = catalog_mod.RelationStats{        .allocator = std.testing.allocator,        .table_root_page = 91,        .table = .{            .branch_pages = 8,            .leaf_pages = 13,            .overflow_pages = 21,            .entries = 12,            .inline_records = 4,            .overflow_records = 7,            .max_depth = 5,            .key_bytes = 44,            .record_bytes = 24,            .value_bytes = 180,        },        .indexes = no_indexes[0..],    };    const first = relationStatsRoot(&left);    const second = relationStatsRoot(&same_logical);    const changed = relationStatsRoot(&different_logical);    try std.testing.expect(same(first.hash, second.hash));    try std.testing.expect(!same(first.hash, changed.hash));}test "index stats hash ignores physical root and shape" {    var left_name = [_]u8{ 'b', 'y', '_', 'v', 'a', 'l', 'u', 'e' };    var right_name = [_]u8{ 'b', 'y', '_', 'v', 'a', 'l', 'u', 'e' };    var changed_name = [_]u8{ 'b', 'y', '_', 'v', 'a', 'l', 'u', 'e' };    const left = catalog_mod.IndexStats{        .name = left_name[0..],        .root_page = 19,        .summary = .{            .branch_pages = 1,            .leaf_pages = 3,            .overflow_pages = 5,            .entries = 7,            .inline_records = 6,            .overflow_records = 1,            .max_depth = 2,            .key_bytes = 33,            .record_bytes = 120,            .value_bytes = 88,        },        .distribution = .{            .distinct_values = 6,            .max_equal = 2,        },    };    const same_logical = catalog_mod.IndexStats{        .name = right_name[0..],        .root_page = 71,        .summary = .{            .branch_pages = 11,            .leaf_pages = 13,            .overflow_pages = 17,            .entries = 7,            .inline_records = 2,            .overflow_records = 5,            .max_depth = 4,            .key_bytes = 33,            .record_bytes = 60,            .value_bytes = 88,        },        .distribution = .{            .distinct_values = 6,            .max_equal = 2,        },    };    const different_distribution = catalog_mod.IndexStats{        .name = changed_name[0..],        .root_page = 71,        .summary = .{            .branch_pages = 11,            .leaf_pages = 13,            .overflow_pages = 17,            .entries = 7,            .inline_records = 2,            .overflow_records = 5,            .max_depth = 4,            .key_bytes = 33,            .record_bytes = 60,            .value_bytes = 88,        },        .distribution = .{            .distinct_values = 7,            .max_equal = 2,        },    };    try std.testing.expect(same(indexStatsHash(&left), indexStatsHash(&same_logical)));    try std.testing.expect(!same(indexStatsHash(&left), indexStatsHash(&different_distribution)));}test "commit hash depends on root and parents" {    const root = emptyHash("root");    const parent = emptyHash("parent");    const other_parent = emptyHash("other-parent");    const first = Commit.init(root, &.{parent});    const second = Commit.init(root, &.{parent});    const other = Commit.init(root, &.{other_parent});    try std.testing.expect(same(first.hash, second.hash));    try std.testing.expect(!same(first.hash, other.hash));}test "working set tracks explicit base working and staged roots" {    const base = emptyHash("base");    const working = emptyHash("working");    const next = emptyHash("next");    const clean = WorkingSet.init(base);    try std.testing.expect(same(clean.base, base));    try std.testing.expect(!clean.dirty());    try std.testing.expect(!clean.hasStaged());    const changed = clean.withWorking(working);    try std.testing.expect(changed.dirty());    try std.testing.expect(!changed.hasStaged());    const staged = changed.stage();    try std.testing.expect(staged.dirty());    try std.testing.expect(staged.hasStaged());    try std.testing.expect(same(staged.staged, working));    const advanced = staged.advance(next);    try std.testing.expect(!advanced.dirty());    try std.testing.expect(!advanced.hasStaged());    try std.testing.expect(same(advanced.base, next));}test "relation root ignores unrelated catalog schema version bumps" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "version-schema.db", .wal = "version-schema.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 256 });    var catalog = try catalog_mod.Catalog.open(&database, .{});    const first = try catalog.createRelation(std.testing.allocator, .{        .name = "items",        .columns = &.{.{ .name = "value" }},    }, .{ .durability = .buffered });    var handle = try catalog.openRelation(std.testing.allocator, "items");    defer handle.deinit();    _ = try handle.relation.put(std.testing.allocator, 1, &.{.{ .text = "one" }}, .{ .durability = .buffered });    var before = try relationRoot(std.testing.allocator, "items", first.schema, &handle, null);    defer before.deinit();    const second = try catalog.createRelation(std.testing.allocator, .{        .name = "users",    }, .{ .durability = .buffered });    try std.testing.expect(second.schema.version > first.schema.version);    var after = try relationRoot(std.testing.allocator, "items", second.schema, &handle, null);    defer after.deinit();    try std.testing.expect(same(before.hash, after.hash));}test "database root from catalog tracks all relation roots" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "database-root.db", .wal = "database-root.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 512 });    var catalog = try catalog_mod.Catalog.open(&database, .{});    _ = try catalog.createRelation(std.testing.allocator, .{        .name = "items",        .columns = &.{.{ .name = "value" }},    }, .{ .durability = .buffered });    _ = try catalog.createRelation(std.testing.allocator, .{        .name = "users",        .columns = &.{.{ .name = "name" }},    }, .{ .durability = .buffered });    var before = try databaseRoot(        std.testing.allocator,        &catalog,        ConflictRoot.empty().hash,    );    defer before.deinit();    var users = try catalog.openRelation(std.testing.allocator, "users");    defer users.deinit();    _ = try users.relation.put(std.testing.allocator, 1, &.{.{ .text = "ada" }}, .{ .durability = .buffered });    var after = try databaseRoot(        std.testing.allocator,        &catalog,        ConflictRoot.empty().hash,    );    defer after.deinit();    try std.testing.expect(!same(before.hash, after.hash));    try std.testing.expectEqual(@as(usize, 2), after.entries.len);    try std.testing.expectEqualStrings("items", after.entries[0].name);    try std.testing.expectEqualStrings("users", after.entries[1].name);}test "database value accepts replacement relation values" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "database-value-replacement.db", .wal = "database-value-replacement.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 512 });    var catalog = try catalog_mod.Catalog.open(&database, .{});    _ = try catalog.createRelation(std.testing.allocator, .{        .name = "items",        .columns = &.{.{ .name = "value" }},    }, .{ .durability = .buffered });    _ = try catalog.createRelation(std.testing.allocator, .{        .name = "users",        .columns = &.{.{ .name = "name" }},    }, .{ .durability = .buffered });    var live = try databaseValue(        std.testing.allocator,        &catalog,        ConflictRoot.empty().hash,    );    defer live.deinit();    var replacement = try live.relations[0].clone(std.testing.allocator);    defer replacement.deinit(std.testing.allocator);    replacement.root.hash = emptyHash("sql.test.replacement");    var replaced = try databaseValueReplacingRelations(        std.testing.allocator,        &catalog,        &.{replacement},        ConflictRoot.empty().hash,    );    defer replaced.deinit();    try std.testing.expect(same(replaced.relations[0].root.hash, replacement.root.hash));    try std.testing.expect(same(replaced.root.entries[0].hash, replacement.root.hash));    try std.testing.expect(same(replaced.relations[1].root.hash, live.relations[1].root.hash));}test "relation value applies row edits from an immutable base" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "relation-value-edits.db", .wal = "relation-value-edits.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 512 });    var catalog = try catalog_mod.Catalog.open(&database, .{});    _ = try catalog.createRelation(std.testing.allocator, .{        .name = "items",        .columns = &.{.{ .name = "value" }},    }, .{ .durability = .buffered });    var handle = try catalog.openRelation(std.testing.allocator, "items");    defer handle.deinit();    _ = try handle.relation.put(std.testing.allocator, 1, &.{.{ .text = "one" }}, .{ .durability = .buffered });    _ = try handle.relation.put(std.testing.allocator, 3, &.{.{ .text = "three" }}, .{ .durability = .buffered });    var base = try databaseValue(        std.testing.allocator,        &catalog,        ConflictRoot.empty().hash,    );    defer base.deinit();    const base_items = base.findRelation("items").?;    var two_buffer: [128]u8 = undefined;    const two = try row.encode(&two_buffer, &.{.{ .text = "two" }});    var updated_buffer: [128]u8 = undefined;    const updated = try row.encode(&updated_buffer, &.{.{ .text = "updated" }});    var applied = try relationValueApplyingMaterializedEdits(std.testing.allocator, base_items, &.{        .{ .put = .{ .rowid = 2, .bytes = two } },        .{ .put = .{ .rowid = 3, .bytes = updated } },        .{ .update = .{ .rowid = 2, .assignments = &.{.{ .column = 0, .value = .{ .text = "two-edited" } }} } },        .{ .delete = 1 },    });    defer applied.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 2), applied.rows.len);    try std.testing.expectEqual(@as(i64, 2), applied.rows[0].rowid);    try std.testing.expectEqual(@as(i64, 3), applied.rows[1].rowid);    const inserted = try row.View.init(applied.rows[0].bytes);    const replaced = try row.View.init(applied.rows[1].bytes);    try std.testing.expectEqualStrings("two-edited", (try inserted.column(0)).text);    try std.testing.expectEqualStrings("updated", (try replaced.column(0)).text);    try std.testing.expectError(error.KeyNotFound, relationValueApplyingMaterializedEdits(std.testing.allocator, base_items, &.{.{ .delete = 99 }}));    try std.testing.expectError(error.KeyNotFound, relationValueApplyingMaterializedEdits(std.testing.allocator, base_items, &.{.{ .update = .{ .rowid = 99, .assignments = &.{.{ .column = 0, .value = .nil }} } }}));}test "relation root from materialized rows matches live logical maps" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "relation-root-rows.db", .wal = "relation-root-rows.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 512 });    var catalog = try catalog_mod.Catalog.open(&database, .{});    _ = try catalog.createRelation(std.testing.allocator, .{        .name = "items",        .columns = &.{            .{ .name = "name" },            .{ .name = "score" },        },        .indexes = &.{.{            .name = "items_score",            .fields = &.{1},            .columns = &.{.{}},        }},    }, .{ .durability = .buffered });    var handle = try catalog.openRelation(std.testing.allocator, "items");    defer handle.deinit();    _ = try handle.relation.put(std.testing.allocator, 3, &.{ .{ .text = "Ada" }, .{ .integer = 7 } }, .{ .durability = .buffered });    _ = try handle.relation.put(std.testing.allocator, 1, &.{ .{ .text = "Bea" }, .{ .integer = 4 } }, .{ .durability = .buffered });    const schema = try catalog.schemaState(std.testing.allocator);    var live_root = try relationRoot(std.testing.allocator, "items", schema, &handle, null);    defer live_root.deinit();    const rows = try relationRows(std.testing.allocator, &handle);    defer freeRelationRows(std.testing.allocator, rows);    var materialized_root = try relationRootFromRows(std.testing.allocator, &live_root, rows);    defer materialized_root.deinit();    try std.testing.expect(same(live_root.hash, materialized_root.hash));    try std.testing.expect(same(live_root.table.hash, materialized_root.table.hash));    try std.testing.expectEqual(@as(usize, 1), materialized_root.indexes.len);    try std.testing.expect(same(live_root.indexes[0].map.hash, materialized_root.indexes[0].map.hash));}test "index root from rows retains exact encoded key storage" {    const row_count = 512;    var row_buffer: [64]u8 = undefined;    const encoded = try row.encode(&row_buffer, &.{.{ .text = "compact" }});    var rows: [row_count]RelationRow = undefined;    for (&rows, 0..) |*row_value, index| {        row_value.* = .{            .rowid = @intCast(index + 1),            .bytes = @constCast(encoded),        };    }    var storage: [256 * 1024]u8 = undefined;    var fixed = std.heap.FixedBufferAllocator.init(&storage);    var root = try indexRootFromRows(        fixed.allocator(),        &rows,        &.{0},        &.{.{}},    );    defer root.deinit();    try std.testing.expectEqual(@as(usize, row_count), root.summary.entries);    try std.testing.expect(fixed.end_index < storage.len);}test "relation root hash is stable across reopen and changes after row write" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    {        var database = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{            .paths = .{ .database = "version.db", .wal = "version.wal" },            .header = testingHeader(),        });        defer database.deinit();        try database.reserve(.{ .wal_frames = 256 });        var catalog = try catalog_mod.Catalog.open(&database, .{});        _ = try catalog.createRelation(std.testing.allocator, .{            .name = "items",            .columns = &.{.{ .name = "value" }},        }, .{ .durability = .buffered });        var handle = try catalog.openRelation(std.testing.allocator, "items");        defer handle.deinit();        _ = try handle.relation.put(std.testing.allocator, 1, &.{.{ .text = "one" }}, .{ .durability = .buffered });        const schema = try catalog.schemaState(std.testing.allocator);        var stats = try catalog.relationStats(std.testing.allocator, "items");        defer if (stats) |*s| s.deinit();        var root = try relationRoot(std.testing.allocator, "items", schema, &handle, if (stats) |*s| s else null);        defer root.deinit();        try std.testing.expect(root.table.summary.entries == 1);        try std.testing.expectEqual(@as(usize, 1), root.schema_descriptor.columns.len);        try std.testing.expectEqualStrings("value", root.schema_descriptor.columns[0].name);        try std.testing.expectEqual(@as(usize, 0), root.schema_descriptor.indexes.len);        try database.syncWal();    }    var reopened = try @import("file.zig").Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "version.db", .wal = "version.wal" },        .header = recoveredHeader(),    });    defer reopened.deinit();    try reopened.reserve(.{ .wal_frames = 256 });    var catalog = try catalog_mod.Catalog.open(&reopened, .{});    var handle = try catalog.openRelation(std.testing.allocator, "items");    defer handle.deinit();    const schema = try catalog.schemaState(std.testing.allocator);    var stats = try catalog.relationStats(std.testing.allocator, "items");    defer if (stats) |*s| s.deinit();    var before = try relationRoot(std.testing.allocator, "items", schema, &handle, if (stats) |*s| s else null);    defer before.deinit();    var again = try relationRoot(std.testing.allocator, "items", schema, &handle, if (stats) |*s| s else null);    defer again.deinit();    try std.testing.expect(same(before.hash, again.hash));    _ = try handle.relation.put(std.testing.allocator, 2, &.{.{ .text = "two" }}, .{ .durability = .buffered });    var after = try relationRoot(std.testing.allocator, "items", schema, &handle, if (stats) |*s| s else null);    defer after.deinit();    try std.testing.expect(!same(before.hash, after.hash));}fn testingHeader() wal.Header {    return .{        .sequence = 1801,        .salt = .{ .first = 0xabcd_0101, .second = 0xdcba_0202 },    };}fn recoveredHeader() wal.Header {    return .{        .sequence = 1802,        .salt = .{ .first = 0x0101_abcd, .second = 0x0202_dcba },    };}test "relation key matches the maintained root identity across the fixture matrix" {    const testing = std.testing;    var tmp = testing.tmpDir(.{});    defer tmp.cleanup();    var database = try @import("file.zig").Database.openForTesting(testing.allocator, tmp.dir, .{        .paths = .{ .database = "key.db", .wal = "key.wal" },        .header = .{            .sequence = 3101,            .salt = .{ .first = 0x1a2b_0101, .second = 0x2b1a_0202 },        },    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 512 });    var catalog = try catalog_mod.Catalog.open(&database, .{});    _ = try catalog.createRelation(testing.allocator, .{        .name = "bare",        .columns = &.{},    }, .{ .durability = .buffered });    _ = try catalog.createRelation(testing.allocator, .{        .name = "items",        .columns = &.{ .{ .name = "value" }, .{ .name = "grade" } },    }, .{ .durability = .buffered });    try expectKeyMatchesRoot(&catalog, "bare");    try expectKeyMatchesRoot(&catalog, "items");    var relation = try catalog.openRelation(testing.allocator, "items");    _ = try relation.relation.put(testing.allocator, 1, &.{ .{ .text = "one" }, .{ .integer = 7 } }, .{ .durability = .buffered });    _ = try relation.relation.put(testing.allocator, 2, &.{ .{ .text = "two" }, .{ .integer = 9 } }, .{ .durability = .buffered });    relation.deinit();    try expectKeyMatchesRoot(&catalog, "items");    _ = try catalog.createIndex(testing.allocator, "items", .{        .name = "items_by_grade",        .fields = &.{1},    }, .{ .durability = .buffered });    try expectKeyMatchesRoot(&catalog, "items");    _ = try catalog.analyzeRelation(testing.allocator, "items", .{ .durability = .buffered });    try expectKeyMatchesRoot(&catalog, "items");    try expectKeyMatchesRoot(&catalog, "bare");}fn expectKeyMatchesRoot(catalog: *const catalog_mod.Catalog, name: []const u8) !void {    const testing = std.testing;    const schema = try catalog.schemaState(testing.allocator);    var handle = try catalog.openRelation(testing.allocator, name);    defer handle.deinit();    var stats = try catalog.relationStats(testing.allocator, name);    defer if (stats) |*relation_stats| relation_stats.deinit();    const stats_pointer: ?*const catalog_mod.RelationStats =        if (stats) |*relation_stats| relation_stats else null;    const derived = try relationKey(name, &handle, stats_pointer);    var root = try relationRootMaintained(testing.allocator, name, schema, &handle, stats_pointer);    defer root.deinit();    try testing.expect(same(derived.schema, root.schema));    try testing.expect(same(derived.table, root.table.hash));    try testing.expect(same(derived.stats, root.stats.hash));    try testing.expect(same(derived.hash, root.hash));    var lease = try catalog.database.beginRead();    defer lease.deinit();    const reader = try catalog_mod.Reader.open(lease.snapshot(), .{        .meta_page = catalog.meta_page,        .root_page = catalog.root_page,    });    var read_handle = try reader.openRelation(testing.allocator, name);    defer read_handle.deinit();    var read_root = try readRelationRootMaintained(        testing.allocator,        name,        schema,        &read_handle,        stats_pointer,    );    defer read_root.deinit();    try testing.expect(same(root.hash, read_root.hash));}

Complete caller list for version.emptyHash

77 direct callers.

Complete caller list for version.freeRelationRows

33 direct callers.

Complete caller list for version.relationKey

7 direct callers.

Complete call list for version.relationKey

11 direct calls.

Complete caller list for version.relationRootMaintained

7 direct callers.

Complete caller list for version.relationValue

7 direct callers.

Complete caller list for version.same

207 direct callers.

Complete caller list for version.schemaHash

8 direct callers.

Audit

Definitions28
Public names28
Members8
Version26.7.0
Revisiondaab053ee433