Skip to documentation
SLOP

tiny.smg.storage.sync

Reference tiny.smg storage sync

Defined in storage.

API (16)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsstorage.graph.Replacementdeinitprivate; no linktools.smg.src.storage.syncfreeSlicestorage.sync.ChangeSetdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsstorage.GraphDiffPlaninittest; no linktools.smg.src.storage.synctest: graph diff plan capacity matche...test; no linktools.smg.src.storage.synctest: graph edits commit in bounded b...private; no linktools.smg.src.storage.syncbitmapBytesprivate; no linktools.smg.src.storage.syncgraphPlanBytesstorage.GraphDiffPlan.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsstorage.syncreplaceGraphObservedtest; no linktools.smg.src.storage.synctest: graph diff plan initialization ...test; no linktools.smg.src.storage.synctest: graph diff plan stays sealed wh...test; no linktools.smg.src.storage.synctest: graph edits commit in bounded b...storage.GraphDiffPlanactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.storage.synccheckGraphPlanInitAllocationFailuresstorage.syncreplaceGraphObservedtest; no linktools.smg.src.storage.synctest: graph diff plan initialization ...test; no linktools.smg.src.storage.synctest: graph diff plan stays sealed wh...test; no linktools.smg.src.storage.synctest: graph edits commit in bounded b...private; no linktools.smg.src.storage.syncfreeSlicestorage.GraphDiffPlandeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.storage.synccheckGraphPlanInitAllocationFailuresstorage.syncreplaceGraphObservedtest; no linktools.smg.src.storage.synctest: graph diff plan initialization ...test; no linktools.smg.src.storage.synctest: graph diff plan stays sealed wh...test; no linktools.smg.src.storage.synctest: graph edits commit in bounded b...storage.GraphDiffPlan.Capacityderiveprivate; no linktools.smg.src.storage.syncallocSliceprivate; no linktools.smg.src.storage.syncfreeSlicestorage.GraphDiffPlaninit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsstorage.storebeginPublishstorage.storebeginRepairPublishprivate; no linktools.smg.src.storage.syncrefreshPathstorage.syncbeginRefresh
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsstorage.store.Publisherfinishprivate; no linktools.smg.src.storage.syncrefreshPathstorage.syncfinishRefresh
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest; no linktools.smg.src.conceptstest: stale concept publication prese...test; no linktools.smg.src.storage.graphtest: stale graph publication preserv...private; no linktools.smg.src.storage.storeopenWriterprivate; no linktools.smg.src.storage.storereadStoretest; no linktools.smg.src.storage.storetest: expected publication rejects st...private; no linktools.smg.src.storage.syncrefreshPathstorage.syncrefreshIncomplete
Static calls · unresolved targets: 1 · external targets: 1.
Called byCallsprivate; no linktools.smg.src.storage.contextsaveGraphprivate; no linktools.smg.src.storage.nodessaveGraphprivate; no linktools.smg.src.storage.summarypublishTestingGraphtest; no linktools.smg.src.storage.summarytest: in-memory summary matches the s...test; no linktools.smg.src.storage.synctest: graph replacement streams store...storage.syncreplaceGraphObservedstorage.syncreplaceGraph
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsstorage.graphreplaceObservedstorage.syncreplaceGraphstorage.database.readgraphCountsstorage.GraphDiffPlanactivatestorage.GraphDiffPlandeinitprivate; no linktools.smg.src.storage.sync.GraphPlaneditsstorage.GraphDiffPlaninit+5 morestorage.syncreplaceGraphObserved
Static calls · unresolved targets: 0 · external targets: 1.

Source: tools/smg/src/storage/root.zig:13

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

