Skip to content

Commit d22488d

Browse files
committed
Extract graph resource ordering
1 parent f05a8ff commit d22488d

3 files changed

Lines changed: 120 additions & 55 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ Exit criteria:
569569

570570
- M0 is complete as the Zig `0.16.0` runtime scaffold.
571571
- M1 has started on stacked branches with native Zig artifact-first parser slices.
572-
- M1A has started with behavior-preserving `src/project/types.zig`, `src/project/selector.zig`, `src/project/manifest.zig`, `src/project/util.zig`, `src/project/config.zig`, `src/project/fs.zig`, `src/project/jinja.zig`, `src/project/resolve.zig`, and `src/project/parse.zig` extractions. `src/project/manifest.zig` owns selected-resource JSON and partial `manifest.json` writing with native tests for selected JSON shape, JSON escaping, exposure dependency ordering, disabled-resource filtering, and graph-map output. `src/project/util.zig` owns shared display, membership, append-dedup, string sorting, and narrow YAML scalar/list helpers. `src/project/config.zig` owns `dbt_project.yml` loading, project path/docs config parsing, and applying parsed project path/docs configs to graph nodes. `src/project/fs.zig` owns deterministic resource file discovery, Linux directory traversal helpers, and resource path/name helpers. `src/project/jinja.zig` owns lexical Jinja call, parenthesis, quoted string, and literal argument helpers while graph-aware scanning remains in `src/project.zig`. `src/project/resolve.zig` owns graph lookup/count helpers, duplicate resource validation, low-level ref/source resolution helpers, and dependency-map mutation for refs, sources, and known macro dependencies. `src/project/parse.zig` owns narrow parser scalar helpers for YAML booleans, JSON-compatible scalar classification, generic-test YAML item names, generic-test definition construction/cloning, and generic-test identity/name/hash helpers with native tests. `src/project.zig` remains the public parser/list facade and still owns most loader, resource parser, scanner, and resolver orchestration code until follow-up extractions move those pieces behind focused internal modules.
572+
- M1A has started with behavior-preserving `src/project/types.zig`, `src/project/selector.zig`, `src/project/manifest.zig`, `src/project/util.zig`, `src/project/config.zig`, `src/project/fs.zig`, `src/project/jinja.zig`, `src/project/resolve.zig`, and `src/project/parse.zig` extractions. `src/project/manifest.zig` owns selected-resource JSON and partial `manifest.json` writing with native tests for selected JSON shape, JSON escaping, exposure dependency ordering, disabled-resource filtering, and graph-map output. `src/project/util.zig` owns shared display, membership, append-dedup, string sorting, and narrow YAML scalar/list helpers. `src/project/config.zig` owns `dbt_project.yml` loading, project path/docs config parsing, and applying parsed project path/docs configs to graph nodes. `src/project/fs.zig` owns deterministic resource file discovery, Linux directory traversal helpers, and resource path/name helpers. `src/project/jinja.zig` owns lexical Jinja call, parenthesis, quoted string, and literal argument helpers while graph-aware scanning remains in `src/project.zig`. `src/project/resolve.zig` owns graph lookup/count helpers, canonical graph resource ordering, duplicate resource validation, low-level ref/source resolution helpers, and dependency-map mutation for refs, sources, and known macro dependencies. `src/project/parse.zig` owns narrow parser scalar helpers for YAML booleans, JSON-compatible scalar classification, generic-test YAML item names, generic-test definition construction/cloning, and generic-test identity/name/hash helpers with native tests. `src/project.zig` remains the public parser/list facade and still owns most loader, resource parser, scanner, and resolver orchestration code until follow-up extractions move those pieces behind focused internal modules.
573573
- `dxt parse` now targets the supported Tier 0 subset: project name/model paths/seed paths/macro paths, project and package model path configs for literal `+materialized`, `+tags`, and model/seed `+docs.node_color`, root-project model config overrides for installed packages, SQL model discovery, CSV seed discovery, installed package SQL model and CSV seed discovery from `dbt_packages`, source discovery, installed package source discovery, exposure discovery, installed package exposure discovery, project macro discovery, installed package macro discovery from `dbt_packages`, macro property YAML for project macro descriptions and arguments, project and package docs block discovery, literal `ref` to models or seeds, two-argument package refs, package-local refs in installed package models and exposures, unique installed-package fallback for unqualified refs, literal `source`, package-local sources in installed package models, unique installed-package fallback for unqualified sources, literal `doc` in project and package descriptions, inline `config(materialized=..., tags=...)`, known project/package-qualified/package-local macro call dependencies, narrow project and package YAML model properties for scalar descriptions, simple columns, tags, materialization, disabled SQL models, dbt-shaped `unique`, `not_null`, `accepted_values`, and `relationships` generic test nodes, model/test `refs` and `sources` artifact fields, dependency maps, and deterministic partial `manifest.json`. The manifest includes the v12 top-level maps needed by the M1 artifact shape and is covered by a pinned local dbt Manifest v12 schema slice. YAML generic test arguments are currently supported for scalar values plus inline and block lists required by public Jaffle Shop DuckDB-style tests.
574574
- `dxt ls` now lists dbt-selectable resources from the same parser graph with stable text/JSON output and basic name/FQN wildcards, tag wildcards, slash-aware `path:` wildcards, exact `package:`/`package:this`, `source:` wildcards including package-qualified source selectors, `exposure:` wildcards, `resource_type:`, `test_type:generic`, config materialization, comma intersection, whitespace union, multi-argument selector lists, repeated selector flags, leading/trailing `+` graph expansion, and exact exclude filters; macros are emitted in artifacts but not exposed as `ls` resources.
575575
- Synthetic fixtures cover one model, model refs, seed refs, source refs, exposure refs to models and sources, combined source/model YAML, inline config/tag selection, config materialization selection, comma-intersection selection, YAML model properties and columns, emitted `unique`, `not_null`, `accepted_values`, and `relationships` generic test nodes, project macro artifacts and macro properties, configured `macro-paths` replacing the default macro directory, installed package macros with package-qualified calls and package-local macro calls, installed package models, seeds, sources, docs, exposures, package YAML model properties, root package config overrides, and package-qualified/package-local refs/sources, macro calls recorded in model and macro `depends_on.macros`, docs blocks with literal `doc` descriptions, disabled models, disabled ref diagnostics, unmatched model-property warnings, duplicate model and docs diagnostics, unsupported dynamic ref/doc diagnostics, missing doc diagnostics, malformed docs block diagnostics, unresolved package macro diagnostics, and unsupported unknown macro-call diagnostics.

