tiny.sql.History
Defined in tiny.sql.
API (109)
Actions
Public operations.
appendPackRecordPayloadappendRecordappendRelationSpansRecordbaseChunkListbeginFastForwardbeginRelationRowsbeginRelationRowsSuffixbeginWriteBatchcheckoutBranchchunkRowsIntocommitBranchcommitDatabaseRootcommitEntriescommitValueconflictconflictArtifactsconflictEntriescreateBranchdatabaseRootdatabaseRootViewdatabaseValuedeinitdeleteReffastForwardBranchfastForwardRecoveryfindCommitfindConflictfindConflictRootfindDatabaseRootfindReffindRefIndexfindRelationRootfindRowChunkfindTreeNodefinishRelationRowsflushSynchasCommithasConflicthasConflictRoothasDatabaseRoothasIndexPagehasRelationRoothasRelationRowshasRowChunkhasTreeNodeimportPackCommitimportPackRecordindexPageChunkslenmergeCommitBranchopenpackRecordPresentpoisonputCommitputCommitAndRefputConflictputConflictRootputDatabaseRootputDatabaseValueputIndexPageputRefputRefIfMatchesputRelationRootputRelationRowsputRelationRowsSuffixputRelationSpansputRowChunkrefrefListrelationKeysViewrelationRootrelationRowsrelationRowsNeedrelationRowsPagesrelationSpansrequiresRecoverytreeNodeChildrenvalidateConflictEntriesvalidateConflictRoot
Types and contracts
Public types and contracts.
Fields and members
Public fields and members.
allocatorbytes_writtencommitsconflict_rootsconflictsdatabase_rootsfast_forwardfileindex_pagesiolookupmaterialized_relation_rootsneeds_syncpending_writeread_onlyrecoveryrecovery_requiredrefsrelation_rootsrelation_rowsrelation_spansrow_chunkssidecar_dirsidecar_pathtree_nodeswrite_batch_depthwrite_io
Source
Source: lib/sql/src/history/store.zig:126
zig
pub const History = struct { pub const ByteRange = struct { pub const Count = u64; pub const unit_precision_bytes: Count = 1; pub const maximum_units_per_second: Count = 512 * 1024 * 1024; pub const maximum_seconds_per_year: Count = 366 * 24 * 60 * 60; pub const service_lifetime_years: Count = 1_000; pub const service_lifetime_seconds: Count = maximum_seconds_per_year * service_lifetime_years; pub const maximum_append_bytes: Count = @as(Count, record_mod.record_header_size) + std.math.maxInt(u32); pub const budget_bytes: Count = total( maximum_units_per_second, service_lifetime_seconds, ).?; pub fn total( units_per_second: Count, lifetime_seconds: Count, ) ?Count { const bytes_per_second = std.math.mul( Count, unit_precision_bytes, units_per_second, ) catch return null; return std.math.mul(Count, bytes_per_second, lifetime_seconds) catch null; } }; comptime { std.debug.assert( @as(u128, ByteRange.budget_bytes) + @as(u128, ByteRange.maximum_append_bytes) <= std.math.maxInt(ByteRange.Count), ); std.debug.assert(@bitSizeOf(usize) >= @bitSizeOf(ByteRange.Count)); } allocator: Allocator, io: std.Io, file: ?std.Io.File = null, read_only: bool = false, sidecar_dir: ?std.Io.Dir = null, sidecar_path: ?[]u8 = null, /// The length of the history file once pending records are written. /// The file holds every byte before the pending records and nothing /// after them, so writing them ends the file where the write stops. bytes_written: usize = 0, recovery: Recovery = .clean, needs_sync: bool = false, write_batch_depth: usize = 0, pending_write: std.ArrayList(u8) = .empty, write_io: WriteIo = .{}, fast_forward: ?FastForwardRecovery = null, recovery_required: bool = false, database_roots: std.ArrayList(record_mod.DatabaseRootRecord) = .empty, relation_roots: std.ArrayList(record_mod.RelationRootRecord) = .empty, materialized_relation_roots: std.ArrayList(record_mod.MaterializedRelationRoot) = .empty, relation_rows: std.ArrayList(record_mod.RelationRowsRecord) = .empty, relation_spans: std.ArrayList(record_mod.RelationSpansRecord) = .empty, row_chunks: std.ArrayList(record_mod.RowChunkRecord) = .empty, index_pages: std.ArrayList(record_mod.IndexPageRecord) = .empty, tree_nodes: std.ArrayList(record_mod.TreeNodeRecord) = .empty, commits: std.ArrayList(record_mod.CommitRecord) = .empty, refs: std.ArrayList(record_mod.RefRecord) = .empty, conflicts: std.ArrayList(record_mod.ConflictRecord) = .empty, conflict_roots: std.ArrayList(record_mod.ConflictRootRecord) = .empty, lookup: RecordLookup = .{}, pub const SuffixPlan = struct { reused: usize, boundary: i64, }; pub const RowsNeed = union(enum) { none, full, suffix: SuffixPlan, }; pub fn open(allocator: Allocator, dir: std.Io.Dir, options: Options) Error!History { var history = History{ .allocator = allocator, .io = options.io, .read_only = options.read_only, }; errdefer history.deinit(); const recorded_length: ?usize = if (dir.statFile(options.io, options.path, .{})) |stat| @intCast(stat.size) else |err| switch (err) { error.FileNotFound => if (options.create) null else return error.HistoryNotFound, else => return err, }; history.file = if (options.read_only) try dir.openFile(options.io, options.path, .{}) else try dir.createFile(options.io, options.path, .{ .read = true, .truncate = false }); if (!options.read_only) { if (dir.openDir(options.io, ".", .{})) |owned_dir| { history.sidecar_dir = owned_dir; history.sidecar_path = try allocator.dupe(u8, options.path); } else |_| {} } if (recorded_length) |length| { history.bytes_written = try recover_mod.replayFile( &history, length, options.replay_instrumentation, options.control, ); if (length != history.bytes_written) { if (options.read_only or options.recovery == .reject) return error.TruncatedHistory; try history.file.?.setLength(options.io, history.bytes_written); try history.file.?.sync(options.io); history.recovery = .{ .truncated = .{ .original_length = length, .valid_length = history.bytes_written, } }; } history.refreshRefsSidecar(); } return history; } pub fn deinit(self: *History) void { if (self.file) |*file| { if (!self.recovery_required) { if (self.pending_write.items.len != 0) self.flushPendingWrite() catch {}; if (self.needs_sync) file.sync(self.io) catch {}; } file.close(self.io); } if (self.sidecar_dir) |*dir| dir.close(self.io); if (self.sidecar_path) |path| self.allocator.free(path); for (self.database_roots.items) |*record| record.deinit(); for (self.materialized_relation_roots.items) |*record| record.deinit(); for (self.relation_rows.items) |*record| record.deinit(self.allocator); for (self.relation_spans.items) |*record| record.deinit(self.allocator); for (self.index_pages.items) |*record| record.deinit(self.allocator); for (self.commits.items) |*record| record.deinit(self.allocator); for (self.refs.items) |*record| record.deinit(self.allocator); for (self.conflicts.items) |*record| record.deinit(self.allocator); for (self.conflict_roots.items) |*record| record.deinit(self.allocator); self.database_roots.deinit(self.allocator); self.relation_roots.deinit(self.allocator); self.materialized_relation_roots.deinit(self.allocator); self.relation_rows.deinit(self.allocator); self.relation_spans.deinit(self.allocator); self.row_chunks.deinit(self.allocator); self.index_pages.deinit(self.allocator); self.tree_nodes.deinit(self.allocator); self.commits.deinit(self.allocator); self.refs.deinit(self.allocator); self.conflicts.deinit(self.allocator); self.conflict_roots.deinit(self.allocator); self.lookup.deinit(self.allocator); self.pending_write.deinit(self.allocator); self.* = undefined; } pub fn putDatabaseRoot(self: *History, root: version.DatabaseRoot) Error!void { try self.ensureUsable(); if (self.findDatabaseRoot(root.hash) != null) return; var owned = try root.clone(self.allocator); errdefer owned.deinit(); try self.database_roots.ensureUnusedCapacity(self.allocator, 1); try self.lookup.database_roots.ensureUnusedCapacity(self.allocator, 1); try self.appendDatabaseRootRecord(owned); self.lookup.database_roots.putAssumeCapacity(owned.hash, self.database_roots.items.len); self.database_roots.appendAssumeCapacity(.{ .root = owned }); try self.flushSync(); } pub fn putRelationRoot(self: *History, root: version.RelationRoot) Error!void { try self.ensureUsable(); if (self.hasRelationRoot(root.hash)) return; var owned = try root.clone(self.allocator); var owned_transferred = false; errdefer if (!owned_transferred) owned.deinit(); var stage = NodeStage.init(self); defer stage.deinit(); const index_keys = try self.allocator.alloc(?version.Hash, owned.indexes.len); var index_keys_owned = true; errdefer if (index_keys_owned) self.allocator.free(index_keys); const table_key = try stage.mapKey(&owned.table); for (owned.indexes, index_keys) |*index, *index_key| index_key.* = try stage.mapKey(&index.map); try stage.flush(); try self.relation_roots.ensureUnusedCapacity(self.allocator, 1); try self.materialized_relation_roots.ensureUnusedCapacity(self.allocator, 1); try self.lookup.relation_roots.ensureUnusedCapacity(self.allocator, 1); try self.appendRelationRootRecord(owned, table_key, index_keys); materialize_mod.shrinkRelationRootMaps(self.allocator, &owned); self.lookup.relation_roots.putAssumeCapacity(owned.hash, self.relation_roots.items.len); self.relation_roots.appendAssumeCapacity(.{ .hash = owned.hash, .storage = .{ .materialized = self.materialized_relation_roots.items.len }, }); self.materialized_relation_roots.appendAssumeCapacity(.{ .root = owned, .table_key = table_key, .index_keys = index_keys, }); owned_transferred = true; index_keys_owned = false; try self.flushSync(); } pub fn putRelationRows(self: *History, root: version.Hash, rows: []const version.RelationRow) Error!void { try self.ensureUsable(); if (self.hasRelationRows(root)) return; const sorted = try self.allocator.dupe(version.RelationRow, rows); defer if (sorted.len != 0) self.allocator.free(sorted); std.mem.sort(version.RelationRow, sorted, {}, version.relationRowLessThan); var writer = try self.beginRelationRows(root); defer writer.deinit(); for (sorted) |row_value| try writer.append(row_value.rowid, row_value.bytes); try writer.finish(); } pub fn beginRelationRows(self: *History, root: version.Hash) Error!RelationRowsWriter { try self.ensureMutable(); std.debug.assert(!self.hasRelationRows(root)); return .{ .history = self, .root = root, .boundary = null }; } pub fn beginRelationRowsSuffix( self: *History, root: version.Hash, base: IncrementalBase, plan: SuffixPlan, ) Error!RelationRowsWriter { try self.ensureUsable(); std.debug.assert(!self.hasRelationRows(root)); var base_pages = (try self.relationRowsPages(self.allocator, base.root)) orelse return error.InvalidHistory; defer base_pages.deinit(); var base_spans = (try self.relationSpans(self.allocator, base.root)) orelse return error.InvalidHistory; defer base_spans.deinit(); if (plan.reused == 0 or plan.reused >= base_spans.items.len) return error.InvalidHistory; if (base_spans.items[plan.reused - 1].last != plan.boundary) return error.InvalidHistory; const base_chunks = try self.baseChunkList(self.allocator, base_pages.items); defer if (base_chunks.len != 0) self.allocator.free(base_chunks); if (base_chunks.len != base_spans.items.len) return error.InvalidHistory; var writer = RelationRowsWriter{ .history = self, .root = root, .boundary = plan.boundary }; errdefer writer.deinit(); try writer.chunks.appendSlice(self.allocator, base_chunks[0..plan.reused]); try writer.spans.appendSlice(self.allocator, base_spans.items[0..plan.reused]); return writer; } pub fn relationRowsNeed(self: *const History, allocator: Allocator, root: version.Hash, base: ?IncrementalBase) Error!RowsNeed { try self.ensureUsable(); if (self.hasRelationRows(root)) return .none; const info = base orelse return .full; var base_pages = (try self.relationRowsPages(allocator, info.root)) orelse return .full; defer base_pages.deinit(); var base_spans = (try self.relationSpans(allocator, info.root)) orelse return .full; defer base_spans.deinit(); if (base_spans.items.len == 0) return .full; const base_chunks = try self.baseChunkList(allocator, base_pages.items); defer allocator.free(base_chunks); if (base_chunks.len != base_spans.items.len) return .full; const first_affected = firstAffectedSpan(base_spans.items, info.edited); if (first_affected == 0) return .full; return .{ .suffix = .{ .reused = first_affected, .boundary = base_spans.items[first_affected - 1].last, } }; } pub fn putRelationRowsSuffix(self: *History, root: version.Hash, base: IncrementalBase, plan: SuffixPlan, tail: []const version.RelationRow) Error!void { try self.ensureUsable(); if (self.hasRelationRows(root)) return; if (!relationRowsSorted(tail)) return error.InvalidHistory; var writer = try self.beginRelationRowsSuffix(root, base, plan); defer writer.deinit(); for (tail) |row_value| try writer.append(row_value.rowid, row_value.bytes); try writer.finish(); } pub fn finishRelationRows(self: *History, root: version.Hash, chunks: []const version.Hash, spans: []const record_mod.ChunkSpan) Error!void { try self.ensureUsable(); const page_bounds = try chunk_mod.pageBoundaries(self.allocator, chunks); defer if (page_bounds.len != 0) self.allocator.free(page_bounds); const pages = try self.allocator.alloc(version.Hash, page_bounds.len); errdefer if (pages.len != 0) self.allocator.free(pages); for (page_bounds, pages) |bound, *digest| { const slice = chunks[bound.start..bound.end]; digest.* = chunk_mod.pageDigest(slice); try self.putIndexPage(digest.*, slice); } try self.relation_rows.ensureUnusedCapacity(self.allocator, 1); try self.lookup.relation_rows.ensureUnusedCapacity(self.allocator, 1); try self.appendRelationRowsRecord(root, pages); self.lookup.relation_rows.putAssumeCapacity(root, self.relation_rows.items.len); self.relation_rows.appendAssumeCapacity(.{ .root = root, .storage = .{ .materialized = pages }, }); try self.putRelationSpans(root, spans); try self.flushSync(); } pub fn relationSpans(self: *const History, allocator: Allocator, hash: version.Hash) Error!?record_mod.ChunkSpanView { try self.ensureUsable(); const index = self.lookup.relation_spans.get(hash) orelse return null; const record = &self.relation_spans.items[index]; return try materialize_mod.readRelationSpansRecord(self, allocator, record.root, record.storage); } pub fn putRelationSpans(self: *History, root: version.Hash, spans: []const record_mod.ChunkSpan) Error!void { try self.ensureUsable(); if (self.lookup.relation_spans.get(root) != null) return; const owned = try self.allocator.dupe(record_mod.ChunkSpan, spans); errdefer self.allocator.free(owned); try self.relation_spans.ensureUnusedCapacity(self.allocator, 1); try self.lookup.relation_spans.ensureUnusedCapacity(self.allocator, 1); try self.appendRelationSpansRecord(root, owned); self.lookup.relation_spans.putAssumeCapacity(root, self.relation_spans.items.len); self.relation_spans.appendAssumeCapacity(.{ .root = root, .storage = .{ .materialized = owned }, }); } pub fn appendRelationSpansRecord(self: *History, root: version.Hash, spans: []const record_mod.ChunkSpan) Error!void { try self.ensureUsable(); if (spans.len > std.math.maxInt(u32)) return error.InvalidHistory; var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendHash(self.allocator, &payload, root); try record_mod.appendU32(self.allocator, &payload, @intCast(spans.len)); for (spans) |span| { try record_mod.appendU64(self.allocator, &payload, @as(u64, @bitCast(span.first))); try record_mod.appendU64(self.allocator, &payload, @as(u64, @bitCast(span.last))); } try self.appendRecord(.relation_spans, payload.items); } pub fn baseChunkList(self: *const History, allocator: Allocator, pages: []const version.Hash) Error![]version.Hash { try self.ensureUsable(); var chunks: std.ArrayList(version.Hash) = .empty; errdefer chunks.deinit(allocator); for (pages) |page_digest| { var page = (try self.indexPageChunks(allocator, page_digest)) orelse return error.InvalidHistory; defer page.deinit(); try chunks.appendSlice(allocator, page.items); } return try chunks.toOwnedSlice(allocator); } pub fn putRowChunk(self: *History, digest: version.Hash, rows: []const version.RelationRow) Error!void { try self.ensureUsable(); if (self.findRowChunk(digest) != null) return; var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendHash(self.allocator, &payload, digest); try record_mod.appendRelationRows(self.allocator, &payload, rows); try self.row_chunks.ensureUnusedCapacity(self.allocator, 1); try self.lookup.row_chunks.ensureUnusedCapacity(self.allocator, 1); const payload_offset = self.bytes_written + record_mod.record_header_size; const expected = record_mod.recordHash(@backingInt(record_mod.RecordKind.row_chunk), payload.items); try self.appendRecord(.row_chunk, payload.items); self.lookup.row_chunks.putAssumeCapacity(digest, self.row_chunks.items.len); self.row_chunks.appendAssumeCapacity(.{ .digest = digest, .payload = .{ .expected = expected, .offset = payload_offset, .len = payload.items.len, }, }); } pub fn putIndexPage(self: *History, digest: version.Hash, chunks: []const version.Hash) Error!void { try self.ensureUsable(); if (self.hasIndexPage(digest)) return; const owned = try self.allocator.dupe(version.Hash, chunks); errdefer self.allocator.free(owned); try self.index_pages.ensureUnusedCapacity(self.allocator, 1); try self.lookup.index_pages.ensureUnusedCapacity(self.allocator, 1); try self.appendChunkIndexPageRecord(digest, owned); self.lookup.index_pages.putAssumeCapacity(digest, self.index_pages.items.len); self.index_pages.appendAssumeCapacity(.{ .digest = digest, .storage = .{ .materialized = owned }, }); } pub fn putDatabaseValue(self: *History, value: *const version.DatabaseValue) Error!void { try self.ensureUsable(); var batch = try self.beginWriteBatch(); errdefer batch.deinit(); for (value.relations) |relation| { try self.putRelationRoot(relation.root); try self.putRelationRows(relation.root.hash, relation.rows); } try self.putDatabaseRoot(value.root); try batch.finish(); } pub fn putCommit(self: *History, commit: version.Commit) Error!void { try self.ensureUsable(); const canonical = version.Commit.init(commit.root, commit.parents); if (self.findCommit(canonical.hash) != null) return; const parents = try self.allocator.dupe(version.Hash, commit.parents); errdefer self.allocator.free(parents); try self.commits.ensureUnusedCapacity(self.allocator, 1); try self.lookup.commits.ensureUnusedCapacity(self.allocator, 1); try self.appendCommitRecord(canonical.root, parents); try self.flushSync(); self.lookup.commits.putAssumeCapacity(canonical.hash, self.commits.items.len); self.commits.appendAssumeCapacity(.{ .parents = parents, .commit = .{ .root = canonical.root, .parents = parents, .hash = canonical.hash, }, }); } pub fn putRef(self: *History, ref_value: version.Ref) Error!void { try self.ensureUsable(); if (self.findCommit(ref_value.target) == null) return error.CommitNotFound; if (self.findRef(ref_value.name)) |record| { try self.appendRefRecord(ref_value.name, ref_value.target); try self.flushSync(); record.ref.target = ref_value.target; self.refreshRefsSidecar(); return; } const name = try self.allocator.dupe(u8, ref_value.name); errdefer self.allocator.free(name); try self.refs.ensureUnusedCapacity(self.allocator, 1); try self.appendRefRecord(name, ref_value.target); try self.flushSync(); self.refs.appendAssumeCapacity(.{ .name = name, .ref = .{ .name = name, .target = ref_value.target, }, }); self.refreshRefsSidecar(); } pub fn putRefIfMatches(self: *History, ref_value: version.Ref, expected: ?version.Hash) Error!void { try self.ensureUsable(); if (self.findCommit(ref_value.target) == null) return error.CommitNotFound; if (self.findRef(ref_value.name)) |record| { const expected_hash = expected orelse return error.RefChanged; if (!version.same(record.ref.target, expected_hash)) return error.RefChanged; } else if (expected != null) { return error.RefChanged; } try self.putRef(ref_value); } pub fn beginFastForward( self: *History, name: []const u8, expected: version.Hash, target: version.Hash, ) Error!FastForwardUpdate { try self.ensureUsable(); if (self.fast_forward != null) return error.RecoveryRequired; if (self.pending_write.items.len != 0 or self.needs_sync or self.write_batch_depth != 0) { self.poison(); return error.RecoveryRequired; } const ref_record = self.findRef(name) orelse return error.RefNotFound; if (!version.same(ref_record.ref.target, expected)) return error.RefChanged; if (self.findCommit(expected) == null or self.findCommit(target) == null) { return error.CommitNotFound; } var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendBytes(self.allocator, &payload, name); try record_mod.appendHash(self.allocator, &payload, expected); try record_mod.appendHash(self.allocator, &payload, target); const id = record_mod.recordHash( @backingInt(record_mod.RecordKind.fast_forward_prepare), payload.items, ); try self.appendCoordinatorRecord(.fast_forward_prepare, payload.items); self.fast_forward = .{ .id = id, .name = ref_record.name, .expected = expected, .target = target, }; return .{ .history = self, .id = id }; } pub fn fastForwardRecovery(self: *const History) ?*const FastForwardRecovery { return if (self.fast_forward) |*recovery| recovery else null; } pub fn poison(self: *History) void { self.recovery_required = true; } pub fn requiresRecovery(self: *const History) bool { return self.recovery_required; } fn decideFastForward( self: *History, id: version.Hash, decision: FastForwardDecision, ) Error!void { try self.ensureUsable(); const recovery = if (self.fast_forward) |*active| active else return error.InvalidHistory; if (!version.same(recovery.id, id) or recovery.decision != .pending) { return error.InvalidHistory; } const ref_record = self.findRef(recovery.name) orelse return error.InvalidHistory; if (!version.same(ref_record.ref.target, recovery.expected)) return error.InvalidHistory; const kind: record_mod.RecordKind = switch (decision) { .pending => return error.InvalidHistory, .baseline => .fast_forward_abort, .target => .fast_forward_commit, }; try self.appendCoordinatorHashRecord(kind, id); if (decision == .target) ref_record.ref.target = recovery.target; recovery.decision = decision; } fn completeFastForward(self: *History, id: version.Hash) Error!void { try self.ensureUsable(); const recovery = if (self.fast_forward) |*active| active else return error.InvalidHistory; if (!version.same(recovery.id, id) or recovery.decision == .pending) { return error.InvalidHistory; } const ref_record = self.findRef(recovery.name) orelse return error.InvalidHistory; const selected = switch (recovery.decision) { .pending => unreachable, .baseline => recovery.expected, .target => recovery.target, }; if (!version.same(ref_record.ref.target, selected)) return error.InvalidHistory; try self.appendCoordinatorHashRecord(.fast_forward_complete, id); self.fast_forward = null; self.refreshRefsSidecar(); } fn ensureUsable(self: *const History) Error!void { if (self.recovery_required) return error.RecoveryRequired; } fn ensureMutable(self: *const History) Error!void { try self.ensureUsable(); if (self.read_only) return error.ReadOnly; if (self.fast_forward != null) return error.RecoveryRequired; } pub fn deleteRef(self: *History, name: []const u8) Error!void { try self.ensureUsable(); const index = self.findRefIndex(name) orelse return error.RefNotFound; try self.appendRefDeleteRecord(name); try self.flushSync(); var removed = self.refs.orderedRemove(index); removed.deinit(self.allocator); self.refreshRefsSidecar(); } pub fn createBranch(self: *History, name: []const u8, target: version.Hash) Error!version.Ref { try self.ensureUsable(); if (self.findRef(name) != null) return error.RefExists; if (self.findCommit(target) == null) return error.CommitNotFound; const ref_value = version.Ref{ .name = name, .target = target, }; try self.putRef(ref_value); return (try self.ref(name)).?; } pub fn checkoutBranch(self: *const History, name: []const u8) Error!branch.Checkout { try self.ensureUsable(); const record = self.findRef(name) orelse return error.RefNotFound; const commit = self.findCommit(record.ref.target) orelse return error.CommitNotFound; return branch.checkout(record.ref, commit.commit.root); } pub fn fastForwardBranch(self: *History, allocator: Allocator, name: []const u8, target: version.Hash) Error!void { try self.ensureUsable(); const record = self.findRef(name) orelse return error.RefNotFound; var ref_value = record.ref; const entries = try self.commitEntries(allocator); defer allocator.free(entries); try branch.fastForwardRef(allocator, entries, &ref_value, target); try self.putRef(ref_value); } pub fn commitBranch(self: *History, name: []const u8, root: version.Hash) Error!version.Hash { try self.ensureUsable(); const record = self.findRef(name) orelse return error.RefNotFound; if (self.findCommit(record.ref.target) == null) return error.CommitNotFound; var parents = [_]version.Hash{record.ref.target}; const commit = version.Commit.init(root, parents[0..]); try self.putCommitAndRef(commit, record, commit.hash); return commit.hash; } pub fn mergeCommitBranch(self: *History, name: []const u8, root: version.Hash, theirs: version.Hash) Error!version.Hash { try self.ensureUsable(); const record = self.findRef(name) orelse return error.RefNotFound; if (self.findCommit(record.ref.target) == null) return error.CommitNotFound; if (self.findCommit(theirs) == null) return error.CommitNotFound; var parents = [_]version.Hash{ record.ref.target, theirs }; const commit = version.Commit.init(root, parents[0..]); try self.putCommitAndRef(commit, record, commit.hash); return commit.hash; } pub fn putCommitAndRef(self: *History, commit: version.Commit, ref_record: *record_mod.RefRecord, target: version.Hash) Error!void { try self.ensureUsable(); const canonical = version.Commit.init(commit.root, commit.parents); const append_commit = self.findCommit(canonical.hash) == null; var parents: []version.Hash = &.{}; if (append_commit) parents = try self.allocator.dupe(version.Hash, canonical.parents); var parents_live = append_commit; errdefer if (parents_live) self.allocator.free(parents); if (append_commit) { try self.commits.ensureUnusedCapacity(self.allocator, 1); try self.lookup.commits.ensureUnusedCapacity(self.allocator, 1); try self.appendCommitRecord(canonical.root, parents); } try self.appendRefRecord(ref_record.name, target); try self.flushSync(); if (append_commit) { self.lookup.commits.putAssumeCapacity(canonical.hash, self.commits.items.len); self.commits.appendAssumeCapacity(.{ .parents = parents, .commit = .{ .root = canonical.root, .parents = parents, .hash = canonical.hash, }, }); parents_live = false; } ref_record.ref.target = target; self.refreshRefsSidecar(); } pub fn putConflict(self: *History, artifact: version.ConflictArtifact) Error!void { try self.ensureUsable(); const canonical = conflict_mod.canonicalConflictArtifact(artifact); if (self.findConflict(canonical.hash) != null) return; var owned = try conflict_mod.cloneConflictArtifact(self.allocator, artifact); errdefer conflict_mod.deinitConflictArtifact(self.allocator, &owned); try self.conflicts.ensureUnusedCapacity(self.allocator, 1); try self.lookup.conflicts.ensureUnusedCapacity(self.allocator, 1); try self.appendConflictRecord(canonical); try self.flushSync(); self.lookup.conflicts.putAssumeCapacity(canonical.hash, self.conflicts.items.len); self.conflicts.appendAssumeCapacity(.{ .artifact = owned }); } pub fn putConflictRoot(self: *History, entries: []const version.ConflictEntry) Error!version.ConflictRoot { try self.ensureUsable(); if (entries.len == 0) return version.ConflictRoot.empty(); const owned = try conflict_mod.cloneConflictEntriesSorted(self.allocator, entries); var owned_live = true; errdefer if (owned_live) conflict_mod.deinitConflictEntries(self.allocator, owned); try self.validateConflictEntries(owned); const root = version.ConflictRoot.init(owned); if (self.findConflictRoot(root.hash) != null) { try self.validateConflictRoot(root.hash); conflict_mod.deinitConflictEntries(self.allocator, owned); owned_live = false; return root; } try self.conflict_roots.ensureUnusedCapacity(self.allocator, 1); try self.lookup.conflict_roots.ensureUnusedCapacity(self.allocator, 1); try self.appendConflictRootRecord(owned); try self.flushSync(); self.lookup.conflict_roots.putAssumeCapacity(root.hash, self.conflict_roots.items.len); self.conflict_roots.appendAssumeCapacity(.{ .root = root, .entries = owned, }); owned_live = false; return root; } pub fn ref(self: *const History, name: []const u8) Error!?version.Ref { try self.ensureUsable(); for (self.refs.items) |record| { if (std.mem.eql(u8, record.name, name)) return record.ref; } return null; } pub fn refList(self: *const History, allocator: Allocator) Error![]version.Ref { try self.ensureUsable(); const refs = try allocator.alloc(version.Ref, self.refs.items.len); var count: usize = 0; errdefer freeRefList(allocator, refs[0..count]); for (self.refs.items, refs) |record, *target| { const name = try allocator.dupe(u8, record.name); target.* = .{ .name = name, .target = record.ref.target, }; count += 1; } return refs; } pub fn conflict(self: *const History, hash: version.Hash) ?version.ConflictArtifact { if (self.findConflict(hash)) |record| return record.artifact; return null; } pub fn hasCommit(self: *const History, hash: version.Hash) bool { return self.findCommit(hash) != null; } pub fn hasDatabaseRoot(self: *const History, hash: version.Hash) bool { return self.findDatabaseRoot(hash) != null; } pub fn hasConflict(self: *const History, hash: version.Hash) bool { return self.findConflict(hash) != null; } pub fn hasConflictRoot(self: *const History, hash: version.Hash) Error!bool { self.validateConflictRoot(hash) catch |err| switch (err) { error.ConflictRootNotFound => return false, else => return err, }; return true; } pub fn validateConflictRoot(self: *const History, hash: version.Hash) Error!void { try self.ensureUsable(); if (version.same(hash, version.ConflictRoot.empty().hash)) return; const record = self.findConflictRoot(hash) orelse return error.ConflictRootNotFound; const derived = version.ConflictRoot.init(record.entries); if (!version.same(record.root.hash, hash) or !version.same(derived.hash, hash) or derived.count != record.root.count) { return error.InvalidHistory; } try self.validateConflictEntries(record.entries); } pub fn validateConflictEntries( self: *const History, entries: []const version.ConflictEntry, ) error{ InvalidHistory, ConflictArtifactNotFound }!void { var previous: ?version.ConflictEntry = null; for (entries) |entry| { if (previous) |prior| { if (prior.sameSlot(entry) or !version.ConflictEntry.lessThan({}, prior, entry)) { return error.InvalidHistory; } } const artifact_record = self.findConflict(entry.hash) orelse { return error.ConflictArtifactNotFound; }; if (!entry.eql(artifact_record.artifact.entry())) { return error.InvalidHistory; } previous = entry; } } pub fn conflictEntries(self: *const History, allocator: Allocator, root: version.Hash) Error!conflict_mod.ConflictEntries { try self.ensureUsable(); if (version.same(root, version.ConflictRoot.empty().hash)) { return .{ .allocator = allocator, .root = version.ConflictRoot.empty(), .entries = &.{}, }; } try self.validateConflictRoot(root); const record = self.findConflictRoot(root) orelse return error.ConflictRootNotFound; const entries = try conflict_mod.cloneConflictEntries(allocator, record.entries); errdefer conflict_mod.deinitConflictEntries(allocator, entries); return .{ .allocator = allocator, .root = record.root, .entries = entries, }; } pub fn conflictArtifacts(self: *const History, allocator: Allocator, root: version.Hash) Error!conflict_mod.ConflictArtifacts { try self.ensureUsable(); var entries = try self.conflictEntries(allocator, root); defer entries.deinit(); if (entries.entries.len == 0) { return .{ .allocator = allocator, .root = entries.root, .artifacts = &.{}, }; } const artifacts = try allocator.alloc(version.ConflictArtifact, entries.entries.len); var count: usize = 0; errdefer { for (artifacts[0..count]) |*artifact| conflict_mod.deinitConflictArtifact(allocator, artifact); allocator.free(artifacts); } for (entries.entries, artifacts) |entry, *target| { const record = self.findConflict(entry.hash) orelse return error.ConflictArtifactNotFound; target.* = try conflict_mod.cloneConflictArtifact(allocator, record.artifact); count += 1; } return .{ .allocator = allocator, .root = entries.root, .artifacts = artifacts, }; } pub fn commitValue(self: *const History, hash: version.Hash) Error!version.Commit { try self.ensureUsable(); const record = self.findCommit(hash) orelse return error.CommitNotFound; return record.commit; } pub fn databaseRoot(self: *const History, allocator: Allocator, root: version.Hash) Error!version.DatabaseRoot { try self.ensureUsable(); const record = self.findDatabaseRoot(root) orelse return error.DatabaseRootNotFound; return try record.root.clone(allocator); } pub fn databaseValue(self: *const History, allocator: Allocator, root: version.Hash) Error!version.DatabaseValue { try self.ensureUsable(); var database_root = try self.databaseRoot(allocator, root); defer database_root.deinit(); const relations = try allocator.alloc(version.RelationValue, database_root.entries.len); var relation_count: usize = 0; var relations_owned = true; errdefer { if (relations_owned) { for (relations[0..relation_count]) |*relation| relation.deinit(allocator); if (relations.len != 0) allocator.free(relations); } } for (database_root.entries, relations) |entry, *relation| { var relation_root = try self.relationRoot(allocator, entry.hash); errdefer relation_root.deinit(); const rows = try self.relationRows(allocator, entry.hash); errdefer version.freeRelationRows(allocator, rows); relation.* = .{ .root = relation_root, .rows = rows, }; relation_count += 1; } var value = try version.databaseValueFromOwnedRelations(allocator, relations, database_root.conflicts); relations_owned = false; errdefer value.deinit(); if (!version.same(value.root.hash, database_root.hash)) return error.InvalidHistory; return value; } pub fn relationRoot(self: *const History, allocator: Allocator, root: version.Hash) Error!version.RelationRoot { try self.ensureUsable(); const record = self.findRelationRoot(root) orelse return error.RelationRootNotFound; var out: version.RelationRoot = undefined; var table_key: ?version.Hash = null; var index_keys: []const ?version.Hash = &.{}; var owned_index_keys: ?[]?version.Hash = null; defer if (owned_index_keys) |owned| allocator.free(owned); switch (record.storage) { .materialized => |index| { const materialized = &self.materialized_relation_roots.items[index]; out = try materialized.root.clone(allocator); table_key = materialized.table_key; index_keys = materialized.index_keys; }, .indexed => |location| { const decoded = try materialize_mod.readIndexedRelationRoot(self, allocator, record.hash, location); out = decoded.root; table_key = decoded.table_key; index_keys = decoded.index_keys; owned_index_keys = decoded.index_keys; }, } errdefer out.deinit(); const table = try materialize_mod.merkleMapRoot(self, allocator, out.table.summary, out.table.hash, out.table.subtree, table_key); var table_swapped = out.table; out.table = table; table_swapped.deinit(); for (out.indexes, index_keys) |*index, index_key| { const map = try materialize_mod.merkleMapRoot(self, allocator, index.map.summary, index.map.hash, index.map.subtree, index_key); var map_swapped = index.map; index.map = map; map_swapped.deinit(); } return out; } pub fn relationRows(self: *const History, allocator: Allocator, root: version.Hash) Error![]version.RelationRow { try self.ensureUsable(); var pages = (try self.relationRowsPages(allocator, root)) orelse return error.RelationRowsNotFound; defer pages.deinit(); var rows: std.ArrayList(version.RelationRow) = .empty; errdefer { for (rows.items) |row_value| allocator.free(row_value.bytes); rows.deinit(allocator); } for (pages.items) |page_digest| { var chunks = (try self.indexPageChunks(allocator, page_digest)) orelse return error.RelationRowsNotFound; defer chunks.deinit(); for (chunks.items) |digest| { const chunk_record = self.findRowChunk(digest) orelse return error.RelationRowsNotFound; try self.chunkRowsInto(allocator, chunk_record, &rows); } } return try rows.toOwnedSlice(allocator); } pub fn chunkRowsInto(self: *const History, allocator: Allocator, record: *const record_mod.RowChunkRecord, rows: *std.ArrayList(version.RelationRow)) Error!void { try self.ensureUsable(); const payload = try materialize_mod.readPayload(self, allocator, .row_chunk, record.payload); defer allocator.free(payload); var reader = record_mod.PayloadReader.init(payload); const digest = try reader.hash(); if (!version.same(digest, record.digest)) return error.InvalidHistory; const row_count = try reader.readU32(); try rows.ensureUnusedCapacity(allocator, row_count); var read_rows: u32 = 0; while (read_rows < row_count) : (read_rows += 1) { const rowid = try reader.readI64(); const bytes = try allocator.dupe(u8, try reader.readBytes()); rows.appendAssumeCapacity(.{ .rowid = rowid, .bytes = bytes, }); } try reader.finish(); } pub fn commitDatabaseRoot(self: *const History, allocator: Allocator, commit_hash: version.Hash) Error!version.DatabaseRoot { try self.ensureUsable(); const commit_value = try self.commitValue(commit_hash); return try self.databaseRoot(allocator, commit_value.root); } pub fn commitEntries(self: *const History, allocator: Allocator) Error![]branch.CommitEntry { try self.ensureUsable(); const entries = try allocator.alloc(branch.CommitEntry, self.commits.items.len); for (self.commits.items, entries) |record, *entry| { entry.* = branch.commitEntry(record.commit); } return entries; } pub fn len(self: *const History) usize { return self.bytes_written; } pub fn hasRelationRoot(self: *const History, hash: version.Hash) bool { return self.lookup.relation_roots.get(hash) != null; } pub fn hasRelationRows(self: *const History, root: version.Hash) bool { return self.lookup.relation_rows.get(root) != null; } pub fn hasIndexPage(self: *const History, digest: version.Hash) bool { return self.lookup.index_pages.get(digest) != null; } pub fn hasRowChunk(self: *const History, digest: version.Hash) bool { return self.findRowChunk(digest) != null; } pub fn hasTreeNode(self: *const History, key: version.Hash) bool { return self.findTreeNode(key) != null; } pub fn databaseRootView(self: *const History, hash: version.Hash) ?DatabaseRootView { const record = self.findDatabaseRoot(hash) orelse return null; return .{ .conflicts = record.root.conflicts, .entries = record.root.entries, }; } pub fn relationKeysView(self: *const History, allocator: Allocator, hash: version.Hash) Error!?RelationKeysView { try self.ensureUsable(); const record = self.findRelationRoot(hash) orelse return null; switch (record.storage) { .materialized => |index| { const materialized = &self.materialized_relation_roots.items[index]; return .{ .allocator = allocator, .table_key = materialized.table_key, .index_keys = materialized.index_keys, }; }, .indexed => |location| { var decoded = try materialize_mod.readIndexedRelationRoot(self, allocator, record.hash, location); defer decoded.root.deinit(); return .{ .allocator = allocator, .owned = decoded.index_keys, .table_key = decoded.table_key, .index_keys = decoded.index_keys, }; }, } } pub fn relationRowsPages(self: *const History, allocator: Allocator, root: version.Hash) Error!?record_mod.HashListView { try self.ensureUsable(); const index = self.lookup.relation_rows.get(root) orelse return null; const record = &self.relation_rows.items[index]; return try materialize_mod.readHashListRecord(self, allocator, .relation_rows, record.root, record.storage); } pub fn indexPageChunks(self: *const History, allocator: Allocator, digest: version.Hash) Error!?record_mod.HashListView { try self.ensureUsable(); const index = self.lookup.index_pages.get(digest) orelse return null; const record = &self.index_pages.items[index]; return try materialize_mod.readHashListRecord(self, allocator, .chunk_index_page, record.digest, record.storage); } pub fn treeNodeChildren(self: *const History, allocator: Allocator, key: version.Hash) Error![]version.Hash { try self.ensureUsable(); const index = self.lookup.tree_nodes.get(key) orelse return error.InvalidHistory; const node = try materialize_mod.readTreeNodeRecord(self, allocator, &self.tree_nodes.items[index]); allocator.free(node.node.lower); if (node.node.upper) |upper| allocator.free(upper); return node.children; } pub fn appendPackRecordPayload(self: *const History, allocator: Allocator, target: *std.ArrayList(u8), kind: record_mod.PackRecordKind, hash: version.Hash) Error!void { try self.ensureUsable(); switch (kind) { .row_chunk => try pack_mod.appendRowChunkPackPayload(self, allocator, target, hash), .chunk_index_page => try pack_mod.appendChunkIndexPagePackPayload(self, allocator, target, hash), .tree_node => try pack_mod.appendTreeNodePackPayload(self, allocator, target, hash), .relation_rows => try pack_mod.appendRelationRowsPackPayload(self, allocator, target, hash), .relation_root => try pack_mod.appendRelationRootPackPayload(self, allocator, target, hash), .database_root => try pack_mod.appendDatabaseRootPackPayload(self, allocator, target, hash), .conflict => try pack_mod.appendConflictPackPayload(self, allocator, target, hash), .conflict_root => try pack_mod.appendConflictRootPackPayload(self, allocator, target, hash), } } pub fn packRecordPresent(self: *const History, kind: record_mod.PackRecordKind, payload: []const u8) Error!bool { try self.ensureUsable(); var reader = record_mod.PayloadReader.init(payload); switch (kind) { .row_chunk => return self.findRowChunk(try reader.hash()) != null, .chunk_index_page => return self.hasIndexPage(try reader.hash()), .tree_node => { if (try reader.readU32() != 1) return error.InvalidHistory; return self.findTreeNode(try reader.hash()) != null; }, .relation_rows => return self.hasRelationRows(try reader.hash()), .relation_root => { var decoded = try materialize_mod.readRelationRootShallow(self.allocator, &reader); defer decoded.root.deinit(); defer self.allocator.free(decoded.index_keys); return self.findRelationRoot(decoded.root.hash) != null; }, .database_root => { const conflicts = try reader.hash(); const entry_count = try reader.readU32(); const entries = try self.allocator.alloc(version.RelationEntry, entry_count); defer self.allocator.free(entries); for (entries) |*entry| { const name = try reader.readBytes(); const hash = try reader.hash(); entry.* = .{ .name = name, .hash = hash, }; } var root = try version.DatabaseRoot.initSorted(self.allocator, entries, .{ .hash = conflicts }); const hash = root.hash; root.deinit(); return self.findDatabaseRoot(hash) != null; }, .conflict => { const decoded = try conflict_mod.decodeConflictArtifactPayload(&reader); return self.findConflict(decoded.hash) != null; }, .conflict_root => { const entries = try conflict_mod.readConflictEntries(self.allocator, &reader); defer conflict_mod.deinitConflictEntries(self.allocator, entries); try reader.finish(); self.validateConflictEntries(entries) catch { return error.InvalidHistory; }; const root = version.ConflictRoot.init(entries); if (self.findConflictRoot(root.hash) == null) return false; try self.validateConflictRoot(root.hash); return true; }, } } pub fn importPackRecord(self: *History, kind: record_mod.PackRecordKind, payload: []const u8) Error!bool { try self.ensureUsable(); return switch (kind) { .row_chunk => try pack_mod.importRowChunkPayload(self, payload), .chunk_index_page => try pack_mod.importChunkIndexPagePayload(self, payload), .tree_node => try pack_mod.importTreeNodePayload(self, payload), .relation_rows => try pack_mod.importRelationRowsPayload(self, payload), .relation_root => try pack_mod.importRelationRootPayload(self, payload), .database_root => try pack_mod.importDatabaseRootPayload(self, payload), .conflict => try pack_mod.importConflictPayload(self, payload), .conflict_root => try pack_mod.importConflictRootPayload(self, payload), }; } pub fn importPackCommit(self: *History, root: version.Hash, parents: []const version.Hash) Error!bool { try self.ensureUsable(); const canonical = version.Commit.init(root, parents); if (self.findCommit(canonical.hash) != null) return false; if (self.findDatabaseRoot(root) == null) return error.InvalidHistory; const owned = try self.allocator.dupe(version.Hash, parents); errdefer self.allocator.free(owned); try self.commits.ensureUnusedCapacity(self.allocator, 1); try self.lookup.commits.ensureUnusedCapacity(self.allocator, 1); try self.appendCommitRecord(canonical.root, owned); self.lookup.commits.putAssumeCapacity(canonical.hash, self.commits.items.len); self.commits.appendAssumeCapacity(.{ .parents = owned, .commit = .{ .root = canonical.root, .parents = owned, .hash = canonical.hash, }, }); return true; } fn appendDatabaseRootRecord(self: *History, root: version.DatabaseRoot) Error!void { var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendDatabaseRootValue(self.allocator, &payload, root); try self.appendRecord(.database_root, payload.items); } fn appendRelationRootRecord(self: *History, root: version.RelationRoot, table_key: ?version.Hash, index_keys: []const ?version.Hash) Error!void { var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendRelationRootMerkle(self.allocator, &payload, root, table_key, index_keys); try self.appendRecord(.relation_root, payload.items); } fn appendRelationRowsRecord(self: *History, root: version.Hash, pages: []const version.Hash) Error!void { if (pages.len > std.math.maxInt(u32)) return error.InvalidHistory; var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendHash(self.allocator, &payload, root); try record_mod.appendU32(self.allocator, &payload, @intCast(pages.len)); for (pages) |digest| try record_mod.appendHash(self.allocator, &payload, digest); try self.appendRecord(.relation_rows, payload.items); } fn appendChunkIndexPageRecord(self: *History, digest: version.Hash, chunks: []const version.Hash) Error!void { if (chunks.len > std.math.maxInt(u32)) return error.InvalidHistory; var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendHash(self.allocator, &payload, digest); try record_mod.appendU32(self.allocator, &payload, @intCast(chunks.len)); for (chunks) |chunk_digest| try record_mod.appendHash(self.allocator, &payload, chunk_digest); try self.appendRecord(.chunk_index_page, payload.items); } fn appendCommitRecord(self: *History, root: version.Hash, parents: []const version.Hash) Error!void { if (parents.len > std.math.maxInt(u32)) return error.InvalidHistory; var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendHash(self.allocator, &payload, root); try record_mod.appendU32(self.allocator, &payload, @intCast(parents.len)); for (parents) |parent| try record_mod.appendHash(self.allocator, &payload, parent); try self.appendRecord(.commit, payload.items); } fn appendRefRecord(self: *History, name: []const u8, target: version.Hash) Error!void { var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendHash(self.allocator, &payload, target); try record_mod.appendBytes(self.allocator, &payload, name); try self.appendRecord(.ref, payload.items); } fn appendRefDeleteRecord(self: *History, name: []const u8) Error!void { var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try record_mod.appendBytes(self.allocator, &payload, name); try self.appendRecord(.ref_delete, payload.items); } fn appendConflictRecord(self: *History, artifact: version.ConflictArtifact) Error!void { var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try conflict_mod.appendConflictArtifactRecordPayload(self.allocator, &payload, artifact); try self.appendRecord(.conflict, payload.items); } fn appendConflictRootRecord(self: *History, entries: []const version.ConflictEntry) Error!void { var payload: std.ArrayList(u8) = .empty; defer payload.deinit(self.allocator); try conflict_mod.appendConflictEntries(self.allocator, &payload, entries); try self.appendRecord(.conflict_root, payload.items); } pub fn appendRecord(self: *History, kind: record_mod.RecordKind, payload: []const u8) Error!void { try self.ensureMutable(); const record = try encodeRecord(self.allocator, kind, payload); defer self.allocator.free(record); const next_bytes_written = std.math.add( usize, self.bytes_written, record.len, ) catch return error.InvalidHistory; try self.pending_write.appendSlice(self.allocator, record); self.bytes_written = next_bytes_written; self.needs_sync = true; if (self.write_batch_depth == 0) { try self.flushPendingWrite(); try self.flushSync(); } } fn appendCoordinatorRecord(self: *History, kind: record_mod.RecordKind, payload: []const u8) Error!void { if (self.write_batch_depth != 0 or self.pending_write.items.len != 0 or self.needs_sync) { return error.InvalidHistory; } const record = try encodeRecord(self.allocator, kind, payload); defer self.allocator.free(record); try self.pending_write.ensureUnusedCapacity(self.allocator, record.len); try self.persistCoordinatorRecord(record); } fn appendCoordinatorHashRecord(self: *History, kind: record_mod.RecordKind, id: version.Hash) Error!void { if (self.write_batch_depth != 0 or self.pending_write.items.len != 0 or self.needs_sync) { return error.InvalidHistory; } var record: [record_mod.record_header_size + version.hash_bytes]u8 = undefined; std.mem.writeInt(u32, record[0..4], record_mod.magic, .big); std.mem.writeInt(u32, record[4..8], record_mod.format_version, .big); std.mem.writeInt(u32, record[8..12], @backingInt(kind), .big); std.mem.writeInt(u32, record[12..16], version.hash_bytes, .big); const digest = record_mod.recordHash(@backingInt(kind), id[0..]); @memcpy(record[16..record_mod.record_header_size], digest[0..]); @memcpy(record[record_mod.record_header_size..], id[0..]); try self.pending_write.ensureUnusedCapacity(self.allocator, record.len); try self.persistCoordinatorRecord(record[0..]); } fn persistCoordinatorRecord(self: *History, record: []const u8) Error!void { if (self.read_only) return error.ReadOnly; const start = self.bytes_written; self.pending_write.appendSliceAssumeCapacity(record); self.bytes_written = std.math.add(usize, start, record.len) catch { self.pending_write.clearRetainingCapacity(); return error.InvalidHistory; }; self.needs_sync = true; self.flushPendingWrite() catch |err| { self.restorePreparedAppend(start) catch { self.poison(); return error.RecoveryRequired; }; return err; }; self.flushSync() catch |err| { self.restorePreparedAppend(start) catch { self.poison(); return error.RecoveryRequired; }; return err; }; } pub fn flushSync(self: *History) Error!void { try self.ensureUsable(); if (!self.needs_sync) return; if (self.write_batch_depth != 0) return; try self.flushPendingWrite(); const file = self.file orelse return error.InvalidHistory; try file.sync(self.io); self.write_io.syncs += 1; self.needs_sync = false; } fn refreshRefsSidecar(self: *History) void { if (self.fast_forward != null or self.recovery_required) return; const dir = self.sidecar_dir orelse return; const path = self.sidecar_path orelse return; if (self.needs_sync or self.write_batch_depth != 0) return; if (self.pending_write.items.len != 0) return; if (self.refs.items.len > refs_mod.max_entries) return; const entries = self.allocator.alloc(refs_mod.Entry, self.refs.items.len) catch return; defer self.allocator.free(entries); for (self.refs.items, 0..) |record, index| { std.debug.assert(record.ref.name.len != 0); const commit = self.findCommit(record.ref.target) orelse return; const root = self.findDatabaseRoot(commit.commit.root) orelse return; entries[index] = .{ .name = record.ref.name, .head = record.ref.target, .root = commit.commit.root, .conflicts = root.root.conflicts, }; } refs_mod.store(self.allocator, self.io, dir, path, self.bytes_written, entries) catch return; } fn flushPendingWrite(self: *History) Error!void { if (self.pending_write.items.len == 0) return; const file = self.file orelse return error.InvalidHistory; const start = self.bytes_written - self.pending_write.items.len; errdefer if (file.setLength(self.io, start)) { self.write_io.resizes += 1; } else |_| {}; try file.writePositionalAll(self.io, self.pending_write.items, start); self.write_io.writes += 1; self.pending_write.clearRetainingCapacity(); } fn restorePreparedAppend(self: *History, bytes_written: usize) Error!void { const file = self.file orelse return error.InvalidHistory; try file.setLength(self.io, bytes_written); self.write_io.resizes += 1; self.pending_write.clearRetainingCapacity(); self.bytes_written = bytes_written; self.needs_sync = false; try file.sync(self.io); } pub fn beginWriteBatch(self: *History) Error!WriteBatch { try self.ensureMutable(); self.write_batch_depth += 1; return .{ .history = self }; } pub fn findCommit(self: *const History, hash: version.Hash) ?*record_mod.CommitRecord { const index = self.lookup.commits.get(hash) orelse return null; return &self.commits.items[index]; } pub fn findDatabaseRoot(self: *const History, hash: version.Hash) ?*record_mod.DatabaseRootRecord { const index = self.lookup.database_roots.get(hash) orelse return null; return &self.database_roots.items[index]; } pub fn findRelationRoot(self: *const History, hash: version.Hash) ?*record_mod.RelationRootRecord { const index = self.lookup.relation_roots.get(hash) orelse return null; return &self.relation_roots.items[index]; } pub fn findRowChunk(self: *const History, digest: version.Hash) ?*record_mod.RowChunkRecord { const index = self.lookup.row_chunks.get(digest) orelse return null; return &self.row_chunks.items[index]; } pub fn findTreeNode(self: *const History, key: version.Hash) ?*record_mod.TreeNodeRecord { const index = self.lookup.tree_nodes.get(key) orelse return null; return &self.tree_nodes.items[index]; } pub fn findRef(self: *const History, name: []const u8) ?*record_mod.RefRecord { for (self.refs.items) |*record| { if (std.mem.eql(u8, record.name, name)) return record; } return null; } pub fn findRefIndex(self: *const History, name: []const u8) ?usize { for (self.refs.items, 0..) |record, index| { if (std.mem.eql(u8, record.name, name)) return index; } return null; } pub fn findConflict(self: *const History, hash: version.Hash) ?*record_mod.ConflictRecord { const index = self.lookup.conflicts.get(hash) orelse return null; return &self.conflicts.items[index]; } pub fn findConflictRoot(self: *const History, hash: version.Hash) ?*record_mod.ConflictRootRecord { const index = self.lookup.conflict_roots.get(hash) orelse return null; return &self.conflict_roots.items[index]; }};Source: lib/sql/src/root.zig:84
zig
pub const History = history.History;Also reachable as
Complete caller list for History.appendRecord
11 direct callers.
lib.sql.src.history.store.History.appendChunkIndexPageRecord[method] — private source atlib/sql/src/history/store.zig:1306in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendCommitRecord[method] — private source atlib/sql/src/history/store.zig:1316in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendConflictRecord[method] — private source atlib/sql/src/history/store.zig:1341in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendConflictRootRecord[method] — private source atlib/sql/src/history/store.zig:1348in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendDatabaseRootRecord[method] — private source atlib/sql/src/history/store.zig:1282in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendRefDeleteRecord[method] — private source atlib/sql/src/history/store.zig:1334in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendRefRecord[method] — private source atlib/sql/src/history/store.zig:1326in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendRelationRootRecord[method] — private source atlib/sql/src/history/store.zig:1289in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendRelationRowsRecord[method] — private source atlib/sql/src/history/store.zig:1296in nearest public ownerlib.sql.src.history.storetiny.sql.History.appendRelationSpansRecord[method] atlib/sql/src/history/store.zig:455tiny.sql.History.putRowChunk[method] atlib/sql/src/history/store.zig:481
Complete call list for History.databaseValue
7 direct calls.
tiny.sql.History.databaseRoot[method] atlib/sql/src/history/store.zig:965lib.sql.src.history.store.History.ensureUsable[method] — private source atlib/sql/src/history/store.zig:681in nearest public ownerlib.sql.src.history.storetiny.sql.History.relationRoot[method] atlib/sql/src/history/store.zig:1005tiny.sql.History.relationRows[method] atlib/sql/src/history/store.zig:1042tiny.sql.version.databaseValueFromOwnedRelations[function] atlib/sql/src/version.zig:949tiny.sql.version.freeRelationRows[function] atlib/sql/src/version.zig:744tiny.sql.version.same[function] atlib/sql/src/version.zig:498
Complete caller list for History.findCommit
13 direct callers.
tiny.sql.History.beginFastForward[method] atlib/sql/src/history/store.zig:590tiny.sql.History.checkoutBranch[method] atlib/sql/src/history/store.zig:713tiny.sql.History.commitBranch[method] atlib/sql/src/history/store.zig:730tiny.sql.History.commitValue[method] atlib/sql/src/history/store.zig:959tiny.sql.History.createBranch[method] atlib/sql/src/history/store.zig:701tiny.sql.History.hasCommit[method] atlib/sql/src/history/store.zig:852tiny.sql.History.importPackCommit[method] atlib/sql/src/history/store.zig:1260tiny.sql.History.mergeCommitBranch[method] atlib/sql/src/history/store.zig:740tiny.sql.History.putCommit[method] atlib/sql/src/history/store.zig:531tiny.sql.History.putCommitAndRef[method] atlib/sql/src/history/store.zig:751tiny.sql.History.putRef[method] atlib/sql/src/history/store.zig:552tiny.sql.History.putRefIfMatches[method] atlib/sql/src/history/store.zig:578lib.sql.src.history.store.History.refreshRefsSidecar[method] — private source atlib/sql/src/history/store.zig:1442in nearest public ownerlib.sql.src.history.store
Complete caller list for History.findDatabaseRoot
7 direct callers.
tiny.sql.History.databaseRoot[method] atlib/sql/src/history/store.zig:965tiny.sql.History.databaseRootView[method] atlib/sql/src/history/store.zig:1122tiny.sql.History.hasDatabaseRoot[method] atlib/sql/src/history/store.zig:856tiny.sql.History.importPackCommit[method] atlib/sql/src/history/store.zig:1260tiny.sql.History.packRecordPresent[method] atlib/sql/src/history/store.zig:1192tiny.sql.History.putDatabaseRoot[method] atlib/sql/src/history/store.zig:288lib.sql.src.history.store.History.refreshRefsSidecar[method] — private source atlib/sql/src/history/store.zig:1442in nearest public ownerlib.sql.src.history.store
Complete caller list for History.findRef
10 direct callers.
tiny.sql.History.beginFastForward[method] atlib/sql/src/history/store.zig:590tiny.sql.History.checkoutBranch[method] atlib/sql/src/history/store.zig:713tiny.sql.History.commitBranch[method] atlib/sql/src/history/store.zig:730lib.sql.src.history.store.History.completeFastForward[method] — private source atlib/sql/src/history/store.zig:663in nearest public ownerlib.sql.src.history.storetiny.sql.History.createBranch[method] atlib/sql/src/history/store.zig:701lib.sql.src.history.store.History.decideFastForward[method] — private source atlib/sql/src/history/store.zig:641in nearest public ownerlib.sql.src.history.storetiny.sql.History.fastForwardBranch[method] atlib/sql/src/history/store.zig:720tiny.sql.History.mergeCommitBranch[method] atlib/sql/src/history/store.zig:740tiny.sql.History.putRef[method] atlib/sql/src/history/store.zig:552tiny.sql.History.putRefIfMatches[method] atlib/sql/src/history/store.zig:578
Complete caller list for History.flushSync
12 direct callers.
tiny.sql.History.appendRecord[method] atlib/sql/src/history/store.zig:1355tiny.sql.History.deleteRef[method] atlib/sql/src/history/store.zig:691tiny.sql.History.finishRelationRows[method] atlib/sql/src/history/store.zig:410lib.sql.src.history.store.History.persistCoordinatorRecord[method] — private source atlib/sql/src/history/store.zig:1406in nearest public ownerlib.sql.src.history.storetiny.sql.History.putCommit[method] atlib/sql/src/history/store.zig:531tiny.sql.History.putCommitAndRef[method] atlib/sql/src/history/store.zig:751tiny.sql.History.putConflict[method] atlib/sql/src/history/store.zig:782tiny.sql.History.putConflictRoot[method] atlib/sql/src/history/store.zig:796tiny.sql.History.putDatabaseRoot[method] atlib/sql/src/history/store.zig:288tiny.sql.History.putRef[method] atlib/sql/src/history/store.zig:552tiny.sql.History.putRelationRoot[method] atlib/sql/src/history/store.zig:301tiny.sql.history.WriteBatch.finish[method] atlib/sql/src/history/store.zig:1631
Complete caller list for History.open
41 direct callers.
lib.sql.src.history.store.test_fast_forward_coordinator_fences_history_until_completion[function] — test source atlib/sql/src/history/store.zig:2683in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_fast_forward_poisons_an_ambiguous_preexisting_write_batch[function] — test source atlib/sql/src/history/store.zig:2733in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_appends_after_a_truncating_recovery_end_the_file_where_they_stop[function] — test source atlib/sql/src/history/store.zig:3851in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_creates_checkouts_and_advances_branches_durably[function] — test source atlib/sql/src/history/store.zig:3480in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_deletes_refs_durably[function] — test source atlib/sql/src/history/store.zig:3631in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_merkle_relation_roots_preserve_diff_behavior_across_reopen[function] — test source atlib/sql/src/history/store.zig:2127in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_open_rejects_an_unreadable_first_record[function] — test source atlib/sql/src/history/store.zig:3789in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_open_rejects_prior_format_records[function] — test source atlib/sql/src/history/store.zig:3802in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_open_without_create_requires_an_existing_file[function] — test source atlib/sql/src/history/store.zig:2389in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_pack_rejects_noncanonical_conflict_roots_before_append[function] — test source atlib/sql/src/history/store.zig:2570in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_preserves_semantically_invalid_coordinator_evidence[function] — test source atlib/sql/src/history/store.zig:2932in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_reads_indexed_row_topology_for_incremental_access[function] — test source atlib/sql/src/history/store.zig:3205in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_rechecks_indexed_relation_roots_during_access[function] — test source atlib/sql/src/history/store.zig:3142in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_rechecks_indexed_row_topology_during_access[function] — test source atlib/sql/src/history/store.zig:3421in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovers_branch_commits_and_merge_commits[function] — test source atlib/sql/src/history/store.zig:3542in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovers_commits_refs_and_conflicts[function] — test source atlib/sql/src/history/store.zig:2404in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovers_database_roots_by_commit[function] — test source atlib/sql/src/history/store.zig:3012in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovers_relation_roots_by_hash[function] — test source atlib/sql/src/history/store.zig:3044in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovers_relation_rows_by_root_hash[function] — test source atlib/sql/src/history/store.zig:3175in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovery_truncates_corrupt_tail[function] — test source atlib/sql/src/history/store.zig:3821in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_recovery_truncates_refs_with_missing_commits[function] — test source atlib/sql/src/history/store.zig:3604in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_refs_recover_the_latest_target[function] — test source atlib/sql/src/history/store.zig:3582in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_rejects_duplicate_conflict_slots_before_persistence[function] — test source atlib/sql/src/history/store.zig:2530in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_rejects_relation_roots_referencing_missing_tree_nodes[function] — test source atlib/sql/src/history/store.zig:2319in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_relation_root_duplicates_keep_the_first_valid_record[function] — test source atlib/sql/src/history/store.zig:3106in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_reopens_logs_larger_than_sixteen_mebibytes[function] — test source atlib/sql/src/history/store.zig:3451in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_replay_truncates_noncanonical_conflict_roots[function] — test source atlib/sql/src/history/store.zig:2604in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_replays_committed_fast_forward_until_completion[function] — test source atlib/sql/src/history/store.zig:2825in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_replays_pending_fast_forward_through_abort_completion[function] — test source atlib/sql/src/history/store.zig:2773in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_row_topology_duplicates_keep_the_first_records[function] — test source atlib/sql/src/history/store.zig:3318in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_shares_index_pages_across_large_relation_versions[function] — test source atlib/sql/src/history/store.zig:3680in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_shares_row_chunks_across_relation_versions[function] — test source atlib/sql/src/history/store.zig:3711in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_shares_tree_nodes_across_relation_root_versions[function] — test source atlib/sql/src/history/store.zig:2216in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_truncates_a_torn_fast_forward_decision_to_pending_prepare[function] — test source atlib/sql/src/history/store.zig:2872in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_updates_refs_only_when_expected_target_matches[function] — test source atlib/sql/src/history/store.zig:3516in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_validates_exact_conflict_root_closure[function] — test source atlib/sql/src/history/store.zig:2483in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_validates_indexed_row_topology_during_open[function] — test source atlib/sql/src/history/store.zig:3391in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_verifies_row_chunk_payloads_during_open[function] — test source atlib/sql/src/history/store.zig:3747in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_verifies_tree_node_batches_during_open[function] — test source atlib/sql/src/history/store.zig:2269in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_write_batch_emits_one_write_and_one_sync[function] — test source atlib/sql/src/history/store.zig:2649in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.test_history_write_batch_preserves_completed_records_after_allocation_failure[function] — test source atlib/sql/src/history/store.zig:2985in nearest public ownerlib.sql.src.history.store
Complete call list for History.packRecordPresent
13 direct calls.
lib.sql.src.history.store.History.ensureUsable[method] — private source atlib/sql/src/history/store.zig:681in nearest public ownerlib.sql.src.history.storetiny.sql.History.findConflict[method] atlib/sql/src/history/store.zig:1532tiny.sql.History.findConflictRoot[method] atlib/sql/src/history/store.zig:1537tiny.sql.History.findDatabaseRoot[method] atlib/sql/src/history/store.zig:1498tiny.sql.History.findRelationRoot[method] atlib/sql/src/history/store.zig:1503tiny.sql.History.findRowChunk[method] atlib/sql/src/history/store.zig:1508tiny.sql.History.findTreeNode[method] atlib/sql/src/history/store.zig:1513tiny.sql.History.hasIndexPage[method] atlib/sql/src/history/store.zig:1110tiny.sql.History.hasRelationRows[method] atlib/sql/src/history/store.zig:1106tiny.sql.History.validateConflictEntries[method] atlib/sql/src/history/store.zig:886tiny.sql.History.validateConflictRoot[method] atlib/sql/src/history/store.zig:872tiny.sql.ConflictRoot.init[function] atlib/sql/src/version.zig:231tiny.sql.DatabaseRoot.initSorted[function] atlib/sql/src/version.zig:338
Complete call list for History.putCommitAndRef
7 direct calls.
lib.sql.src.history.store.History.appendCommitRecord[method] — private source atlib/sql/src/history/store.zig:1316in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.appendRefRecord[method] — private source atlib/sql/src/history/store.zig:1326in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.ensureUsable[method] — private source atlib/sql/src/history/store.zig:681in nearest public ownerlib.sql.src.history.storetiny.sql.History.findCommit[method] atlib/sql/src/history/store.zig:1493tiny.sql.History.flushSync[method] atlib/sql/src/history/store.zig:1431lib.sql.src.history.store.History.refreshRefsSidecar[method] — private source atlib/sql/src/history/store.zig:1442in nearest public ownerlib.sql.src.history.storetiny.sql.Commit.init[function] atlib/sql/src/version.zig:432
Complete call list for History.putConflictRoot
8 direct calls.
lib.sql.src.history.store.History.appendConflictRootRecord[method] — private source atlib/sql/src/history/store.zig:1348in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.ensureUsable[method] — private source atlib/sql/src/history/store.zig:681in nearest public ownerlib.sql.src.history.storetiny.sql.History.findConflictRoot[method] atlib/sql/src/history/store.zig:1537tiny.sql.History.flushSync[method] atlib/sql/src/history/store.zig:1431tiny.sql.History.validateConflictEntries[method] atlib/sql/src/history/store.zig:886tiny.sql.History.validateConflictRoot[method] atlib/sql/src/history/store.zig:872tiny.sql.ConflictRoot.empty[function] atlib/sql/src/version.zig:225tiny.sql.ConflictRoot.init[function] atlib/sql/src/version.zig:231
Complete call list for History.putRelationRoot
8 direct calls.
lib.sql.src.history.store.History.appendRelationRootRecord[method] — private source atlib/sql/src/history/store.zig:1289in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.History.ensureUsable[method] — private source atlib/sql/src/history/store.zig:681in nearest public ownerlib.sql.src.history.storetiny.sql.History.flushSync[method] atlib/sql/src/history/store.zig:1431tiny.sql.History.hasRelationRoot[method] atlib/sql/src/history/store.zig:1102lib.sql.src.history.store.NodeStage.deinit[method] — private source atlib/sql/src/history/store.zig:1692in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.NodeStage.flush[method] — private source atlib/sql/src/history/store.zig:1738in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.NodeStage.init[function] — private source atlib/sql/src/history/store.zig:1688in nearest public ownerlib.sql.src.history.storelib.sql.src.history.store.NodeStage.mapKey[method] — private source atlib/sql/src/history/store.zig:1700in nearest public ownerlib.sql.src.history.store
Audit
| Definitions | 82 |
|---|---|
| Public names | 164 |
| Members | 32 |
| Version | 26.7.0 |
| Revision | daab053ee433 |