Source: tools/smg/src/storage/sync.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 edits = database.edits;const read = database.read;const rows = storage.rows;const graph_mod = smg.graph;const model = smg.model;const text = smg.text;const PlannedPut = struct {    rowid: i64,    index: u32,};const GraphIndex = u32;const PlannedEdits = struct {    node_puts: []const PlannedPut,    node_deletes: []const i64,    edge_puts: []const PlannedPut,    edge_deletes: []const i64,};pub const ChangeSet = struct {    node_puts: []rows.NodePut,    node_deletes: []i64,    edge_puts: usize,    edge_deletes: usize,    pub fn deinit(self: *ChangeSet, allocator: std.mem.Allocator) void {        freeSlice(rows.NodePut, allocator, self.node_puts);        freeSlice(i64, allocator, self.node_deletes);        self.* = undefined;    }};pub const GraphPlan = struct {    pub const Limits = struct {        desired_nodes: usize,        stored_nodes: usize,        desired_edges: usize,        stored_edges: usize,        batch_size: usize,    };    pub const Capacity = struct {        node_order: usize,        node_puts: usize,        node_deletes: usize,        edge_order: usize,        edge_key_ptrs: usize,        edge_puts: usize,        edge_deletes: usize,        seen_node_bytes: usize,        seen_edge_bytes: usize,        batch_node_puts: usize,        batch_edge_puts: usize,        batch_size: usize,        bytes: usize,        pub fn derive(limits: Limits) error{ CapacityOverflow, InvalidEditBatchSize }!Capacity {            if (limits.batch_size == 0) return error.InvalidEditBatchSize;            if (limits.desired_nodes > std.math.maxInt(u32)) return error.CapacityOverflow;            if (limits.desired_edges > std.math.maxInt(u32)) return error.CapacityOverflow;            return .{                .node_order = limits.desired_nodes,                .node_puts = limits.desired_nodes,                .node_deletes = limits.stored_nodes,                .edge_order = limits.desired_edges,                .edge_key_ptrs = limits.desired_edges,                .edge_puts = limits.desired_edges,                .edge_deletes = limits.stored_edges,                .seen_node_bytes = bitmapBytes(limits.desired_nodes),                .seen_edge_bytes = bitmapBytes(limits.desired_edges),                .batch_node_puts = @min(limits.desired_nodes, limits.batch_size),                .batch_edge_puts = @min(limits.desired_edges, limits.batch_size),                .batch_size = limits.batch_size,                .bytes = try graphPlanBytes(limits),            };        }    };    pub const InitError = std.mem.Allocator.Error || error{ CapacityOverflow, InvalidEditBatchSize };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "smg.graph_diff_plan",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{                        .id = "compact_graph_relative_node_and_edge_order_indexes_29865def4529",                        .lifetime = .steady,                        .detail = "compact graph-relative node and edge order indexes bounded by desired row counts",                    },                    .{                        .id = "borrowed_combined_edge_key_pointers_indexed_by_the_34559d226c70",                        .lifetime = .steady,                        .detail = "borrowed combined-edge-key pointers indexed by the retained graph",                    },                    .{                        .id = "compact_node_and_edge_put_descriptors_bounded_by_de_139e5f58f713",                        .lifetime = .steady,                        .detail = "compact node and edge put descriptors bounded by desired row counts",                    },                    .{                        .id = "node_and_edge_delete_plans_bounded_by_stored_row_counts",                        .lifetime = .steady,                        .detail = "node and edge delete plans bounded by stored row counts",                    },                    .{                        .id = "packed_desired_row_seen_bitmaps",                        .lifetime = .steady,                        .detail = "packed desired-row seen bitmaps",                    },                    .{                        .id = "full_width_database_edits_bounded_by_one_commit_batch",                        .lifetime = .steady,                        .detail = "full-width database edits bounded by one commit batch",                    },                },                .excluded = &.{                    "retained graph models, indexes, edge keys, suffixes, and adjacency lists",                    "SQL catalog, relation scans, database pages, and bounded row decode scratch",                    "database write staging, pager, WAL, history, and search-index storage",                },            },            .capacity = .{                .inputs = &.{                    alloc_phase.capacity.bindInput(Limits, "desired_nodes", "desired_nodes"),                    alloc_phase.capacity.bindInput(Limits, "desired_edges", "desired_edges"),                    alloc_phase.capacity.bindInput(Limits, "stored_nodes", "stored_nodes"),                    alloc_phase.capacity.bindInput(Limits, "stored_edges", "stored_edges"),                    alloc_phase.capacity.bindInput(Limits, "batch_size", "batch_size"),                },                .type_selectors = &.{                    alloc_phase.capacity.bindType(GraphIndex, "graphindex"),                    alloc_phase.capacity.bindType(PlannedPut, "plannedput"),                    alloc_phase.capacity.bindType(i64, "i64"),                    alloc_phase.capacity.bindType([*]const u8, "edge_key_pointer"),                    alloc_phase.capacity.bindType(rows.NodePut, "node_put"),                    alloc_phase.capacity.bindType(rows.EdgePut, "edge_put"),                },                .nodes = &.{                    .{ .input = 0 },                    .{ .input = 1 },                    .{ .input = 2 },                    .{ .input = 3 },                    .{ .constant = 1 },                    .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 0 } } },                    .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 1 } } },                    .{ .add = .{ .left = 5, .right = 6 } },                    .{ .product = .{ .left = 0, .right = 7 } },                    .{ .scale = .{ .node = 2, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 0 } } },                    .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 3 } } },                    .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 1 } } },                    .{ .add = .{ .left = 10, .right = 11 } },                    .{ .add = .{ .left = 13, .right = 12 } },                    .{ .product = .{ .left = 1, .right = 14 } },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .constant = 8 },                    .{ .ceiling_division = .{ .left = 0, .right = 17 } },                    .{ .ceiling_division = .{ .left = 1, .right = 17 } },                    .{ .input = 4 },                    .{ .conditional = .{ .predicate = .{ .comparison = .less_than, .left = 0, .right = 20 }, .when_true = 0, .when_false = 20 } },                    .{ .scale = .{ .node = 21, .coefficient = .{ .size_of_concrete_type = 4 } } },                    .{ .conditional = .{ .predicate = .{ .comparison = .less_than, .left = 1, .right = 20 }, .when_true = 1, .when_false = 20 } },                    .{ .scale = .{ .node = 23, .coefficient = .{ .size_of_concrete_type = 5 } } },                    .{ .add = .{ .left = 8, .right = 9 } },                    .{ .add = .{ .left = 25, .right = 15 } },                    .{ .add = .{ .left = 26, .right = 16 } },                    .{ .add = .{ .left = 27, .right = 18 } },                    .{ .add = .{ .left = 28, .right = 19 } },                    .{ .add = .{ .left = 29, .right = 22 } },                    .{ .add = .{ .left = 30, .right = 24 } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 31,                }},            },            .overload = .{                .kind = .reject_before_seal,                .detail = "checked byte arithmetic and exact acquisitions reject overflow or OOM before the plan activates",            },            .risks = .{                .transitive = .{                    .status = .open,                    .detail = "streaming row decode and SQL scans remain outside the fixed plan and lack a transitive allocation-closure certificate",                },                .foreign = .{                    .status = .excluded,                    .detail = "filesystem and durable database effects occur only after plan construction and are outside plan-owned storage",                },            },            .obligations = &.{                .{ .key = "smg_graph_diff_plan_capacity_capacity_model", .role = .capacity_model },                .{ .key = "smg_graph_diff_plan_capacity_overload", .role = .overload },                .{ .key = "smg_graph_diff_plan_oom", .role = .overload },                .{ .key = "smg_graph_diff_plan_sealed", .role = .custom },                .{ .key = "smg_graph_diff_plan_streaming_transitive_risk", .role = .transitive_risk },                .{ .key = "smg_graph_diff_plan_streaming_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_order: []GraphIndex,    node_puts: []PlannedPut,    node_deletes: []i64,    edge_order: []GraphIndex,    edge_key_ptrs: [][*]const u8,    edge_puts: []PlannedPut,    edge_deletes: []i64,    seen_nodes: []u8,    seen_edges: []u8,    batch_node_puts: []rows.NodePut,    batch_edge_puts: []rows.EdgePut,    node_put_count: usize = 0,    node_delete_count: usize = 0,    edge_put_count: usize = 0,    edge_delete_count: usize = 0,    pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!GraphPlan {        const capacity = try Capacity.derive(limits);        const node_order = try allocSlice(GraphIndex, allocator, capacity.node_order);        errdefer freeSlice(GraphIndex, allocator, node_order);        const node_puts = try allocSlice(PlannedPut, allocator, capacity.node_puts);        errdefer freeSlice(PlannedPut, allocator, node_puts);        const node_deletes = try allocSlice(i64, allocator, capacity.node_deletes);        errdefer freeSlice(i64, allocator, node_deletes);        const edge_order = try allocSlice(GraphIndex, allocator, capacity.edge_order);        errdefer freeSlice(GraphIndex, allocator, edge_order);        const edge_key_ptrs = try allocSlice([*]const u8, allocator, capacity.edge_key_ptrs);        errdefer freeSlice([*]const u8, allocator, edge_key_ptrs);        const edge_puts = try allocSlice(PlannedPut, allocator, capacity.edge_puts);        errdefer freeSlice(PlannedPut, allocator, edge_puts);        const edge_deletes = try allocSlice(i64, allocator, capacity.edge_deletes);        errdefer freeSlice(i64, allocator, edge_deletes);        const seen_nodes = try allocSlice(u8, allocator, capacity.seen_node_bytes);        errdefer freeSlice(u8, allocator, seen_nodes);        const seen_edges = try allocSlice(u8, allocator, capacity.seen_edge_bytes);        errdefer freeSlice(u8, allocator, seen_edges);        const batch_node_puts = try allocSlice(rows.NodePut, allocator, capacity.batch_node_puts);        errdefer freeSlice(rows.NodePut, allocator, batch_node_puts);        const batch_edge_puts = try allocSlice(rows.EdgePut, allocator, capacity.batch_edge_puts);        return .{            .phase = .initialization,            .capacity = capacity,            .node_order = node_order,            .node_puts = node_puts,            .node_deletes = node_deletes,            .edge_order = edge_order,            .edge_key_ptrs = edge_key_ptrs,            .edge_puts = edge_puts,            .edge_deletes = edge_deletes,            .seen_nodes = seen_nodes,            .seen_edges = seen_edges,            .batch_node_puts = batch_node_puts,            .batch_edge_puts = batch_edge_puts,        };    }    fn order(self: *GraphPlan, graph: graph_mod.Graph) void {        std.debug.assert(self.phase == .initialization);        std.debug.assert(self.node_order.len == graph.nodes.items.len);        std.debug.assert(self.edge_order.len == graph.edges.items.len);        std.debug.assert(self.edge_key_ptrs.len == graph.edges.items.len);        for (self.node_order, 0..) |*entry, index| entry.* = @intCast(index);        for (self.edge_order, 0..) |*entry, index| entry.* = @intCast(index);        var edge_iterator = graph.edge_index.iterator();        var edge_key_count: usize = 0;        while (edge_iterator.next()) |entry| : (edge_key_count += 1) {            self.edge_key_ptrs[entry.value_ptr.*] = entry.key_ptr.*.ptr;        }        std.debug.assert(edge_key_count == self.edge_key_ptrs.len);        std.mem.sort(GraphIndex, self.node_order, graph.nodes.items, nodeIndexLess);        std.mem.sort(GraphIndex, self.edge_order, EdgeSort{            .edges = graph.edges.items,            .key_ptrs = self.edge_key_ptrs,            .encoded_order = encodedEdgeOrderMatchesFields(graph.edges.items),        }, edgeIndexLess);    }    pub fn activate(self: *GraphPlan) void {        std.debug.assert(self.phase == .initialization);        @memset(self.seen_nodes, 0);        @memset(self.seen_edges, 0);        self.phase = .steady;    }    pub fn deinit(self: *GraphPlan, allocator: std.mem.Allocator) void {        std.debug.assert(self.phase != .teardown);        self.phase = .teardown;        freeSlice(GraphIndex, allocator, self.node_order);        freeSlice(PlannedPut, allocator, self.node_puts);        freeSlice(i64, allocator, self.node_deletes);        freeSlice(GraphIndex, allocator, self.edge_order);        freeSlice([*]const u8, allocator, self.edge_key_ptrs);        freeSlice(PlannedPut, allocator, self.edge_puts);        freeSlice(i64, allocator, self.edge_deletes);        freeSlice(u8, allocator, self.seen_nodes);        freeSlice(u8, allocator, self.seen_edges);        freeSlice(rows.NodePut, allocator, self.batch_node_puts);        freeSlice(rows.EdgePut, allocator, self.batch_edge_puts);        self.* = undefined;    }    fn markNode(self: *GraphPlan, index: usize) bool {        std.debug.assert(self.phase == .steady);        std.debug.assert(index < self.node_order.len);        return markBit(self.seen_nodes, index);    }    fn markEdge(self: *GraphPlan, index: usize) bool {        std.debug.assert(self.phase == .steady);        std.debug.assert(index < self.edge_order.len);        return markBit(self.seen_edges, index);    }    fn nodeSeen(self: *const GraphPlan, index: usize) bool {        std.debug.assert(self.phase == .steady);        std.debug.assert(index < self.node_order.len);        return bitSet(self.seen_nodes, index);    }    fn edgeSeen(self: *const GraphPlan, index: usize) bool {        std.debug.assert(self.phase == .steady);        std.debug.assert(index < self.edge_order.len);        return bitSet(self.seen_edges, index);    }    fn putNode(self: *GraphPlan, rowid: i64, index: usize) void {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.node_put_count < self.node_puts.len);        std.debug.assert(index < self.node_order.len);        self.node_puts[self.node_put_count] = .{ .rowid = rowid, .index = @intCast(index) };        self.node_put_count += 1;    }    fn deleteNode(self: *GraphPlan, rowid: i64) void {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.node_delete_count < self.node_deletes.len);        self.node_deletes[self.node_delete_count] = rowid;        self.node_delete_count += 1;    }    fn putEdge(self: *GraphPlan, rowid: i64, index: usize) void {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.edge_put_count < self.edge_puts.len);        std.debug.assert(index < self.edge_order.len);        self.edge_puts[self.edge_put_count] = .{ .rowid = rowid, .index = @intCast(index) };        self.edge_put_count += 1;    }    fn deleteEdge(self: *GraphPlan, rowid: i64) void {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.edge_delete_count < self.edge_deletes.len);        self.edge_deletes[self.edge_delete_count] = rowid;        self.edge_delete_count += 1;    }    fn edits(self: *const GraphPlan) PlannedEdits {        std.debug.assert(self.phase == .steady);        return .{            .node_puts = self.node_puts[0..self.node_put_count],            .node_deletes = self.node_deletes[0..self.node_delete_count],            .edge_puts = self.edge_puts[0..self.edge_put_count],            .edge_deletes = self.edge_deletes[0..self.edge_delete_count],        };    }    fn materializeNodePuts(self: *GraphPlan, graph: graph_mod.Graph, planned: []const PlannedPut) []const rows.NodePut {        std.debug.assert(self.phase == .steady);        std.debug.assert(planned.len <= self.batch_node_puts.len);        for (planned, 0..) |put, index| {            self.batch_node_puts[index] = .{                .rowid = put.rowid,                .node = graph.nodes.items[put.index],            };        }        return self.batch_node_puts[0..planned.len];    }    fn materializeEdgePuts(self: *GraphPlan, graph: graph_mod.Graph, planned: []const PlannedPut) []const rows.EdgePut {        std.debug.assert(self.phase == .steady);        std.debug.assert(planned.len <= self.batch_edge_puts.len);        for (planned, 0..) |put, index| {            self.batch_edge_puts[index] = .{                .rowid = put.rowid,                .edge = graph.edges.items[put.index],            };        }        return self.batch_edge_puts[0..planned.len];    }};comptime {    alloc_phase.capacity.requireAllocatorExactOwnerShape(GraphPlan);}pub fn beginRefresh(allocator: std.mem.Allocator, root: []const u8) !void {    const path = try refreshPath(allocator, root);    defer allocator.free(path);    try text.writeFile(path, "graph refresh in progress\n");}pub fn finishRefresh(allocator: std.mem.Allocator, root: []const u8) !void {    const path = try refreshPath(allocator, root);    defer allocator.free(path);    sys.fs.deleteFile(path) catch |err| switch (err) {        error.FileNotFound => {},        else => return err,    };}pub fn refreshIncomplete(allocator: std.mem.Allocator, root: []const u8) !bool {    const path = try refreshPath(allocator, root);    defer allocator.free(path);    return sys.fs.exists(path);}fn refreshPath(allocator: std.mem.Allocator, root: []const u8) ![]const u8 {    return try std.fs.path.join(allocator, &.{ root, storage.paths.smg_dir_name, storage.paths.refresh_file_name });}pub fn replaceGraph(    allocator: std.mem.Allocator,    store_database: *database.Database,    graph: graph_mod.Graph,) !void {    var changes = try replaceGraphObserved(allocator, store_database, graph);    changes.deinit(allocator);}pub fn replaceGraphObserved(    allocator: std.mem.Allocator,    store_database: *database.Database,    graph: graph_mod.Graph,) !ChangeSet {    const stored = try read.graphCounts(store_database);    var plan = try GraphPlan.init(allocator, .{        .desired_nodes = graph.nodes.items.len,        .stored_nodes = stored.nodes,        .desired_edges = graph.edges.items.len,        .stored_edges = stored.edges,        .batch_size = store_database.limits.edit_batch_size,    });    defer plan.deinit(allocator);    plan.order(graph);    plan.activate();    try fillNodeDiff(allocator, store_database, graph, &plan);    try fillEdgeDiff(allocator, store_database, graph, &plan);    var changes = try changeSet(allocator, graph, plan.edits());    errdefer changes.deinit(allocator);    _ = try applyBatches(store_database, graph, &plan);    return changes;}fn changeSet(    allocator: std.mem.Allocator,    graph: graph_mod.Graph,    planned: PlannedEdits,) !ChangeSet {    const node_puts = try allocSlice(rows.NodePut, allocator, planned.node_puts.len);    errdefer freeSlice(rows.NodePut, allocator, node_puts);    for (planned.node_puts, 0..) |put, index| {        node_puts[index] = .{            .rowid = put.rowid,            .node = graph.nodes.items[put.index],        };    }    const node_deletes = try allocSlice(i64, allocator, planned.node_deletes.len);    @memcpy(node_deletes, planned.node_deletes);    return .{        .node_puts = node_puts,        .node_deletes = node_deletes,        .edge_puts = planned.edge_puts.len,        .edge_deletes = planned.edge_deletes.len,    };}fn applyBatches(    store_database: *database.Database,    graph: graph_mod.Graph,    plan: *GraphPlan,) !usize {    const planned = plan.edits();    var node_put_offset: usize = 0;    var node_delete_offset: usize = 0;    var edge_put_offset: usize = 0;    var edge_delete_offset: usize = 0;    var batches: usize = 0;    while (node_put_offset < planned.node_puts.len or node_delete_offset < planned.node_deletes.len or edge_put_offset < planned.edge_puts.len or edge_delete_offset < planned.edge_deletes.len) {        var remaining = plan.capacity.batch_size;        const node_put_end = @min(node_put_offset + remaining, planned.node_puts.len);        remaining -= node_put_end - node_put_offset;        const edge_put_end = @min(edge_put_offset + remaining, planned.edge_puts.len);        remaining -= edge_put_end - edge_put_offset;        const edge_delete_end = @min(edge_delete_offset + remaining, planned.edge_deletes.len);        remaining -= edge_delete_end - edge_delete_offset;        const node_delete_end = @min(node_delete_offset + remaining, planned.node_deletes.len);        const node_puts = plan.materializeNodePuts(graph, planned.node_puts[node_put_offset..node_put_end]);        const edge_puts = plan.materializeEdgePuts(graph, planned.edge_puts[edge_put_offset..edge_put_end]);        try edits.apply(store_database, .{            .node_puts = node_puts,            .node_deletes = planned.node_deletes[node_delete_offset..node_delete_end],            .edge_puts = edge_puts,            .edge_deletes = planned.edge_deletes[edge_delete_offset..edge_delete_end],        });        try database.commitIfDirty(store_database);        node_put_offset = node_put_end;        node_delete_offset = node_delete_end;        edge_put_offset = edge_put_end;        edge_delete_offset = edge_delete_end;        batches += 1;    }    return batches;}fn fillNodeDiff(    allocator: std.mem.Allocator,    store_database: *database.Database,    graph: graph_mod.Graph,    plan: *GraphPlan,) !void {    var handle = store_database.connection.catalog.openRelation(        store_database.allocator,        rows.nodes_relation,    ) catch |err| switch (err) {        error.RelationNotFound => return fillNewNodes(0, plan),        else => return err,    };    defer handle.deinit();    var scan: sql.TableScan = undefined;    try handle.relation.scan(&scan, store_database.allocator, null, null);    defer scan.deinit();    var row_arena = std.heap.ArenaAllocator.init(allocator);    defer row_arena.deinit();    var max_rowid: i64 = 0;    while (try scan.next()) |entry| {        max_rowid = @max(max_rowid, entry.rowid);        const name = try rows.nodeNameView(entry.bytes);        const desired_index = graph.node_index.get(name) orelse {            plan.deleteNode(entry.rowid);            continue;        };        if (!plan.markNode(desired_index)) {            plan.deleteNode(entry.rowid);            continue;        }        _ = row_arena.reset(.retain_capacity);        const loaded = try rows.decodeNodeRow(            row_arena.allocator(),            entry.rowid,            entry.bytes,        );        if (!model.nodeEquals(loaded.node, graph.nodes.items[desired_index])) {            plan.putNode(entry.rowid, desired_index);        }    }    try fillNewNodes(max_rowid, plan);}fn fillEdgeDiff(    allocator: std.mem.Allocator,    store_database: *database.Database,    graph: graph_mod.Graph,    plan: *GraphPlan,) !void {    const key_buffer = try allocSlice(u8, allocator, maxEdgeKeyLen(graph.edges.items));    defer freeSlice(u8, allocator, key_buffer);    var handle = store_database.connection.catalog.openRelation(        store_database.allocator,        rows.edges_relation,    ) catch |err| switch (err) {        error.RelationNotFound => return fillNewEdges(0, plan),        else => return err,    };    defer handle.deinit();    var scan: sql.TableScan = undefined;    try handle.relation.scan(&scan, store_database.allocator, null, null);    defer scan.deinit();    var row_arena = std.heap.ArenaAllocator.init(allocator);    defer row_arena.deinit();    var max_rowid: i64 = 0;    while (try scan.next()) |entry| {        max_rowid = @max(max_rowid, entry.rowid);        const desired_index = try desiredEdgeIndex(graph, entry.bytes, key_buffer);        if (desired_index == null or !plan.markEdge(desired_index.?)) {            plan.deleteEdge(entry.rowid);            continue;        }        _ = row_arena.reset(.retain_capacity);        const loaded = try rows.decodeEdgeRow(            row_arena.allocator(),            entry.rowid,            entry.bytes,        );        if (!model.edgeEquals(loaded.edge, graph.edges.items[desired_index.?])) {            plan.putEdge(entry.rowid, desired_index.?);        }    }    try fillNewEdges(max_rowid, plan);}fn fillNewNodes(    max_rowid: i64,    plan: *GraphPlan,) !void {    var next_rowid: ?i64 = std.math.add(i64, max_rowid, 1) catch null;    for (plan.node_order) |order| {        const index: usize = order;        if (plan.nodeSeen(index)) continue;        plan.putNode(next_rowid orelse return error.RowIdOverflow, index);        next_rowid = std.math.add(i64, next_rowid.?, 1) catch null;    }}fn fillNewEdges(    max_rowid: i64,    plan: *GraphPlan,) !void {    var next_rowid: ?i64 = std.math.add(i64, max_rowid, 1) catch null;    for (plan.edge_order) |order| {        const index: usize = order;        if (plan.edgeSeen(index)) continue;        plan.putEdge(next_rowid orelse return error.RowIdOverflow, index);        next_rowid = std.math.add(i64, next_rowid.?, 1) catch null;    }}fn desiredEdgeIndex(    graph: graph_mod.Graph,    bytes: []const u8,    key_buffer: []u8,) rows.Error!?usize {    const source = try rows.edgeSourceView(bytes);    const rel = try rows.edgeRelView(bytes);    const target = try rows.edgeTargetView(bytes);    const key_len = graph_mod.edgeKeyLen(source, rel, target);    if (key_len > key_buffer.len) return null;    const key = graph_mod.edgeKeyInto(key_buffer[0..key_len], source, rel, target);    return graph.edge_index.get(key);}fn maxEdgeKeyLen(edges: []const model.Edge) usize {    var maximum: usize = 0;    for (edges) |edge| {        maximum = @max(            maximum,            graph_mod.edgeKeyLen(edge.source, edge.rel, edge.target),        );    }    return maximum;}fn nodeIndexLess(nodes: []const model.Node, left: GraphIndex, right: GraphIndex) bool {    const order = std.mem.order(u8, nodes[left].name, nodes[right].name);    if (order != .eq) return order == .lt;    return left < right;}const EdgeSort = struct {    edges: []const model.Edge,    key_ptrs: []const [*]const u8,    encoded_order: bool,};fn edgeIndexLess(context: EdgeSort, left: GraphIndex, right: GraphIndex) bool {    const left_edge = context.edges[left];    const right_edge = context.edges[right];    if (!context.encoded_order) return edgeFieldIndexLess(left_edge, right_edge, left, right);    const left_key = context.key_ptrs[left][0..graph_mod.edgeKeyLen(left_edge.source, left_edge.rel, left_edge.target)];    const right_key = context.key_ptrs[right][0..graph_mod.edgeKeyLen(right_edge.source, right_edge.rel, right_edge.target)];    const order = std.mem.order(u8, left_key, right_key);    if (order != .eq) return order == .lt;    return left < right;}fn edgeFieldIndexLess(left_edge: model.Edge, right_edge: model.Edge, left: GraphIndex, right: GraphIndex) 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;    const target = std.mem.order(u8, left_edge.target, right_edge.target);    if (target != .eq) return target == .lt;    return left < right;}fn encodedEdgeOrderMatchesFields(edges: []const model.Edge) bool {    for (edges) |edge| {        for (edge.source) |byte| if (byte <= graph_mod.edge_key_separator) return false;        for (edge.rel) |byte| if (byte <= graph_mod.edge_key_separator) return false;    }    return true;}fn bitmapBytes(count: usize) usize {    return count / 8 + @intFromBool(count % 8 != 0);}fn markBit(bitmap: []u8, index: usize) bool {    const byte_index = index / 8;    const mask = bitMask(index);    if (bitmap[byte_index] & mask != 0) return false;    bitmap[byte_index] |= mask;    return true;}fn bitSet(bitmap: []const u8, index: usize) bool {    return bitmap[index / 8] & bitMask(index) != 0;}fn bitMask(index: usize) u8 {    return @as(u8, 1) << @intCast(index % 8);}fn graphPlanBytes(limits: GraphPlan.Limits) error{CapacityOverflow}!usize {    if (limits.desired_nodes > std.math.maxInt(u32)) return error.CapacityOverflow;    if (limits.desired_edges > std.math.maxInt(u32)) return error.CapacityOverflow;    var total: usize = 0;    total = try addPlanBytes(total, limits.desired_nodes, @sizeOf(GraphIndex));    total = try addPlanBytes(total, limits.desired_nodes, @sizeOf(PlannedPut));    total = try addPlanBytes(total, limits.stored_nodes, @sizeOf(i64));    total = try addPlanBytes(total, limits.desired_edges, @sizeOf(GraphIndex));    total = try addPlanBytes(total, limits.desired_edges, @sizeOf([*]const u8));    total = try addPlanBytes(total, limits.desired_edges, @sizeOf(PlannedPut));    total = try addPlanBytes(total, limits.stored_edges, @sizeOf(i64));    total = try addPlanBytes(total, bitmapBytes(limits.desired_nodes), @sizeOf(u8));    total = try addPlanBytes(total, bitmapBytes(limits.desired_edges), @sizeOf(u8));    total = try addPlanBytes(total, @min(limits.desired_nodes, limits.batch_size), @sizeOf(rows.NodePut));    return try addPlanBytes(total, @min(limits.desired_edges, limits.batch_size), @sizeOf(rows.EdgePut));}fn addPlanBytes(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);}test "graph edits commit in bounded batches" {    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var opened = try database.open(std.testing.allocator, tmp.dir, smg.default_limits.storage);    defer opened.deinit();    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = "a", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "b", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "c", .type = model.NodeType.module });    var plan = try GraphPlan.init(std.testing.allocator, .{        .desired_nodes = 3,        .stored_nodes = 0,        .desired_edges = 0,        .stored_edges = 0,        .batch_size = 2,    });    defer plan.deinit(std.testing.allocator);    plan.order(graph);    plan.activate();    plan.putNode(1, 0);    plan.putNode(2, 1);    plan.putNode(3, 2);    try std.testing.expectEqual(@as(usize, 2), try applyBatches(&opened, graph, &plan));    const loaded = try read.databaseNodes(&opened, std.testing.allocator, std.testing.allocator);    defer rows.freeNodeRowSlice(std.testing.allocator, std.testing.allocator, loaded);    try std.testing.expectEqual(@as(usize, 3), loaded.len);    try std.testing.expectError(error.InvalidEditBatchSize, GraphPlan.Capacity.derive(.{        .desired_nodes = 0,        .stored_nodes = 0,        .desired_edges = 0,        .stored_edges = 0,        .batch_size = 0,    }));}fn modelGraphPlanBytes(limits: GraphPlan.Limits) ?usize {    if (limits.batch_size == 0) return null;    if (limits.desired_nodes > std.math.maxInt(u32)) return null;    if (limits.desired_edges > std.math.maxInt(u32)) return null;    const counts = [_]usize{        limits.desired_nodes,        limits.desired_nodes,        limits.stored_nodes,        limits.desired_edges,        limits.desired_edges,        limits.desired_edges,        limits.stored_edges,        limits.desired_nodes / 8 + @intFromBool(limits.desired_nodes % 8 != 0),        limits.desired_edges / 8 + @intFromBool(limits.desired_edges % 8 != 0),        @min(limits.desired_nodes, limits.batch_size),        @min(limits.desired_edges, limits.batch_size),    };    const sizes = [_]usize{        @sizeOf(GraphIndex),        @sizeOf(PlannedPut),        @sizeOf(i64),        @sizeOf(GraphIndex),        @sizeOf([*]const u8),        @sizeOf(PlannedPut),        @sizeOf(i64),        @sizeOf(u8),        @sizeOf(u8),        @sizeOf(rows.NodePut),        @sizeOf(rows.EdgePut),    };    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 diff plan capacity matches an independent typed storage model" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(GraphPlan, "smg_graph_diff_plan_capacity_capacity_model"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(GraphPlan, "smg_graph_diff_plan_capacity_overload"),            null,            null,            null,            null,            null,            null,        );    }    for (0..64) |count| {        const limits = GraphPlan.Limits{            .desired_nodes = count,            .stored_nodes = count + 1,            .desired_edges = count * 2,            .stored_edges = count * 3,            .batch_size = 7,        };        const capacity = try GraphPlan.Capacity.derive(limits);        try std.testing.expectEqual(modelGraphPlanBytes(limits).?, capacity.bytes);    }    try std.testing.expect(@sizeOf(PlannedPut) < @sizeOf(rows.NodePut));    try std.testing.expect(@sizeOf(PlannedPut) < @sizeOf(rows.EdgePut));    try std.testing.expect(@sizeOf(GraphIndex) < @sizeOf(model.Node));    try std.testing.expect(@sizeOf(GraphIndex) < @sizeOf(model.Edge));    const overflow = GraphPlan.Limits{        .desired_nodes = std.math.maxInt(usize),        .stored_nodes = 0,        .desired_edges = 0,        .stored_edges = 0,        .batch_size = 1,    };    try std.testing.expect(modelGraphPlanBytes(overflow) == null);    try std.testing.expectError(        error.CapacityOverflow,        GraphPlan.Capacity.derive(overflow),    );}test "graph diff plan packs seen bits across byte boundaries" {    var bitmap = [_]u8{ 0, 0 };    try std.testing.expect(markBit(&bitmap, 0));    try std.testing.expect(markBit(&bitmap, 8));    try std.testing.expect(!markBit(&bitmap, 0));    try std.testing.expect(bitSet(&bitmap, 0));    try std.testing.expect(!bitSet(&bitmap, 7));    try std.testing.expect(bitSet(&bitmap, 8));}test "graph edge index ordering preserves field tuple semantics" {    const edges = [_]model.Edge{        .{ .source = "a", .rel = model.RelType.calls, .target = "z" },        .{ .source = "a\n", .rel = model.RelType.calls, .target = "z" },    };    var first_key: [graph_mod.edgeKeyLen("a", model.RelType.calls, "z")]u8 = undefined;    var second_key: [graph_mod.edgeKeyLen("a\n", model.RelType.calls, "z")]u8 = undefined;    const first = graph_mod.edgeKeyInto(&first_key, edges[0].source, edges[0].rel, edges[0].target);    const second = graph_mod.edgeKeyInto(&second_key, edges[1].source, edges[1].rel, edges[1].target);    const key_ptrs = [_][*]const u8{ first.ptr, second.ptr };    const context = EdgeSort{        .edges = &edges,        .key_ptrs = &key_ptrs,        .encoded_order = encodedEdgeOrderMatchesFields(&edges),    };    try std.testing.expect(!context.encoded_order);    try std.testing.expect(edgeIndexLess(context, 0, 1));}fn checkGraphPlanInitAllocationFailures(allocator: std.mem.Allocator) !void {    var plan = try GraphPlan.init(allocator, .{        .desired_nodes = 3,        .stored_nodes = 4,        .desired_edges = 5,        .stored_edges = 6,        .batch_size = 2,    });    plan.deinit(allocator);}test "graph diff plan initialization cleans allocation failure and retries" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(GraphPlan, "smg_graph_diff_plan_oom"),            null,            null,            null,            null,            null,            null,        );    }    try std.testing.checkAllAllocationFailures(        std.testing.allocator,        checkGraphPlanInitAllocationFailures,        .{},    );    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = "a", .type = model.NodeType.module });    try graph_mod.addEdge(&graph, .{ .source = "a", .target = "a", .rel = model.RelType.calls });    var plan = try GraphPlan.init(std.testing.allocator, .{        .desired_nodes = 1,        .stored_nodes = 1,        .desired_edges = 1,        .stored_edges = 1,        .batch_size = 1,    });    defer plan.deinit(std.testing.allocator);    plan.order(graph);    plan.activate();    try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, plan.phase);}test "graph diff plan stays sealed while filling bounded storage" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(GraphPlan, "smg_graph_diff_plan_sealed"),            null,            null,            null,            null,            null,            null,        );    }    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = "b", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "a", .type = model.NodeType.module });    try graph_mod.addEdge(&graph, .{ .source = "b", .target = "a", .rel = model.RelType.calls });    try graph_mod.addEdge(&graph, .{ .source = "a", .target = "b", .rel = model.RelType.contains });    var phase_allocator = try alloc_phase.SealedPhaseAllocator.init(        std.testing.allocator,    );    var plan = try GraphPlan.init(phase_allocator.initializationAllocator(), .{        .desired_nodes = 2,        .stored_nodes = 2,        .desired_edges = 2,        .stored_edges = 2,        .batch_size = 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_puts_pointer = plan.node_puts.ptr;    const edge_puts_pointer = plan.edge_puts.ptr;    const batch_node_puts_pointer = plan.batch_node_puts.ptr;    const batch_edge_puts_pointer = plan.batch_edge_puts.ptr;    phase_allocator.seal();    plan.order(graph);    plan.activate();    try std.testing.expectEqual(@as(u32, 1), plan.node_order[0]);    try std.testing.expectEqual(@as(u32, 0), plan.node_order[1]);    try std.testing.expectEqual(@as(u32, 1), plan.edge_order[0]);    try std.testing.expectEqual(@as(u32, 0), plan.edge_order[1]);    _ = plan.markNode(0);    _ = plan.markEdge(1);    plan.putNode(1, 0);    plan.deleteNode(2);    plan.putEdge(3, 1);    plan.deleteEdge(4);    const graph_edits = plan.edits();    try std.testing.expectEqual(@as(usize, 1), graph_edits.node_puts.len);    try std.testing.expectEqual(@as(usize, 1), graph_edits.edge_puts.len);    try std.testing.expectEqualStrings("b", plan.materializeNodePuts(graph, graph_edits.node_puts)[0].node.name);    try std.testing.expectEqualStrings(model.RelType.contains, plan.materializeEdgePuts(graph, graph_edits.edge_puts)[0].edge.rel);    try std.testing.expectEqual(node_puts_pointer, plan.node_puts.ptr);    try std.testing.expectEqual(edge_puts_pointer, plan.edge_puts.ptr);    try std.testing.expectEqual(batch_node_puts_pointer, plan.batch_node_puts.ptr);    try std.testing.expectEqual(batch_edge_puts_pointer, plan.batch_edge_puts.ptr);    try std.testing.expectEqual(@as(u64, 0), phase_allocator.violations().total());}test "graph replacement streams stored rows and preserves stable rowids" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(GraphPlan, "smg_graph_diff_plan_streaming_transitive_risk"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(GraphPlan, "smg_graph_diff_plan_streaming_foreign_risk"),            null,            null,            null,            null,            null,            null,        );    }    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    var opened = try database.open(std.testing.allocator, tmp.dir, smg.default_limits.storage);    defer opened.deinit();    try edits.apply(&opened, .{        .node_puts = &.{            .{ .rowid = 5, .node = .{ .name = "a", .type = "module", .line = 1 } },            .{ .rowid = 8, .node = .{ .name = "a", .type = "module", .line = 2 } },            .{ .rowid = 9, .node = .{ .name = "orphan", .type = "module" } },        },        .edge_puts = &.{            .{ .rowid = 3, .edge = .{ .source = "a", .target = "b", .rel = "contains" } },            .{ .rowid = 4, .edge = .{ .source = "a", .target = "b", .rel = "contains" } },            .{ .rowid = 7, .edge = .{ .source = "orphan", .target = "a", .rel = "calls" } },        },    });    _ = try database.commit(&opened);    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = "b", .type = "module" });    try graph_mod.addNode(&graph, .{ .name = "a", .type = "module", .line = 42 });    try graph_mod.addEdge(&graph, .{ .source = "b", .target = "a", .rel = "calls" });    try graph_mod.addEdge(&graph, .{ .source = "a", .target = "b", .rel = "contains" });    try replaceGraph(std.testing.allocator, &opened, graph);    const loaded_nodes = try read.databaseNodes(&opened, std.testing.allocator, std.testing.allocator);    defer rows.freeNodeRowSlice(std.testing.allocator, std.testing.allocator, loaded_nodes);    const loaded_edges = try read.databaseEdges(&opened, std.testing.allocator, std.testing.allocator);    defer rows.freeEdgeRowSlice(std.testing.allocator, std.testing.allocator, loaded_edges);    try std.testing.expectEqual(@as(usize, 2), loaded_nodes.len);    try std.testing.expectEqual(@as(i64, 5), loaded_nodes[0].rowid);    try std.testing.expectEqual(@as(?i64, 42), loaded_nodes[0].node.line);    try std.testing.expectEqual(@as(i64, 10), loaded_nodes[1].rowid);    try std.testing.expectEqual(@as(usize, 2), loaded_edges.len);    try std.testing.expectEqual(@as(i64, 3), loaded_edges[0].rowid);    try std.testing.expectEqual(@as(i64, 8), loaded_edges[1].rowid);}

Complete call list for storage.sync.replaceGraphObserved

10 direct calls.

Audit

Definitions17
Public names26
Members39
Version26.7.0
Revisiondaab053ee433