src/project.zig

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const rejectDuplicateModels = project_resolve.rejectDuplicateModels;
8686
const rejectDuplicateSeeds = project_resolve.rejectDuplicateSeeds;
8787
const resolveDependencies = project_resolve.resolveDependencies;
8888
const resolveRefDependency = project_resolve.resolveRefDependency;
89+
const sortGraphResources = project_resolve.sortGraphResources;
8990

9091
pub fn parse(runtime: Runtime, options: Options, stdout: *Io.Writer, stderr: *Io.Writer) !void {
9192
var graph = try loadGraph(runtime, options.project_dir);
@@ -200,12 +201,7 @@ fn loadGraph(runtime: Runtime, project_dir: []const u8) !Graph {
200201
try applyMacroProperties(&graph);
201202
try applyModelProperties(&graph, config.name);
202203
try materializeGenericTests(&graph);
203-
sortNodes(graph.nodes.items);
204-
sortTests(graph.tests.items);
205-
sortSources(graph.sources.items);
206-
sortExposures(graph.exposures.items);
207-
sortDocs(graph.docs.items);
208-
sortMacros(graph.macros.items);
204+
sortGraphResources(&graph);
209205
try rejectDuplicateModels(&graph);
210206
try rejectDuplicateSeeds(&graph);
211207
try rejectDuplicateDocs(&graph);
@@ -1430,38 +1426,6 @@ fn sortGenericTestDefs(tests: []GenericTestDef) void {
14301426
}.lessThan);
14311427
}
14321428

