tiny.smg.storage.nodes
Defined in storage.
API (9)
Actions
Public operations.
Types and contracts
Public types and contracts.
Source
Source: tools/smg/src/storage/rows/types.zig:18
Source: tools/smg/src/storage/nodes.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_read = storage.database.read;const store = storage.store;const sync = storage.sync;const rows = storage.rows;const graph_mod = smg.graph;const model = smg.model;const view = smg.view;pub const NodeRow = rows.NodeRow;pub const ListedNodes = struct { nodes: []const model.Node, total: usize,};pub fn rowsById( allocator: std.mem.Allocator, root: []const u8, rowids: []const i64, limits: smg.StorageLimits,) ![]NodeRow { var opened = try store.openRead(allocator, root, limits); defer opened.close(); return try rowsByIdFromReader(allocator, &opened.reader, rowids);}pub fn rowsByIdFromReader(allocator: std.mem.Allocator, reader: *storage.database.Reader, rowids: []const i64) ![]NodeRow { const requested = try uniquePositiveRowids(allocator, rowids); defer allocator.free(requested); if (requested.len == 0) return &.{}; var handle = try reader.catalog.openRelation(reader.allocator, rows.nodes_relation); defer handle.deinit(); var selected_rows: std.ArrayList(NodeRow) = .empty; errdefer { for (selected_rows.items) |*row| model.deinitNode(&row.node, allocator); selected_rows.deinit(allocator); } for (requested) |rowid| { const bytes = (try handle.relation.get(allocator, rowid)) orelse continue; defer allocator.free(bytes); try selected_rows.append(allocator, try rows.decodeNodeRow(allocator, rowid, bytes)); } return try selected_rows.toOwnedSlice(allocator);}pub fn listed( allocator: std.mem.Allocator, root: []const u8, type_filter: ?[]const u8, limit: usize, limits: smg.StorageLimits,) !ListedNodes { if (limit != 0) return try listedBounded(allocator, root, type_filter, limit, limits); const sorted = try sortedRows(allocator, root, limits); defer allocator.free(sorted); var listed_nodes: std.ArrayList(model.Node) = .empty; errdefer { for (listed_nodes.items) |*node| model.deinitNode(node, allocator); listed_nodes.deinit(allocator); } var total: usize = 0; for (sorted) |loaded| { var node = loaded.node; if (type_filter) |filter| { if (!std.mem.eql(u8, node.type, filter)) { model.deinitNode(&node, allocator); continue; } } total += 1; if (limit != 0 and listed_nodes.items.len == limit) { model.deinitNode(&node, allocator); continue; } try listed_nodes.append(allocator, node); } return .{ .nodes = try listed_nodes.toOwnedSlice(allocator), .total = total };}fn listedBounded( allocator: std.mem.Allocator, root: []const u8, type_filter: ?[]const u8, limit: usize, limits: smg.StorageLimits,) !ListedNodes { var opened = try store.openRead(allocator, root, limits); defer opened.close(); var handle = opened.reader.catalog.openRelation(opened.reader.allocator, rows.nodes_relation) catch |err| switch (err) { error.RelationNotFound => return .{ .nodes = &.{}, .total = 0 }, else => return err, }; defer handle.deinit(); var scan: sql.TableScan = undefined; try handle.relation.scan(&scan, opened.reader.allocator, null, null); defer scan.deinit(); var listed_nodes: std.ArrayList(model.Node) = .empty; errdefer { for (listed_nodes.items) |*node| model.deinitNode(node, allocator); listed_nodes.deinit(allocator); } var total: usize = 0; while (try scan.next()) |entry| { const node_type = try rows.nodeTypeView(entry.bytes); if (type_filter) |filter| { if (!std.mem.eql(u8, node_type, filter)) continue; } total += 1; const name = try rows.nodeNameView(entry.bytes); try retainBoundedNode(allocator, &listed_nodes, limit, name, entry.rowid, entry.bytes); } std.mem.sort(model.Node, listed_nodes.items, {}, nodeLess); return .{ .nodes = try listed_nodes.toOwnedSlice(allocator), .total = total };}fn retainBoundedNode(allocator: std.mem.Allocator, listed_nodes: *std.ArrayList(model.Node), limit: usize, name: []const u8, rowid: i64, bytes: []const u8) !void { if (listed_nodes.items.len < limit) { var node = (try rows.decodeNodeRow(allocator, rowid, bytes)).node; errdefer model.deinitNode(&node, allocator); try listed_nodes.append(allocator, node); return; } const worst_index = worstNodeIndex(listed_nodes.items); if (!std.mem.lessThan(u8, name, listed_nodes.items[worst_index].name)) return; const row = try rows.decodeNodeRow(allocator, rowid, bytes); var old = listed_nodes.items[worst_index]; listed_nodes.items[worst_index] = row.node; model.deinitNode(&old, allocator);}fn worstNodeIndex(nodes: []const model.Node) usize { var worst: usize = 0; for (nodes[1..], 1..) |node, index| { if (std.mem.lessThan(u8, nodes[worst].name, node.name)) worst = index; } return worst;}pub fn freeRows(allocator: std.mem.Allocator, node_rows: []NodeRow) void { for (node_rows) |*row| model.deinitNode(&row.node, allocator); if (node_rows.len != 0) allocator.free(node_rows);}pub fn freeListed(allocator: std.mem.Allocator, loaded: ListedNodes) void { for (loaded.nodes) |*node_const| { var node = node_const.*; model.deinitNode(&node, allocator); } if (loaded.nodes.len != 0) allocator.free(loaded.nodes);}pub fn sortedRows( allocator: std.mem.Allocator, root: []const u8, limits: smg.StorageLimits,) ![]rows.NodeRow { var opened = try store.openRead(allocator, root, limits); defer opened.close(); const node_rows = try database_read.readerNodes(&opened.reader, allocator, allocator); sortRows(node_rows); return node_rows;}pub fn sortRows(node_rows: []rows.NodeRow) void { std.mem.sort(rows.NodeRow, node_rows, {}, rowLess);}fn rowLess(_: void, left: rows.NodeRow, right: rows.NodeRow) bool { return std.mem.lessThan(u8, left.node.name, right.node.name);}fn nodeLess(_: void, left: model.Node, right: model.Node) bool { return std.mem.lessThan(u8, left.name, right.name);}fn uniquePositiveRowids(allocator: std.mem.Allocator, rowids: []const i64) ![]i64 { var requested: std.ArrayList(i64) = .empty; errdefer requested.deinit(allocator); for (rowids) |rowid| if (rowid > 0) try requested.append(allocator, rowid); std.mem.sort(i64, requested.items, {}, rowidLess); var write: usize = 0; for (requested.items) |rowid| { if (write != 0 and requested.items[write - 1] == rowid) continue; requested.items[write] = rowid; write += 1; } requested.items.len = write; return try requested.toOwnedSlice(allocator);}fn rowidLess(_: void, left: i64, right: i64) bool { return left < right;}test "node projections load search rows listings and stable rowids" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 0); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); var stored = graph_mod.init(allocator); const meta = try model.sourcePair(allocator, "scan"); try graph_mod.addNode(&stored, .{ .name = "a", .type = model.NodeType.module, .metadata = meta }); try graph_mod.addNode(&stored, .{ .name = "a.f", .type = model.NodeType.function, .file = "a.zig", .line = 3, .docstring = "search body" }); try graph_mod.addNode(&stored, .{ .name = "m.item", .type = model.NodeType.module, .file = "m.zig" }); try graph_mod.addNode(&stored, .{ .name = "z.item", .type = model.NodeType.function, .file = "z.zig", .line = 9 }); try graph_mod.addEdge(&stored, .{ .source = "a", .target = "a.f", .rel = model.RelType.contains }); try graph_mod.addEdge(&stored, .{ .source = "z.item", .target = "a.f", .rel = model.RelType.contains }); _ = try saveGraph(allocator, stored, root); const loaded = try listed(allocator, root, null, 2, smg.default_limits.storage); defer freeListed(allocator, loaded); try std.testing.expectEqual(@as(usize, 4), loaded.total); try std.testing.expectEqual(@as(usize, 2), loaded.nodes.len); try std.testing.expectEqualStrings("a", loaded.nodes[0].name); try std.testing.expectEqualStrings("a.f", loaded.nodes[1].name); const filtered = try listed( allocator, root, model.NodeType.function, 1, smg.default_limits.storage, ); defer freeListed(allocator, filtered); try std.testing.expectEqual(@as(usize, 2), filtered.total); try std.testing.expectEqual(@as(usize, 1), filtered.nodes.len); try std.testing.expectEqualStrings("a.f", filtered.nodes[0].name); var grown = graph_mod.init(allocator); try graph_mod.addNode(&grown, .{ .name = "a", .type = model.NodeType.module }); try graph_mod.addNode(&grown, .{ .name = "a.f", .type = model.NodeType.function, .file = "a.zig", .line = 3, .docstring = "search body" }); try graph_mod.addNode(&grown, .{ .name = "c", .type = model.NodeType.module }); try graph_mod.addNode(&grown, .{ .name = "m.item", .type = model.NodeType.module, .file = "m.zig" }); try graph_mod.addNode(&grown, .{ .name = "z.item", .type = model.NodeType.function, .file = "z.zig", .line = 9 }); const saved_nodes = try saveGraph(allocator, grown, root); try std.testing.expectEqual(@as(usize, 5), saved_nodes.len); try std.testing.expectEqualStrings("c", saved_nodes[2].name); const loaded_rows = try rowsById( allocator, root, &.{ 3, 1, 3, -1 }, smg.default_limits.storage, ); try std.testing.expectEqual(@as(usize, 2), loaded_rows.len); try std.testing.expectEqual(@as(i64, 1), loaded_rows[0].rowid); try std.testing.expectEqualStrings("a", loaded_rows[0].node.name); try std.testing.expectEqual(@as(i64, 3), loaded_rows[1].rowid); try std.testing.expectEqualStrings("m.item", loaded_rows[1].node.name);}test "bounded node listing keeps lexical order independent of rowid" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const root = try testRoot(allocator, 3); defer sys.fs.deleteTree(root) catch {}; try initStore(allocator, root); { var opened = try store.open(allocator, root, smg.default_limits.storage); defer opened.close(); try storage.database.edits.apply(&opened.database, .{ .node_puts = &.{ .{ .rowid = 1, .node = .{ .name = "z", .type = model.NodeType.module } }, .{ .rowid = 2, .node = .{ .name = "a", .type = model.NodeType.function } }, .{ .rowid = 3, .node = .{ .name = "m", .type = model.NodeType.function } }, .{ .rowid = 4, .node = .{ .name = "b", .type = model.NodeType.type } }, }, }); try storage.database.commitIfDirty(&opened.database); } const loaded = try listed(allocator, root, null, 2, smg.default_limits.storage); defer freeListed(allocator, loaded); try std.testing.expectEqual(@as(usize, 4), loaded.total); try std.testing.expectEqual(@as(usize, 2), loaded.nodes.len); try std.testing.expectEqualStrings("a", loaded.nodes[0].name); try std.testing.expectEqualStrings("b", loaded.nodes[1].name); const filtered = try listed( allocator, root, model.NodeType.function, 2, smg.default_limits.storage, ); defer freeListed(allocator, filtered); try std.testing.expectEqual(@as(usize, 2), filtered.total); try std.testing.expectEqual(@as(usize, 2), filtered.nodes.len); try std.testing.expectEqualStrings("a", filtered.nodes[0].name); try std.testing.expectEqualStrings("m", filtered.nodes[1].name);}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) ![]const model.Node { const stored_nodes = try view.allNodes(graph, allocator, null); var opened = try store.open(allocator, root, smg.default_limits.storage); defer opened.close(); try sync.replaceGraph(allocator, &opened.database, graph); return stored_nodes;}fn testRoot(allocator: std.mem.Allocator, offset: i64) ![]const u8 { return try std.fmt.allocPrint(allocator, "/tmp/smg-storage-nodes-test-{x}", .{@as(u64, @intCast(@max(0, sys.time.realMilliTimestamp() + offset)))});}Source: tools/smg/src/storage/root.zig:6
zig
pub const nodes = @import("nodes.zig");Audit
| Definitions | 10 |
|---|---|
| Public names | 11 |
| Members | 4 |
| Version | 26.7.0 |
| Revision | daab053ee433 |