Skip to documentation
SLOP

tiny.smg.storage.graph

Reference tiny.smg storage graph

Defined in storage.

API (23)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsstorage.GraphLoadPlaninittest; no linktools.smg.src.storage.graphtest: graph load plan capacity matche...private; no linktools.smg.src.storage.graphloadPlanBytesstorage.GraphLoadPlan.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linktools.smg.src.storage.graphloadRecordsFromReadertest; no linktools.smg.src.storage.graphtest: graph load plan fills admitted ...test; no linktools.smg.src.storage.graphtest: graph load plan initialization ...storage.GraphLoadPlanactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.storage.graphcheckLoadPlanInitAllocationFailuresprivate; no linktools.smg.src.storage.graphloadRecordsFromReadertest; no linktools.smg.src.storage.graphtest: graph load plan fills admitted ...test; no linktools.smg.src.storage.graphtest: graph load plan initialization ...private; no linktools.smg.src.storage.graphfreeSlicestorage.GraphLoadPlandeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.storage.graphcheckLoadPlanInitAllocationFailuresprivate; no linktools.smg.src.storage.graphloadRecordsFromReadertest; no linktools.smg.src.storage.graphtest: graph load plan fills admitted ...test; no linktools.smg.src.storage.graphtest: graph load plan initialization ...storage.GraphLoadPlan.Capacityderiveprivate; no linktools.smg.src.storage.graphallocSliceprivate; no linktools.smg.src.storage.graphfreeSlicestorage.GraphLoadPlaninit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersstorage.sync.ChangeSetdeinitstorage.graph.Replacementdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.command.check.refreshrefreshGraphtest; no linktools.smg.src.storage.graphtest: graph load reserves stable reta...test; no linktools.smg.src.storage.graphtest: graph save load and node-only l...test; no linktools.smg.src.storage.graphtest: matched publication preserves t...test; no linktools.smg.src.storage.graphtest: save graph orders nodes and pre...+4 morestorage.graphloadSnapshotstorage.graphload
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallscommand.sessionloadGraphAndConceptsprivate; no linktools.smg.src.storage.graphloadRecordsFromReaderstorage.graphloadFromReader
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest; no linktools.smg.src.storage.graphtest: graph save load and node-only l...private; no linktools.smg.src.storage.graphloadRecordsstorage.graphloadNodes
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.command.scan.commandrunConfiguredcommand.sessionloadMutationAttest; no linktools.smg.src.command.sessiontest: graph publication releases pers...test; no linktools.smg.src.searchtest: matched graph publication rebui...storage.graphload+3 moreprivate; no linktools.smg.src.storage.graphloadRecordsFromReaderstorage.storeopenReadstorage.graphloadSnapshot
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.command.scan.commandsaveAndRendertest; no linktools.smg.src.command.sessiontest: graph publication releases pers...test; no linktools.smg.src.storage.graphtest: stale graph publication preserv...storage.graphpublishTimedstorage.graphpublish
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.command.check.refreshrefreshGraphtest; no linktools.smg.src.searchtest: matched graph publication rebui...test; no linktools.smg.src.storage.graphtest: matched publication preserves t...test; no linktools.smg.src.storage.graphtest: stale graph publication preserv...private; no linktools.smg.src.storage.graphelapsedNanosecondsprivate; no linktools.smg.src.storage.graphfinishPublishTimedstorage.storebeginPublishstorage.graphpublishMatchedTimed
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallsprivate; no linktools.smg.src.command.check.refreshrefreshGraphtest; no linktools.smg.src.searchtest: matched graph publication rebui...storage.graphpublishtest; no linktools.smg.src.storage.graphtest: matched publication preserves t...private; no linktools.smg.src.storage.graphelapsedNanosecondsprivate; no linktools.smg.src.storage.graphfinishPublishTimedstorage.graphreplaceObservedstorage.storebeginPublishstorage.graphpublishTimed
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsprivate; no linktools.smg.src.searchreplaceGraphForTesttest; no linktools.smg.src.searchtest: stored search document plan pre...private; no linktools.smg.src.storage.graphreplaceForTestprivate; no linktools.smg.src.testreplaceStoredGraphstorage.graphreplaceObservedstorage.graphreplace
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate; no linktools.smg.src.searchreplaceAndSyncForTeststorage.graphpublishTimedstorage.graphreplacestorage.syncreplaceGraphObservedstorage.graphreplaceObserved
Static calls · unresolved targets: 0 · external targets: 2.

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.

Complete caller list for storage.graph.loadSnapshot

8 direct callers.

Audit

Definitions24
Public names33
Members28
Version26.7.0
Revisiondaab053ee433