Skip to documentation
SLOP

tiny.smg.storage.context

Reference tiny.smg storage context

Defined in storage.

API (8)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallstest; no linktools.smg.src.storage.contexttest: load node context returns shall...storage.contextloadFromReaderstorage.storeopenReadstorage.contextload
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.command.about.commandstoredstorage.contextloadtraversal.SourcenodemodeldeinitNodeprivate; no linktools.smg.src.storage.contextloadEdgesprivate; no linktools.smg.src.storage.contextnodeByNamestorage.contextloadFromReader
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callersstorage.contextloadNodesFromReaderstorage.storeopenReadstorage.contextloadNodes
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsstorage.contextloadNodesmodeldeinitNodeprivate; no linktools.smg.src.storage.contextfindNodeprivate; no linktools.smg.src.storage.contextnodeByNamestorage.contextloadNodesFromReader
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallstest; no linktools.smg.src.storage.contexttest: resolve node name finds exact s...storage.contextresolveNodeNameFromReaderstorage.storeopenReadstorage.contextresolveNodeName
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallscli.resolvestoredNodeprivate; no linktools.smg.src.storage.contextcheckResolveFallbackAllocationFailuresprivate; no linktools.smg.src.storage.contextexpectProjectionMatchesFallbackstorage.contextresolveNodeNameprivate; no linktools.smg.src.storage.context.CandidateNamesdeinitprivate; no linktools.smg.src.storage.context.CandidateNamesinitprivate; no linktools.smg.src.storage.context.CandidateNamestakeprivate; no linktools.smg.src.storage.contextexactNodeNameprivate; no linktools.smg.src.storage.contextindexSlot+2 morestorage.contextresolveNodeNameFromReader
Static calls · unresolved targets: 0 · external targets: 4.

Source: tools/smg/src/storage/context.zig

