tiny.smg.storage.context
Defined in storage.
API (8)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
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.
tools.smg.src.storage.context.CandidateNames.deinit[method] — private; no exact target attools/smg/src/storage/context.zig:226in nearest public ownertiny.smg.storage.contexttools.smg.src.storage.context.CandidateNames.init[function] — private; no exact target attools/smg/src/storage/context.zig:164in nearest public ownertiny.smg.storage.contexttools.smg.src.storage.context.CandidateNames.take[method] — private; no exact target attools/smg/src/storage/context.zig:206in nearest public ownertiny.smg.storage.contexttools.smg.src.storage.context.exactNodeName[function] — private; no exact target attools/smg/src/storage/context.zig:107in nearest public ownertiny.smg.storage.contexttools.smg.src.storage.context.indexSlot[function] — private; no exact target attools/smg/src/storage/context.zig:140in nearest public ownertiny.smg.storage.contexttools.smg.src.storage.context.scanIndexedNames[function] — private; no exact target attools/smg/src/storage/context.zig:90in nearest public ownertiny.smg.storage.contexttiny.smg.storage.names.scan[function] attools/smg/src/storage/names.zig:43
Audit
| Definitions | 9 |
|---|---|
| Public names | 9 |
| Members | 7 |
| Version | 26.7.0 |
| Revision | daab053ee433 |