Skip to content

Commit fe4ae4b

Browse files
authored
Add project dispatch config parsing (#67)
1 parent 468f188 commit fe4ae4b

17 files changed

Lines changed: 492 additions & 2 deletions

File tree

.agent/research/dbt-upstream-reference-map.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ Every compatibility slice should record:
8282
- Existing extracted modules are `types`, `util`, `config`, `fs`, `jinja`,
8383
`loader`, `resolve`, `parse`, `selector`, `manifest`, `compiler`, and
8484
`catalog`.
85+
- Root-project `dispatch:` config is parsed in `src/project/config.zig`, copied
86+
into the graph by `src/project/loader.zig`, and consumed by
87+
`src/project/resolve.zig` for static `adapter.dispatch(...)`
88+
`depends_on.macros` extraction. This is documented in
89+
`.agent/research/m2-project-dispatch-config.md`.
8590
- The test base includes native Zig tests for module-level helpers and pytest
8691
integration tests for CLI/artifact fixtures plus a pinned local Manifest v12
8792
schema slice.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# M2 Project Dispatch Config Slice
2+
3+
This slice adds root-project `dbt_project.yml` `dispatch:` parsing for static
4+
`adapter.dispatch(...)` dependency extraction. It records `depends_on.macros`
5+
only. It does not execute dispatched macros, implement macro runtime,
6+
materializations, bundled dbt internal macros, adapter connections, or dynamic
7+
dispatch names/namespaces.
8+
9+
## Upstream References
10+
11+
dbt Core v1, branch `1.latest`, commit `566b75d`:
12+
13+
- `core/dbt/contracts/project.py::Project` defines `dispatch` as a list and
14+
validates each non-empty entry requires `macro_namespace` and list-valued
15+
`search_order`.
16+
- `core/dbt/config/project.py::Project.from_project_config` carries
17+
`cfg.dispatch` into runtime project config.
18+
- `core/dbt/config/project.py::Project.get_macro_search_order` returns the
19+
first matching entry's `search_order` for a namespace.
20+
- `core/dbt/context/providers.py::BaseDatabaseWrapper._get_search_packages`
21+
applies dispatch config before dependency fallback. No namespace searches the
22+
flattened namespace, configured namespace search order wins, dependency
23+
namespaces fall back to root project then dependency package, and empty search
24+
order behaves like no configured order because dbt Core checks truthiness.
25+
- `core/dbt/context/providers.py::BaseDatabaseWrapper.dispatch` rejects dotted
26+
macro names and deprecated `packages`, then loops packages first and adapter
27+
prefixes second for `{prefix}__{macro_name}`.
28+
29+
dbt Core v2 / Fusion foundation, branch `main`, commit `0529e06`:
30+
31+
- `crates/dbt-schemas/src/schemas/project/dbt_project.rs::_Dispatch` models
32+
`macro_namespace: String` and `search_order: Vec<String>`.
33+
- `crates/dbt-loader/src/loader.rs` loads root-project dispatch config into the
34+
global `DISPATCH_CONFIG` map.
35+
- `crates/dbt-jinja-utils/src/phases/compile_and_run_context.rs` exposes that
36+
map as `MACRO_DISPATCH_ORDER` using typed `Vec<String>` values.
37+
- `crates/dbt-jinja/minijinja/src/dispatch_object.rs::DispatchObject::get_search_packages`
38+
uses configured dispatch order before dependency fallback.
39+
- `dispatch_object.rs::get_adapter_prefixes` preserves the adapter prefix
40+
fallback shape used by the prior profile-derived adapter identity slice.
41+
42+
## dxt Ownership
43+
44+
- `src/project/config.zig` parses the narrow root-project `dispatch:` YAML
45+
surface.
46+
- `src/project/types.zig` stores `DispatchConfig` on `ProjectConfig` and
47+
`Graph`.
48+
- `src/project/loader.zig` copies only root project dispatch config into the
49+
graph before macro and SQL scanning. Installed package `dispatch:` does not
50+
affect root dispatch config in this slice.
51+
- `src/project/resolve.zig` applies configured package search order before the
52+
existing dependency fallback.
53+
- `src/project/jinja.zig` continues to recognize literal
54+
`adapter.dispatch(...)` and records the resolved macro ID.
55+
56+
## Supported Surface
57+
58+
- Root `dbt_project.yml` block list:
59+
- `macro_namespace: <string>`
60+
- `search_order: [<package>, ...]`
61+
- Block-form `search_order` lists.
62+
- Empty inline `search_order: []` is accepted and follows dbt Core v1 behavior:
63+
it falls through as no truthy configured order.
64+
65+
Unsupported shapes fail through the existing narrow YAML boundary: missing
66+
`macro_namespace`, missing `search_order`, scalar `search_order`, extra dispatch
67+
entry keys, inline maps, dynamic dispatch values, deprecated `packages=`, and
68+
Jinja-rendered project config.
69+
70+
## Validation
71+
72+
- Native Zig tests cover dispatch config parsing, malformed config rejection,
73+
configured search-order lookup, dbt Core v1 empty-list fallback, and SQL
74+
scanner dependency recording.
75+
- Pytest fixture `adapter_dispatch_project_config` validates manifest
76+
`depends_on.macros` through the Zig binary.
77+
- Manifest schema-slice validation remains the artifact gate.
78+
79+
## Stop Conditions
80+
81+
- Do not execute dispatched macros in this slice.
82+
- Do not implement general macro runtime or materialization lookup.
83+
- Do not add adapter connections, DuckDB execution, catalog introspection,
84+
`run_results.json`, or `docs serve`.
85+
- Do not parse installed-package dispatch config as root config.
86+
- Do not add Python product runtime behavior.

PLAN.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,17 @@ narrow scalar `profiles.yml` parser. This slice lets parse-time static
318318
profiles, validate credentials, read host-global profile locations, implement
319319
project `dispatch:` config, execute macros, or open adapter connections.
320320

321+
Current project dispatch config source note:
322+
`.agent/research/m2-project-dispatch-config.md` maps upstream dbt Core v1
323+
project `dispatch:` validation, `get_macro_search_order`, and
324+
`BaseDatabaseWrapper.dispatch` package/prefix search order plus Fusion
325+
`DISPATCH_CONFIG` and `MACRO_DISPATCH_ORDER` behavior to dxt's narrow
326+
root-project dispatch config parser. This slice lets static
327+
`adapter.dispatch(...)` dependency extraction honor configured package
328+
`search_order` for a literal namespace. It does not execute dispatched macros,
329+
parse installed-package dispatch as root config, render Jinja in project config,
330+
or run adapters.
331+
321332
The next source-grounded M1/M2 slices after macro block variant support are:
322333

323334
1. Extend the render-only artifact boundary to adapter-free docs generation:
@@ -684,4 +695,4 @@ Exit criteria:
684695
- Selector wildcard behavior is currently pinned to observed dbt Core 1.10 behavior. dbt Fusion preview currently differs for resource-type-prefixed wildcard selectors such as `model.<package>.*` and filename-suffix path selectors such as `path:*orders.sql`; a future Fusion-compatibility slice must decide whether to support a selector dialect switch or a compatible superset.
685696
- Compatibility planning now uses a source-grounded reference map under `.agent/research/dbt-upstream-reference-map.md`; future feature slices should name upstream dbt v1/v2 source references, dxt Zig owners, affected artifact fields, validation gates, and stop conditions before implementation.
686697
- The committed dbt Core M1 oracle harness lives in `scripts/check_dbt_core_m1_oracle.py`. It is optional developer-side Python tooling that requires `dbt-core` and `dbt-duckdb`, invokes dbt Core through its Python runner, runs `dxt parse` through the Zig binary, and compares stable manifest slices for the supported synthetic M1 fixture ladder. It ignores dbt internal package docs/macros that are outside the current dxt artifact scope, records a known allowed gap for installed-package exposure refs that dbt Core resolves to a root same-name model while dxt currently resolves package-local, and leaves full source-map parity, full artifact schemas, and execution parity for later slices.
687-
- Before broadening M2 product implementation, close or explicitly re-scope the remaining M1 macro-compatibility behavior gaps. Macro `docs`/`meta` patch fields are covered for the current scalar artifact subset. Macro argument extraction under dbt Core v1 `flags.validate_macro_args` semantics and YAML patch argument validation/replacement are implemented for the manifest artifact surface. Static macro dependency lookup now uses the supported dbt order of current package, root project, other-package fallback for macro bodies, graph-present internal `dbt` macros, and literal `adapter.dispatch(...)` dependency extraction. Parse-time dispatch prefixes now come from a narrow source-grounded `profiles.yml` adapter identity parser and emit manifest `metadata.adapter_type`, with default DuckDB behavior preserved when no profile file is loaded. Macro execution, bundled dbt internal macros, project `dispatch:` config, full target context, credential validation, and materialization runtime lookup remain planned. `{% data_test %}` has native source-grounded parser coverage, but the local dbt Core 1.10 oracle rejects that tag before writing artifacts, so dbt-oracle coverage currently pins `{% test %}` and `{% materialization %}` block parity.
698+
- Before broadening M2 product implementation, close or explicitly re-scope the remaining M1 macro-compatibility behavior gaps. Macro `docs`/`meta` patch fields are covered for the current scalar artifact subset. Macro argument extraction under dbt Core v1 `flags.validate_macro_args` semantics and YAML patch argument validation/replacement are implemented for the manifest artifact surface. Static macro dependency lookup now uses the supported dbt order of current package, root project, other-package fallback for macro bodies, graph-present internal `dbt` macros, and literal `adapter.dispatch(...)` dependency extraction. Parse-time dispatch prefixes now come from a narrow source-grounded `profiles.yml` adapter identity parser and emit manifest `metadata.adapter_type`, with default DuckDB behavior preserved when no profile file is loaded. Root-project `dispatch:` config search order is now honored for static `adapter.dispatch(...)` dependency extraction. Macro execution, bundled dbt internal macros, full target context, credential validation, and materialization runtime lookup remain planned. `{% data_test %}` has native source-grounded parser coverage, but the local dbt Core 1.10 oracle rejects that tag before writing artifacts, so dbt-oracle coverage currently pins `{% test %}` and `{% materialization %}` block parity.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Implemented pre-alpha commands:
3232
```sh
3333
./zig-out/bin/dxt parse --project-dir tests/fixtures/model_ref --target-path target-dxt
3434
./zig-out/bin/dxt parse --project-dir tests/fixtures/profile_adapter_dispatch --profiles-dir tests/fixtures/profile_adapter_dispatch --target-path target-dxt
35+
./zig-out/bin/dxt parse --project-dir tests/fixtures/adapter_dispatch_project_config --target-path target-dxt
3536
./zig-out/bin/dxt ls --project-dir tests/fixtures/model_ref
3637
./zig-out/bin/dxt ls --project-dir tests/fixtures/model_ref --output json
3738
./zig-out/bin/dxt compile --project-dir tests/fixtures/compile_basic --target-path target-dxt
@@ -41,7 +42,7 @@ Implemented pre-alpha commands:
4142
./zig-out/bin/dxt docs generate --project-dir tests/fixtures/docs_blocks --target-path target-dxt
4243
```
4344

44-
`parse`, `ls`, `compile`, `run`, `build`, and `docs generate` currently support only the documented M1/M2 parser and render-only subset: `dbt_project.yml` name/profile/model paths/seed paths/macro paths/target path, `flags.validate_macro_args` for macro manifest argument validation, narrow scalar `profiles.yml` adapter type selection for parse-time dispatch identity, and top-level scalar `vars`, CLI `--vars` scalar overrides, 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`, project and package docs block discovery, literal and narrow scalar `var('name')` / `var('name', 'default')`-backed `ref` to models or seeds, two-argument package refs, package-local refs in installed package models and exposures, dbt-style fallback from unqualified refs to a unique installed-package model or seed, literal and narrow scalar `var('name')` / `var('name', 'default')`-backed `source`, package-local sources in installed package models, dbt-style fallback from unqualified sources to a unique installed-package source, literal `doc` in descriptions, package-qualified, package-local, root-fallback, macro-body other-package fallback, graph-present internal `dbt` macro dependencies, and literal `adapter.dispatch(...)` macro dependencies using the selected adapter prefix plus `default` with source-grounded parent fallbacks for Redshift and Databricks, basic inline `config`, narrow project and package YAML model properties and macro properties including patched macro `docs`, scalar `meta`, YAML `arguments`, and dbt Core v1-style macro argument annotation warnings, project and package model/seed `+docs.node_color`, root-project model config overrides for installed packages, simple columns, tags, materialization and disabled SQL models, dbt-shaped generic test nodes for `unique`, `not_null`, `accepted_values`, and `relationships`, model/test `refs` and `sources` artifact fields, deterministic partial `manifest.json`, and basic name/tag/path/package/resource/config materialization selectors with exact `package:`/`package:this`, comma intersections, whitespace unions, multi-argument selector lists, repeated selector flags, and graph expansion. This is not full dbt `var()` or profile compatibility yet: `vars.yml`, nested/package-scoped vars, non-string values, `var.has_var`, Jinja-rendered var values, profile/project rendering with vars, credential validation, host-global profile lookup, and general `var()` usage remain planned. `compile` applies selectors/excludes to enabled SQL models, writes supported compiled SQL to `target/compiled/<package>/...`, and emits compile fields for compiled models. `run` and `build` are truthful execution preflight commands: they parse, resolve, apply selectors/excludes, compile supported selected models, write `manifest.json`, and then fail before execution with a clear adapter-runner boundary error. They do not run SQL, materialize relations, run tests, or write `run_results.json`. `docs generate` uses the same render-only compiler, writes `manifest.json`, writes compiled SQL for selected enabled SQL models, and emits an adapter-free empty `catalog.json` until relation introspection exists. The compiler currently renders only `config`, literal and narrow scalar var-backed `ref`, and literal and narrow scalar var-backed `source` calls; macro execution, project `dispatch:` config, full target context, materializations, tests, real `run`/`build` execution, non-empty catalog introspection, and `docs serve` remain planned.
45+
`parse`, `ls`, `compile`, `run`, `build`, and `docs generate` currently support only the documented M1/M2 parser and render-only subset: `dbt_project.yml` name/profile/model paths/seed paths/macro paths/target path, root-project `dispatch:` search order for static `adapter.dispatch(...)` dependency extraction, `flags.validate_macro_args` for macro manifest argument validation, narrow scalar `profiles.yml` adapter type selection for parse-time dispatch identity, and top-level scalar `vars`, CLI `--vars` scalar overrides, 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`, project and package docs block discovery, literal and narrow scalar `var('name')` / `var('name', 'default')`-backed `ref` to models or seeds, two-argument package refs, package-local refs in installed package models and exposures, dbt-style fallback from unqualified refs to a unique installed-package model or seed, literal and narrow scalar `var('name')` / `var('name', 'default')`-backed `source`, package-local sources in installed package models, dbt-style fallback from unqualified sources to a unique installed-package source, literal `doc` in descriptions, package-qualified, package-local, root-fallback, macro-body other-package fallback, graph-present internal `dbt` macro dependencies, and literal `adapter.dispatch(...)` macro dependencies using the selected adapter prefix plus `default` with source-grounded parent fallbacks for Redshift and Databricks, basic inline `config`, narrow project and package YAML model properties and macro properties including patched macro `docs`, scalar `meta`, YAML `arguments`, and dbt Core v1-style macro argument annotation warnings, project and package model/seed `+docs.node_color`, root-project model config overrides for installed packages, simple columns, tags, materialization and disabled SQL models, dbt-shaped generic test nodes for `unique`, `not_null`, `accepted_values`, and `relationships`, model/test `refs` and `sources` artifact fields, deterministic partial `manifest.json`, and basic name/tag/path/package/resource/config materialization selectors with exact `package:`/`package:this`, comma intersections, whitespace unions, multi-argument selector lists, repeated selector flags, and graph expansion. This is not full dbt `var()` or profile compatibility yet: `vars.yml`, nested/package-scoped vars, non-string values, `var.has_var`, Jinja-rendered var values, profile/project rendering with vars, credential validation, host-global profile lookup, and general `var()` usage remain planned. `compile` applies selectors/excludes to enabled SQL models, writes supported compiled SQL to `target/compiled/<package>/...`, and emits compile fields for compiled models. `run` and `build` are truthful execution preflight commands: they parse, resolve, apply selectors/excludes, compile supported selected models, write `manifest.json`, and then fail before execution with a clear adapter-runner boundary error. They do not run SQL, materialize relations, run tests, or write `run_results.json`. `docs generate` uses the same render-only compiler, writes `manifest.json`, writes compiled SQL for selected enabled SQL models, and emits an adapter-free empty `catalog.json` until relation introspection exists. The compiler currently renders only `config`, literal and narrow scalar var-backed `ref`, and literal and narrow scalar var-backed `source` calls; macro execution, dynamic dispatch, full target context, materializations, tests, real `run`/`build` execution, non-empty catalog introspection, and `docs serve` remain planned.
4546

4647
## Development
4748

0 commit comments

Comments
 (0)