Skip to content

Commit 1223d88

Browse files
authored
[dagster-dbt] Fix #33856 - incorrect asset key translation when generating column lineage and using DbtProjectComponent (#33857)
## Summary Fixes [#33856](#33856) — column lineage emitted by `.fetch_column_metadata()` referenced default-derived `AssetKey`s instead of the translated keys actually present in the asset graph when assets were defined via `DBTProjectComponent`. `_build_column_lineage_metadata()` resolved upstream dependencies by calling `dagster_dbt_translator.get_asset_key(parent_resource_props)`. But `DbtProjectComponentTranslator` (and any translator that customizes translation via `get_asset_spec`) does **not** override `get_asset_key` — it overrides `get_asset_spec`. As a result, every upstream `TableColumnDep` pointed at a key that didn't exist in the graph, and column lineage failed to resolve. ## Fix Switch the upstream key resolution to `dagster_dbt_translator.get_asset_spec(manifest, parent_unique_id, project).key`. This is already the canonical pattern in the same file (`_to_model_events` at `dbt_cli_event.py:401`) and in `asset_utils.py:823`. Chose this over an `isinstance(translator, DbtProjectComponentTranslator)` branch because: 1. **Consistency** — matches how the materialized asset's own key is resolved a few lines away. Using a different mechanism for upstream resolution is what caused this drift bug in the first place. 2. **Generality** — works for any translator subclass that customizes via `get_asset_spec` (the documented extension point), not just `DbtProjectComponentTranslator`. 3. **No layering violation** — avoids importing a component-layer class into core event code. 4. **Default path preserved** — `DagsterDbtTranslator.get_asset_spec()` internally calls `self.get_asset_key()`, so users who only override `get_asset_key` are unaffected. To make this work, `project: DbtProject | None` is plumbed through `_build_column_lineage_metadata` → `_get_lineage_metadata` → `_get_materialization_metadata`. `_to_model_events` already had `project` available, and `DbtCliInvocation.project` is the source at the `_fetch_column_metadata` call site. ### Out of scope `_to_observation_events_for_test` (`dbt_cli_event.py:498`) and `freshness_builder.py` use the same `get_asset_key()` pattern and likely have the same bug for `DbtProjectComponent` users. Kept this PR tight to the reported column-lineage issue; those are follow-ups. ## Test plan - [x] New regression test `test_column_lineage_uses_get_asset_spec_for_upstream_keys` in `test_columns_metadata.py` — uses a translator that overrides only `get_asset_spec` (mimicking `DbtProjectComponentTranslator`'s pattern) and asserts every upstream key in the emitted `TableColumnLineage` carries the translated `renamed/...` prefix. - [x] Verified the new test **fails** without the fix (`AssertionError: Upstream key AssetKey(['raw_source_customers']) in column lineage was not translated via get_asset_spec`) and passes with it. - [x] All 12 duckdb-based column-lineage tests in `test_columns_metadata.py` pass locally (snowflake/bigquery variants skipped — require cloud credentials). - [x] `make ruff` clean.
1 parent 3afb562 commit 1223d88

3 files changed

Lines changed: 91 additions & 3 deletions

File tree

