From 192208fe942f93f358c24e635d85804342eff67d Mon Sep 17 00:00:00 2001 From: aaramg Date: Sun, 8 Nov 2026 18:30:03 +0400 Subject: [PATCH 1/5] fix: fill every elementary graph entry even when the mapping already exists dbt v2 shares the Jinja graph between parsing and the run, so a get_cache call rendered during parsing leaves an empty "elementary" mapping. init_elementary_graph's single setdefault on the whole mapping then kept it empty, and every later get_cache returned None. Each entry is now filled on its own; existing values are kept. --- .../macros/test_init_elementary_graph.sql | 6 +++ .../tests/test_init_elementary_graph.py | 38 +++++++++++++++++++ .../on_run_start/init_elementary_graph.sql | 32 +++++++++------- 3 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 integration_tests/dbt_project/macros/test_init_elementary_graph.sql create mode 100644 integration_tests/tests/test_init_elementary_graph.py diff --git a/integration_tests/dbt_project/macros/test_init_elementary_graph.sql b/integration_tests/dbt_project/macros/test_init_elementary_graph.sql new file mode 100644 index 000000000..cf864b359 --- /dev/null +++ b/integration_tests/dbt_project/macros/test_init_elementary_graph.sql @@ -0,0 +1,6 @@ +{% macro test_init_elementary_graph(existing) %} + {#- What a cache read before the on-run-start hook can leave on the graph -#} + {% do graph.update({"elementary": existing}) %} + {% do elementary.init_elementary_graph() %} + {{ return(graph["elementary"]) }} +{% endmacro %} diff --git a/integration_tests/tests/test_init_elementary_graph.py b/integration_tests/tests/test_init_elementary_graph.py new file mode 100644 index 000000000..4e946a47a --- /dev/null +++ b/integration_tests/tests/test_init_elementary_graph.py @@ -0,0 +1,38 @@ +import json + +import pytest +from dbt_project import DbtProject + +ENTRIES = { + "elementary_test_results", + "elementary_test_failed_row_counts", + "tests_schema_name", + "tables", + "temp_test_table_relations_map", + "duration_context_stack", + "microbatch_compiled_code_by_unique_id", +} + + +@pytest.mark.parametrize( + "existing", + [ + pytest.param({}, id="empty_entry_left_by_an_earlier_read"), + pytest.param({"tests_schema_name": "kept"}, id="entry_with_a_value"), + ], +) +def test_init_elementary_graph_fills_every_missing_entry( + dbt_project: DbtProject, existing: dict +): + result = dbt_project.dbt_runner.run_operation( + "elementary_tests.test_init_elementary_graph", + macro_args={"existing": existing}, + ) + elementary_graph = json.loads(result[0]) + assert set(elementary_graph) == ENTRIES + assert elementary_graph["tables"] == { + "metrics": {"relations": [], "rows": []}, + "schema_snapshots": [], + } + for entry, value in existing.items(): + assert elementary_graph[entry] == value diff --git a/macros/edr/tests/on_run_start/init_elementary_graph.sql b/macros/edr/tests/on_run_start/init_elementary_graph.sql index 00fda83de..409a373ce 100644 --- a/macros/edr/tests/on_run_start/init_elementary_graph.sql +++ b/macros/edr/tests/on_run_start/init_elementary_graph.sql @@ -1,17 +1,21 @@ {% macro init_elementary_graph() %} - {% do graph.setdefault( - "elementary", - { - "elementary_test_results": {}, - "elementary_test_failed_row_counts": {}, - "tests_schema_name": none, - "tables": { - "metrics": {"relations": [], "rows": []}, - "schema_snapshots": [], - }, - "temp_test_table_relations_map": {}, - "duration_context_stack": {}, - "microbatch_compiled_code_by_unique_id": {}, + {% set defaults = { + "elementary_test_results": {}, + "elementary_test_failed_row_counts": {}, + "tests_schema_name": none, + "tables": { + "metrics": {"relations": [], "rows": []}, + "schema_snapshots": [], }, - ) %} + "temp_test_table_relations_map": {}, + "duration_context_stack": {}, + "microbatch_compiled_code_by_unique_id": {}, + } %} + {#- Each entry is filled on its own: a cache read before this hook (dbt v2 shares + `graph` between parsing and the run) can leave an empty "elementary" mapping, + which a single setdefault on the whole mapping would keep as it is. -#} + {% set elementary_graph = graph.setdefault("elementary", {}) %} + {% for entry, default in defaults.items() %} + {% do elementary_graph.setdefault(entry, default) %} + {% endfor %} {% endmacro %} From de32cf8277817b5debd2121d86dcafc3b9f9a1f5 Mon Sep 17 00:00:00 2001 From: aaramg Date: Sun, 8 Nov 2026 18:36:21 +0400 Subject: [PATCH 2/5] test: keep the results a cache entry already holds --- integration_tests/tests/test_init_elementary_graph.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integration_tests/tests/test_init_elementary_graph.py b/integration_tests/tests/test_init_elementary_graph.py index 4e946a47a..8798928cd 100644 --- a/integration_tests/tests/test_init_elementary_graph.py +++ b/integration_tests/tests/test_init_elementary_graph.py @@ -19,6 +19,10 @@ [ pytest.param({}, id="empty_entry_left_by_an_earlier_read"), pytest.param({"tests_schema_name": "kept"}, id="entry_with_a_value"), + pytest.param( + {"elementary_test_results": {"test.pkg.not_null_id": [{"status": "pass"}]}}, + id="cache_entry_with_results", + ), ], ) def test_init_elementary_graph_fills_every_missing_entry( From 9fd2824b67d3f07b9fed60ad1e74e3be3fcc2972 Mon Sep 17 00:00:00 2001 From: aaramg Date: Sun, 8 Nov 2026 18:39:11 +0400 Subject: [PATCH 3/5] test: say what the init graph test proves --- integration_tests/tests/test_init_elementary_graph.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/integration_tests/tests/test_init_elementary_graph.py b/integration_tests/tests/test_init_elementary_graph.py index 8798928cd..6e539e597 100644 --- a/integration_tests/tests/test_init_elementary_graph.py +++ b/integration_tests/tests/test_init_elementary_graph.py @@ -28,6 +28,12 @@ def test_init_elementary_graph_fills_every_missing_entry( dbt_project: DbtProject, existing: dict ): + """init_elementary_graph fills each missing entry and keeps the ones present. + + `existing` is what an earlier read can leave under graph["elementary"]: dbt v2 + shares `graph` between parsing and the run, so a get_cache rendered at parse + time leaves an empty mapping before the on-run-start hook runs. + """ result = dbt_project.dbt_runner.run_operation( "elementary_tests.test_init_elementary_graph", macro_args={"existing": existing}, From 1d761015a937a5663eca9f268c7811098d31908a Mon Sep 17 00:00:00 2001 From: aaramg Date: Sun, 8 Nov 2026 18:48:52 +0400 Subject: [PATCH 4/5] test: check every entry's default, not only its presence --- .../tests/test_init_elementary_graph.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/integration_tests/tests/test_init_elementary_graph.py b/integration_tests/tests/test_init_elementary_graph.py index 6e539e597..57646ff8e 100644 --- a/integration_tests/tests/test_init_elementary_graph.py +++ b/integration_tests/tests/test_init_elementary_graph.py @@ -3,14 +3,17 @@ import pytest from dbt_project import DbtProject -ENTRIES = { - "elementary_test_results", - "elementary_test_failed_row_counts", - "tests_schema_name", - "tables", - "temp_test_table_relations_map", - "duration_context_stack", - "microbatch_compiled_code_by_unique_id", +DEFAULTS = { + "elementary_test_results": {}, + "elementary_test_failed_row_counts": {}, + "tests_schema_name": None, + "tables": { + "metrics": {"relations": [], "rows": []}, + "schema_snapshots": [], + }, + "temp_test_table_relations_map": {}, + "duration_context_stack": {}, + "microbatch_compiled_code_by_unique_id": {}, } @@ -39,10 +42,4 @@ def test_init_elementary_graph_fills_every_missing_entry( macro_args={"existing": existing}, ) elementary_graph = json.loads(result[0]) - assert set(elementary_graph) == ENTRIES - assert elementary_graph["tables"] == { - "metrics": {"relations": [], "rows": []}, - "schema_snapshots": [], - } - for entry, value in existing.items(): - assert elementary_graph[entry] == value + assert elementary_graph == {**DEFAULTS, **existing} From bb09c477d706cbc7dc2e292bfcb0fc712ae0da28 Mon Sep 17 00:00:00 2001 From: aaramg Date: Sun, 8 Nov 2026 19:34:05 +0400 Subject: [PATCH 5/5] test: cover a graph with no elementary entry yet --- .../dbt_project/macros/test_init_elementary_graph.sql | 6 ++++-- integration_tests/tests/test_init_elementary_graph.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/integration_tests/dbt_project/macros/test_init_elementary_graph.sql b/integration_tests/dbt_project/macros/test_init_elementary_graph.sql index cf864b359..d5ed96667 100644 --- a/integration_tests/dbt_project/macros/test_init_elementary_graph.sql +++ b/integration_tests/dbt_project/macros/test_init_elementary_graph.sql @@ -1,6 +1,8 @@ {% macro test_init_elementary_graph(existing) %} - {#- What a cache read before the on-run-start hook can leave on the graph -#} - {% do graph.update({"elementary": existing}) %} + {#- `existing` is what an earlier cache read can leave on the graph; none means nothing yet -#} + {% if existing is none %} {% do graph.pop("elementary", none) %} + {% else %} {% do graph.update({"elementary": existing}) %} + {% endif %} {% do elementary.init_elementary_graph() %} {{ return(graph["elementary"]) }} {% endmacro %} diff --git a/integration_tests/tests/test_init_elementary_graph.py b/integration_tests/tests/test_init_elementary_graph.py index 57646ff8e..8cf62c6aa 100644 --- a/integration_tests/tests/test_init_elementary_graph.py +++ b/integration_tests/tests/test_init_elementary_graph.py @@ -1,4 +1,5 @@ import json +from typing import Optional import pytest from dbt_project import DbtProject @@ -20,6 +21,7 @@ @pytest.mark.parametrize( "existing", [ + pytest.param(None, id="no_entry_yet"), pytest.param({}, id="empty_entry_left_by_an_earlier_read"), pytest.param({"tests_schema_name": "kept"}, id="entry_with_a_value"), pytest.param( @@ -29,11 +31,12 @@ ], ) def test_init_elementary_graph_fills_every_missing_entry( - dbt_project: DbtProject, existing: dict + dbt_project: DbtProject, existing: Optional[dict] ): """init_elementary_graph fills each missing entry and keeps the ones present. - `existing` is what an earlier read can leave under graph["elementary"]: dbt v2 + `existing` is what an earlier read can leave under graph["elementary"] (None: no + entry yet, as on dbt v1): dbt v2 shares `graph` between parsing and the run, so a get_cache rendered at parse time leaves an empty mapping before the on-run-start hook runs. """ @@ -42,4 +45,4 @@ def test_init_elementary_graph_fills_every_missing_entry( macro_args={"existing": existing}, ) elementary_graph = json.loads(result[0]) - assert elementary_graph == {**DEFAULTS, **existing} + assert elementary_graph == {**DEFAULTS, **(existing or {})}