tiny.smg.storage.graph
Defined in storage.
API (23)
Actions
Public operations.
LoadPlan.Capacity.deriveLoadPlan.activateLoadPlan.deinitLoadPlan.initPublishTiming.atomicNanosecondsReplacement.deinitloadloadFromReaderloadNodesloadSnapshotpublishpublishMatchedTimedpublishTimedreplacereplaceObserved
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: tools/smg/src/storage/graph.zig
zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const sql = @import("sql");const sys = @import("sys");const smg = @import("../root.zig");const storage = smg.storage;const database = storage.database;const database_read = database.read;const search = smg.search;const store = storage.store;const sync = storage.sync;const rows = storage.rows;const graph_mod = smg.graph;const model = smg.model;pub const Snapshot = struct { graph: graph_mod.Graph, head: sql.Hash,};pub const PublishTiming = struct { head: sql.Hash, begin_ns: u64, graph_ns: u64, summary_ns: u64, index_ns: u64, index_kind: search.SyncKind, node_puts: usize, node_deletes: usize, edge_puts: usize, edge_deletes: usize, finish_ns: u64, pub fn atomicNanoseconds(self: PublishTiming) u64 { return self.begin_ns +| self.finish_ns; }};pub const Replacement = struct { head: sql.Hash, changes: sync.ChangeSet, pub fn deinit(self: *Replacement, allocator: std.mem.Allocator) void { self.changes.deinit(allocator); self.* = undefined; }};const PublishedChanges = struct { node_puts: []const rows.NodePut = &.{}, node_deletes: []const i64 = &.{}, edge_puts: usize = 0, edge_deletes: usize = 0,};const PublishState = struct { previous_head: sql.Hash, head: sql.Hash, changes: PublishedChanges = .{}, begin_ns: u64, graph_ns: u64,};pub const LoadPlan = struct { pub const Limits = struct { nodes: usize, edges: usize, }; pub const Capacity = struct { node_rows: usize, edge_rows: usize, incoming: usize, outgoing: usize, bytes: usize, pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity { return .{ .node_rows = limits.nodes, .edge_rows = limits.edges, .incoming = limits.nodes, .outgoing = limits.nodes, .bytes = try loadPlanBytes(limits), }; } }; pub const InitError = std.mem.Allocator.Error || error{CapacityOverflow}; pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "smg.graph_load_plan", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "decoded_node_and_edge_row_records_admitted_from_catalog_counts", .lifetime = .initialization, .detail = "decoded node and edge row records admitted from catalog counts", }, .{ .id = "incoming_and_outgoing_degree_counters_admitted_from_node_count", .lifetime = .initialization, .detail = "incoming and outgoing degree counters admitted from node count", }, }, .excluded = &.{ "decoded model strings and metadata transferred into the retained graph", "graph arrays, hash indexes, adjacency entries, edge keys, and suffix index", "SQL catalog, relation scan, pager, and operating-system read storage", }, }, .capacity = .{ .inputs = &.{ alloc_phase.capacity.bindInput(Limits, "nodes", "nodes"), alloc_phase.capacity.bindInput(Limits, "edges", "edges"), }, .type_selectors = &.{ alloc_phase.capacity.bindType(rows.NodeRow, "node_row"), alloc_phase.capacity.bindType(usize, "usize"), alloc_phase.capacity.bindType(rows.EdgeRow, "edge_row"), }, .nodes = &.{ .{ .input = 0 }, .{ .constant = 1 }, .{ .scale = .{ .node = 1, .coefficient = .{ .size_of_concrete_type = 0 } } }, .{ .scale = .{ .node = 1, .coefficient = .{ .size_of_concrete_type = 1 } } }, .{ .scale = .{ .node = 3, .coefficient = .{ .literal = 2 } } }, .{ .add = .{ .left = 2, .right = 4 } }, .{ .product = .{ .left = 0, .right = 5 } }, .{ .input = 1 }, .{ .scale = .{ .node = 7, .coefficient = .{ .size_of_concrete_type = 2 } } }, .{ .add = .{ .left = 6, .right = 8 } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .exact, .expression = 9, }}, }, .overload = .{ .kind = .reject_before_seal, .detail = "checked byte arithmetic and exact acquisitions reject overflow or OOM before row decoding begins", }, .risks = .{ .transitive = .{ .status = .open, .detail = "SQL scans and model decoding use excluded allocators and lack a transitive allocation-closure certificate", }, .foreign = .{ .status = .excluded, .detail = "database and filesystem reads are outside the caller-owned load-plan slices", }, }, .obligations = &.{ .{ .key = "smg_graph_load_plan_capacity_capacity_model", .role = .capacity_model }, .{ .key = "smg_graph_load_plan_capacity_overload", .role = .overload }, .{ .key = "smg_graph_load_plan_oom", .role = .overload }, .{ .key = "smg_graph_load_plan_sealed", .role = .custom }, .{ .key = "smg_graph_load_plan_integration_transitive_risk", .role = .transitive_risk }, .{ .key = "smg_graph_load_plan_integration_foreign_risk", .role = .foreign_risk }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, }, }; phase: alloc_phase.capacity.Phase, capacity: Capacity, node_rows: []rows.NodeRow, edge_rows: []rows.EdgeRow, incoming: []usize, outgoing: []usize, pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!LoadPlan { const capacity = try Capacity.derive(limits); const node_rows = try allocSlice(rows.NodeRow, allocator, capacity.node_rows); errdefer freeSlice(rows.NodeRow, allocator, node_rows); const edge_rows = try allocSlice(rows.EdgeRow, allocator, capacity.edge_rows); errdefer freeSlice(rows.EdgeRow, allocator, edge_rows); const incoming = try allocSlice(usize, allocator, capacity.incoming); errdefer freeSlice(usize, allocator, incoming); const outgoing = try allocSlice(usize, allocator, capacity.outgoing); return .{ .phase = .initialization, .capacity = capacity, .node_rows = node_rows, .edge_rows = edge_rows, .incoming = incoming, .outgoing = outgoing, }; } pub fn activate(self: *LoadPlan) void { std.debug.assert(self.phase == .initialization); @memset(self.incoming, 0); @memset(self.outgoing, 0); self.phase = .steady; } pub fn deinit(self: *LoadPlan, allocator: std.mem.Allocator) void { std.debug.assert(self.phase != .teardown); self.phase = .teardown; freeSlice(rows.NodeRow, allocator, self.node_rows); freeSlice(rows.EdgeRow, allocator, self.edge_rows); freeSlice(usize, allocator, self.incoming); freeSlice(usize, allocator, self.outgoing); self.* = undefined; }};comptime { alloc_phase.capacity.requireAllocatorExactOwnerShape(LoadPlan);}pub fn load( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits,) !graph_mod.Graph { return (try loadSnapshot(allocator, scratch, root, limits)).graph;}pub fn loadSnapshot( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits,) !Snapshot { var opened = try store.openRead(scratch, root, limits); defer opened.close(); return .{ .graph = try loadRecordsFromReader(allocator, scratch, &opened.reader, true), .head = opened.reader.head, };}pub fn loadFromReader(allocator: std.mem.Allocator, scratch: std.mem.Allocator, reader: *database.Reader) !graph_mod.Graph { return try loadRecordsFromReader(allocator, scratch, reader, true);}pub fn loadNodes( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits,) !graph_mod.Graph { return try loadRecords(allocator, scratch, root, limits, false);}pub fn replace(allocator: std.mem.Allocator, opened: *database.Database, graph: graph_mod.Graph) !sql.Hash { var replaced = try replaceObserved(allocator, opened, graph); defer replaced.deinit(allocator); return replaced.head;}pub fn replaceObserved( allocator: std.mem.Allocator, opened: *database.Database, graph: graph_mod.Graph,) !Replacement { var changes = try sync.replaceGraphObserved(allocator, opened, graph); errdefer changes.deinit(allocator); return .{ .head = (try opened.connection.checkout()).head, .changes = changes, };}pub fn publish( allocator: std.mem.Allocator, root: []const u8, graph: graph_mod.Graph, expected_head: sql.Hash, limits: smg.Limits,) !void { _ = try publishTimed(allocator, root, graph, expected_head, limits);}pub fn publishTimed( allocator: std.mem.Allocator, root: []const u8, graph: graph_mod.Graph, expected_head: sql.Hash, limits: smg.Limits,) !PublishTiming { var started = sys.time.nanoTimestamp(); var publisher = try store.beginPublish(allocator, root, expected_head, limits.storage); defer publisher.close(); const begin_ns = elapsedNanoseconds(&started); var replaced = try replaceObserved(allocator, &publisher.store.database, graph); defer replaced.deinit(allocator); const graph_ns = elapsedNanoseconds(&started); return try finishPublishTimed( allocator, root, graph, &publisher, .{ .previous_head = expected_head, .head = replaced.head, .changes = .{ .node_puts = replaced.changes.node_puts, .node_deletes = replaced.changes.node_deletes, .edge_puts = replaced.changes.edge_puts, .edge_deletes = replaced.changes.edge_deletes, }, .begin_ns = begin_ns, .graph_ns = graph_ns, }, limits, &started, );}pub fn publishMatchedTimed( allocator: std.mem.Allocator, root: []const u8, graph: graph_mod.Graph, expected_head: sql.Hash, limits: smg.Limits,) !PublishTiming { var started = sys.time.nanoTimestamp(); var publisher = try store.beginPublish(allocator, root, expected_head, limits.storage); defer publisher.close(); const begin_ns = elapsedNanoseconds(&started); return try finishPublishTimed( allocator, root, graph, &publisher, .{ .previous_head = expected_head, .head = expected_head, .begin_ns = begin_ns, .graph_ns = 0, }, limits, &started, );}fn finishPublishTimed( allocator: std.mem.Allocator, root: []const u8, graph: graph_mod.Graph, publisher: *store.Publisher, state: PublishState, limits: smg.Limits, started: *i128,) !PublishTiming { try storage.summary.writeGraphForHead( allocator, root, graph, state.head, ); const summary_ns = elapsedNanoseconds(started); const index_kind = try search.syncStoredFromDatabase( allocator, root, &publisher.store.database, state.previous_head, state.head, state.changes.node_puts, state.changes.node_deletes, limits.search, ); const index_ns = elapsedNanoseconds(started); try publisher.finish(); return .{ .head = state.head, .begin_ns = state.begin_ns, .graph_ns = state.graph_ns, .summary_ns = summary_ns, .index_ns = index_ns, .index_kind = index_kind, .node_puts = state.changes.node_puts.len, .node_deletes = state.changes.node_deletes.len, .edge_puts = state.changes.edge_puts, .edge_deletes = state.changes.edge_deletes, .finish_ns = elapsedNanoseconds(started), };}fn elapsedNanoseconds(started: *i128) u64 { const finished = sys.time.nanoTimestamp(); std.debug.assert(finished >= started.*); const elapsed: u64 = @intCast(finished - started.*); started.* = finished; return elapsed;}fn loadRecords( allocator: std.mem.Allocator, scratch: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits, include_edges: bool,) !graph_mod.Graph { var opened = try store.openRead(scratch, root, limits); defer opened.close(); return try loadRecordsFromReader(allocator, scratch, &opened.reader, include_edges);}fn loadRecordsFromReader(allocator: std.mem.Allocator, scratch: std.mem.Allocator, reader: *database.Reader, include_edges: bool) !graph_mod.Graph { const counts = try database_read.readerGraphCounts(reader); var plan = try LoadPlan.init(scratch, .{ .nodes = counts.nodes, .edges = if (include_edges) counts.edges else 0, }); defer plan.deinit(scratch); plan.activate(); var graph = graph_mod.init(allocator); errdefer graph_mod.deinit(&graph); try graph_mod.reserveNodes(&graph, counts.nodes); try database_read.readerNodesInto(reader, allocator, plan.node_rows); std.mem.sort(rows.NodeRow, plan.node_rows, {}, nodeRowLess); for (plan.node_rows) |loaded| try graph_mod.addNode(&graph, loaded.node); if (!include_edges) return graph; try graph_mod.reserveEdges(&graph, counts.edges); try database_read.readerEdgesInto(reader, allocator, plan.edge_rows); sortEdgeRows(plan.edge_rows); for (plan.edge_rows) |loaded| { const source = graph.node_index.get(loaded.edge.source) orelse continue; const target = graph.node_index.get(loaded.edge.target) orelse continue; plan.outgoing[source] = std.math.add(usize, plan.outgoing[source], 1) catch return graph_mod.Error.CapacityOverflow; plan.incoming[target] = std.math.add(usize, plan.incoming[target], 1) catch return graph_mod.Error.CapacityOverflow; } try graph_mod.reserveAdjacency(&graph, plan.incoming, plan.outgoing); for (plan.edge_rows) |loaded| { var edge = loaded.edge; graph_mod.addEdge(&graph, edge) catch |err| { model.deinitEdge(&edge, allocator); if (err != graph_mod.Error.NodeNotFound) return err; }; } return graph;}fn loadPlanBytes(limits: LoadPlan.Limits) error{CapacityOverflow}!usize { var total: usize = 0; total = try addLoadBytes(total, limits.nodes, @sizeOf(rows.NodeRow)); total = try addLoadBytes(total, limits.edges, @sizeOf(rows.EdgeRow)); total = try addLoadBytes(total, limits.nodes, @sizeOf(usize)); return try addLoadBytes(total, limits.nodes, @sizeOf(usize));}fn addLoadBytes(total: usize, count: usize, size: usize) error{CapacityOverflow}!usize { const bytes = std.math.mul(usize, count, size) catch return error.CapacityOverflow; return std.math.add(usize, total, bytes) catch error.CapacityOverflow;}fn allocSlice(comptime T: type, allocator: std.mem.Allocator, count: usize) std.mem.Allocator.Error![]T { if (count == 0) return @constCast((&[_]T{})[0..]); return try allocator.alloc(T, count);}fn freeSlice(comptime T: type, allocator: std.mem.Allocator, values: []T) void { if (values.len != 0) allocator.free(values);}fn sortEdgeRows(edge_rows: []rows.EdgeRow) void { std.mem.sort(rows.EdgeRow, edge_rows, {}, edgeRowLess);}fn nodeRowLess(_: void, left: rows.NodeRow, right: rows.NodeRow) bool { return std.mem.lessThan(u8, left.node.name, right.node.name);}fn edgeRowLess(_: void, left: rows.EdgeRow, right: rows.EdgeRow) bool { const source = std.mem.order(u8, left.edge.source, right.edge.source); if (source != .eq) return source == .lt; const rel = std.mem.order(u8, left.edge.rel, right.edge.rel); if (rel != .eq) return rel == .lt; return std.mem.lessThan(u8, left.edge.target, right.edge.target);}test "graph save load and node-only load" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 0); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); var stored = graph_mod.init(allocator); const meta = try model.sourcePair(allocator, "manual"); try graph_mod.addNode(&stored, .{ .name = "a", .type = model.NodeType.module, .metadata = meta }); try graph_mod.addNode(&stored, .{ .name = "a.f", .type = model.NodeType.function }); try graph_mod.addEdge(&stored, .{ .source = "a", .target = "a.f", .rel = model.RelType.contains }); _ = try replaceForTest(allocator, stored, root); const loaded = try load(allocator, allocator, root, smg.default_limits.storage); try std.testing.expect(graph_mod.getNode(&loaded, "a") != null); try std.testing.expectEqual(@as(usize, 1), loaded.edges.items.len); const loaded_nodes = try loadNodes(allocator, allocator, root, smg.default_limits.storage); try std.testing.expectEqual(@as(usize, 2), loaded_nodes.nodes.items.len); try std.testing.expectEqual(@as(usize, 0), loaded_nodes.edges.items.len); try std.testing.expect(graph_mod.getNode(&loaded_nodes, "a.f") != null);}test "save graph orders nodes and preserves json metadata" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 1); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); var stored = graph_mod.init(allocator); const async_meta = try allocator.alloc(model.Pair, 1); async_meta[0] = .{ .key = "async", .value = "true", .json = true }; try graph_mod.addNode(&stored, .{ .name = "z", .type = model.NodeType.module }); try graph_mod.addNode(&stored, .{ .name = "a", .type = model.NodeType.function, .metadata = async_meta }); try graph_mod.addEdge(&stored, .{ .source = "z", .target = "a", .rel = model.RelType.imports }); _ = try replaceForTest(allocator, stored, root); const loaded = try load(allocator, allocator, root, smg.default_limits.storage); try std.testing.expectEqual(@as(usize, 2), loaded.nodes.items.len); try std.testing.expectEqualStrings("a", loaded.nodes.items[0].name); try std.testing.expectEqualStrings("z", loaded.nodes.items[1].name); try std.testing.expectEqual(@as(usize, 1), loaded.edges.items.len); const node = graph_mod.getNode(&loaded, "a").?; try std.testing.expectEqualStrings("true", model.pairValue(node.metadata, "async").?); try std.testing.expect(node.metadata[0].json);}test "unchanged save adds no commit and changed save advances head" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 2); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); var stored = graph_mod.init(allocator); try graph_mod.addNode(&stored, .{ .name = "a", .type = model.NodeType.module }); try graph_mod.addNode(&stored, .{ .name = "a.f", .type = model.NodeType.function, .line = 3 }); try graph_mod.addEdge(&stored, .{ .source = "a", .target = "a.f", .rel = model.RelType.contains }); _ = try replaceForTest(allocator, stored, root); const first = try testHeadHash(allocator, root); var rescanned = graph_mod.init(allocator); try graph_mod.addNode(&rescanned, .{ .name = "a", .type = model.NodeType.module }); try graph_mod.addNode(&rescanned, .{ .name = "a.f", .type = model.NodeType.function, .line = 3 }); try graph_mod.addEdge(&rescanned, .{ .source = "a", .target = "a.f", .rel = model.RelType.contains }); _ = try replaceForTest(allocator, rescanned, root); const second = try testHeadHash(allocator, root); try std.testing.expect(std.mem.eql(u8, first[0..], second[0..])); var changed = graph_mod.init(allocator); try graph_mod.addNode(&changed, .{ .name = "a", .type = model.NodeType.module }); try graph_mod.addNode(&changed, .{ .name = "a.f", .type = model.NodeType.function, .line = 9 }); try graph_mod.addEdge(&changed, .{ .source = "a", .target = "a.f", .rel = model.RelType.contains }); _ = try replaceForTest(allocator, changed, root); const third = try testHeadHash(allocator, root); try std.testing.expect(!std.mem.eql(u8, first[0..], third[0..])); const loaded = try load(allocator, allocator, root, smg.default_limits.storage); const node = graph_mod.getNode(&loaded, "a.f").?; try std.testing.expectEqual(@as(?i64, 9), node.line);}test "matched publication preserves the graph and repairs summary and names" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 5); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); const source = try loadSnapshot(allocator, allocator, root, smg.default_limits.storage); var graph = graph_mod.init(allocator); try graph_mod.addNode(&graph, .{ .name = "matched", .type = model.NodeType.module }); const published = try publishTimed(allocator, root, graph, source.head, smg.default_limits); const empty = graph_mod.init(allocator); try storage.summary.writeGraphForHead(allocator, root, empty, published.head); try std.testing.expectEqual( @as(usize, 0), (try storage.summary.load(allocator, root, smg.default_limits.storage)).nodes, ); const summary_repair = try publishMatchedTimed( allocator, root, graph, published.head, smg.default_limits, ); try expectMatchedPublish(published.head, summary_repair, .matched); try std.testing.expectEqual( @as(usize, 1), (try storage.summary.load(allocator, root, smg.default_limits.storage)).nodes, ); const wrong_head: sql.Hash = @splat(0x7f); { var opened = try store.open(allocator, root, smg.default_limits.storage); defer opened.close(); try storage.names.writeForHead(allocator, root, &opened.database, wrong_head); } try std.testing.expect(!try storage.names.matchesHead(allocator, root, published.head)); const names_repair = try publishMatchedTimed( allocator, root, graph, published.head, smg.default_limits, ); try expectMatchedPublish(published.head, names_repair, .matched); try std.testing.expect(try storage.names.matchesHead(allocator, root, published.head)); try std.testing.expectEqual(@as(usize, 1), (try load( allocator, allocator, root, smg.default_limits.storage, )).nodes.items.len);}test "stale graph publication preserves the winning generation" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 4); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); const source = try loadSnapshot(allocator, allocator, root, smg.default_limits.storage); var winner = try graph_mod.clone(allocator, allocator, source.graph); try graph_mod.addNode(&winner, .{ .name = "winner", .type = model.NodeType.module }); var loser = try graph_mod.clone(allocator, allocator, source.graph); try graph_mod.addNode(&loser, .{ .name = "loser", .type = model.NodeType.module }); try publish(allocator, root, winner, source.head, smg.default_limits); const winner_head = try testHeadHash(allocator, root); try std.testing.expectError( error.StaleSourceHead, publish(allocator, root, loser, source.head, smg.default_limits), ); try std.testing.expectError( error.StaleSourceHead, publishMatchedTimed(allocator, root, winner, source.head, smg.default_limits), ); try std.testing.expect(!try sync.refreshIncomplete(allocator, root)); try std.testing.expect(sql.version.same(winner_head, try testHeadHash(allocator, root))); const reloaded = try load(allocator, allocator, root, smg.default_limits.storage); try std.testing.expect(graph_mod.getNode(&reloaded, "winner") != null); try std.testing.expect(graph_mod.getNode(&reloaded, "loser") == null); try std.testing.expectEqual( @as(usize, 1), (try storage.summary.load(allocator, root, smg.default_limits.storage)).nodes, ); try std.testing.expectEqual( @as(i64, 1), (try search.searchStored( allocator, root, "winner", null, 10, smg.default_limits.storage, )).total, ); try std.testing.expectEqual( @as(i64, 0), (try search.searchStored( allocator, root, "loser", null, 10, smg.default_limits.storage, )).total, );}fn expectMatchedPublish( expected_head: sql.Hash, timing: PublishTiming, index_kind: search.SyncKind,) !void { try std.testing.expect(sql.version.same(expected_head, timing.head)); try std.testing.expectEqual(@as(u64, 0), timing.graph_ns); try std.testing.expectEqual(index_kind, timing.index_kind); try std.testing.expectEqual(@as(usize, 0), timing.node_puts); try std.testing.expectEqual(@as(usize, 0), timing.node_deletes); try std.testing.expectEqual(@as(usize, 0), timing.edge_puts); try std.testing.expectEqual(@as(usize, 0), timing.edge_deletes);}fn modelLoadPlanBytes(limits: LoadPlan.Limits) ?usize { const counts = [_]usize{ limits.nodes, limits.edges, limits.nodes, limits.nodes }; const sizes = [_]usize{ @sizeOf(rows.NodeRow), @sizeOf(rows.EdgeRow), @sizeOf(usize), @sizeOf(usize) }; var total: usize = 0; for (counts, sizes) |count, size| { if (count > std.math.maxInt(usize) / size) return null; const bytes = count * size; if (total > std.math.maxInt(usize) - bytes) return null; total += bytes; } return total;}test "graph load plan capacity matches an independent typed storage model" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(LoadPlan, "smg_graph_load_plan_capacity_capacity_model"), null, null, null, null, null, null, ); } comptime { @stardustClaim( @import("alloc_phase").capacity.witness(LoadPlan, "smg_graph_load_plan_capacity_overload"), null, null, null, null, null, null, ); } for (0..64) |count| { const limits = LoadPlan.Limits{ .nodes = count, .edges = count * 3 }; const capacity = try LoadPlan.Capacity.derive(limits); try std.testing.expectEqual(modelLoadPlanBytes(limits).?, capacity.bytes); } const overflow = LoadPlan.Limits{ .nodes = std.math.maxInt(usize), .edges = 0 }; try std.testing.expect(modelLoadPlanBytes(overflow) == null); try std.testing.expectError(error.CapacityOverflow, LoadPlan.Capacity.derive(overflow));}fn checkLoadPlanInitAllocationFailures(allocator: std.mem.Allocator) !void { var plan = try LoadPlan.init(allocator, .{ .nodes = 3, .edges = 5 }); plan.deinit(allocator);}test "graph load plan initialization cleans allocation failure and retries" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(LoadPlan, "smg_graph_load_plan_oom"), null, null, null, null, null, null, ); } try std.testing.checkAllAllocationFailures( std.testing.allocator, checkLoadPlanInitAllocationFailures, .{}, ); var plan = try LoadPlan.init(std.testing.allocator, .{ .nodes = 1, .edges = 1 }); defer plan.deinit(std.testing.allocator); plan.activate(); try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, plan.phase);}test "graph load plan fills admitted records and degrees while sealed" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(LoadPlan, "smg_graph_load_plan_sealed"), null, null, null, null, null, null, ); } var phase_allocator = try alloc_phase.SealedPhaseAllocator.init(std.testing.allocator); var plan = try LoadPlan.init( phase_allocator.initializationAllocator(), .{ .nodes = 2, .edges = 1 }, ); defer { if (phase_allocator.phase() == .initialization) phase_allocator.abortInitialization(); if (phase_allocator.phase() == .steady) phase_allocator.beginTeardown(); plan.deinit(phase_allocator.teardownAllocator()); phase_allocator.deinit(); } const node_rows_pointer = plan.node_rows.ptr; const edge_rows_pointer = plan.edge_rows.ptr; phase_allocator.seal(); plan.activate(); plan.node_rows[0] = .{ .rowid = 1, .node = .{ .name = "a", .type = model.NodeType.module } }; plan.edge_rows[0] = .{ .rowid = 2, .edge = .{ .source = "a", .target = "b", .rel = model.RelType.contains } }; plan.outgoing[0] = 1; plan.incoming[1] = 1; try std.testing.expectEqual(node_rows_pointer, plan.node_rows.ptr); try std.testing.expectEqual(edge_rows_pointer, plan.edge_rows.ptr); try std.testing.expectEqual(@as(u64, 0), phase_allocator.violations().total());}test "graph load reserves stable retained containers from persisted counts and degrees" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(LoadPlan, "smg_graph_load_plan_integration_transitive_risk"), null, null, null, null, null, null, ); } comptime { @stardustClaim( @import("alloc_phase").capacity.witness(LoadPlan, "smg_graph_load_plan_integration_foreign_risk"), null, null, null, null, null, null, ); } var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 3); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); var stored = graph_mod.init(allocator); try graph_mod.addNode(&stored, .{ .name = "a", .type = model.NodeType.module }); try graph_mod.addNode(&stored, .{ .name = "a.f", .type = model.NodeType.function }); try graph_mod.addEdge(&stored, .{ .source = "a", .target = "a.f", .rel = model.RelType.contains }); _ = try replaceForTest(allocator, stored, root); var scratch: std.heap.DebugAllocator(.{}) = .init; defer std.debug.assert(scratch.deinit() == .ok); const loaded = try load( allocator, scratch.allocator(), root, smg.default_limits.storage, ); try std.testing.expectEqual(@as(usize, 2), loaded.nodes.capacity); try std.testing.expectEqual(@as(usize, 1), loaded.edges.capacity); try std.testing.expectEqual(@as(usize, 2), loaded.incoming_index.capacity); try std.testing.expectEqual(@as(usize, 2), loaded.outgoing_index.capacity); try std.testing.expectEqual(@as(usize, 0), loaded.incoming_index.items[0].capacity); try std.testing.expectEqual(@as(usize, 1), loaded.outgoing_index.items[0].capacity); try std.testing.expectEqual(@as(usize, 1), loaded.incoming_index.items[1].capacity); try std.testing.expectEqual(@as(usize, 0), loaded.outgoing_index.items[1].capacity);}fn initStore(allocator: std.mem.Allocator, root: []const u8) !void { try sys.fs.createDirPath(root); var opened = try store.open(allocator, root, smg.default_limits.storage); opened.close();}fn replaceForTest(allocator: std.mem.Allocator, graph: graph_mod.Graph, root: []const u8) !sql.Hash { var opened = try store.open(allocator, root, smg.default_limits.storage); defer opened.close(); return try replace(allocator, &opened.database, graph);}fn testHeadHash(allocator: std.mem.Allocator, root: []const u8) !sql.Hash { var opened = try store.open(allocator, root, smg.default_limits.storage); defer opened.close(); return (try opened.database.connection.checkout()).head;}fn testRoot(allocator: std.mem.Allocator, offset: i64) ![]const u8 { return try std.fmt.allocPrint(allocator, "/tmp/smg-storage-graph-test-{x}", .{@as(u64, @intCast(@max(0, sys.time.realMilliTimestamp() + offset)))});}Source: tools/smg/src/storage/root.zig:4
zig
pub const graph = @import("graph.zig");Complete caller list for storage.graph.load
9 direct callers.
tools.smg.src.command.check.refresh.refreshGraph[function] — private; no exact target attools/smg/src/command/check/refresh.zig:134in nearest public ownertiny.smg.command.check.refreshtools.smg.src.storage.graph.test_graph_load_reserves_stable_retained_containers_from_persisted_counts_and_degrees[function] — test; no exact target attools/smg/src/storage/graph.zig:822in nearest public ownertiny.smg.storage.graphtools.smg.src.storage.graph.test_graph_save_load_and_node-only_load[function] — test; no exact target attools/smg/src/storage/graph.zig:500in nearest public ownertiny.smg.storage.graphtools.smg.src.storage.graph.test_matched_publication_preserves_the_graph_and_repairs_summary_and_names[function] — test; no exact target attools/smg/src/storage/graph.zig:582in nearest public ownertiny.smg.storage.graphtools.smg.src.storage.graph.test_save_graph_orders_nodes_and_preserves_json_metadata[function] — test; no exact target attools/smg/src/storage/graph.zig:523in nearest public ownertiny.smg.storage.graphtools.smg.src.storage.graph.test_stale_graph_publication_preserves_the_winning_generation[function] — test; no exact target attools/smg/src/storage/graph.zig:637in nearest public ownertiny.smg.storage.graphtools.smg.src.storage.graph.test_unchanged_save_adds_no_commit_and_changed_save_advances_head[function] — test; no exact target attools/smg/src/storage/graph.zig:547in nearest public ownertiny.smg.storage.graphtools.smg.src.test.test_persisted_clean_rescan_preserves_edges_to_surviving_symbols[function] — test; no exact target attools/smg/src/test.zig:109in nearest public ownertools.smg.src.testtools.smg.src.test.test_persisted_ordinary_scan_retires_deleted_source_nodes[function] — test; no exact target attools/smg/src/test.zig:70in nearest public ownertools.smg.src.test
Complete caller list for storage.graph.loadSnapshot
8 direct callers.
tools.smg.src.command.scan.command.runConfigured[function] — private; no exact target attools/smg/src/command/scan/command.zig:66in nearest public ownertiny.smg.command.scan.commandtiny.smg.command.session.loadMutationAt[function] attools/smg/src/command/session.zig:40tools.smg.src.command.session.test_graph_publication_releases_persistence_scratch_and_preserves_search_row_order[function] — test; no exact target attools/smg/src/command/session.zig:140in nearest public ownertiny.smg.command.sessiontools.smg.src.search.test_matched_graph_publication_rebuilds_a_malformed_search_cache[function] — test; no exact target attools/smg/src/search.zig:1694in nearest public ownertiny.smg.searchtiny.smg.storage.graph.load[function] attools/smg/src/storage/graph.zig:234tools.smg.src.storage.graph.test_matched_publication_preserves_the_graph_and_repairs_summary_and_names[function] — test; no exact target attools/smg/src/storage/graph.zig:582in nearest public ownertiny.smg.storage.graphtools.smg.src.storage.graph.test_stale_graph_publication_preserves_the_winning_generation[function] — test; no exact target attools/smg/src/storage/graph.zig:637in nearest public ownertiny.smg.storage.graphtiny.smg.watch.rescan[function] attools/smg/src/watch.zig:177
Audit
| Definitions | 24 |
|---|---|
| Public names | 33 |
| Members | 28 |
| Version | 26.7.0 |
| Revision | daab053ee433 |