zig
const std = @import("std");const sql = @import("sql");const sys = @import("sys");const smg = @import("../root.zig");const storage = smg.storage;const database = storage.database;const store = storage.store;const sync = storage.sync;const graph_mod = smg.graph;const model = smg.model;const name_mod = smg.name;const view = smg.view;pub const NameResolution = union(enum) {    resolved: []const u8,    missing: []const []const u8,    ambiguous: []const []const u8,};pub const NodeContext = struct {    node: model.Node,    incoming: []const model.Edge,    outgoing: []const model.Edge,    path: []const []const u8,};const EdgeContext = struct {    incoming: []const model.Edge,    outgoing: []const model.Edge,    path: []const []const u8,};pub fn resolveNodeName(allocator: std.mem.Allocator, root: []const u8, raw: []const u8, limits: smg.Limits) !NameResolution {    var opened = try store.openRead(allocator, root, limits.storage);    defer opened.close();    return try resolveNodeNameFromReader(allocator, root, &opened.reader, raw, limits.suggestions);}pub fn resolveNodeNameFromReader(    allocator: std.mem.Allocator,    root: []const u8,    reader: *database.Reader,    raw: []const u8,    limits: smg.SuggestionLimits,) !NameResolution {    var handle = try reader.catalog.openRelation(reader.allocator, storage.rows.nodes_relation);    defer handle.deinit();    const slot = indexSlot(&handle, storage.rows.node_name_index) orelse        return error.StorageIndexMissing;    if (try exactNodeName(allocator, reader.allocator, &handle, slot, raw)) |exact| {        return .{ .resolved = exact };    }    const translated = try name_mod.dottedFromPath(allocator, raw);    defer if (translated) |name| allocator.free(name);    var translated_exact = if (translated) |name|        try exactNodeName(allocator, reader.allocator, &handle, slot, name)    else        null;    defer if (translated_exact) |name| allocator.free(name);    var candidates = try CandidateNames.init(        allocator,        raw,        translated,        translated_exact == null,        limits,    );    defer candidates.deinit();    var projection_ready = true;    storage.names.scan(allocator, root, reader.head, &candidates) catch |err| switch (err) {        error.OutOfMemory => return err,        else => projection_ready = false,    };    if (!projection_ready) {        const replacement = try CandidateNames.init(            allocator,            raw,            translated,            translated_exact == null,            limits,        );        candidates.deinit();        candidates = replacement;        try scanIndexedNames(reader.allocator, &handle, slot, &candidates);    }    return try candidates.take(&translated_exact);}fn scanIndexedNames(    allocator: std.mem.Allocator,    handle: anytype,    slot: usize,    candidates: *CandidateNames,) !void {    var scan: sql.IndexScan = undefined;    try handle.relation.indexScan(&scan, allocator, slot, null, null);    defer scan.deinit();    var values: [1]sql.row.Value = undefined;    var key_scratch: [sql.page.size]u8 = undefined;    while (try scan.next()) |entry| {        const name = try storage.names.indexedName(&values, &key_scratch, entry.key);        try candidates.visit(name);    }}fn exactNodeName(    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    handle: anytype,    slot: usize,    name: []const u8,) !?[]const u8 {    var lookup: sql.IndexScan = undefined;    try handle.relation.lookup(&lookup, scratch, slot, &.{.{ .text = name }});    defer lookup.deinit();    const entry = (try lookup.next()) orelse return null;    var values: [1]sql.row.Value = undefined;    var key_scratch: [sql.page.size]u8 = undefined;    const indexed_name = try storage.names.indexedName(&values, &key_scratch, entry.key);    return try allocator.dupe(u8, indexed_name);}fn nodeByName(allocator: std.mem.Allocator, scratch: std.mem.Allocator, handle: anytype, name: []const u8) !?model.Node {    const rowid = (try firstIndexedRowid(scratch, handle, storage.rows.node_name_index, name)) orelse return null;    const bytes = (try handle.relation.get(allocator, rowid)) orelse return error.StorageIndexCorrupt;    defer allocator.free(bytes);    return (try storage.rows.decodeNodeRow(allocator, rowid, bytes)).node;}fn firstIndexedRowid(scratch: std.mem.Allocator, handle: anytype, index_name: []const u8, value: []const u8) !?i64 {    const slot = indexSlot(handle, index_name) orelse return error.StorageIndexMissing;    var lookup: sql.IndexScan = undefined;    try handle.relation.lookup(&lookup, scratch, slot, &.{.{ .text = value }});    defer lookup.deinit();    const entry = (try lookup.next()) orelse return null;    return entry.rowid;}fn indexSlot(handle: anytype, name: []const u8) ?usize {    for (handle.index_definitions, 0..) |definition, slot| {        if (std.mem.eql(u8, definition.name, name)) return slot;    }    return null;}fn freeNames(allocator: std.mem.Allocator, names: []const []const u8) void {    for (names) |name| allocator.free(name);    if (names.len != 0) allocator.free(names);}const CandidateNames = struct {    allocator: std.mem.Allocator,    raw: []const u8,    fallback_query: []const u8,    translated: bool,    include_fallback: bool,    limits: smg.SuggestionLimits,    suffixes: OwnedNames = .{},    fallback_suffixes: OwnedNames = .{},    subsequences: OwnedNames = .{},    suggestions: Suggestions,    fn init(        allocator: std.mem.Allocator,        raw: []const u8,        translated: ?[]const u8,        include_fallback: bool,        limits: smg.SuggestionLimits,    ) !CandidateNames {        return .{            .allocator = allocator,            .raw = raw,            .fallback_query = translated orelse raw,            .translated = translated != null,            .include_fallback = include_fallback,            .limits = limits,            .suggestions = try Suggestions.init(allocator, raw, limits),        };    }    pub fn visit(self: *CandidateNames, name: []const u8) std.mem.Allocator.Error!void {        if (name_mod.matchesSuffix(name, self.raw)) {            try self.suffixes.append(self.allocator, name, null);        }        if (!self.include_fallback) return;        if (self.translated and name_mod.matchesSuffix(name, self.fallback_query)) {            try self.fallback_suffixes.append(                self.allocator,                name,                self.limits.fallback_match_count,            );        }        if (name_mod.matchesSubsequence(name, self.fallback_query)) {            try self.subsequences.append(                self.allocator,                name,                self.limits.fallback_match_count,            );        }        if (!std.mem.eql(u8, name, self.raw)) {            try self.suggestions.consider(self.allocator, name);        }    }    fn take(        self: *CandidateNames,        translated_exact: *?[]const u8,    ) !NameResolution {        if (self.suffixes.items.items.len != 0) {            return try self.suffixes.takeResolution(self.allocator);        }        if (translated_exact.*) |name| {            translated_exact.* = null;            return .{ .resolved = name };        }        if (self.fallback_suffixes.items.items.len != 0) {            return try self.fallback_suffixes.takeResolution(self.allocator);        }        if (self.subsequences.items.items.len != 0) {            return try self.subsequences.takeResolution(self.allocator);        }        return .{ .missing = try self.suggestions.take(self.allocator) };    }    fn deinit(self: *CandidateNames) void {        self.suffixes.deinit(self.allocator);        self.fallback_suffixes.deinit(self.allocator);        self.subsequences.deinit(self.allocator);        self.suggestions.deinit(self.allocator);        self.* = undefined;    }};const OwnedNames = struct {    items: std.ArrayList([]const u8) = .empty,    overflowed: bool = false,    fn append(        self: *OwnedNames,        allocator: std.mem.Allocator,        name: []const u8,        limit: ?usize,    ) !void {        if (self.overflowed) return;        if (limit) |maximum| {            if (self.items.items.len == maximum) {                self.clear(allocator);                self.overflowed = true;                return;            }        }        const owned = try allocator.dupe(u8, name);        errdefer allocator.free(owned);        try self.items.append(allocator, owned);    }    fn takeResolution(        self: *OwnedNames,        allocator: std.mem.Allocator,    ) !NameResolution {        std.debug.assert(self.items.items.len != 0);        std.mem.sort([]const u8, self.items.items, {}, stringLess);        if (self.items.items.len == 1) {            const name = self.items.items[0];            self.items.deinit(allocator);            self.items = .empty;            return .{ .resolved = name };        }        const names = try self.items.toOwnedSlice(allocator);        self.items = .empty;        return .{ .ambiguous = names };    }    fn clear(self: *OwnedNames, allocator: std.mem.Allocator) void {        for (self.items.items) |name| allocator.free(name);        self.items.clearRetainingCapacity();    }    fn deinit(self: *OwnedNames, allocator: std.mem.Allocator) void {        self.clear(allocator);        self.items.deinit(allocator);        self.* = .{};    }};const SuggestedName = struct {    name: []const u8,    rank: view.SuggestionRank,};const Suggestions = struct {    query: ?view.SuggestionQuery,    items: []SuggestedName,    count: usize = 0,    fn init(allocator: std.mem.Allocator, raw: []const u8, limits: smg.SuggestionLimits) !Suggestions {        const items = try allocator.alloc(SuggestedName, limits.displayed_name_count);        errdefer allocator.free(items);        return .{            .query = try view.SuggestionQuery.init(allocator, raw, limits),            .items = items,        };    }    fn consider(        self: *Suggestions,        allocator: std.mem.Allocator,        name: []const u8,    ) !void {        const query = if (self.query) |*query| query else return;        const rank = query.rank(name) orelse return;        const candidate = SuggestedName{ .name = name, .rank = rank };        if (self.count == self.items.len and            !suggestedNameLess({}, candidate, self.items[self.count - 1]))        {            return;        }        const owned = try allocator.dupe(u8, name);        if (self.count == self.items.len) {            allocator.free(self.items[self.count - 1].name);            self.items[self.count - 1] = .{ .name = owned, .rank = rank };        } else {            self.items[self.count] = .{ .name = owned, .rank = rank };            self.count += 1;        }        std.mem.sort(SuggestedName, self.items[0..self.count], {}, suggestedNameLess);    }    fn take(self: *Suggestions, allocator: std.mem.Allocator) ![]const []const u8 {        if (self.count == 0) return &.{};        const names = try allocator.alloc([]const u8, self.count);        for (self.items[0..self.count], 0..) |entry, index| names[index] = entry.name;        self.count = 0;        return names;    }    fn deinit(self: *Suggestions, allocator: std.mem.Allocator) void {        for (self.items[0..self.count]) |entry| allocator.free(entry.name);        if (self.query) |*query| query.deinit();        allocator.free(self.items);        self.* = undefined;    }};fn suggestedNameLess(_: void, a: SuggestedName, b: SuggestedName) bool {    return view.suggestionLessThan(a.name, a.rank, b.name, b.rank);}pub fn load(allocator: std.mem.Allocator, root: []const u8, name: []const u8, include_edges: bool, limits: smg.StorageLimits) !NodeContext {    var opened = try store.openRead(allocator, root, limits);    defer opened.close();    return try loadFromReader(allocator, &opened.reader, name, include_edges);}pub fn loadFromReader(allocator: std.mem.Allocator, reader: *database.Reader, name: []const u8, include_edges: bool) !NodeContext {    var node_handle = try reader.catalog.openRelation(reader.allocator, storage.rows.nodes_relation);    defer node_handle.deinit();    var loaded_node = (try nodeByName(allocator, reader.allocator, &node_handle, name)) orelse return error.NodeNotFound;    errdefer model.deinitNode(&loaded_node, allocator);    if (!include_edges) return .{        .node = loaded_node,        .incoming = &.{},        .outgoing = &.{},        .path = &.{},    };    const edges = try loadEdges(allocator, reader, name);    return .{        .node = loaded_node,        .incoming = edges.incoming,        .outgoing = edges.outgoing,        .path = edges.path,    };}pub fn loadNodes(allocator: std.mem.Allocator, root: []const u8, names: []const []const u8, limits: smg.StorageLimits) ![]model.Node {    var opened = try store.openRead(allocator, root, limits);    defer opened.close();    return try loadNodesFromReader(allocator, &opened.reader, names);}pub fn loadNodesFromReader(allocator: std.mem.Allocator, reader: *database.Reader, names: []const []const u8) ![]model.Node {    var handle = try reader.catalog.openRelation(reader.allocator, storage.rows.nodes_relation);    defer handle.deinit();    var loaded: std.ArrayList(model.Node) = .empty;    errdefer {        for (loaded.items) |*node| model.deinitNode(node, allocator);        loaded.deinit(allocator);    }    for (names) |name| {        if (findNode(loaded.items, name) != null) continue;        if (try nodeByName(allocator, reader.allocator, &handle, name)) |node| try loaded.append(allocator, node);    }    return try loaded.toOwnedSlice(allocator);}fn findNode(loaded: []const model.Node, name: []const u8) ?model.Node {    for (loaded) |node| {        if (std.mem.eql(u8, node.name, name)) return node;    }    return null;}fn loadEdges(allocator: std.mem.Allocator, reader: *database.Reader, name: []const u8) !EdgeContext {    var handle = try reader.catalog.openRelation(reader.allocator, storage.rows.edges_relation);    defer handle.deinit();    const incoming = try edgesByIndex(allocator, reader.allocator, &handle, storage.rows.edge_target_index, name);    const outgoing = try edgesByIndex(allocator, reader.allocator, &handle, storage.rows.edge_source_index, name);    std.mem.sort(model.Edge, incoming, {}, storedIncomingLess);    std.mem.sort(model.Edge, outgoing, {}, storedOutgoingLess);    return .{        .incoming = incoming,        .outgoing = outgoing,        .path = try storedContainmentPath(allocator, reader.allocator, &handle, name),    };}fn stringLess(_: void, a: []const u8, b: []const u8) bool {    return std.mem.lessThan(u8, a, b);}fn storedIncomingLess(_: void, a: model.Edge, b: model.Edge) bool {    const rel = std.mem.order(u8, a.rel, b.rel);    if (rel != .eq) return rel == .lt;    return std.mem.lessThan(u8, a.source, b.source);}fn storedOutgoingLess(_: void, a: model.Edge, b: model.Edge) bool {    const rel = std.mem.order(u8, a.rel, b.rel);    if (rel != .eq) return rel == .lt;    return std.mem.lessThan(u8, a.target, b.target);}fn edgesByIndex(allocator: std.mem.Allocator, scratch: std.mem.Allocator, handle: anytype, index_name: []const u8, value: []const u8) ![]model.Edge {    const slot = indexSlot(handle, index_name) orelse return error.StorageIndexMissing;    var lookup: sql.IndexScan = undefined;    try handle.relation.lookup(&lookup, scratch, slot, &.{.{ .text = value }});    defer lookup.deinit();    var edges: std.ArrayList(model.Edge) = .empty;    errdefer {        for (edges.items) |*edge| model.deinitEdge(edge, allocator);        edges.deinit(allocator);    }    while (try lookup.next()) |entry| {        const bytes = (try handle.relation.get(allocator, entry.rowid)) orelse return error.StorageIndexCorrupt;        const decoded = storage.rows.decodeEdgeRow(allocator, entry.rowid, bytes) catch |err| {            allocator.free(bytes);            return err;        };        allocator.free(bytes);        edges.append(allocator, decoded.edge) catch |err| {            var edge = decoded.edge;            model.deinitEdge(&edge, allocator);            return err;        };    }    return try edges.toOwnedSlice(allocator);}fn parentForTarget(allocator: std.mem.Allocator, scratch: std.mem.Allocator, handle: anytype, target: []const u8) !?[]const u8 {    const slot = indexSlot(handle, storage.rows.edge_target_index) orelse return error.StorageIndexMissing;    var lookup: sql.IndexScan = undefined;    try handle.relation.lookup(&lookup, scratch, slot, &.{.{ .text = target }});    defer lookup.deinit();    var best: ?[]const u8 = null;    errdefer if (best) |name| allocator.free(name);    while (try lookup.next()) |entry| {        const bytes = (try handle.relation.get(allocator, entry.rowid)) orelse return error.StorageIndexCorrupt;        const relation = storage.rows.edgeRelView(bytes) catch |err| {            allocator.free(bytes);            return err;        };        if (!std.mem.eql(u8, relation, model.RelType.contains)) {            allocator.free(bytes);            continue;        }        const source = storage.rows.edgeSourceView(bytes) catch |err| {            allocator.free(bytes);            return err;        };        if (best == null or std.mem.lessThan(u8, source, best.?)) {            const owned = try allocator.dupe(u8, source);            if (best) |previous| allocator.free(previous);            best = owned;        }        allocator.free(bytes);    }    return best;}fn containsName(names: []const []const u8, target: []const u8) bool {    for (names) |name| {        if (std.mem.eql(u8, name, target)) return true;    }    return false;}fn storedContainmentPath(allocator: std.mem.Allocator, scratch: std.mem.Allocator, handle: anytype, name: []const u8) ![]const []const u8 {    var reversed: std.ArrayList([]const u8) = .empty;    errdefer {        for (reversed.items) |entry| allocator.free(entry);        reversed.deinit(allocator);    }    try reversed.append(allocator, try allocator.dupe(u8, name));    var current = name;    while (try parentForTarget(allocator, scratch, handle, current)) |parent| {        if (containsName(reversed.items, parent)) {            allocator.free(parent);            break;        }        try reversed.append(allocator, parent);        current = parent;    }    std.mem.reverse([]const u8, reversed.items);    return try reversed.toOwnedSlice(allocator);}test "resolve node name finds exact suffix ambiguous and missing matches" {    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);    for ([_][]const u8{ "app", "app.helper", "app.main", "lib.main", "lib.pkg.src.core.merge" }) |name| try graph_mod.addNode(&stored, .{ .name = name, .type = model.NodeType.module });    try saveGraph(allocator, stored, root);    const exact = try resolveNodeName(allocator, root, "app.main", smg.default_limits);    try std.testing.expectEqualStrings("app.main", exact.resolved);    const suffix = try resolveNodeName(allocator, root, "helper", smg.default_limits);    try std.testing.expectEqualStrings("app.helper", suffix.resolved);    const missing = try resolveNodeName(allocator, root, "absent", smg.default_limits);    try std.testing.expectEqual(@as(usize, 0), missing.missing.len);    const typo = try resolveNodeName(allocator, root, "app.mian", smg.default_limits);    try std.testing.expect(typo.missing.len >= 1);    try std.testing.expectEqualStrings("app.main", typo.missing[0]);    const ambiguous = try resolveNodeName(allocator, root, "main", smg.default_limits);    try std.testing.expectEqual(@as(usize, 2), ambiguous.ambiguous.len);    try std.testing.expectEqualStrings("app.main", ambiguous.ambiguous[0]);    try std.testing.expectEqualStrings("lib.main", ambiguous.ambiguous[1]);    const path = try resolveNodeName(allocator, root, "lib/pkg/src/core/merge.zig", smg.default_limits);    try std.testing.expectEqualStrings("lib.pkg.src.core.merge", path.resolved);    const subsequence = try resolveNodeName(allocator, root, "pkg.core.merge", smg.default_limits);    try std.testing.expectEqualStrings("lib.pkg.src.core.merge", subsequence.resolved);    const path_missing = try resolveNodeName(allocator, root, "pkg/main.zig", smg.default_limits);    try std.testing.expect(path_missing.missing.len >= 2);    try std.testing.expectEqualStrings("app.main", path_missing.missing[0]);}const NameCount = struct {    count: usize = 0,    pub fn visit(self: *NameCount, _: []const u8) std.mem.Allocator.Error!void {        self.count += 1;    }};test "name projection preserves index fallback resolutions" {    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);    var stored = graph_mod.init(allocator);    for ([_][]const u8{        "app",        "app.helper",        "app.main",        "lib.main",        "lib.pkg.src.core.merge",    }) |name| {        try graph_mod.addNode(            &stored,            .{ .name = name, .type = model.NodeType.module },        );    }    try saveGraph(allocator, stored, root);    try writeNameProjection(allocator, root);    var opened = try store.openRead(allocator, root, smg.default_limits.storage);    defer opened.close();    try std.testing.expect(try storage.names.matchesHead(        allocator,        root,        opened.reader.head,    ));    var counted = NameCount{};    try storage.names.scan(allocator, root, opened.reader.head, &counted);    try std.testing.expectEqual(@as(usize, 5), counted.count);    try expectProjectionMatchesFallback(allocator, root, &opened.reader);}fn expectProjectionMatchesFallback(    allocator: std.mem.Allocator,    root: []const u8,    reader: *database.Reader,) !void {    const queries = [_][]const u8{        "app.main",        "helper",        "absent",        "app.mian",        "main",        "lib/pkg/src/core/merge.zig",        "pkg.core.merge",    };    var projected: [queries.len]NameResolution = undefined;    for (queries, 0..) |query, index| {        projected[index] = try resolveNodeNameFromReader(            allocator,            root,            reader,            query,            smg.default_limits.suggestions,        );    }    const projection_path = try std.fs.path.join(        allocator,        &.{ root, storage.paths.smg_dir_name, storage.names.file_name },    );    const hidden_path = try std.fmt.allocPrint(        allocator,        "{s}.hidden",        .{projection_path},    );    try sys.fs.rename(projection_path, hidden_path);    var hidden = true;    defer if (hidden) sys.fs.rename(hidden_path, projection_path) catch {};    for (queries, projected) |query, expected| {        const fallback = try resolveNodeNameFromReader(            allocator,            root,            reader,            query,            smg.default_limits.suggestions,        );        try expectNameResolutionEqual(expected, fallback);    }    try sys.fs.rename(hidden_path, projection_path);    hidden = false;}fn expectNameResolutionEqual(expected: NameResolution, actual: NameResolution) !void {    try std.testing.expectEqual(        std.meta.activeTag(expected),        std.meta.activeTag(actual),    );    switch (expected) {        .resolved => |name| try std.testing.expectEqualStrings(name, actual.resolved),        .missing => |names| try expectNameSlicesEqual(names, actual.missing),        .ambiguous => |names| try expectNameSlicesEqual(names, actual.ambiguous),    }}fn expectNameSlicesEqual(expected: []const []const u8, actual: []const []const u8) !void {    try std.testing.expectEqual(expected.len, actual.len);    for (expected, actual) |expected_name, actual_name| {        try std.testing.expectEqualStrings(expected_name, actual_name);    }}test "streamed suggestions preserve batch ranking" {    const allocator = std.testing.allocator;    const names = [_][]const u8{        "zeta.mark_status",        "epsilon.mark_status",        "delta.mark_status",        "gamma.mark_status",        "beta.mark_status",        "alpha.mark_status",        "eta.mark_status",    };    const expected = try view.suggest(        allocator,        &names,        "markstatus",        smg.default_limits.suggestions.displayed_name_count,        smg.default_limits.suggestions,    );    defer allocator.free(expected);    var streamed = try Suggestions.init(allocator, "markstatus", smg.default_limits.suggestions);    defer streamed.deinit(allocator);    for (names) |name| try streamed.consider(allocator, name);    const actual = try streamed.take(allocator);    defer freeNames(allocator, actual);    try std.testing.expectEqual(expected.len, actual.len);    for (expected, actual) |expected_name, actual_name| {        try std.testing.expectEqualStrings(expected_name, actual_name);    }}fn deinitNameResolution(allocator: std.mem.Allocator, resolution: NameResolution) void {    switch (resolution) {        .resolved => |name| allocator.free(name),        .missing, .ambiguous => |names| freeNames(allocator, names),    }}fn checkResolveFallbackAllocationFailures(    allocator: std.mem.Allocator,    root: []const u8,    reader: *database.Reader,) !void {    const resolution = try resolveNodeNameFromReader(        allocator,        root,        reader,        "app.mian",        smg.default_limits.suggestions,    );    defer deinitNameResolution(allocator, resolution);    try std.testing.expectEqualStrings("app.main", resolution.missing[0]);}test "resolve node name cleans every fallback allocation failure" {    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 = "app.main", .type = model.NodeType.module },    );    try saveGraph(allocator, stored, root);    var opened = try store.openRead(allocator, root, smg.default_limits.storage);    defer opened.close();    try std.testing.checkAllAllocationFailures(        std.testing.allocator,        checkResolveFallbackAllocationFailures,        .{ root, &opened.reader },    );}test "load node context returns shallow edges and containment path" {    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);    try graph_mod.addNode(&stored, .{ .name = "app", .type = model.NodeType.module });    try graph_mod.addNode(&stored, .{ .name = "app.main", .type = model.NodeType.function, .file = "src/app.zig", .line = 7 });    try graph_mod.addNode(&stored, .{ .name = "app.helper", .type = model.NodeType.function });    try graph_mod.addNode(&stored, .{ .name = "lib.util", .type = model.NodeType.function });    try graph_mod.addEdge(&stored, .{ .source = "app", .target = "app.main", .rel = model.RelType.contains });    try graph_mod.addEdge(&stored, .{ .source = "lib.util", .target = "app.main", .rel = model.RelType.calls });    try graph_mod.addEdge(&stored, .{ .source = "app.main", .target = "app.helper", .rel = model.RelType.imports });    try saveGraph(allocator, stored, root);    const shallow = try load(allocator, root, "app.main", false, smg.default_limits.storage);    try std.testing.expectEqualStrings("app.main", shallow.node.name);    try std.testing.expectEqual(@as(usize, 0), shallow.incoming.len);    try std.testing.expectEqual(@as(usize, 0), shallow.outgoing.len);    try std.testing.expectEqual(@as(usize, 0), shallow.path.len);    const loaded = try load(allocator, root, "app.main", true, smg.default_limits.storage);    try std.testing.expectEqualStrings("app.main", loaded.node.name);    try std.testing.expectEqualStrings("src/app.zig", loaded.node.file.?);    try std.testing.expectEqual(@as(?i64, 7), loaded.node.line);    try std.testing.expectEqual(@as(usize, 2), loaded.incoming.len);    try std.testing.expectEqualStrings(model.RelType.calls, loaded.incoming[0].rel);    try std.testing.expectEqualStrings(model.RelType.contains, loaded.incoming[1].rel);    try std.testing.expectEqual(@as(usize, 1), loaded.outgoing.len);    try std.testing.expectEqualStrings(model.RelType.imports, loaded.outgoing[0].rel);    try std.testing.expectEqual(@as(usize, 2), loaded.path.len);    try std.testing.expectEqualStrings("app", loaded.path[0]);    try std.testing.expectEqualStrings("app.main", loaded.path[1]);    try std.testing.expectError(error.NodeNotFound, load(allocator, root, "absent", true, smg.default_limits.storage));}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 saveGraph(allocator: std.mem.Allocator, graph: graph_mod.Graph, root: []const u8) !void {    var opened = try store.open(allocator, root, smg.default_limits.storage);    defer opened.close();    try sync.replaceGraph(allocator, &opened.database, graph);}fn writeNameProjection(allocator: std.mem.Allocator, root: []const u8) !void {    var opened = try store.open(allocator, root, smg.default_limits.storage);    defer opened.close();    const head = (try opened.database.connection.checkout()).head;    try storage.names.writeForHead(allocator, root, &opened.database, head);}fn testRoot(allocator: std.mem.Allocator, offset: i64) ![]const u8 {    return try std.fmt.allocPrint(allocator, "/tmp/smg-storage-context-test-{x}", .{@as(u64, @intCast(@max(0, sys.time.realMilliTimestamp() + offset)))});}

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

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

Complete call list for storage.context.resolveNodeNameFromReader

7 direct calls.

Audit

Definitions9
Public names9
Members7
Version26.7.0
Revisiondaab053ee433