1433-
fn sortNodes(nodes: []Node) void {
1434-
std.mem.sort(Node, nodes, {}, struct {
1435-
fn lessThan(_: void, a: Node, b: Node) bool {
1436-
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
1437-
}
1438-
}.lessThan);
1439-
}
1440-
1441-
fn sortTests(tests: []GenericTestNode) void {
1442-
std.mem.sort(GenericTestNode, tests, {}, struct {
1443-
fn lessThan(_: void, a: GenericTestNode, b: GenericTestNode) bool {
1444-
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
1445-
}
1446-
}.lessThan);
1447-
}
1448-
1449-
fn sortSources(sources: []SourceDef) void {
1450-
std.mem.sort(SourceDef, sources, {}, struct {
1451-
fn lessThan(_: void, a: SourceDef, b: SourceDef) bool {
1452-
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
1453-
}
1454-
}.lessThan);
1455-
}
1456-
1457-
fn sortExposures(exposures: []ExposureDef) void {
1458-
std.mem.sort(ExposureDef, exposures, {}, struct {
1459-
fn lessThan(_: void, a: ExposureDef, b: ExposureDef) bool {
1460-
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
1461-
}
1462-
}.lessThan);
1463-
}
1464-
14651429
fn sortMetaEntries(entries: []MetaEntry) void {
14661430
std.mem.sort(MetaEntry, entries, {}, struct {
14671431
fn lessThan(_: void, a: MetaEntry, b: MetaEntry) bool {
@@ -1470,22 +1434,6 @@ fn sortMetaEntries(entries: []MetaEntry) void {
14701434
}.lessThan);
14711435
}
14721436

1473-
fn sortDocs(docs: []DocBlock) void {
1474-
std.mem.sort(DocBlock, docs, {}, struct {
1475-
fn lessThan(_: void, a: DocBlock, b: DocBlock) bool {
1476-
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
1477-
}
1478-
}.lessThan);
1479-
}
1480-
1481-
fn sortMacros(macros: []MacroDef) void {
1482-
std.mem.sort(MacroDef, macros, {}, struct {
1483-
fn lessThan(_: void, a: MacroDef, b: MacroDef) bool {
1484-
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
1485-
}
1486-
}.lessThan);
1487-
}
1488-
14891437
fn sortColumns(columns: []ColumnDef) void {
14901438
std.mem.sort(ColumnDef, columns, {}, struct {
14911439
fn lessThan(_: void, a: ColumnDef, b: ColumnDef) bool {

src/project/resolve.zig

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ const types = @import("types.zig");
33
const util = @import("util.zig");
44

55
const DocBlock = types.DocBlock;
6+
const ExposureDef = types.ExposureDef;
7+
const GenericTestNode = types.GenericTestNode;
68
const Graph = types.Graph;
9+
const MacroDef = types.MacroDef;
10+
const Node = types.Node;
711
const RefDep = types.RefDep;
812
const SourceDep = types.SourceDep;
13+
const SourceDef = types.SourceDef;
914

1015
const appendUnique = util.appendUnique;
1116
const sortStrings = util.sortStrings;
@@ -141,6 +146,15 @@ pub fn resolveDependencies(graph: *Graph) !void {
141146
}
142147
}
143148

149+
pub fn sortGraphResources(graph: *Graph) void {
150+
sortNodes(graph.nodes.items);
151+
sortTests(graph.tests.items);
152+
sortSources(graph.sources.items);
153+
sortExposures(graph.exposures.items);
154+
sortDocs(graph.docs.items);
155+
sortMacros(graph.macros.items);
156+
}
157+
144158
pub fn rejectDuplicateModels(graph: *const Graph) !void {
145159
var i: usize = 0;
146160
while (i < graph.nodes.items.len) : (i += 1) {
@@ -253,6 +267,54 @@ fn resolveRefInPackage(graph: *const Graph, package: []const u8, name: []const u
253267
return null;
254268
}
255269

270+
fn sortNodes(nodes: []Node) void {
271+
std.mem.sort(Node, nodes, {}, struct {
272+
fn lessThan(_: void, a: Node, b: Node) bool {
273+
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
274+
}
275+
}.lessThan);
276+
}
277+
278+
fn sortTests(tests: []GenericTestNode) void {
279+
std.mem.sort(GenericTestNode, tests, {}, struct {
280+
fn lessThan(_: void, a: GenericTestNode, b: GenericTestNode) bool {
281+
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
282+
}
283+
}.lessThan);
284+
}
285+
286+
fn sortSources(sources: []SourceDef) void {
287+
std.mem.sort(SourceDef, sources, {}, struct {
288+
fn lessThan(_: void, a: SourceDef, b: SourceDef) bool {
289+
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
290+
}
291+
}.lessThan);
292+
}
293+
294+
fn sortExposures(exposures: []ExposureDef) void {
295+
std.mem.sort(ExposureDef, exposures, {}, struct {
296+
fn lessThan(_: void, a: ExposureDef, b: ExposureDef) bool {
297+
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
298+
}
299+
}.lessThan);
300+
}
301+
302+
fn sortDocs(docs: []DocBlock) void {
303+
std.mem.sort(DocBlock, docs, {}, struct {
304+
fn lessThan(_: void, a: DocBlock, b: DocBlock) bool {
305+
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
306+
}
307+
}.lessThan);
308+
}
309+
310+
fn sortMacros(macros: []MacroDef) void {
311+
std.mem.sort(MacroDef, macros, {}, struct {
312+
fn lessThan(_: void, a: MacroDef, b: MacroDef) bool {
313+
return std.mem.lessThan(u8, a.unique_id, b.unique_id);
314+
}
315+
}.lessThan);
316+
}
317+
256318
fn findProjectMacroIdByName(graph: *const Graph, name: []const u8) ?[]const u8 {
257319
return findMacroIdByPackageAndName(graph, graph.project_name, name);
258320
}
@@ -467,3 +529,58 @@ test "dependency resolution rejects unresolved macro dependencies" {
467529

468530
try std.testing.expectError(error.UnresolvedMacro, resolveDependencies(&graph));
469531
}
532+
533+
test "graph resource sorting preserves deterministic unique id order" {
534+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
535+
defer arena.deinit();
536+
var graph = Graph{ .allocator = arena.allocator(), .project_name = "demo" };
537+
defer graph.deinit();
538+
539+
try appendNode(&graph, "model", "demo", "model.demo.z_orders", "z_orders", true);
540+
try appendNode(&graph, "model", "demo", "model.demo.a_customers", "a_customers", true);
541+
try graph.tests.append(graph.allocator, .{
542+
.package_name = "demo",
543+
.unique_id = "test.demo.z_test.2222222222",
544+
.name = "z_test",
545+
.alias = "z_test",
546+
.path = "",
547+
.original_file_path = "",
548+
.raw_code = "",
549+
.test_name = "unique",
550+
.attached_node = "model.demo.z_orders",
551+
});
552+
try graph.tests.append(graph.allocator, .{
553+
.package_name = "demo",
554+
.unique_id = "test.demo.a_test.1111111111",
555+
.name = "a_test",
556+
.alias = "a_test",
557+
.path = "",
558+
.original_file_path = "",
559+
.raw_code = "",
560+
.test_name = "not_null",
561+
.attached_node = "model.demo.a_customers",
562+
});
563+
try appendSource(&graph, "demo", "raw", "z_orders");
564+
try appendSource(&graph, "demo", "raw", "a_customers");
565+
try graph.exposures.append(graph.allocator, .{ .package_name = "demo", .unique_id = "exposure.demo.z_dashboard", .name = "z_dashboard", .path = "", .original_file_path = "" });
566+
try graph.exposures.append(graph.allocator, .{ .package_name = "demo", .unique_id = "exposure.demo.a_dashboard", .name = "a_dashboard", .path = "", .original_file_path = "" });
567+
try graph.docs.append(graph.allocator, .{ .package_name = "demo", .unique_id = "doc.demo.z_doc", .name = "z_doc", .path = "", .original_file_path = "", .block_contents = "" });
568+
try graph.docs.append(graph.allocator, .{ .package_name = "demo", .unique_id = "doc.demo.a_doc", .name = "a_doc", .path = "", .original_file_path = "", .block_contents = "" });
569+
try appendMacro(&graph, "demo", "z_macro");
570+
try appendMacro(&graph, "demo", "a_macro");
571+
572+
sortGraphResources(&graph);
573+
574+
try std.testing.expectEqualStrings("model.demo.a_customers", graph.nodes.items[0].unique_id);
575+
try std.testing.expectEqualStrings("model.demo.z_orders", graph.nodes.items[1].unique_id);
576+
try std.testing.expectEqualStrings("test.demo.a_test.1111111111", graph.tests.items[0].unique_id);
577+
try std.testing.expectEqualStrings("test.demo.z_test.2222222222", graph.tests.items[1].unique_id);
578+
try std.testing.expectEqualStrings("source.demo.raw.a_customers", graph.sources.items[0].unique_id);
579+
try std.testing.expectEqualStrings("source.demo.raw.z_orders", graph.sources.items[1].unique_id);
580+
try std.testing.expectEqualStrings("exposure.demo.a_dashboard", graph.exposures.items[0].unique_id);
581+
try std.testing.expectEqualStrings("exposure.demo.z_dashboard", graph.exposures.items[1].unique_id);
582+
try std.testing.expectEqualStrings("doc.demo.a_doc", graph.docs.items[0].unique_id);
583+
try std.testing.expectEqualStrings("doc.demo.z_doc", graph.docs.items[1].unique_id);
584+
try std.testing.expectEqualStrings("macro.demo.a_macro", graph.macros.items[0].unique_id);
585+
try std.testing.expectEqualStrings("macro.demo.z_macro", graph.macros.items[1].unique_id);
586+
}

0 commit comments

Comments
 (0)