Skip to documentation
SLOP

tiny.sql.connection

Reference tiny.sql connection

Defined in tiny.sql.

API (8)

Types and contracts

Public types and contracts.

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

Source

Source: lib/sql/src/connection.zig

zig
const std = @import("std");const simd = @import("simd");const branch_mod = @import("branch.zig");const catalog_mod = @import("catalog.zig");const diff_mod = @import("diff.zig");const file = @import("file.zig");const history_mod = @import("history/root.zig");const merge_mod = @import("merge.zig");const plan_mod = @import("plan.zig");const relation_mod = @import("relation.zig");const session_mod = @import("session/root.zig");const statement_mod = @import("statement/root.zig");const row = @import("row.zig");const tree = @import("tree.zig");const version = @import("version.zig");const wal = @import("wal.zig");const Bytes = simd.ScalableTag(u8);const Allocator = std.mem.Allocator;const ConnectionError = error{    ConflictNotFound,    NoMergeBase,    UnsupportedCheckoutRoot,    WorkingSetChanged,};pub const Error =    statement_mod.Error ||    history_mod.Error ||    diff_mod.Error ||    merge_mod.Error ||    version.Error ||    ConnectionError;pub const Options = struct {    branch: []const u8 = "main",    catalog: catalog_mod.Options = .{},};pub const ExecuteOptions = struct {    durability: file.CommitDurability = .synced,    validate_indexes: bool = false,    write: ?*session_mod.DatabaseWrite = null,    fn statement(self: ExecuteOptions, session: *session_mod.DatabaseSession) statement_mod.ExecuteOptions {        return .{            .durability = self.durability,            .validate_indexes = self.validate_indexes,            .session = session,            .write = self.write,        };    }};pub const WriteOptions = struct {    limits: session_mod.DatabaseWrite.Limits,    durability: file.CommitDurability = .synced,    validate_indexes: bool = false,    fn commit(self: WriteOptions) file.CommitOptions {        return .{            .durability = self.durability,            .validate_indexes = self.validate_indexes,        };    }};pub const MergeOptions = struct {    commit: file.CommitOptions = .{ .durability = .buffered },};pub const RelationView = struct {    allocator: Allocator,    root: version.RelationRoot,    handle: ?catalog_mod.RelationHandle = null,    rows: []version.RelationRow = &.{},    pub fn snapshot(self: *const RelationView) diff_mod.RelationSnapshot {        return .{            .root = &self.root,            .rows = if (self.handle) |*handle| .{ .live = handle } else .{ .materialized = self.rows },        };    }    pub fn deinit(self: *RelationView) void {        self.root.deinit();        if (self.handle) |*handle| handle.deinit();        version.freeRelationRows(self.allocator, self.rows);        self.* = undefined;    }};pub const DatabaseView = struct {    allocator: Allocator,    relations: []RelationView,    snapshots: []diff_mod.RelationSnapshot,    conflicts: history_mod.ConflictArtifacts,    pub fn snapshot(self: *const DatabaseView) merge_mod.DatabaseSnapshot {        return .{            .relations = self.snapshots,            .conflicts = .{                .root = self.conflicts.root,                .artifacts = self.conflicts.artifacts,            },        };    }    pub fn deinit(self: *DatabaseView) void {        for (self.relations) |*relation_view| relation_view.deinit();        if (self.relations.len != 0) self.allocator.free(self.relations);        if (self.snapshots.len != 0) self.allocator.free(self.snapshots);        self.conflicts.deinit();        self.* = undefined;    }};pub const Connection = struct {    catalog: catalog_mod.Catalog,    session: session_mod.DatabaseSession,    recovery_required: bool = false,    pub fn init(catalog: catalog_mod.Catalog, session: session_mod.DatabaseSession) Connection {        return .{            .catalog = catalog,            .session = session,        };    }    pub fn create(allocator: Allocator, database: *file.Database, history: *history_mod.History, options: Options) Error!Connection {        var catalog = try catalog_mod.Catalog.open(database, options.catalog);        var value = try version.databaseValue(            allocator,            &catalog,            version.ConflictRoot.empty().hash,        );        var value_live = true;        errdefer if (value_live) value.deinit();        const root_commit = version.Commit.init(value.root.hash, &.{});        try database.syncWal();        try publishDatabaseValue(history, &value);        try history.putCommit(root_commit);        _ = try history.createBranch(options.branch, root_commit.hash);        const checkout_value = try history.checkoutBranch(options.branch);        var root = value.intoRoot();        value_live = false;        const session = session_mod.DatabaseSession.initWithRoot(allocator, checkout_value, &root);        return Connection.init(catalog, session);    }    pub fn openLocal(allocator: Allocator, database: *file.Database, options: Options) Error!Connection {        const catalog = try catalog_mod.Catalog.open(database, options.catalog);        var root = try version.databaseRootMaintained(            allocator,            &catalog,            version.ConflictRoot.empty().hash,        );        errdefer root.deinit();        const root_commit = version.Commit.init(root.hash, &.{});        const checkout_value = branch_mod.checkout(.{            .name = options.branch,            .target = root_commit.hash,        }, root.hash);        return Connection.init(            catalog,            session_mod.DatabaseSession.initWithRoot(allocator, checkout_value, &root),        );    }    pub fn open(allocator: Allocator, database: *file.Database, history: *history_mod.History, options: Options) Error!Connection {        try recoverFastForward(allocator, database, history, options.catalog);        const catalog = try catalog_mod.Catalog.open(database, options.catalog);        const checkout_value = try history.checkoutBranch(options.branch);        var target = try history.databaseRoot(allocator, checkout_value.working.working);        defer target.deinit();        try history.validateConflictRoot(target.conflicts);        var root = try version.databaseRootMaintained(allocator, &catalog, target.conflicts);        errdefer root.deinit();        const live_checkout = checkout_value.withWorking(root.hash);        return Connection.init(catalog, session_mod.DatabaseSession.initWithRoot(allocator, live_checkout, &root));    }    pub fn adoptRebuiltHistory(self: *Connection, history: *history_mod.History, branch_name: []const u8) Error!void {        try self.ensureUsable();        std.debug.assert(history.len() == 0);        const allocator = self.session.allocator;        var value = try version.databaseValue(            allocator,            &self.catalog,            version.ConflictRoot.empty().hash,        );        var value_live = true;        errdefer if (value_live) value.deinit();        const root_commit = version.Commit.init(value.root.hash, &.{});        try self.catalog.database.syncWal();        try publishDatabaseValue(history, &value);        try history.putCommit(root_commit);        _ = try history.createBranch(branch_name, root_commit.hash);        const checkout_value = try history.checkoutBranch(branch_name);        var root = value.intoRoot();        value_live = false;        self.session.reinitWithRoot(checkout_value, &root);    }    pub fn deinit(self: *Connection) void {        self.session.deinit();        self.* = undefined;    }    pub fn lastRowId(self: *const Connection, allocator: Allocator, table_name: []const u8) Error!?i64 {        try self.ensureUsable();        var handle = try self.catalog.openRelation(allocator, table_name);        defer handle.deinit();        return try handle.relation.lastRowId();    }    pub fn prepare(self: *const Connection, allocator: Allocator, source: []const u8) statement_mod.Error!statement_mod.Prepared {        try self.ensureUsable();        return try statement_mod.prepare(&self.catalog, allocator, source);    }    pub fn execute(self: *Connection, allocator: Allocator, source: []const u8, options: ExecuteOptions) statement_mod.Error!statement_mod.Result {        var prepared = try self.prepare(allocator, source);        defer prepared.deinit();        return try self.executePrepared(&prepared, allocator, options);    }    pub fn executePrepared(self: *Connection, prepared: *statement_mod.Prepared, allocator: Allocator, options: ExecuteOptions) statement_mod.Error!statement_mod.Result {        try self.ensureUsable();        return try prepared.execute(allocator, options.statement(&self.session));    }    /// Opens a cursor over the rows `prepared` selects in `target`.    pub fn openCursor(        self: *Connection,        target: *statement_mod.Cursor,        prepared: *statement_mod.Prepared,        allocator: Allocator,    ) statement_mod.Error!void {        try self.ensureUsable();        try prepared.openCursor(target, allocator);    }    pub fn beginWrite(        self: *Connection,        workspace: *session_mod.DatabaseWrite.Workspace,        flush_allocator: Allocator,        options: WriteOptions,    ) session_mod.DatabaseError!session_mod.DatabaseWrite {        try self.ensureUsable();        return try self.session.beginWrite(            workspace,            flush_allocator,            options.limits,            options.commit(),        );    }    pub fn stage(self: *Connection) Error!void {        try self.ensureUsable();        self.session.stage();    }    pub fn commit(self: *Connection, history: *history_mod.History) Error!version.Hash {        try self.ensureUsable();        if (!self.session.checkout.working.hasStaged()) return error.NoStagedRoot;        try self.catalog.database.syncWal();        try self.publishStagedValue(history, self.session.checkout.working.staged);        return try self.session.commit(history);    }    pub fn commitLocal(self: *Connection) Error!version.Hash {        try self.ensureUsable();        if (!self.session.checkout.working.hasStaged()) return error.NoStagedRoot;        const root = self.session.checkout.working.staged;        const parents = [_]version.Hash{self.session.checkout.head};        const commit_value = version.Commit.init(root, &parents);        try self.session.advance(commit_value.hash, root);        return commit_value.hash;    }    pub fn mergeCommit(self: *Connection, history: *history_mod.History, theirs: version.Hash) Error!version.Hash {        try self.ensureUsable();        const root = self.session.checkout.working.working;        try self.catalog.database.syncWal();        try self.publishStagedValue(history, root);        const commit_hash = try history.mergeCommitBranch(self.session.checkout.name, root, theirs);        try self.session.advance(commit_hash, root);        return commit_hash;    }    pub fn createBranch(self: *const Connection, history: *history_mod.History, name: []const u8) history_mod.Error!version.Ref {        try self.ensureUsable();        return try history.createBranch(name, self.session.checkout.head);    }    pub fn checkoutBranch(self: *Connection, allocator: Allocator, history: *const history_mod.History, name: []const u8) Error!void {        try self.ensureUsable();        const checkout_value = try history.checkoutBranch(name);        var value = try history.databaseValue(allocator, checkout_value.working.working);        var value_live = true;        errdefer if (value_live) value.deinit();        try history.validateConflictRoot(value.root.conflicts);        try self.materializeDatabaseValue(allocator, &value, .{ .durability = .synced });        var root = value.intoRoot();        value_live = false;        self.session.reinitWithRoot(checkout_value, &root);    }    pub fn fastForwardBranch(self: *Connection, allocator: Allocator, history: *history_mod.History, target: version.Hash) Error!void {        try self.ensureUsable();        var next_ref = (try history.ref(self.session.checkout.name)) orelse return error.RefNotFound;        const baseline = try self.validateFastForwardBaseline(allocator, history, next_ref);        const entries = try history.commitEntries(allocator);        defer allocator.free(entries);        try branch_mod.fastForwardRef(allocator, entries, &next_ref, target);        const commit_value = try history.commitValue(target);        var value = try history.databaseValue(allocator, commit_value.root);        var value_live = true;        errdefer if (value_live) value.deinit();        try history.validateConflictRoot(value.root.conflicts);        var plan = try DatabaseMaterialization.init(self, allocator, &value);        defer plan.deinit();        try self.coordinateFastForward(            allocator,            history,            next_ref,            baseline,            &value,            &value_live,            &plan,        );    }    fn validateFastForwardBaseline(        self: *Connection,        allocator: Allocator,        history: *const history_mod.History,        current_ref: version.Ref,    ) Error!version.Hash {        if (!self.catalog.database.walSynced()) return error.WorkingSetChanged;        if (!version.same(current_ref.target, self.session.checkout.head)) return error.RefChanged;        const working = self.session.checkout.working;        if (!version.same(working.base, working.working) or            !version.same(working.base, working.staged) or            !version.same(working.base, self.session.workingRoot().hash) or            self.session.pendingRelations() != 0)        {            return error.WorkingSetChanged;        }        const current_commit = try history.commitValue(self.session.checkout.head);        if (!version.same(current_commit.root, working.base)) return error.WorkingSetChanged;        var live_root = try version.databaseRootMaintained(            allocator,            &self.catalog,            self.session.workingRoot().conflicts,        );        defer live_root.deinit();        if (!version.same(live_root.hash, working.base)) return error.WorkingSetChanged;        return working.base;    }    fn coordinateFastForward(        self: *Connection,        allocator: Allocator,        history: *history_mod.History,        next_ref: version.Ref,        baseline: version.Hash,        value: *version.DatabaseValue,        value_live: *bool,        plan: *DatabaseMaterialization,    ) Error!void {        try self.catalog.database.syncWal();        const savepoint = try self.catalog.database.savepoint();        try self.catalog.database.beginCoordinator();        var coordinator_active = true;        errdefer if (coordinator_active and !self.catalog.database.requiresRecovery()) {            self.catalog.database.endCoordinator();        };        var update = history.beginFastForward(            next_ref.name,            self.session.checkout.head,            next_ref.target,        ) catch |err| {            if (err == error.RecoveryRequired) {                self.poisonFastForward(history);                return error.RecoveryRequired;            }            self.catalog.database.endCoordinator();            coordinator_active = false;            return err;        };        plan.apply(self, .{ .durability = .synced }) catch |err| {            try self.restoreFastForward(history, &update, savepoint, baseline);            coordinator_active = false;            return err;        };        self.verifyMaterializedDatabaseRoot(allocator, &value.root) catch |err| {            try self.restoreFastForward(history, &update, savepoint, baseline);            coordinator_active = false;            return err;        };        update.commit() catch |err| {            if (err == error.RecoveryRequired) {                self.poisonFastForward(history);                return error.RecoveryRequired;            }            try self.restoreFastForward(history, &update, savepoint, baseline);            coordinator_active = false;            return err;        };        const checkout_value = branch_mod.checkout(next_ref, value.root.hash);        var root = value.intoRoot();        value_live.* = false;        self.session.reinitWithRoot(checkout_value, &root);        try self.completeFastForward(history, &update);        self.catalog.database.endCoordinator();        coordinator_active = false;    }    fn verifyMaterializedDatabaseRoot(        self: *Connection,        allocator: Allocator,        target: *const version.DatabaseRoot,    ) Error!void {        var names = try self.catalog.relationNames(allocator);        defer names.deinit();        if (names.names.len != target.entries.len) return error.InvalidHistory;        for (names.names) |name| {            const entry = relationEntry(target.entries, name) orelse return error.InvalidHistory;            var state = try self.catalog.readRelation(allocator, name);            defer state.deinit();            const live = try version.relationKey(name, &state.handle, state.relationStats());            if (!version.same(live.hash, entry.hash)) return error.InvalidHistory;        }        var verified = try version.databaseRootMaintained(            allocator,            &self.catalog,            target.conflicts,        );        defer verified.deinit();        if (!version.same(verified.hash, target.hash)) return error.InvalidHistory;    }    fn restoreFastForward(        self: *Connection,        history: *history_mod.History,        update: *history_mod.FastForwardUpdate,        savepoint: file.Savepoint,        baseline: version.Hash,    ) Error!void {        self.rollbackFastForward(update, savepoint, baseline) catch {            self.poisonFastForward(history);            return error.RecoveryRequired;        };    }    fn completeFastForward(        self: *Connection,        history: *history_mod.History,        update: *history_mod.FastForwardUpdate,    ) Error!void {        update.complete() catch |err| {            if (err != error.RecoveryRequired) update.complete() catch {                self.poisonFastForward(history);                return error.RecoveryRequired;            } else {                self.poisonFastForward(history);                return error.RecoveryRequired;            }        };    }    fn rollbackFastForward(        self: *Connection,        update: *history_mod.FastForwardUpdate,        savepoint: file.Savepoint,        baseline: version.Hash,    ) Error!void {        try self.catalog.database.restore(savepoint);        try self.catalog.database.syncWal();        var restored = try version.databaseRootMaintained(            self.session.allocator,            &self.catalog,            self.session.workingRoot().conflicts,        );        defer restored.deinit();        if (!version.same(restored.hash, baseline)) return error.RecoveryRequired;        try update.abort();        try update.complete();        self.catalog.database.endCoordinator();    }    fn poisonFastForward(self: *Connection, history: *history_mod.History) void {        self.recovery_required = true;        self.catalog.database.poison();        history.poison();    }    fn ensureUsable(self: *const Connection) error{RecoveryRequired}!void {        if (self.recovery_required or self.catalog.database.requiresRecovery()) {            return error.RecoveryRequired;        }    }    pub fn mergeBase(self: *const Connection, allocator: Allocator, history: *const history_mod.History, theirs: []const u8) history_mod.Error!?version.Hash {        try self.ensureUsable();        const theirs_ref = (try history.ref(theirs)) orelse return error.RefNotFound;        const entries = try history.commitEntries(allocator);        defer allocator.free(entries);        return try branch_mod.mergeBase(allocator, entries, self.session.checkout.head, theirs_ref.target);    }    pub fn relationView(self: *const Connection, allocator: Allocator, name: []const u8) Error!RelationView {        try self.ensureUsable();        var state = try self.catalog.readRelation(allocator, name);        errdefer state.handle.deinit();        defer if (state.stats) |*relation_stats| relation_stats.deinit();        var root = try version.relationRoot(            allocator,            name,            state.schema,            &state.handle,            state.relationStats(),        );        errdefer root.deinit();        return .{            .allocator = allocator,            .root = root,            .handle = state.handle,        };    }    pub fn databaseView(self: *const Connection, allocator: Allocator, history: *const history_mod.History, names: []const []const u8) Error!DatabaseView {        try self.ensureUsable();        var conflicts = try history.conflictArtifacts(allocator, self.session.workingRoot().conflicts);        errdefer conflicts.deinit();        const relations = try allocator.alloc(RelationView, names.len);        errdefer allocator.free(relations);        var relation_count: usize = 0;        errdefer for (relations[0..relation_count]) |*relation_view| relation_view.deinit();        const snapshots = try allocator.alloc(diff_mod.RelationSnapshot, names.len);        errdefer allocator.free(snapshots);        for (names, relations, snapshots) |name, *relation_view, *snapshot| {            relation_view.* = try self.relationView(allocator, name);            relation_count += 1;            snapshot.* = relation_view.snapshot();        }        return .{            .allocator = allocator,            .relations = relations,            .snapshots = snapshots,            .conflicts = conflicts,        };    }    pub fn databaseViewAtCommit(self: *const Connection, allocator: Allocator, history: *const history_mod.History, commit_hash: version.Hash) Error!DatabaseView {        try self.ensureUsable();        var root = try history.commitDatabaseRoot(allocator, commit_hash);        defer root.deinit();        return try databaseViewFromRoot(allocator, history, root);    }    pub fn diffRelation(self: *const Connection, allocator: Allocator, name: []const u8, other: diff_mod.RelationSnapshot) Error!diff_mod.RelationDiff {        try self.ensureUsable();        var local = try self.relationView(allocator, name);        defer local.deinit();        return try diff_mod.relation(allocator, local.snapshot(), other);    }    pub fn mergeDatabase(self: *Connection, allocator: Allocator, history: *history_mod.History, base: merge_mod.DatabaseSnapshot, theirs: merge_mod.DatabaseSnapshot, options: MergeOptions) Error!merge_mod.DatabaseMerge {        try self.ensureUsable();        var ours = try self.materializedWorkingValue(allocator);        defer ours.deinit();        var ours_conflicts = try history.conflictArtifacts(allocator, ours.root.conflicts);        defer ours_conflicts.deinit();        var result = try merge_mod.mergeDatabase(allocator, base, .{            .value = &ours,            .conflicts = .{                .root = ours_conflicts.root,                .artifacts = ours_conflicts.artifacts,            },        }, theirs);        errdefer result.deinit();        try self.materializeDatabaseMerge(allocator, history, &result, options.commit);        var root = try result.value.root.clone(self.session.allocator);        self.session.applyRoot(&root);        return result;    }    pub fn mergeBranch(self: *Connection, allocator: Allocator, history: *history_mod.History, theirs: []const u8, options: MergeOptions) Error!merge_mod.DatabaseMerge {        try self.ensureUsable();        const theirs_ref = (try history.ref(theirs)) orelse return error.RefNotFound;        const entries = try history.commitEntries(allocator);        defer allocator.free(entries);        const base_commit = (try branch_mod.mergeBase(allocator, entries, self.session.checkout.head, theirs_ref.target)) orelse return error.NoMergeBase;        var base_view = try self.databaseViewAtCommit(allocator, history, base_commit);        defer base_view.deinit();        var theirs_view = try self.databaseViewAtCommit(allocator, history, theirs_ref.target);        defer theirs_view.deinit();        return try self.mergeDatabase(allocator, history, base_view.snapshot(), theirs_view.snapshot(), options);    }    fn replaceConflictRoot(self: *Connection, conflicts: version.ConflictRoot) Error!version.Hash {        var root = try version.DatabaseRoot.initSorted(self.session.allocator, self.session.workingRoot().entries, conflicts);        self.session.applyRoot(&root);        return (try self.workingRoot());    }    pub fn conflictArtifacts(self: *const Connection, allocator: Allocator, history: *const history_mod.History) Error!history_mod.ConflictArtifacts {        try self.ensureUsable();        return try history.conflictArtifacts(allocator, self.session.workingRoot().conflicts);    }    pub fn resolveConflicts(self: *Connection, allocator: Allocator, history: *history_mod.History, resolved: []const version.Hash) Error!version.Hash {        try self.ensureUsable();        if (resolved.len == 0) return try self.workingRoot();        var entries = try history.conflictEntries(allocator, self.session.workingRoot().conflicts);        defer entries.deinit();        for (resolved) |hash| {            if (!hasConflictHash(entries.entries, hash)) return error.ConflictNotFound;        }        var remaining: std.ArrayList(version.ConflictEntry) = .empty;        defer remaining.deinit(allocator);        for (entries.entries) |entry| {            if (!hasConflictHashValue(resolved, entry.hash)) try remaining.append(allocator, entry);        }        const root = try history.putConflictRoot(remaining.items);        return try self.replaceConflictRoot(root);    }    pub fn checkout(self: *const Connection) Error!@import("branch.zig").Checkout {        try self.ensureUsable();        return self.session.checkout;    }    pub fn workingRoot(self: *const Connection) Error!version.Hash {        try self.ensureUsable();        return self.session.checkout.working.working;    }    fn relationViewFromRoot(allocator: Allocator, history: *const history_mod.History, root_hash: version.Hash) Error!RelationView {        var root = try history.relationRoot(allocator, root_hash);        errdefer root.deinit();        const rows = try history.relationRows(allocator, root_hash);        errdefer version.freeRelationRows(allocator, rows);        return .{            .allocator = allocator,            .root = root,            .rows = rows,        };    }    fn databaseViewFromRoot(allocator: Allocator, history: *const history_mod.History, root: version.DatabaseRoot) Error!DatabaseView {        var conflicts = try history.conflictArtifacts(allocator, root.conflicts);        errdefer conflicts.deinit();        const relations = try allocator.alloc(RelationView, root.entries.len);        errdefer allocator.free(relations);        var relation_count: usize = 0;        errdefer for (relations[0..relation_count]) |*relation_view| relation_view.deinit();        const snapshots = try allocator.alloc(diff_mod.RelationSnapshot, root.entries.len);        errdefer allocator.free(snapshots);        for (root.entries, relations, snapshots) |entry, *relation_view, *snapshot| {            relation_view.* = try relationViewFromRoot(allocator, history, entry.hash);            relation_count += 1;            snapshot.* = relation_view.snapshot();        }        return .{            .allocator = allocator,            .relations = relations,            .snapshots = snapshots,            .conflicts = conflicts,        };    }    fn materializeDatabaseValue(self: *Connection, allocator: Allocator, value: *const version.DatabaseValue, options: file.CommitOptions) Error!void {        var plan = try DatabaseMaterialization.init(self, allocator, value);        defer plan.deinit();        try plan.apply(self, options);    }    fn materializeDatabaseMerge(        self: *Connection,        allocator: Allocator,        history: *history_mod.History,        result: *const merge_mod.DatabaseMerge,        options: file.CommitOptions,    ) Error!void {        if (!version.same(result.value.root.conflicts, result.conflict_root.hash)) {            return error.InvalidHistory;        }        try result.persistConflicts(history);        try history.validateConflictRoot(result.value.root.conflicts);        try self.materializeDatabaseValue(allocator, &result.value, options);        try self.verifyMaterializedDatabaseRoot(allocator, &result.value.root);    }    fn publishStagedValue(self: *Connection, history: *history_mod.History, root: version.Hash) Error!void {        const allocator = self.session.allocator;        const working = self.session.workingRoot();        if (!version.same(working.hash, root)) return error.InvalidHistory;        try history.validateConflictRoot(working.conflicts);        var live_root = try version.databaseRootMaintained(            allocator,            &self.catalog,            working.conflicts,        );        defer live_root.deinit();        if (!version.same(live_root.hash, working.hash)) return error.InvalidHistory;        var batch = try history.beginWriteBatch();        errdefer batch.deinit();        var base_root: ?version.DatabaseRoot = null;        defer if (base_root) |*owned| owned.deinit();        base_root = history.commitDatabaseRoot(allocator, self.session.checkout.head) catch null;        for (working.entries) |entry| {            const base = self.publicationBase(&base_root, entry.name);            const need = try history.relationRowsNeed(allocator, entry.hash, base);            var state = try self.catalog.readRelation(allocator, entry.name);            defer state.deinit();            const live_key = try version.relationKey(                entry.name,                &state.handle,                state.relationStats(),            );            if (!version.same(live_key.hash, entry.hash)) return error.InvalidHistory;            if (need == .none and history.hasRelationRoot(entry.hash)) continue;            var relation_root = try version.relationRootMaintained(                allocator,                entry.name,                state.schema,                &state.handle,                state.relationStats(),            );            defer relation_root.deinit();            try history.putRelationRoot(relation_root);            switch (need) {                .none => {},                .full => {                    var writer = try history.beginRelationRows(entry.hash);                    defer writer.deinit();                    try streamRelationRows(&writer, allocator, &state.handle, null);                    try writer.finish();                },                .suffix => |plan| {                    var writer = try history.beginRelationRowsSuffix(entry.hash, base.?, plan);                    defer writer.deinit();                    try streamRelationRows(&writer, allocator, &state.handle, plan.boundary);                    try writer.finish();                },            }        }        try history.putDatabaseRoot(working.*);        try batch.finish();    }    fn streamRelationRows(        writer: *history_mod.RelationRowsWriter,        allocator: Allocator,        handle: *const catalog_mod.RelationHandle,        boundary: ?i64,    ) Error!void {        if (boundary) |value| {            if (value == std.math.maxInt(i64)) return;        }        const start: ?i64 = if (boundary) |value| value + 1 else null;        var scan: relation_mod.Scan = undefined;        try handle.relation.scan(&scan, allocator, start, null);        defer scan.deinit();        while (try scan.next()) |row_entry| try writer.append(row_entry.rowid, row_entry.bytes);    }    fn publicationBase(self: *const Connection, base_root: *const ?version.DatabaseRoot, name: []const u8) ?history_mod.IncrementalBase {        const root = base_root.* orelse return null;        const edited = self.session.pendingEditRowids(root.hash, name) orelse return null;        for (root.entries) |entry| {            if (std.mem.eql(u8, entry.name, name)) return .{ .root = entry.hash, .edited = edited };        }        return null;    }    pub fn materializedWorkingValue(self: *Connection, allocator: Allocator) Error!version.DatabaseValue {        try self.ensureUsable();        const working = self.session.workingRoot();        return try version.databaseValue(allocator, &self.catalog, working.conflicts);    }};fn recoverFastForward(    allocator: Allocator,    database: *file.Database,    history: *history_mod.History,    catalog_options: catalog_mod.Options,) Error!void {    const active = history.fastForwardRecovery() orelse return;    const id = active.id;    const decision = active.decision;    const selected_head = switch (decision) {        .pending, .baseline => active.expected,        .target => active.target,    };    const name = active.name;    try database.beginCoordinator();    var coordinator_active = true;    var recovered = false;    defer {        if (coordinator_active and !database.requiresRecovery()) database.endCoordinator();        if (!recovered) {            database.poison();            history.poison();        }    }    const commit_value = try history.commitValue(selected_head);    var value = try history.databaseValue(allocator, commit_value.root);    defer value.deinit();    try history.validateConflictRoot(value.root.conflicts);    const catalog = try catalog_mod.Catalog.open(database, catalog_options);    var live_root = try version.databaseRootMaintained(        allocator,        &catalog,        value.root.conflicts,    );    var live_root_owned = true;    defer if (live_root_owned) live_root.deinit();    const checkout_value = branch_mod.checkout(.{        .name = name,        .target = selected_head,    }, live_root.hash);    var connection = Connection.init(        catalog,        session_mod.DatabaseSession.initWithRoot(allocator, checkout_value, &live_root),    );    live_root_owned = false;    defer connection.deinit();    if (!version.same(connection.session.workingRoot().hash, value.root.hash)) {        var plan = try DatabaseMaterialization.init(&connection, allocator, &value);        defer plan.deinit();        try plan.apply(&connection, .{ .durability = .synced });    } else {        try database.syncWal();    }    try connection.verifyMaterializedDatabaseRoot(allocator, &value.root);    var update = history_mod.FastForwardUpdate{ .history = history, .id = id };    if (decision == .pending) try update.abort();    try update.complete();    database.endCoordinator();    coordinator_active = false;    recovered = true;}const PlannedRelation = struct {    target: *const version.RelationValue,    definition: catalog_mod.RelationDefinition,    live: ?RelationView = null,    edits: []relation_mod.Edit = &.{},    prepared_stats: ?catalog_mod.PreparedRelationStats = null,    created: ?catalog_mod.MaterializedRelation = null,    clear_live_stats: bool = false,    fn deinit(self: *PlannedRelation, allocator: Allocator) void {        if (self.prepared_stats) |*prepared| prepared.deinit();        if (self.edits.len != 0) allocator.free(self.edits);        if (self.live) |*live| live.deinit();        self.* = undefined;    }};const DatabaseMaterialization = struct {    allocator: Allocator,    names: catalog_mod.RelationNames,    relations: []PlannedRelation,    relation_count: usize = 0,    fn init(        connection: *Connection,        allocator: Allocator,        value: *const version.DatabaseValue,    ) Error!DatabaseMaterialization {        try validateValue(value);        var names = try connection.catalog.relationNames(allocator);        errdefer names.deinit();        const relations = try allocator.alloc(PlannedRelation, value.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 (value.relations, relations, 0..) |*target, *planned, relation_offset| {            try validateTarget(value, target, relation_offset);            planned.* = try planRelation(                connection,                allocator,                target,                names.names,            );            relation_count += 1;        }        return .{            .allocator = allocator,            .names = names,            .relations = relations,            .relation_count = relation_count,        };    }    fn validateValue(value: *const version.DatabaseValue) Error!void {        if (value.root.format != version.format_version or value.root.feature != 0) {            return error.InvalidHistory;        }        if (value.root.entries.len != value.relations.len) return error.InvalidHistory;        for (value.root.entries, 0..) |entry, index| {            if (entry.name.len == 0) return error.InvalidHistory;            if (index != 0 and                simd.order(Bytes, value.root.entries[index - 1].name, entry.name) != .lt)            {                return error.InvalidHistory;            }        }        const rebuilt = version.DatabaseRoot.init(            value.root.entries,            .{ .hash = value.root.conflicts },        );        if (!version.same(rebuilt.hash, value.root.hash)) return error.InvalidHistory;    }    fn validateTarget(        value: *const version.DatabaseValue,        target: *const version.RelationValue,        relation_offset: usize,    ) Error!void {        for (value.relations[0..relation_offset]) |previous| {            if (std.mem.eql(u8, previous.root.name, target.root.name)) {                return error.InvalidHistory;            }        }        const entry = relationEntry(            value.root.entries,            target.root.name,        ) orelse return error.InvalidHistory;        if (!version.same(entry.hash, target.root.hash)) return error.InvalidHistory;        if (!relationRowsStrictlySorted(target.rows)) return error.InvalidHistory;    }    fn planRelation(        connection: *Connection,        allocator: Allocator,        target: *const version.RelationValue,        names: []const []const u8,    ) Error!PlannedRelation {        var planned = PlannedRelation{            .target = target,            .definition = .{                .name = target.root.name,                .columns = target.root.schema_descriptor.columns,                .indexes = target.root.schema_descriptor.indexes,            },        };        errdefer planned.deinit(allocator);        if (target.root.catalog.format != catalog_mod.format_version) {            return error.InvalidHistory;        }        catalog_mod.validateDefinition(planned.definition) catch |err| switch (err) {            error.OutOfMemory => return err,            else => return error.InvalidHistory,        };        planned.prepared_stats = try validateRelationIdentity(            connection,            allocator,            target,            planned.definition,        );        if (relationNameExists(names, target.root.name)) {            planned.live = try connection.relationView(allocator, target.root.name);            const live = &planned.live.?;            if (!version.same(live.root.schema, target.root.schema)) {                return error.UnsupportedCheckoutRoot;            }            const stats_mismatch = !version.same(                live.root.stats.hash,                target.root.stats.hash,            );            if (stats_mismatch and relationRootHasStats(&target.root)) {                return error.UnsupportedCheckoutRoot;            }            planned.clear_live_stats = stats_mismatch;            planned.edits = try relationMaterializationEdits(                allocator,                live.snapshot(),                target,            );        } else {            planned.edits = try relationPutEdits(allocator, target.rows);        }        return planned;    }    fn validateRelationIdentity(        connection: *Connection,        allocator: Allocator,        target: *const version.RelationValue,        definition: catalog_mod.RelationDefinition,    ) Error!?catalog_mod.PreparedRelationStats {        if (!relationRootHasStats(&target.root)) {            var rebuilt = version.relationRootFromRows(                allocator,                &target.root,                target.rows,            ) catch |err| switch (err) {                error.OutOfMemory => return err,                else => return error.InvalidHistory,            };            defer rebuilt.deinit();            if (!version.same(rebuilt.hash, target.root.hash)) return error.InvalidHistory;            return null;        }        const table_summary = target.root.stats.table orelse return error.InvalidHistory;        if (target.root.stats.indexes != target.root.indexes.len) {            return error.InvalidHistory;        }        var index_summaries: [relation_mod.max_indexes]tree.Summary = undefined;        if (target.root.indexes.len > index_summaries.len) return error.InvalidHistory;        for (            target.root.indexes,            index_summaries[0..target.root.indexes.len],        ) |index_root, *summary| {            summary.* = index_root.map.summary;        }        const puts = try relationPuts(allocator, target.rows);        defer if (puts.len != 0) allocator.free(puts);        var prepared = connection.catalog.prepareRelationStats(            allocator,            definition,            puts,            table_summary,            index_summaries[0..target.root.indexes.len],        ) catch |err| switch (err) {            error.OutOfMemory => return err,            else => return error.InvalidHistory,        };        errdefer prepared.deinit();        var rebuilt = version.relationRootFromRowsWithStats(            allocator,            &target.root,            target.rows,            &prepared.stats,        ) catch |err| switch (err) {            error.OutOfMemory => return err,            else => return error.InvalidHistory,        };        defer rebuilt.deinit();        if (!version.same(rebuilt.hash, target.root.hash)) return error.InvalidHistory;        return prepared;    }    fn deinit(self: *DatabaseMaterialization) void {        for (self.relations[0..self.relation_count]) |*relation| relation.deinit(self.allocator);        if (self.relations.len != 0) self.allocator.free(self.relations);        self.names.deinit();        self.* = undefined;    }    fn apply(        self: *DatabaseMaterialization,        connection: *Connection,        options: file.CommitOptions,    ) Error!void {        var materialization = try connection.catalog.beginMaterialization(self.allocator);        defer materialization.deinit();        for (self.names.names) |name| {            if (!plannedRelationExists(self.relations[0..self.relation_count], name)) {                try materialization.dropRelation(name);            }        }        for (self.relations[0..self.relation_count]) |*planned| {            if (planned.live != null) continue;            planned.created = try materialization.createRelation(                planned.definition,                if (planned.prepared_stats) |*prepared| prepared else null,            );        }        for (self.relations[0..self.relation_count]) |*planned| {            if (planned.clear_live_stats) {                try materialization.clearRelationStats(planned.target.root.name);            }            if (planned.edits.len != 0) {                if (planned.live) |*live| {                    try live.handle.?.relation.applyEditsIn(                        self.allocator,                        materialization.treeWrite(),                        planned.edits,                    );                } else {                    try planned.created.?.relation.applyEditsIn(                        self.allocator,                        materialization.treeWrite(),                        planned.edits,                    );                }            }            if (planned.created) |*created| {                if (planned.prepared_stats) |*prepared| {                    try materialization.refreshRelationStats(planned.definition, created, prepared);                }            }        }        _ = try materialization.commit(options);    }};fn relationNameExists(names: []const []const u8, target: []const u8) bool {    for (names) |name| {        if (std.mem.eql(u8, name, target)) return true;    }    return false;}fn relationEntry(entries: []const version.RelationEntry, name: []const u8) ?version.RelationEntry {    for (entries) |entry| {        if (std.mem.eql(u8, entry.name, name)) return entry;    }    return null;}fn relationRootHasStats(root: *const version.RelationRoot) bool {    return !version.same(root.stats.hash, version.emptyHash("sql.stats.none"));}fn plannedRelationExists(relations: []const PlannedRelation, name: []const u8) bool {    for (relations) |relation| {        if (std.mem.eql(u8, relation.target.root.name, name)) return true;    }    return false;}fn relationRowsStrictlySorted(rows: []const version.RelationRow) bool {    var previous: ?i64 = null;    for (rows) |row_value| {        if (previous) |rowid| {            if (row_value.rowid <= rowid) return false;        }        previous = row_value.rowid;    }    return true;}fn relationPuts(    allocator: Allocator,    rows: []const version.RelationRow,) Allocator.Error![]relation_mod.Edit.Put {    const puts = try allocator.alloc(relation_mod.Edit.Put, rows.len);    for (rows, puts) |row_value, *put| {        put.* = .{            .rowid = row_value.rowid,            .bytes = row_value.bytes,        };    }    return puts;}fn relationPutEdits(    allocator: Allocator,    rows: []const version.RelationRow,) Allocator.Error![]relation_mod.Edit {    const edits = try allocator.alloc(relation_mod.Edit, rows.len);    for (rows, edits) |row_value, *edit| {        edit.* = .{ .put = .{            .rowid = row_value.rowid,            .bytes = row_value.bytes,        } };    }    return edits;}fn relationMaterializationEdits(    allocator: Allocator,    live: diff_mod.RelationSnapshot,    target: *const version.RelationValue,) Error![]relation_mod.Edit {    var result = try diff_mod.relation(allocator, live, .{        .root = &target.root,        .rows = .{ .materialized = target.rows },    });    defer result.deinit();    if (result.schema_changed) return error.UnsupportedCheckoutRoot;    var edits: std.ArrayList(relation_mod.Edit) = .empty;    errdefer edits.deinit(allocator);    for (result.changes) |change| {        switch (change.kind) {            .added, .modified => try edits.append(allocator, .{ .put = .{                .rowid = change.rowid,                .bytes = relationRowBytes(target.rows, change.rowid) orelse                    return error.InvalidHistory,            } }),            .removed => try edits.append(allocator, .{ .delete = change.rowid }),            .schema => return error.UnsupportedCheckoutRoot,        }    }    return try edits.toOwnedSlice(allocator);}fn relationRowBytes(rows: []const version.RelationRow, rowid: i64) ?[]const u8 {    for (rows) |row_value| {        if (row_value.rowid == rowid) return row_value.bytes;        if (row_value.rowid > rowid) return null;    }    return null;}fn publishDatabaseValue(history: *history_mod.History, value: *const version.DatabaseValue) history_mod.Error!void {    try history.putDatabaseValue(value);}test "connection commits reuse history chunks for append shaped edits" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "chunks.db", .wal = "chunks.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 2048 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "chunks.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    var created = try connection.execute(std.testing.allocator, "CREATE TABLE items (name)", .{ .durability = .buffered });    created.deinit(std.testing.allocator);    try connection.stage();    _ = try connection.commit(&history);    var rowid: i64 = 1;    var statement_buffer: [128]u8 = undefined;    while (rowid <= 600) : (rowid += 1) {        const source = try std.fmt.bufPrint(&statement_buffer, "INSERT INTO items VALUES ({d}, 'row-{d}')", .{ rowid, rowid });        var inserted = try connection.execute(std.testing.allocator, source, .{ .durability = .buffered });        inserted.deinit(std.testing.allocator);    }    try connection.stage();    _ = try connection.commit(&history);    const seeded_chunks = history.row_chunks.items.len;    try std.testing.expect(seeded_chunks >= 4);    while (rowid <= 603) : (rowid += 1) {        const source = try std.fmt.bufPrint(&statement_buffer, "INSERT INTO items VALUES ({d}, 'row-{d}')", .{ rowid, rowid });        var inserted = try connection.execute(std.testing.allocator, source, .{ .durability = .buffered });        inserted.deinit(std.testing.allocator);    }    try connection.stage();    try expectAppendPrefixReuse(&connection, &history);    const append_commit = try connection.commit(&history);    const append_chunks = history.row_chunks.items.len;    try std.testing.expect(append_chunks - seeded_chunks <= 2);    var update = try connection.execute(std.testing.allocator, "UPDATE items SET name = 'renamed' WHERE rowid = 5", .{ .durability = .buffered });    update.deinit(std.testing.allocator);    try connection.stage();    const update_commit = try connection.commit(&history);    const update_chunks = history.row_chunks.items.len;    try std.testing.expect(update_chunks - append_chunks <= 3);    var remove = try connection.execute(std.testing.allocator, "DELETE FROM items WHERE rowid = 300", .{ .durability = .buffered });    remove.deinit(std.testing.allocator);    try connection.stage();    const delete_commit = try connection.commit(&history);    for ([_]version.Hash{ append_commit, update_commit, delete_commit }) |commit_hash| {        const commit_value = try history.commitValue(commit_hash);        var value = try history.databaseValue(std.testing.allocator, commit_value.root);        value.deinit();    }    var final = try history.databaseValue(std.testing.allocator, (try connection.checkout()).working.working);    defer final.deinit();    const relation = final.findRelation("items") orelse return error.TestUnexpectedResult;    try std.testing.expectEqual(@as(usize, 602), relation.rows.len);}fn expectAppendPrefixReuse(connection: *Connection, history: *history_mod.History) !void {    const allocator = std.testing.allocator;    var base_root: ?version.DatabaseRoot = try history.commitDatabaseRoot(        allocator,        connection.session.checkout.head,    );    defer base_root.?.deinit();    const base = connection.publicationBase(&base_root, "items").?;    const working = connection.session.workingRoot();    const entry = relationEntry(working.entries, "items").?;    const need = try history.relationRowsNeed(allocator, entry.hash, base);    try std.testing.expect(need == .suffix);    try std.testing.expect(need.suffix.reused > 0);}test "local connection commits the maintained database without history" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    {        var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{            .paths = .{ .database = "local.db", .wal = "local.wal" },            .header = testingHeader(),        });        defer database.deinit();        try database.reserve(.{ .wal_frames = 512 });        var connection = try Connection.openLocal(std.testing.allocator, &database, .{});        defer connection.deinit();        try std.testing.expect(!(try connection.checkout()).working.dirty());        var created = try connection.execute(            std.testing.allocator,            "CREATE TABLE items (name)",            .{ .durability = .buffered },        );        created.deinit(std.testing.allocator);        try connection.stage();        _ = try connection.commitLocal();        try std.testing.expect(!(try connection.checkout()).working.dirty());        var inserted = try connection.execute(            std.testing.allocator,            "INSERT INTO items VALUES (1, 'local')",            .{ .durability = .buffered },        );        inserted.deinit(std.testing.allocator);        try connection.stage();        _ = try connection.commitLocal();        try database.syncWal();    }    var reopened_database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "local.db", .wal = "local.wal" },        .header = testingHeader(),    });    defer reopened_database.deinit();    var reopened = try Connection.openLocal(std.testing.allocator, &reopened_database, .{});    defer reopened.deinit();    var selected = try reopened.execute(        std.testing.allocator,        "SELECT name FROM items WHERE rowid = 1",        .{ .durability = .buffered },    );    defer selected.deinit(std.testing.allocator);    const view = try row.View.init(selected.nextRow().?);    try std.testing.expectEqualStrings("local", (try view.column(0)).text);    try std.testing.expect(selected.nextRow() == null);}test "connection executes statement writes through its database session" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "connection.db", .wal = "connection.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 640 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "connection.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    try std.testing.expect(!(try connection.checkout()).working.dirty());    var created = try connection.execute(std.testing.allocator, "CREATE TABLE items (name DEFAULT 'missing', score, INDEX items_score (score))", .{ .durability = .buffered });    defer created.deinit(std.testing.allocator);    const create_flush = switch (created) {        .catalog => |flush| flush,        else => return error.UnsupportedStatement,    };    try std.testing.expect(version.same(create_flush.database, (try connection.workingRoot())));    try std.testing.expect((try connection.checkout()).working.dirty());    var insert = try connection.prepare(std.testing.allocator, "INSERT INTO items (rowid, name, score) VALUES (?1, ?2, ?3)");    defer insert.deinit();    try insert.bind(1, .{ .integer = 4 });    try insert.bind(2, .{ .text = "Ada" });    try insert.bind(3, .{ .integer = 7 });    var inserted = try connection.executePrepared(&insert, std.testing.allocator, .{ .durability = .buffered });    defer inserted.deinit(std.testing.allocator);    try std.testing.expect(version.same(inserted.mutation.database, (try connection.workingRoot())));    var selected = try connection.execute(std.testing.allocator, "SELECT name, score, rowid FROM items WHERE score = 7", .{ .durability = .buffered });    defer selected.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), selected.rowCount());    const bytes = selected.nextRow().?;    const view = try row.View.init(bytes);    try std.testing.expectEqualStrings("Ada", (try view.column(0)).text);    try std.testing.expectEqual(@as(i64, 7), (try view.column(1)).integer);    try std.testing.expectEqual(@as(i64, 4), (try view.column(2)).integer);    try connection.stage();    const commit_hash = try connection.commit(&history);    try std.testing.expect(version.same(commit_hash, (try history.ref("main")).?.target));    try std.testing.expect(!(try connection.checkout()).working.dirty());    var committed_root = try history.commitDatabaseRoot(std.testing.allocator, commit_hash);    defer committed_root.deinit();    try std.testing.expect(version.same(committed_root.hash, (try connection.workingRoot())));    try std.testing.expect(committed_root.entries.len > 0);    for (committed_root.entries) |entry| {        var relation_root = try history.relationRoot(std.testing.allocator, entry.hash);        defer relation_root.deinit();        try std.testing.expect(version.same(entry.hash, relation_root.hash));        try std.testing.expectEqualStrings(entry.name, relation_root.name);        const relation_rows = try history.relationRows(std.testing.allocator, entry.hash);        defer version.freeRelationRows(std.testing.allocator, relation_rows);        try std.testing.expectEqual(@as(usize, 1), relation_rows.len);        try std.testing.expectEqual(@as(i64, 4), relation_rows[0].rowid);    }    var reopened = try Connection.open(std.testing.allocator, &database, &history, .{});    defer reopened.deinit();    try std.testing.expect(version.same(commit_hash, (try reopened.checkout()).head));    try std.testing.expect(version.same((try connection.workingRoot()), (try reopened.workingRoot())));    try std.testing.expect(version.same((try reopened.workingRoot()), reopened.session.workingRoot().hash));}test "connection publishes catalog session values on commit" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "connection-catalog-commit.db", .wal = "connection-catalog-commit.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 512 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "connection-catalog-commit.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    var created = try connection.execute(std.testing.allocator, "CREATE TABLE items (name DEFAULT 'missing', score, INDEX items_score (score))", .{ .durability = .buffered });    defer created.deinit(std.testing.allocator);    const create_flush = switch (created) {        .catalog => |flush| flush,        else => return error.UnsupportedStatement,    };    const working_root = connection.session.workingRoot();    try std.testing.expect(version.same(create_flush.database, working_root.hash));    try std.testing.expectEqual(@as(usize, 1), working_root.entries.len);    try std.testing.expectError(error.DatabaseRootNotFound, history.databaseRoot(std.testing.allocator, create_flush.database));    try connection.stage();    const commit_hash = try connection.commit(&history);    try std.testing.expect(version.same(create_flush.database, connection.session.workingRoot().hash));    var committed = try history.commitDatabaseRoot(std.testing.allocator, commit_hash);    defer committed.deinit();    try std.testing.expect(version.same(create_flush.database, committed.hash));    try std.testing.expectEqual(@as(usize, 1), committed.entries.len);    var relation_root = try history.relationRoot(std.testing.allocator, committed.entries[0].hash);    defer relation_root.deinit();    try std.testing.expectEqualStrings("items", relation_root.name);    const rows = try history.relationRows(std.testing.allocator, committed.entries[0].hash);    defer version.freeRelationRows(std.testing.allocator, rows);    try std.testing.expectEqual(@as(usize, 0), rows.len);}test "connection open seeds first write from committed database value" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "connection-seed-open.db", .wal = "connection-seed-open.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "connection-seed-open.history", .recovery = .reject });    defer history.deinit();    var author = try Connection.create(std.testing.allocator, &database, &history, .{});    defer author.deinit();    try executeStatement(&author, "CREATE TABLE items (name)");    try executeStatement(&author, "CREATE TABLE users (name)");    try executeStatement(&author, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try author.stage();    const base_commit = try author.commit(&history);    var reopened = try Connection.open(std.testing.allocator, &database, &history, .{});    defer reopened.deinit();    try std.testing.expect(version.same(base_commit, (try reopened.checkout()).head));    const seeded_root = reopened.session.workingRoot();    try std.testing.expect(version.same((try reopened.workingRoot()), seeded_root.hash));    const seeded_items_hash = testEntryHash(seeded_root, "items");    var live_items = try reopened.catalog.openRelation(std.testing.allocator, "items");    defer live_items.deinit();    _ = try live_items.relation.put(std.testing.allocator, 9, &.{.{ .text = "live-only" }}, .{ .durability = .buffered });    var live_root = try version.databaseRootMaintained(        std.testing.allocator,        &reopened.catalog,        version.ConflictRoot.empty().hash,    );    defer live_root.deinit();    try std.testing.expect(!version.same(seeded_items_hash, testEntryHash(&live_root, "items")));    try executeStatement(&reopened, "INSERT INTO users (rowid, name) VALUES (2, 'ada')");    const flushed_root = reopened.session.workingRoot();    try std.testing.expect(version.same(seeded_items_hash, testEntryHash(flushed_root, "items")));    try std.testing.expect(version.same(testEntryHash(&live_root, "users"), testEntryHash(flushed_root, "users")) == false);}test "connection manages branch checkout fast forward and merge commits" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "connection-branch.db", .wal = "connection-branch.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "connection-branch.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try connection.stage();    const base_commit = try connection.commit(&history);    _ = try connection.createBranch(&history, "side");    _ = try connection.createBranch(&history, "behind");    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (2, 'main')");    try connection.stage();    const main_commit = try connection.commit(&history);    try std.testing.expect(version.same(base_commit, (try connection.mergeBase(std.testing.allocator, &history, "side")).?));    try connection.checkoutBranch(std.testing.allocator, &history, "side");    try std.testing.expect(version.same(base_commit, (try connection.checkout()).head));    var main_row_after_side_checkout = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 2", .{ .durability = .buffered });    defer main_row_after_side_checkout.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 0), main_row_after_side_checkout.rowCount());    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (3, 'side')");    try connection.stage();    const side_commit = try connection.commit(&history);    try std.testing.expect(version.same(base_commit, (try connection.mergeBase(std.testing.allocator, &history, "main")).?));    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (4, 'merged')");    try connection.stage();    const merge_commit = try connection.mergeCommit(&history, main_commit);    const entries = try history.commitEntries(std.testing.allocator);    defer std.testing.allocator.free(entries);    var merge_entry: ?branch_mod.CommitEntry = null;    for (entries) |entry| {        if (version.same(entry.hash, merge_commit)) merge_entry = entry;    }    try std.testing.expect(merge_entry != null);    try std.testing.expectEqual(@as(usize, 2), merge_entry.?.parents.len);    try std.testing.expect(version.same(side_commit, merge_entry.?.parents[0]));    try std.testing.expect(version.same(main_commit, merge_entry.?.parents[1]));    try connection.checkoutBranch(std.testing.allocator, &history, "behind");    try connection.fastForwardBranch(std.testing.allocator, &history, merge_commit);    try std.testing.expect(version.same(merge_commit, (try connection.checkout()).head));    try std.testing.expect(version.same((try history.ref("behind")).?.target, merge_commit));    var side_row_after_fast_forward = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 3", .{ .durability = .buffered });    defer side_row_after_fast_forward.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), side_row_after_fast_forward.rowCount());}test "connection checkout creates missing relations from committed schema descriptors" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var author_database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "checkout-create-author.db", .wal = "checkout-create-author.wal" },        .header = testingHeader(),    });    defer author_database.deinit();    try author_database.reserve(.{ .wal_frames = 960 });    var target_database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "checkout-create-target.db", .wal = "checkout-create-target.wal" },        .header = .{            .sequence = 3904,            .salt = .{ .first = 0x1357_3904, .second = 0x2468_3904 },        },    });    defer target_database.deinit();    try target_database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "checkout-create.history", .recovery = .reject });    defer history.deinit();    var author = try Connection.create(std.testing.allocator, &author_database, &history, .{});    defer author.deinit();    _ = try author.createBranch(&history, "side");    try author.checkoutBranch(std.testing.allocator, &history, "side");    try executeStatement(&author, "CREATE TABLE notes (title DEFAULT 'untitled' COLLATE nocase, body, INDEX notes_title (title))");    try executeStatement(&author, "INSERT INTO notes (rowid, title, body) VALUES (9, 'First', 'side note')");    try author.stage();    const side_commit = try author.commit(&history);    var target = try Connection.open(std.testing.allocator, &target_database, &history, .{});    defer target.deinit();    try target.checkoutBranch(std.testing.allocator, &history, "side");    try std.testing.expect(version.same(side_commit, (try target.checkout()).head));    try std.testing.expect(version.same((try history.ref("side")).?.target, (try target.checkout()).head));    var found = try target.execute(std.testing.allocator, "SELECT body FROM notes WHERE title = 'first'", .{ .durability = .buffered });    defer found.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), found.rowCount());    const found_view = try row.View.init(found.nextRow().?);    try std.testing.expectEqualStrings("side note", (try found_view.column(0)).text);    try executeStatement(&target, "INSERT INTO notes (rowid, body) VALUES (10, 'uses default')");    var defaulted = try target.execute(std.testing.allocator, "SELECT title FROM notes WHERE rowid = 10", .{ .durability = .buffered });    defer defaulted.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), defaulted.rowCount());    const defaulted_view = try row.View.init(defaulted.nextRow().?);    try std.testing.expectEqualStrings("untitled", (try defaulted_view.column(0)).text);}test "connection checkout recreates stats for missing analyzed relations" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var author_database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "checkout-stats-author.db", .wal = "checkout-stats-author.wal" },        .header = testingHeader(),    });    defer author_database.deinit();    try author_database.reserve(.{ .wal_frames = 1280 });    var target_database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "checkout-stats-target.db", .wal = "checkout-stats-target.wal" },        .header = .{            .sequence = 4904,            .salt = .{ .first = 0x1357_4904, .second = 0x2468_4904 },        },    });    defer target_database.deinit();    try target_database.reserve(.{ .wal_frames = 1280 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "checkout-stats.history", .recovery = .reject });    defer history.deinit();    var author = try Connection.create(std.testing.allocator, &author_database, &history, .{});    defer author.deinit();    _ = try author.createBranch(&history, "side");    try author.checkoutBranch(std.testing.allocator, &history, "side");    try executeStatement(&author, "CREATE TABLE notes (title COLLATE nocase, body, INDEX notes_title (title))");    try executeStatement(&author, "INSERT INTO notes (rowid, title, body) VALUES (1, 'Alpha', 'first')");    try executeStatement(&author, "INSERT INTO notes (rowid, title, body) VALUES (2, 'Beta', 'second')");    try executeStatement(&author, "INSERT INTO notes (rowid, title, body) VALUES (3, 'Beta', 'third')");    try executeStatement(&author, "ANALYZE notes");    try author.stage();    const side_commit = try author.commit(&history);    var target = try Connection.open(std.testing.allocator, &target_database, &history, .{});    defer target.deinit();    try target.checkoutBranch(std.testing.allocator, &history, "side");    try std.testing.expect(version.same(side_commit, (try target.checkout()).head));    var stats = (try target.catalog.relationStats(std.testing.allocator, "notes")).?;    defer stats.deinit();    try std.testing.expectEqual(@as(usize, 3), stats.table.entries);    try std.testing.expectEqual(@as(usize, 1), stats.indexes.len);    const index_stats = stats.index("notes_title").?;    try std.testing.expectEqual(@as(usize, 3), index_stats.summary.entries);    try std.testing.expectEqual(@as(usize, 2), index_stats.distribution.distinct_values);    try std.testing.expectEqual(@as(usize, 2), index_stats.distribution.max_equal);    var found = try target.execute(std.testing.allocator, "SELECT body FROM notes WHERE title = 'beta'", .{ .durability = .buffered });    defer found.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 2), found.rowCount());}test "connection checkout drops relations absent from target root" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "checkout-drop.db", .wal = "checkout-drop.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "checkout-drop.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (7, 'main')");    try connection.stage();    const main_commit = try connection.commit(&history);    _ = try connection.createBranch(&history, "side");    try connection.checkoutBranch(std.testing.allocator, &history, "side");    try executeStatement(&connection, "CREATE TABLE notes (title, INDEX notes_title (title))");    try executeStatement(&connection, "INSERT INTO notes (rowid, title) VALUES (1, 'side')");    try connection.stage();    const side_commit = try connection.commit(&history);    try std.testing.expect(version.same(side_commit, (try connection.checkout()).head));    var names_on_side = try connection.catalog.relationNames(std.testing.allocator);    defer names_on_side.deinit();    try std.testing.expectEqual(@as(usize, 2), names_on_side.names.len);    try connection.checkoutBranch(std.testing.allocator, &history, "main");    try std.testing.expect(version.same(main_commit, (try connection.checkout()).head));    try std.testing.expectError(error.TableNotFound, connection.execute(std.testing.allocator, "SELECT title FROM notes WHERE rowid = 1", .{ .durability = .buffered }));    var item = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 7", .{ .durability = .buffered });    defer item.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), item.rowCount());    const item_view = try row.View.init(item.nextRow().?);    try std.testing.expectEqualStrings("main", (try item_view.column(0)).text);    var names_on_main = try connection.catalog.relationNames(std.testing.allocator);    defer names_on_main.deinit();    try std.testing.expectEqual(@as(usize, 1), names_on_main.names.len);    try std.testing.expectEqualStrings("items", names_on_main.names[0]);    try connection.checkoutBranch(std.testing.allocator, &history, "side");    var found = try connection.execute(std.testing.allocator, "SELECT title FROM notes WHERE rowid = 1", .{ .durability = .buffered });    defer found.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), found.rowCount());    const found_view = try row.View.init(found.nextRow().?);    try std.testing.expectEqualStrings("side", (try found_view.column(0)).text);}test "connection drop table statement keeps pre-drop commits readable" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "drop-statement.db", .wal = "drop-statement.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "drop-statement.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    try executeStatement(&connection, "INSERT INTO items (rowid, name) VALUES (7, 'kept')");    try connection.stage();    const keep_commit = try connection.commit(&history);    _ = try connection.createBranch(&history, "keep");    var dropped = try connection.execute(std.testing.allocator, "DROP TABLE items", .{ .durability = .buffered });    defer dropped.deinit(std.testing.allocator);    const drop_flush = switch (dropped) {        .catalog => |flush| flush,        else => return error.UnsupportedStatement,    };    try std.testing.expect(version.same(drop_flush.database, (try connection.workingRoot())));    try std.testing.expect(testFindEntry(connection.session.workingRoot(), "items") == null);    try std.testing.expectError(error.TableNotFound, connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 7", .{ .durability = .buffered }));    try connection.stage();    const drop_commit = try connection.commit(&history);    var dropped_root = try history.commitDatabaseRoot(std.testing.allocator, drop_commit);    defer dropped_root.deinit();    try std.testing.expectEqual(@as(usize, 0), dropped_root.entries.len);    var kept_root = try history.commitDatabaseRoot(std.testing.allocator, keep_commit);    defer kept_root.deinit();    try std.testing.expectEqual(@as(usize, 1), kept_root.entries.len);    try std.testing.expectEqualStrings("items", kept_root.entries[0].name);    var names_after_drop = try connection.catalog.relationNames(std.testing.allocator);    defer names_after_drop.deinit();    try std.testing.expectEqual(@as(usize, 0), names_after_drop.names.len);    try connection.checkoutBranch(std.testing.allocator, &history, "keep");    try std.testing.expect(version.same(keep_commit, (try connection.checkout()).head));    var restored = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 7", .{ .durability = .buffered });    defer restored.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), restored.rowCount());    const restored_view = try row.View.init(restored.nextRow().?);    try std.testing.expectEqualStrings("kept", (try restored_view.column(0)).text);}test "connection checkout discards queued relation session edits" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "checkout-session-discard.db", .wal = "checkout-session-discard.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "checkout-session-discard.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    try connection.stage();    const base_commit = try connection.commit(&history);    _ = try connection.createBranch(&history, "side");    var prepared = try plan_mod.prepareRelation(&connection.catalog, std.testing.allocator, "items", plan_mod.emptyParameterShape());    var relation_session = try prepared.writeSession();    var relation_session_live = true;    errdefer if (relation_session_live) relation_session.deinit();    try relation_session.put(42, &.{.{ .text = "queued" }});    const write_limits = try relation_session.stagingLimits();    var workspace = try session_mod.DatabaseWrite.Workspace.allocate(        std.testing.allocator,        write_limits,    );    defer workspace.deallocate(std.testing.allocator);    var write = try connection.beginWrite(&workspace, std.testing.allocator, .{        .limits = write_limits,        .durability = .buffered,    });    try write.stageRelation(&relation_session);    relation_session_live = false;    try std.testing.expectEqual(@as(usize, 1), write.pendingRelations());    prepared.deinit();    try connection.checkoutBranch(std.testing.allocator, &history, "side");    try std.testing.expect(version.same(base_commit, (try connection.checkout()).head));    try std.testing.expectEqual(@as(usize, 0), connection.session.pendingRelations());    const empty_limits = session_mod.DatabaseWrite.Limits{        .relations = 0,        .edits = 0,        .payload_bytes = 0,        .assignments = 0,    };    var replacement_write = try connection.beginWrite(&workspace, std.testing.allocator, .{        .limits = empty_limits,        .durability = .buffered,    });    defer replacement_write.deinit();    write.deinit();    try std.testing.expectError(        error.WriteSessionActive,        connection.beginWrite(&workspace, std.testing.allocator, .{            .limits = empty_limits,            .durability = .buffered,        }),    );    var queued = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 42", .{ .durability = .buffered });    defer queued.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 0), queued.rowCount());}test "connection write session stages statements before one root flush" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "connection-write-session.db", .wal = "connection-write-session.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "connection-write-session.history", .recovery = .reject });    defer history.deinit();    var connection = try Connection.create(std.testing.allocator, &database, &history, .{});    defer connection.deinit();    try executeStatement(&connection, "CREATE TABLE items (name)");    const before = (try connection.workingRoot());    var insert = try connection.prepare(std.testing.allocator, "INSERT INTO items (rowid, name) VALUES (?1, ?2)");    const write_options = WriteOptions{        .limits = .{            .relations = 1,            .edits = 2,            .payload_bytes = 32,            .assignments = 0,        },        .durability = .buffered,    };    var workspace = try session_mod.DatabaseWrite.Workspace.allocate(        std.testing.allocator,        write_options.limits,    );    defer workspace.deallocate(std.testing.allocator);    var write = try connection.beginWrite(        &workspace,        std.testing.allocator,        write_options,    );    defer write.deinit();    try std.testing.expectError(        error.WriteSessionActive,        connection.beginWrite(&workspace, std.testing.allocator, write_options),    );    try insert.bind(1, .{ .integer = 1 });    try insert.bind(2, .{ .text = "one" });    var first = try connection.executePrepared(&insert, std.testing.allocator, .{ .durability = .buffered, .write = &write });    defer first.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), first.staged);    try insert.bind(1, .{ .integer = 2 });    try insert.bind(2, .{ .text = "two" });    var second = try connection.executePrepared(&insert, std.testing.allocator, .{ .durability = .buffered, .write = &write });    defer second.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), second.staged);    insert.deinit();    try std.testing.expect(version.same(before, (try connection.workingRoot())));    var before_flush = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid >= 1", .{ .durability = .buffered });    defer before_flush.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 0), before_flush.rowCount());    var flush = try write.flush();    defer flush.deinit();    try std.testing.expectEqual(@as(usize, 1), flush.relations.len);    try std.testing.expect(version.same(flush.database, (try connection.workingRoot())));    try std.testing.expect(!version.same(before, (try connection.workingRoot())));    const working_root = connection.session.workingRoot();    try std.testing.expect(version.same(flush.database, working_root.hash));    try std.testing.expectEqual(@as(usize, 1), working_root.entries.len);    try std.testing.expectError(error.DatabaseRootNotFound, history.databaseRoot(std.testing.allocator, flush.database));    var after_flush = try connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid >= 1", .{ .durability = .buffered });    defer after_flush.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 2), after_flush.rowCount());    try connection.stage();    const commit_hash = try connection.commit(&history);    try std.testing.expect(version.same(flush.database, connection.session.workingRoot().hash));    var committed = try history.commitDatabaseRoot(std.testing.allocator, commit_hash);    defer committed.deinit();    try std.testing.expect(version.same(flush.database, committed.hash));    const rows = try history.relationRows(std.testing.allocator, flush.relations[0].relation);    defer version.freeRelationRows(std.testing.allocator, rows);    try std.testing.expectEqual(@as(usize, 2), rows.len);}test "connection diffs relation views without raw catalog access" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var left = TestingConnection{};    try left.init(std.testing.allocator, tmp.dir, "left.db", "left.wal", "left.history");    defer left.deinit();    var right = TestingConnection{};    try right.init(std.testing.allocator, tmp.dir, "right.db", "right.wal", "right.history");    defer right.deinit();    try executeStatement(&left.connection, "CREATE TABLE items (name)");    try executeStatement(&left.connection, "INSERT INTO items (rowid, name) VALUES (1, 'same')");    try executeStatement(&left.connection, "INSERT INTO items (rowid, name) VALUES (2, 'old')");    try executeStatement(&right.connection, "CREATE TABLE items (name)");    try executeStatement(&right.connection, "INSERT INTO items (rowid, name) VALUES (1, 'same')");    try executeStatement(&right.connection, "INSERT INTO items (rowid, name) VALUES (2, 'new')");    try executeStatement(&right.connection, "INSERT INTO items (rowid, name) VALUES (3, 'added')");    var right_view = try right.connection.relationView(std.testing.allocator, "items");    defer right_view.deinit();    var result = try left.connection.diffRelation(std.testing.allocator, "items", right_view.snapshot());    defer result.deinit();    try std.testing.expect(!result.schema_changed);    try std.testing.expectEqual(@as(usize, 2), result.changes.len);    try std.testing.expectEqual(diff_mod.ChangeKind.modified, result.changes[0].kind);    try std.testing.expectEqual(@as(i64, 2), result.changes[0].rowid);    try std.testing.expectEqual(diff_mod.ChangeKind.added, result.changes[1].kind);    try std.testing.expectEqual(@as(i64, 3), result.changes[1].rowid);}test "connection applies explicit database merge snapshots into working root" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var base = TestingConnection{};    try base.init(std.testing.allocator, tmp.dir, "merge-base.db", "merge-base.wal", "merge-base.history");    defer base.deinit();    var ours = TestingConnection{};    try ours.init(std.testing.allocator, tmp.dir, "merge-ours.db", "merge-ours.wal", "merge-ours.history");    defer ours.deinit();    var theirs = TestingConnection{};    try theirs.init(std.testing.allocator, tmp.dir, "merge-theirs.db", "merge-theirs.wal", "merge-theirs.history");    defer theirs.deinit();    try createItems(&base.connection);    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (1, 'same')");    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (2, 'base')");    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (3, 'delete')");    try createItems(&ours.connection);    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (1, 'same')");    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (2, 'ours')");    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (3, 'delete')");    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (5, 'ours-add')");    try executeStatement(&ours.connection, "ANALYZE items");    var ours_stats = (try ours.connection.catalog.relationStats(std.testing.allocator, "items")).?;    ours_stats.deinit();    try createItems(&theirs.connection);    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (1, 'same')");    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (2, 'base')");    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (6, 'theirs-add')");    const names = [_][]const u8{"items"};    var base_view = try base.connection.databaseView(std.testing.allocator, &base.history, names[0..]);    defer base_view.deinit();    var theirs_view = try theirs.connection.databaseView(std.testing.allocator, &theirs.history, names[0..]);    defer theirs_view.deinit();    const previous_root = (try ours.connection.workingRoot());    var merged = try ours.connection.mergeDatabase(std.testing.allocator, &ours.history, base_view.snapshot(), theirs_view.snapshot(), .{});    defer merged.deinit();    try std.testing.expect(!merged.hasConflicts());    try std.testing.expect(!version.same(previous_root, (try ours.connection.workingRoot())));    try std.testing.expect(version.same(merged.value.root.hash, (try ours.connection.workingRoot())));    try std.testing.expect(version.same(merged.value.root.hash, ours.connection.session.workingRoot().hash));    try ours.history.validateConflictRoot(merged.value.root.conflicts);    try expectLiveDatabaseRoot(&ours.connection, &merged.value.root);    const merged_relation = merged.value.findRelation("items").?;    try std.testing.expect(version.same(merged_relation.root.stats.hash, version.emptyHash("sql.stats.none")));    try std.testing.expect((try ours.connection.catalog.relationStats(std.testing.allocator, "items")) == null);    try std.testing.expect((try ours.connection.checkout()).working.dirty());    try std.testing.expectError(error.DatabaseRootNotFound, ours.history.databaseRoot(std.testing.allocator, merged.value.root.hash));    var found = try ours.connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 6", .{ .durability = .buffered });    defer found.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), found.rowCount());    const added_view = try row.View.init(found.nextRow().?);    try std.testing.expectEqualStrings("theirs-add", (try added_view.column(0)).text);    var missing = try ours.connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 3", .{ .durability = .buffered });    defer missing.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 0), missing.rowCount());    try ours.connection.stage();    const merge_commit = try ours.connection.commit(&ours.history);    var committed_value = try ours.history.databaseValue(std.testing.allocator, (try ours.connection.workingRoot()));    defer committed_value.deinit();    try std.testing.expect(version.same(merge_commit, (try ours.connection.checkout()).head));    try std.testing.expect(version.same(merged.value.root.hash, committed_value.root.hash));}test "connection rejects a selected schema before changing the working root" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var base = TestingConnection{};    try base.init(std.testing.allocator, tmp.dir, "schema-base.db", "schema-base.wal", "schema-base.history");    defer base.deinit();    var ours = TestingConnection{};    try ours.init(std.testing.allocator, tmp.dir, "schema-ours.db", "schema-ours.wal", "schema-ours.history");    defer ours.deinit();    var theirs = TestingConnection{};    try theirs.init(std.testing.allocator, tmp.dir, "schema-theirs.db", "schema-theirs.wal", "schema-theirs.history");    defer theirs.deinit();    try createItems(&base.connection);    try createItems(&ours.connection);    try createItems(&theirs.connection);    try executeStatement(&base.connection, "CREATE TABLE alpha (name)");    try executeStatement(&ours.connection, "CREATE TABLE alpha (name)");    try executeStatement(&theirs.connection, "CREATE TABLE alpha (name)");    try executeStatement(&base.connection, "INSERT INTO alpha (rowid, name) VALUES (1, 'base')");    try executeStatement(&ours.connection, "INSERT INTO alpha (rowid, name) VALUES (1, 'base')");    try executeStatement(&theirs.connection, "INSERT INTO alpha (rowid, name) VALUES (1, 'theirs')");    try executeStatement(&theirs.connection, "CREATE INDEX items_name ON items (name)");    const names = [_][]const u8{ "alpha", "items" };    var base_view = try base.connection.databaseView(std.testing.allocator, &base.history, names[0..]);    defer base_view.deinit();    var theirs_view = try theirs.connection.databaseView(std.testing.allocator, &theirs.history, names[0..]);    defer theirs_view.deinit();    const previous_root = (try ours.connection.workingRoot());    try std.testing.expectError(error.UnsupportedCheckoutRoot, ours.connection.mergeDatabase(        std.testing.allocator,        &ours.history,        base_view.snapshot(),        theirs_view.snapshot(),        .{},    ));    try std.testing.expect(version.same(previous_root, (try ours.connection.workingRoot())));    var items = try ours.connection.relationView(std.testing.allocator, "items");    defer items.deinit();    try std.testing.expectEqual(@as(usize, 0), items.root.indexes.len);    var alpha = try ours.connection.execute(std.testing.allocator, "SELECT name FROM alpha WHERE rowid = 1", .{ .durability = .buffered });    defer alpha.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), alpha.rowCount());    const alpha_view = try row.View.init(alpha.nextRow().?);    try std.testing.expectEqualStrings("base", (try alpha_view.column(0)).text);}test "connection replaces conflict root after value resolution" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var base = TestingConnection{};    try base.init(std.testing.allocator, tmp.dir, "resolve-base.db", "resolve-base.wal", "resolve-base.history");    defer base.deinit();    var ours = TestingConnection{};    try ours.init(std.testing.allocator, tmp.dir, "resolve-ours.db", "resolve-ours.wal", "resolve-ours.history");    defer ours.deinit();    var theirs = TestingConnection{};    try theirs.init(std.testing.allocator, tmp.dir, "resolve-theirs.db", "resolve-theirs.wal", "resolve-theirs.history");    defer theirs.deinit();    try createItems(&base.connection);    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try createItems(&ours.connection);    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (1, 'ours')");    try createItems(&theirs.connection);    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (1, 'theirs')");    const names = [_][]const u8{"items"};    var base_view = try base.connection.databaseView(std.testing.allocator, &base.history, names[0..]);    defer base_view.deinit();    var theirs_view = try theirs.connection.databaseView(std.testing.allocator, &theirs.history, names[0..]);    defer theirs_view.deinit();    var merged = try ours.connection.mergeDatabase(std.testing.allocator, &ours.history, base_view.snapshot(), theirs_view.snapshot(), .{});    defer merged.deinit();    try std.testing.expect(merged.hasConflicts());    try std.testing.expectEqual(@as(usize, 1), merged.conflict_root.count);    try std.testing.expect(version.same(merged.conflict_root.hash, ours.connection.session.workingRoot().conflicts));    try ours.history.validateConflictRoot(merged.value.root.conflicts);    try expectLiveDatabaseRoot(&ours.connection, &merged.value.root);    var neutral_root = try version.databaseRoot(        std.testing.allocator,        &ours.connection.catalog,        version.ConflictRoot.empty().hash,    );    defer neutral_root.deinit();    try std.testing.expect(!version.same(neutral_root.hash, merged.value.root.hash));    var persisted_entries = try ours.history.conflictEntries(std.testing.allocator, merged.conflict_root.hash);    defer persisted_entries.deinit();    try std.testing.expectEqual(@as(usize, 1), persisted_entries.entries.len);    const conflicted_root = (try ours.connection.workingRoot());    const clean_root = try ours.connection.replaceConflictRoot(version.ConflictRoot.empty());    try std.testing.expect(!version.same(conflicted_root, clean_root));    try std.testing.expect(version.same(version.ConflictRoot.empty().hash, ours.connection.session.workingRoot().conflicts));    var selected = try ours.connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 1", .{ .durability = .buffered });    defer selected.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), selected.rowCount());    const selected_view = try row.View.init(selected.nextRow().?);    try std.testing.expectEqualStrings("ours", (try selected_view.column(0)).text);    try ours.connection.stage();    const commit_hash = try ours.connection.commit(&ours.history);    try std.testing.expect(version.same(commit_hash, (try ours.connection.checkout()).head));    var committed = try ours.history.databaseValue(std.testing.allocator, clean_root);    defer committed.deinit();    try std.testing.expect(version.same(clean_root, committed.root.hash));    try std.testing.expect(version.same(version.ConflictRoot.empty().hash, committed.root.conflicts));}test "connection merge materialization rejects mismatched and unclosed conflicts" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "merge-conflict-closure.db",        "merge-conflict-closure.wal",        "merge-conflict-closure.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'ours')");    const baseline = (try store.connection.workingRoot());    const artifact = version.ConflictArtifact.init("items", 1, "base", "ours", "theirs");    const conflicts = version.ConflictRoot.init(&.{artifact.entry()});    var value = try store.connection.materializedWorkingValue(std.testing.allocator);    const rooted = try value.root.withConflicts(std.testing.allocator, conflicts.hash);    value.root.deinit();    value.root = rooted;    var artifacts = [_]version.ConflictArtifact{artifact};    var result = merge_mod.DatabaseMerge{        .allocator = std.testing.allocator,        .value = value,        .conflict_root = version.ConflictRoot.empty(),        .relations = &.{},        .discovered = &.{},        .artifacts = artifacts[0..],    };    defer result.value.deinit();    try std.testing.expectError(        error.InvalidHistory,        store.connection.materializeDatabaseMerge(            std.testing.allocator,            &store.history,            &result,            .{ .durability = .buffered },        ),    );    try std.testing.expect(version.same(baseline, (try store.connection.workingRoot())));    result.conflict_root = conflicts;    try store.history.putConflict(artifact);    store.history.findConflict(artifact.hash).?.artifact.rowid += 1;    try std.testing.expectError(        error.InvalidHistory,        store.connection.materializeDatabaseMerge(            std.testing.allocator,            &store.history,            &result,            .{ .durability = .buffered },        ),    );    try std.testing.expect(version.same(baseline, (try store.connection.workingRoot())));}test "connection materialized root verifier rejects relation identity drift" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(std.testing.allocator, tmp.dir, "merge-relation-drift.db", "merge-relation-drift.wal", "merge-relation-drift.history");    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'target')");    var target = try store.connection.materializedWorkingValue(std.testing.allocator);    defer target.deinit();    try executeStatement(&store.connection, "UPDATE items SET name = 'drift' WHERE rowid = 1");    try std.testing.expectError(        error.InvalidHistory,        store.connection.verifyMaterializedDatabaseRoot(std.testing.allocator, &target.root),    );    var live = try version.databaseRoot(        std.testing.allocator,        &store.connection.catalog,        target.root.conflicts,    );    defer live.deinit();    try std.testing.expect(!version.same(live.hash, target.root.hash));}test "connection materialized root verifier rejects an extra live relation" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(std.testing.allocator, tmp.dir, "merge-extra-relation.db", "merge-extra-relation.wal", "merge-extra-relation.history");    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'target')");    var target = try store.connection.materializedWorkingValue(std.testing.allocator);    defer target.deinit();    try executeStatement(&store.connection, "CREATE TABLE untracked (value)");    const entry = relationEntry(target.root.entries, "items").?;    var handle = try store.connection.catalog.openRelation(std.testing.allocator, "items");    defer handle.deinit();    var stats = try store.connection.catalog.relationStats(std.testing.allocator, "items");    defer if (stats) |*relation_stats| relation_stats.deinit();    const live = try version.relationKey(        "items",        &handle,        if (stats) |*relation_stats| relation_stats else null,    );    try std.testing.expect(version.same(live.hash, entry.hash));    try std.testing.expectError(        error.InvalidHistory,        store.connection.verifyMaterializedDatabaseRoot(std.testing.allocator, &target.root),    );}test "connection resolves selected conflict artifacts" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var base = TestingConnection{};    try base.init(std.testing.allocator, tmp.dir, "resolve-selected-base.db", "resolve-selected-base.wal", "resolve-selected-base.history");    defer base.deinit();    var ours = TestingConnection{};    try ours.init(std.testing.allocator, tmp.dir, "resolve-selected-ours.db", "resolve-selected-ours.wal", "resolve-selected-ours.history");    defer ours.deinit();    var theirs = TestingConnection{};    try theirs.init(std.testing.allocator, tmp.dir, "resolve-selected-theirs.db", "resolve-selected-theirs.wal", "resolve-selected-theirs.history");    defer theirs.deinit();    try createItems(&base.connection);    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base-one')");    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (2, 'base-two')");    try createItems(&ours.connection);    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (1, 'ours-one')");    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (2, 'ours-two')");    try createItems(&theirs.connection);    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (1, 'theirs-one')");    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (2, 'theirs-two')");    const names = [_][]const u8{"items"};    var base_view = try base.connection.databaseView(std.testing.allocator, &base.history, names[0..]);    defer base_view.deinit();    var theirs_view = try theirs.connection.databaseView(std.testing.allocator, &theirs.history, names[0..]);    defer theirs_view.deinit();    var merged = try ours.connection.mergeDatabase(std.testing.allocator, &ours.history, base_view.snapshot(), theirs_view.snapshot(), .{});    defer merged.deinit();    try std.testing.expect(merged.hasConflicts());    try std.testing.expectEqual(@as(usize, 2), merged.conflict_root.count);    var artifacts = try ours.connection.conflictArtifacts(std.testing.allocator, &ours.history);    defer artifacts.deinit();    try std.testing.expect(version.same(merged.conflict_root.hash, artifacts.root.hash));    try std.testing.expectEqual(@as(usize, 2), artifacts.artifacts.len);    try std.testing.expectEqual(@as(i64, 1), artifacts.artifacts[0].rowid);    try std.testing.expect(artifacts.artifacts[0].base != null);    try std.testing.expect(artifacts.artifacts[0].ours != null);    try std.testing.expect(artifacts.artifacts[0].theirs != null);    try std.testing.expectEqual(@as(i64, 2), artifacts.artifacts[1].rowid);    try std.testing.expect(artifacts.artifacts[1].base != null);    try std.testing.expect(artifacts.artifacts[1].ours != null);    try std.testing.expect(artifacts.artifacts[1].theirs != null);    const conflicted_root = (try ours.connection.workingRoot());    const unchanged_root = try ours.connection.resolveConflicts(std.testing.allocator, &ours.history, &.{});    try std.testing.expect(version.same(conflicted_root, unchanged_root));    const missing = version.emptyHash("sql.conflict.missing");    try std.testing.expectError(error.ConflictNotFound, ours.connection.resolveConflicts(std.testing.allocator, &ours.history, &.{missing}));    try std.testing.expect(version.same(conflicted_root, (try ours.connection.workingRoot())));    const first_hash = artifacts.artifacts[0].hash;    const remaining_hash = artifacts.artifacts[1].hash;    const partially_clean_root = try ours.connection.resolveConflicts(std.testing.allocator, &ours.history, &.{first_hash});    try std.testing.expect(!version.same(conflicted_root, partially_clean_root));    var remaining_artifacts = try ours.connection.conflictArtifacts(std.testing.allocator, &ours.history);    defer remaining_artifacts.deinit();    try std.testing.expectEqual(@as(usize, 1), remaining_artifacts.artifacts.len);    try std.testing.expectEqual(@as(i64, 2), remaining_artifacts.artifacts[0].rowid);    try std.testing.expect(version.same(remaining_hash, remaining_artifacts.artifacts[0].hash));    const clean_root = try ours.connection.resolveConflicts(std.testing.allocator, &ours.history, &.{remaining_hash});    try std.testing.expect(!version.same(partially_clean_root, clean_root));    try std.testing.expect(version.same(version.ConflictRoot.empty().hash, ours.connection.session.workingRoot().conflicts));    var selected_one = try ours.connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 1", .{ .durability = .buffered });    defer selected_one.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), selected_one.rowCount());    const selected_one_view = try row.View.init(selected_one.nextRow().?);    try std.testing.expectEqualStrings("ours-one", (try selected_one_view.column(0)).text);    var selected_two = try ours.connection.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 2", .{ .durability = .buffered });    defer selected_two.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), selected_two.rowCount());    const selected_two_view = try row.View.init(selected_two.nextRow().?);    try std.testing.expectEqualStrings("ours-two", (try selected_two_view.column(0)).text);    try ours.connection.stage();    const commit_hash = try ours.connection.commit(&ours.history);    try std.testing.expect(version.same(commit_hash, (try ours.connection.checkout()).head));    var committed = try ours.history.databaseValue(std.testing.allocator, clean_root);    defer committed.deinit();    try std.testing.expect(version.same(clean_root, committed.root.hash));    try std.testing.expect(version.same(version.ConflictRoot.empty().hash, committed.root.conflicts));}test "connection merge commit records parent when resolved root matches ours" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(std.testing.allocator, tmp.dir, "merge-same-root.db", "merge-same-root.wal", "merge-same-root.history");    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try store.connection.stage();    _ = try store.connection.commit(&store.history);    _ = try store.connection.createBranch(&store.history, "side");    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "side");    try executeStatement(&store.connection, "DELETE FROM items WHERE rowid = 1");    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'theirs')");    try store.connection.stage();    const side_commit = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    try executeStatement(&store.connection, "DELETE FROM items WHERE rowid = 1");    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'ours')");    try store.connection.stage();    const ours_commit = try store.connection.commit(&store.history);    const ours_root = (try store.connection.workingRoot());    var merged = try store.connection.mergeBranch(std.testing.allocator, &store.history, "side", .{});    defer merged.deinit();    try std.testing.expect(merged.hasConflicts());    var artifacts = try store.connection.conflictArtifacts(std.testing.allocator, &store.history);    defer artifacts.deinit();    try std.testing.expectEqual(@as(usize, 1), artifacts.artifacts.len);    _ = try store.connection.resolveConflicts(std.testing.allocator, &store.history, &.{artifacts.artifacts[0].hash});    try std.testing.expect(version.same(ours_root, (try store.connection.workingRoot())));    const merge_commit = try store.connection.mergeCommit(&store.history, side_commit);    const commit = try store.history.commitValue(merge_commit);    try std.testing.expect(version.same(ours_root, commit.root));    try std.testing.expectEqual(@as(usize, 2), commit.parents.len);    try std.testing.expect(version.same(ours_commit, commit.parents[0]));    try std.testing.expect(version.same(side_commit, commit.parents[1]));}test "connection merge materializes independent relation additions" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var base = TestingConnection{};    try base.init(std.testing.allocator, tmp.dir, "merge-add-base.db", "merge-add-base.wal", "merge-add-base.history");    defer base.deinit();    var ours = TestingConnection{};    try ours.init(std.testing.allocator, tmp.dir, "merge-add-ours.db", "merge-add-ours.wal", "merge-add-ours.history");    defer ours.deinit();    var theirs = TestingConnection{};    try theirs.init(std.testing.allocator, tmp.dir, "merge-add-theirs.db", "merge-add-theirs.wal", "merge-add-theirs.history");    defer theirs.deinit();    try createItems(&base.connection);    try executeStatement(&base.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try createItems(&ours.connection);    try executeStatement(&ours.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try executeStatement(&ours.connection, "CREATE TABLE local (body)");    try executeStatement(&ours.connection, "INSERT INTO local (rowid, body) VALUES (7, 'ours-only')");    try createItems(&theirs.connection);    try executeStatement(&theirs.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try executeStatement(&theirs.connection, "CREATE TABLE side (body)");    try executeStatement(&theirs.connection, "INSERT INTO side (rowid, body) VALUES (8, 'theirs-only')");    const base_names = [_][]const u8{"items"};    const theirs_names = [_][]const u8{ "side", "items" };    var base_view = try base.connection.databaseView(std.testing.allocator, &base.history, base_names[0..]);    defer base_view.deinit();    var theirs_view = try theirs.connection.databaseView(std.testing.allocator, &theirs.history, theirs_names[0..]);    defer theirs_view.deinit();    var merged = try ours.connection.mergeDatabase(std.testing.allocator, &ours.history, base_view.snapshot(), theirs_view.snapshot(), .{});    defer merged.deinit();    try std.testing.expect(!merged.hasConflicts());    try std.testing.expect(merged.value.findRelation("items") != null);    try std.testing.expect(merged.value.findRelation("local") != null);    try std.testing.expect(merged.value.findRelation("side") != null);    try std.testing.expect(version.same(merged.value.root.hash, ours.connection.session.workingRoot().hash));    var local = try ours.connection.execute(std.testing.allocator, "SELECT body FROM local WHERE rowid = 7", .{ .durability = .buffered });    defer local.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), local.rowCount());    const local_view = try row.View.init(local.nextRow().?);    try std.testing.expectEqualStrings("ours-only", (try local_view.column(0)).text);    var side = try ours.connection.execute(std.testing.allocator, "SELECT body FROM side WHERE rowid = 8", .{ .durability = .buffered });    defer side.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), side.rowCount());    const side_view = try row.View.init(side.nextRow().?);    try std.testing.expectEqualStrings("theirs-only", (try side_view.column(0)).text);}test "connection merges branch refs from history snapshots" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var database = try file.Database.openForTesting(std.testing.allocator, tmp.dir, .{        .paths = .{ .database = "merge-branch.db", .wal = "merge-branch.wal" },        .header = testingHeader(),    });    defer database.deinit();    try database.reserve(.{ .wal_frames = 960 });    var history = try history_mod.History.open(std.testing.allocator, tmp.dir, .{ .path = "merge-branch.history", .recovery = .reject });    defer history.deinit();    var main = try Connection.create(std.testing.allocator, &database, &history, .{});    defer main.deinit();    try createItems(&main);    try executeStatement(&main, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try main.stage();    const base_commit = try main.commit(&history);    _ = try main.createBranch(&history, "side");    try executeStatement(&main, "INSERT INTO items (rowid, name) VALUES (2, 'main')");    try main.stage();    const main_commit = try main.commit(&history);    try main.checkoutBranch(std.testing.allocator, &history, "side");    var main_row_on_side = try main.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 2", .{ .durability = .buffered });    defer main_row_on_side.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 0), main_row_on_side.rowCount());    try executeStatement(&main, "INSERT INTO items (rowid, name) VALUES (3, 'side')");    try main.stage();    const side_commit = try main.commit(&history);    try std.testing.expect(version.same(side_commit, (try history.ref("side")).?.target));    try main.checkoutBranch(std.testing.allocator, &history, "main");    try std.testing.expect(version.same(main_commit, (try main.checkout()).head));    try std.testing.expect(version.same(base_commit, (try main.mergeBase(std.testing.allocator, &history, "side")).?));    var side_row_on_main = try main.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 3", .{ .durability = .buffered });    defer side_row_on_main.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 0), side_row_on_main.rowCount());    var main_row_on_main = try main.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 2", .{ .durability = .buffered });    defer main_row_on_main.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), main_row_on_main.rowCount());    const previous_root = (try main.workingRoot());    var merged = try main.mergeBranch(std.testing.allocator, &history, "side", .{});    defer merged.deinit();    try std.testing.expect(!merged.hasConflicts());    try std.testing.expect(!version.same(previous_root, (try main.workingRoot())));    try std.testing.expect(version.same(merged.value.root.hash, (try main.workingRoot())));    try std.testing.expect(version.same(merged.value.root.hash, main.session.workingRoot().hash));    var found = try main.execute(std.testing.allocator, "SELECT name FROM items WHERE rowid = 3", .{ .durability = .buffered });    defer found.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), found.rowCount());    const found_view = try row.View.init(found.nextRow().?);    try std.testing.expectEqualStrings("side", (try found_view.column(0)).text);}test "committed conflict survives identical later merge" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(std.testing.allocator, tmp.dir, "conflict-carry.db", "conflict-carry.wal", "conflict-carry.history");    defer store.deinit();    const conflict = try commitTestingConflict(&store);    try std.testing.expect(version.same(conflict.commit, (try store.connection.checkout()).head));    _ = try store.connection.createBranch(&store.history, "same");    var later = try store.connection.mergeBranch(std.testing.allocator, &store.history, "same", .{});    defer later.deinit();    try std.testing.expect(later.hasConflicts());    try std.testing.expectEqual(@as(usize, 1), later.conflict_root.count);    try std.testing.expect(version.same(conflict.root, later.conflict_root.hash));    try std.testing.expect(version.same(conflict.database, (try store.connection.workingRoot())));    try expectTestingConflict(&store, conflict);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "side");    try std.testing.expect(version.same(version.ConflictRoot.empty().hash, store.connection.session.workingRoot().conflicts));    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    try std.testing.expect(version.same(conflict.commit, (try store.connection.checkout()).head));    try std.testing.expect(version.same(conflict.database, (try store.connection.workingRoot())));    try expectTestingConflict(&store, conflict);    try executeStatement(&store.connection, "ANALYZE items");    try executeStatement(&store.connection, "CREATE TABLE notes (value)");    try executeStatement(&store.connection, "DROP TABLE notes");    try expectTestingConflict(&store, conflict);    _ = try store.connection.resolveConflicts(std.testing.allocator, &store.history, &.{conflict.artifact});    try std.testing.expect(version.same(version.ConflictRoot.empty().hash, store.connection.session.workingRoot().conflicts));}test "connection fast forward adopts a committed conflict root" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(std.testing.allocator, tmp.dir, "conflict-forward.db", "conflict-forward.wal", "conflict-forward.history");    defer store.deinit();    const conflict = try commitTestingConflict(&store);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "behind");    try store.connection.fastForwardBranch(std.testing.allocator, &store.history, conflict.commit);    try std.testing.expect(version.same(conflict.commit, (try store.connection.checkout()).head));    try std.testing.expect(version.same(conflict.commit, (try store.history.ref("behind")).?.target));    try std.testing.expect(version.same(conflict.database, (try store.connection.workingRoot())));    try expectTestingConflict(&store, conflict);}test "connection commit rejects an unclosed conflict root" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "conflict-closure.db",        "conflict-closure.wal",        "conflict-closure.history",    );    defer store.deinit();    try createItems(&store.connection);    try store.connection.stage();    const baseline = try store.connection.commit(&store.history);    const artifact = version.ConflictArtifact.init(        "items",        1,        "base",        "ours",        "theirs",    );    const conflicts = version.ConflictRoot.init(&.{artifact.entry()});    const invalid_root = try store.connection.replaceConflictRoot(conflicts);    try store.connection.stage();    try std.testing.expectError(        error.ConflictRootNotFound,        store.connection.commit(&store.history),    );    try std.testing.expect(version.same(baseline, (try store.connection.checkout()).head));    try std.testing.expect(version.same(baseline, (try store.history.ref("main")).?.target));    try std.testing.expect(!store.history.hasDatabaseRoot(invalid_root));}test "connection commit rejects catalog identity drift" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(std.testing.allocator, tmp.dir, "catalog-drift.db", "catalog-drift.wal", "catalog-drift.history");    defer store.deinit();    try createItems(&store.connection);    try store.connection.stage();    const baseline = try store.connection.commit(&store.history);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'staged')");    try store.connection.stage();    const staged = (try store.connection.workingRoot());    _ = try store.connection.catalog.createRelation(std.testing.allocator, .{        .name = "untracked",        .columns = &.{.{ .name = "value" }},    }, .{ .durability = .buffered });    try std.testing.expectError(error.InvalidHistory, store.connection.commit(&store.history));    try std.testing.expect(version.same(baseline, (try store.connection.checkout()).head));    try std.testing.expect(version.same(baseline, (try store.history.ref("main")).?.target));    try std.testing.expect(version.same(staged, (try store.connection.workingRoot())));    try std.testing.expect(!store.history.hasDatabaseRoot(staged));}test "connection fast forward missing database root is failure atomic" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "atomic-missing.db",        "atomic-missing.wal",        "atomic-missing.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'before')");    try store.connection.stage();    const baseline_head = try store.connection.commit(&store.history);    const baseline_root = (try store.connection.workingRoot());    const missing_root = version.emptyHash("sql.checkout.missing.database");    const parents = [_]version.Hash{baseline_head};    const target = version.Commit.init(missing_root, &parents);    try store.history.putCommit(target);    try std.testing.expectError(        error.DatabaseRootNotFound,        store.connection.fastForwardBranch(std.testing.allocator, &store.history, target.hash),    );    try expectAtomicConnectionBaseline(&store, baseline_head, baseline_root, null);}test "connection fast forward rejects a changed working set" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "fast-forward-dirty.db",        "fast-forward-dirty.wal",        "fast-forward-dirty.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'baseline')");    try store.connection.stage();    const baseline = try store.connection.commit(&store.history);    _ = try store.connection.createBranch(&store.history, "target");    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "target");    try executeStatement(&store.connection, "UPDATE items SET name = 'target' WHERE rowid = 1");    try store.connection.stage();    const target = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    try executeStatement(&store.connection, "UPDATE items SET name = 'dirty' WHERE rowid = 1");    const dirty = (try store.connection.workingRoot());    try std.testing.expectError(        error.WorkingSetChanged,        store.connection.fastForwardBranch(std.testing.allocator, &store.history, target),    );    try std.testing.expect(version.same(baseline, (try store.connection.checkout()).head));    try std.testing.expect(version.same(baseline, (try store.history.ref("main")).?.target));    try std.testing.expect(version.same(dirty, (try store.connection.workingRoot())));    var result = try store.connection.execute(        std.testing.allocator,        "SELECT name FROM items WHERE rowid = 1",        .{ .durability = .buffered },    );    defer result.deinit(std.testing.allocator);    const view = try row.View.init(result.nextRow().?);    try std.testing.expectEqualStrings("dirty", (try view.column(0)).text);}test "connection fast forward rejects an unsynced semantic clean state" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "fast-forward-unsynced.db",        "fast-forward-unsynced.wal",        "fast-forward-unsynced.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'baseline')");    try store.connection.stage();    const baseline = try store.connection.commit(&store.history);    _ = try store.connection.createBranch(&store.history, "target");    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "target");    try executeStatement(&store.connection, "UPDATE items SET name = 'target' WHERE rowid = 1");    try store.connection.stage();    const target = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    try executeStatement(&store.connection, "UPDATE items SET name = 'temporary' WHERE rowid = 1");    try executeStatement(&store.connection, "UPDATE items SET name = 'baseline' WHERE rowid = 1");    try std.testing.expect(version.same(baseline, (try store.connection.checkout()).head));    try std.testing.expect(!(try store.connection.checkout()).working.dirty());    try std.testing.expect(!store.database.walSynced());    try std.testing.expectError(        error.WorkingSetChanged,        store.connection.fastForwardBranch(std.testing.allocator, &store.history, target),    );    try std.testing.expect(version.same(baseline, (try store.history.ref("main")).?.target));}test "connection open repairs pending fast forward to baseline" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "recover-prepare.db",        "recover-prepare.wal",        "recover-prepare.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'baseline')");    try store.connection.stage();    const baseline = try store.connection.commit(&store.history);    _ = try store.connection.createBranch(&store.history, "target");    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "target");    try executeStatement(&store.connection, "UPDATE items SET name = 'target' WHERE rowid = 1");    try store.connection.stage();    const target = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    const target_commit = try store.history.commitValue(target);    var target_value = try store.history.databaseValue(        std.testing.allocator,        target_commit.root,    );    defer target_value.deinit();    _ = try store.history.beginFastForward("main", baseline, target);    try store.connection.materializeDatabaseValue(        std.testing.allocator,        &target_value,        .{ .durability = .synced },    );    store.connection.deinit();    store.connection = try Connection.open(        std.testing.allocator,        &store.database,        &store.history,        .{},    );    try std.testing.expect(store.history.fastForwardRecovery() == null);    try std.testing.expect(version.same(baseline, (try store.history.ref("main")).?.target));    try std.testing.expect(version.same(baseline, (try store.connection.checkout()).head));    var result = try store.connection.execute(        std.testing.allocator,        "SELECT name FROM items WHERE rowid = 1",        .{ .durability = .buffered },    );    defer result.deinit(std.testing.allocator);    const view = try row.View.init(result.nextRow().?);    try std.testing.expectEqualStrings("baseline", (try view.column(0)).text);}test "connection open repairs committed fast forward to target" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "recover-decision.db",        "recover-decision.wal",        "recover-decision.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'baseline')");    try store.connection.stage();    const baseline = try store.connection.commit(&store.history);    _ = try store.connection.createBranch(&store.history, "target");    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "target");    try executeStatement(&store.connection, "UPDATE items SET name = 'target' WHERE rowid = 1");    try store.connection.stage();    const target = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    var update = try store.history.beginFastForward("main", baseline, target);    try update.commit();    store.connection.deinit();    store.connection = try Connection.open(        std.testing.allocator,        &store.database,        &store.history,        .{},    );    try std.testing.expect(store.history.fastForwardRecovery() == null);    try std.testing.expect(version.same(target, (try store.history.ref("main")).?.target));    try std.testing.expect(version.same(target, (try store.connection.checkout()).head));    var result = try store.connection.execute(        std.testing.allocator,        "SELECT name FROM items WHERE rowid = 1",        .{ .durability = .buffered },    );    defer result.deinit(std.testing.allocator);    const view = try row.View.init(result.nextRow().?);    try std.testing.expectEqualStrings("target", (try view.column(0)).text);}test "connection checkout row root mismatch is failure atomic" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "atomic-mismatch.db",        "atomic-mismatch.wal",        "atomic-mismatch.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'before')");    try executeStatement(&store.connection, "CREATE TABLE dropme (name)");    try executeStatement(&store.connection, "INSERT INTO dropme (rowid, name) VALUES (7, 'kept')");    try executeStatement(&store.connection, "ANALYZE dropme");    try store.connection.stage();    const baseline_head = try store.connection.commit(&store.history);    const baseline_root = (try store.connection.workingRoot());    var baseline_dropme = try store.connection.relationView(std.testing.allocator, "dropme");    const baseline_stats = baseline_dropme.root.stats.hash;    baseline_dropme.deinit();    try executeStatement(&store.connection, "DROP TABLE dropme");    try executeStatement(&store.connection, "UPDATE items SET name = 'target' WHERE rowid = 1");    var target_value = try store.connection.materializedWorkingValue(std.testing.allocator);    defer target_value.deinit();    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    try executeStatement(&store.connection, "DROP TABLE dropme");    try executeStatement(&store.connection, "UPDATE items SET name = 'wrong' WHERE rowid = 1");    var wrong_value = try store.connection.materializedWorkingValue(std.testing.allocator);    defer wrong_value.deinit();    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    const target_relation = target_value.findRelation("items") orelse        return error.TestUnexpectedResult;    const wrong_relation = wrong_value.findRelation("items") orelse        return error.TestUnexpectedResult;    try store.history.putRelationRoot(target_relation.root);    try store.history.putRelationRows(target_relation.root.hash, wrong_relation.rows);    try store.history.putDatabaseRoot(target_value.root);    const parents = [_]version.Hash{baseline_head};    const target_commit = version.Commit.init(target_value.root.hash, &parents);    try store.history.putCommit(target_commit);    _ = try store.history.createBranch("bad", target_commit.hash);    try std.testing.expectError(        error.InvalidHistory,        store.connection.checkoutBranch(std.testing.allocator, &store.history, "bad"),    );    try expectAtomicConnectionBaseline(&store, baseline_head, baseline_root, baseline_stats);    try std.testing.expect(version.same(target_commit.hash, (try store.history.ref("bad")).?.target));}test "connection fast forward allocation failures are atomic" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var store = TestingConnection{};    try store.init(        std.testing.allocator,        tmp.dir,        "atomic-oom.db",        "atomic-oom.wal",        "atomic-oom.history",    );    defer store.deinit();    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'before')");    try executeStatement(&store.connection, "CREATE TABLE dropme (name)");    try executeStatement(&store.connection, "INSERT INTO dropme (rowid, name) VALUES (7, 'kept')");    try executeStatement(&store.connection, "ANALYZE dropme");    try store.connection.stage();    const baseline_head = try store.connection.commit(&store.history);    const baseline_root = (try store.connection.workingRoot());    var baseline_dropme = try store.connection.relationView(std.testing.allocator, "dropme");    const baseline_stats = baseline_dropme.root.stats.hash;    baseline_dropme.deinit();    _ = try store.connection.createBranch(&store.history, "target");    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "target");    try executeStatement(&store.connection, "DROP TABLE dropme");    try executeStatement(&store.connection, "UPDATE items SET name = 'after' WHERE rowid = 1");    try executeStatement(&store.connection, "CREATE TABLE alpha (name)");    try executeStatement(&store.connection, "INSERT INTO alpha (rowid, name) VALUES (2, 'new')");    try store.connection.stage();    const target_head = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    const warm_start = failing.alloc_index;    try store.connection.fastForwardBranch(failing.allocator(), &store.history, target_head);    const operation_allocations = failing.alloc_index - warm_start;    try std.testing.expect(operation_allocations > 0);    try store.history.putRef(.{ .name = "main", .target = baseline_head });    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    const offsets = [_]usize{ 0, operation_allocations / 2, operation_allocations - 1 };    for (offsets) |offset| {        failing.fail_index = failing.alloc_index + offset;        failing.resize_fail_index = std.math.maxInt(usize);        try std.testing.expectError(            error.OutOfMemory,            store.connection.fastForwardBranch(failing.allocator(), &store.history, target_head),        );        try std.testing.expect(failing.has_induced_failure);        failing.fail_index = std.math.maxInt(usize);        failing.resize_fail_index = std.math.maxInt(usize);        failing.has_induced_failure = false;        try expectAtomicConnectionBaseline(&store, baseline_head, baseline_root, baseline_stats);    }}const TestingConnection = struct {    database: file.Database = undefined,    history: history_mod.History = undefined,    connection: Connection = undefined,    fn init(self: *TestingConnection, allocator: Allocator, dir: std.Io.Dir, database_path: []const u8, wal_path: []const u8, history_path: []const u8) !void {        self.database = try file.Database.openForTesting(allocator, dir, .{            .paths = .{ .database = database_path, .wal = wal_path },            .header = testingHeader(),        });        errdefer self.database.deinit();        try self.database.reserve(.{ .wal_frames = 960 });        self.history = try history_mod.History.open(allocator, dir, .{ .path = history_path, .recovery = .reject });        errdefer self.history.deinit();        self.connection = try Connection.create(allocator, &self.database, &self.history, .{});    }    fn deinit(self: *TestingConnection) void {        self.connection.deinit();        self.history.deinit();        self.database.deinit();        self.* = undefined;    }};const TestingConflict = struct {    commit: version.Hash,    root: version.Hash,    artifact: version.Hash,    database: version.Hash,};fn commitTestingConflict(store: *TestingConnection) !TestingConflict {    try createItems(&store.connection);    try executeStatement(&store.connection, "INSERT INTO items (rowid, name) VALUES (1, 'base')");    try store.connection.stage();    _ = try store.connection.commit(&store.history);    _ = try store.connection.createBranch(&store.history, "side");    _ = try store.connection.createBranch(&store.history, "behind");    try executeStatement(&store.connection, "UPDATE items SET name = 'ours' WHERE rowid = 1");    try store.connection.stage();    _ = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "side");    try executeStatement(&store.connection, "UPDATE items SET name = 'theirs' WHERE rowid = 1");    try store.connection.stage();    const side_commit = try store.connection.commit(&store.history);    try store.connection.checkoutBranch(std.testing.allocator, &store.history, "main");    var merged = try store.connection.mergeBranch(std.testing.allocator, &store.history, "side", .{});    defer merged.deinit();    try std.testing.expect(merged.hasConflicts());    try std.testing.expectEqual(@as(usize, 1), merged.conflict_root.count);    const root = merged.conflict_root.hash;    const artifact = merged.discovered[0].artifact.hash;    try store.connection.stage();    const commit = try store.connection.mergeCommit(&store.history, side_commit);    return .{ .commit = commit, .root = root, .artifact = artifact, .database = (try store.connection.workingRoot()) };}fn expectTestingConflict(store: *TestingConnection, conflict: TestingConflict) !void {    try std.testing.expect(version.same(conflict.root, store.connection.session.workingRoot().conflicts));    var artifacts = try store.connection.conflictArtifacts(std.testing.allocator, &store.history);    defer artifacts.deinit();    try std.testing.expectEqual(@as(usize, 1), artifacts.artifacts.len);    try std.testing.expect(version.same(conflict.artifact, artifacts.artifacts[0].hash));}fn createItems(connection: *Connection) !void {    try executeStatement(connection, "CREATE TABLE items (name)");}fn executeStatement(connection: *Connection, source: []const u8) !void {    var result = try connection.execute(std.testing.allocator, source, .{ .durability = .buffered });    defer result.deinit(std.testing.allocator);}fn expectLiveDatabaseRoot(connection: *Connection, target: *const version.DatabaseRoot) !void {    var live = try version.databaseRoot(        std.testing.allocator,        &connection.catalog,        target.conflicts,    );    defer live.deinit();    try std.testing.expect(version.same(live.hash, target.hash));}fn expectAtomicConnectionBaseline(    store: *TestingConnection,    head: version.Hash,    root: version.Hash,    dropme_stats: ?version.Hash,) !void {    const checkout_value = (try store.connection.checkout());    try std.testing.expectEqualStrings("main", checkout_value.name);    try std.testing.expect(version.same(head, checkout_value.head));    try std.testing.expect(version.same(head, (try store.history.ref("main")).?.target));    try std.testing.expect(version.same(root, checkout_value.working.base));    try std.testing.expect(version.same(root, checkout_value.working.working));    try std.testing.expect(version.same(root, checkout_value.working.staged));    try std.testing.expect(version.same(root, (try store.connection.workingRoot())));    var item = try store.connection.execute(        std.testing.allocator,        "SELECT name FROM items WHERE rowid = 1",        .{ .durability = .buffered },    );    defer item.deinit(std.testing.allocator);    try std.testing.expectEqual(@as(usize, 1), item.rowCount());    const item_view = try row.View.init(item.nextRow().?);    try std.testing.expectEqualStrings("before", (try item_view.column(0)).text);    if (dropme_stats) |stats_hash| {        var kept = try store.connection.execute(            std.testing.allocator,            "SELECT name FROM dropme WHERE rowid = 7",            .{ .durability = .buffered },        );        defer kept.deinit(std.testing.allocator);        try std.testing.expectEqual(@as(usize, 1), kept.rowCount());        const kept_view = try row.View.init(kept.nextRow().?);        try std.testing.expectEqualStrings("kept", (try kept_view.column(0)).text);        var stats = (try store.connection.catalog.relationStats(std.testing.allocator, "dropme")).?;        defer stats.deinit();        try std.testing.expectEqual(@as(usize, 1), stats.table.entries);        var relation_view = try store.connection.relationView(std.testing.allocator, "dropme");        defer relation_view.deinit();        try std.testing.expect(version.same(stats_hash, relation_view.root.stats.hash));    }    var live_root = try version.databaseRootMaintained(        std.testing.allocator,        &store.connection.catalog,        store.connection.session.workingRoot().conflicts,    );    defer live_root.deinit();    try std.testing.expect(version.same(root, live_root.hash));}fn testEntryHash(root: *const version.DatabaseRoot, name: []const u8) version.Hash {    return testFindEntry(root, name) orelse unreachable;}fn testFindEntry(root: *const version.DatabaseRoot, name: []const u8) ?version.Hash {    for (root.entries) |entry| {        if (std.mem.eql(u8, entry.name, name)) return entry.hash;    }    return null;}fn hasConflictHash(entries: []const version.ConflictEntry, hash: version.Hash) bool {    for (entries) |entry| {        if (version.same(entry.hash, hash)) return true;    }    return false;}fn hasConflictHashValue(hashes: []const version.Hash, hash: version.Hash) bool {    for (hashes) |candidate| {        if (version.same(candidate, hash)) return true;    }    return false;}fn testingHeader() wal.Header {    return .{        .sequence = 3901,        .salt = .{ .first = 0x1357_3901, .second = 0x2468_3901 },    };}

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

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

Audit

Definitions2
Public names2
Members0
Version26.7.0
Revisiondaab053ee433