python_modules/libraries/dagster-dbt/dagster_dbt/core/dbt_cli_event.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def _build_column_lineage_metadata(
6565
manifest: Mapping[str, Any],
6666
dagster_dbt_translator: DagsterDbtTranslator,
6767
target_path: Path | None,
68+
project: DbtProject | None,
6869
) -> dict[str, Any]:
6970
"""Process the lineage metadata for a dbt CLI event.
7071
@@ -200,7 +201,9 @@ def _build_column_lineage_metadata(
200201
# Add the column dependency.
201202
column_deps.add(
202203
TableColumnDep(
203-
asset_key=dagster_dbt_translator.get_asset_key(parent_resource_props),
204+
asset_key=dagster_dbt_translator.get_asset_spec(
205+
manifest, parent_resource_props["unique_id"], project
206+
).key,
204207
column_name=parent_column_name,
205208
)
206209
)
@@ -342,6 +345,7 @@ def _get_lineage_metadata(
342345
translator: DagsterDbtTranslator,
343346
manifest: Mapping[str, Any],
344347
target_path: Path | None,
348+
project: DbtProject | None,
345349
) -> Mapping[str, Any]:
346350
try:
347351
column_data = self._event_history_metadata.get("columns", {})
@@ -362,6 +366,7 @@ def _get_lineage_metadata(
362366
manifest=manifest,
363367
dagster_dbt_translator=translator,
364368
target_path=target_path,
369+
project=project,
365370
)
366371
except Exception as e:
367372
logger.warning(
@@ -378,10 +383,11 @@ def _get_materialization_metadata(
378383
translator: DagsterDbtTranslator,
379384
manifest: Mapping[str, Any],
380385
target_path: Path | None,
386+
project: DbtProject | None,
381387
) -> dict[str, Any]:
382388
return {
383389
**self._get_default_metadata(manifest),
384-
**self._get_lineage_metadata(translator, manifest, target_path),
390+
**self._get_lineage_metadata(translator, manifest, target_path, project),
385391
}
386392

387393
def _to_model_events(
@@ -393,7 +399,9 @@ def _to_model_events(
393399
project: DbtProject | None,
394400
) -> Iterator[Output | AssetMaterialization]:
395401
asset_key = dagster_dbt_translator.get_asset_spec(manifest, self._unique_id, project).key
396-
metadata = self._get_materialization_metadata(dagster_dbt_translator, manifest, target_path)
402+
metadata = self._get_materialization_metadata(
403+
dagster_dbt_translator, manifest, target_path, project
404+
)
397405
if context and context.has_assets_def:
398406
yield Output(
399407
value=None, output_name=asset_key.to_python_identifier(), metadata=metadata

python_modules/libraries/dagster-dbt/dagster_dbt/core/dbt_event_iterator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def _fetch_column_metadata(
127127
manifest=invocation.manifest,
128128
dagster_dbt_translator=invocation.dagster_dbt_translator,
129129
target_path=invocation.target_path,
130+
project=invocation.project,
130131
)
131132

132133
except Exception as e:

python_modules/libraries/dagster-dbt/dagster_dbt_tests/core/dbt_packages/test_columns_metadata.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import shutil
44
import subprocess
5+
from collections.abc import Mapping
56
from pathlib import Path
67
from typing import Any, cast
78

@@ -11,6 +12,7 @@
1112
AssetExecutionContext,
1213
AssetKey,
1314
AssetSelection,
15+
AssetSpec,
1416
TableColumn,
1517
TableColumnDep,
1618
TableColumnLineage,
@@ -21,6 +23,8 @@
2123
from dagster._core.definitions.metadata.table import TableColumnConstraints
2224
from dagster_dbt.asset_decorator import dbt_assets
2325
from dagster_dbt.core.resource import DbtCliResource
26+
from dagster_dbt.dagster_dbt_translator import DagsterDbtTranslator
27+
from dagster_dbt.dbt_project import DbtProject
2428
from pytest_mock import MockFixture
2529
from sqlglot import Dialect
2630

@@ -294,6 +298,81 @@ def my_dbt_assets(context: AssetExecutionContext, dbt: DbtCliResource):
294298
)
295299

296300

301+
@pytest.mark.parametrize(
302+
"use_fetch_column_metadata",
303+
[True, False],
304+
ids=["adapter_path", "native_event_history_path"],
305+
)
306+
def test_column_lineage_uses_get_asset_spec_for_upstream_keys(
307+
test_metadata_manifest: dict[str, Any],
308+
use_fetch_column_metadata: bool,
309+
) -> None:
310+
"""Regression test for https://github.com/dagster-io/dagster/issues/33856.
311+
312+
Column lineage upstream asset keys must be resolved via
313+
``translator.get_asset_spec(...).key`` so that translators which customize
314+
translation only by overriding ``get_asset_spec`` (e.g.
315+
``DbtProjectComponentTranslator``) produce lineage entries that point at the
316+
*translated* keys actually present in the asset graph, rather than at the
317+
default-derived keys returned by ``get_asset_key``.
318+
319+
Exercised against both lineage-building paths through
320+
``_build_column_lineage_metadata``:
321+
322+
- ``adapter_path``: the post-run adapter-querying thread invoked when the
323+
user calls ``.fetch_column_metadata()``
324+
(``dbt_event_iterator._fetch_column_metadata``).
325+
- ``native_event_history_path``: the path driven by dbt's structured event
326+
history when ``has_column_lineage_metadata`` is ``True``
327+
(``dbt_cli_event._get_lineage_metadata``).
328+
"""
329+
330+
class SpecOverrideTranslator(DagsterDbtTranslator):
331+
def get_asset_spec(
332+
self,
333+
manifest: Mapping[str, Any],
334+
unique_id: str,
335+
project: DbtProject | None,
336+
) -> AssetSpec:
337+
spec = super().get_asset_spec(manifest, unique_id, project)
338+
return spec.replace_attributes(key=AssetKey(["renamed", *spec.key.path]))
339+
340+
translator = SpecOverrideTranslator()
341+
342+
@dbt_assets(manifest=test_metadata_manifest, dagster_dbt_translator=translator)
343+
def my_dbt_assets(context: AssetExecutionContext, dbt: DbtCliResource):
344+
cli_invocation = dbt.cli(["build"], context=context).stream()
345+
if use_fetch_column_metadata:
346+
cli_invocation = cli_invocation.fetch_column_metadata()
347+
yield from cli_invocation
348+
349+
result = materialize(
350+
[my_dbt_assets],
351+
resources={"dbt": DbtCliResource(project_dir=os.fspath(test_metadata_path))},
352+
)
353+
assert result.success
354+
355+
upstream_keys_in_lineage: set[AssetKey] = set()
356+
for event in result.get_asset_materialization_events():
357+
lineage = TableMetadataSet.extract(event.materialization.metadata).column_lineage
358+
if lineage is None:
359+
continue
360+
for col_deps in lineage.deps_by_column.values():
361+
for dep in col_deps:
362+
upstream_keys_in_lineage.add(dep.asset_key)
363+
364+
# We need at least one lineage entry for the assertion to be meaningful.
365+
assert upstream_keys_in_lineage, (
366+
"Expected at least one column lineage entry in the materialization metadata"
367+
)
368+
# Every upstream key referenced in column lineage must be the translated key
369+
# ("renamed/..."), not the default-derived key.
370+
for key in upstream_keys_in_lineage:
371+
assert key.path[0] == "renamed", (
372+
f"Upstream key {key} in column lineage was not translated via get_asset_spec"
373+
)
374+
375+
297376
@pytest.mark.parametrize(
298377
"use_experimental_fetch_column_schema",
299378
[True, False],

0 commit comments

Comments
 (0)