Skip to content

fix: handle dbt deferral in get_elementary_relation#1006

Merged
NoyaArie merged 2 commits into
masterfrom
devin/1778682933-fix-get-elementary-relation-deferral
May 14, 2026
Merged

fix: handle dbt deferral in get_elementary_relation#1006
NoyaArie merged 2 commits into
masterfrom
devin/1778682933-fix-get-elementary-relation-deferral

Conversation

@NoyaArie
Copy link
Copy Markdown
Contributor

@NoyaArie NoyaArie commented May 13, 2026

Summary

When dbt's --favor-state or --defer flags are active (common in dbt Cloud CI jobs), Elementary models are deferred to the production schema and not built in the CI target schema. The get_elementary_relation() macro uses adapter.get_relation() — a physical catalog lookup — to resolve Elementary tables like data_monitoring_metrics. This returns None when the table doesn't exist in the CI schema, which gets rendered literally as from None in the generated SQL, causing all anomaly tests to fail with:

Database Error: relation "none" does not exist
LINE 25:             from None

This fix adds a fallback gated on deferral being active (invocation_args_dict.defer or invocation_args_dict.favor_state): when adapter.get_relation() returns None but the model node exists in the dbt graph and deferral is active, we construct a Relation object from the graph node coordinates using api.Relation.create(). This allows the generated SQL to reference the correct (deferred) schema instead of failing.

Without deferral, the function returns None as before, preserving the existing contract for all callers that use truthiness checks to detect missing tables.

Affected test types: all anomaly monitors (volume, freshness, all_columns, dimension), plus test_execution_sla and result-handling macros.

Related: Pylon #3812, closed PR #1002.

Review & Testing Checklist for Human

  • Verify deferral behavior: Test with a dbt project using --favor-state where Elementary models exist in prod but not in the CI schema. Anomaly tests should now generate SQL referencing the deferred schema instead of from None.
  • Verify non-deferral behavior is unchanged: Run anomaly tests without --defer/--favor-stateadapter.get_relation() should still return a real relation as before, and if the table doesn't exist, None is returned (fallback is skipped because deferral is not active).
  • Verify missing-model error path without deferral: Run a partial dbt run (not including Elementary models) followed by anomaly tests — callers should still get None and raise the "missing Elementary models" error as before, since the deferral guard prevents the fallback from firing.

Notes

The fallback is gated on invocation_args_dict.get("defer", false) or invocation_args_dict.get("favor_state", false), which ensures it only activates during dbt commands that explicitly use deferral. This preserves backward compatibility for all existing callers.

Link to Devin session: https://app.devin.ai/sessions/b9eb2d85cd8f4489bf67686c7b413af0
Requested by: @NoyaArie

Summary by CodeRabbit

  • Bug Fixes
    • Added a deferral-aware fallback for relation lookups so missing database/schema entries no longer cause SQL generation failures in deferred scenarios; relations are now constructed with resolved schema and database info when the initial lookup fails.

Review Change Stack

When --favor-state or --defer is active, Elementary models may not be
built in the CI target schema. adapter.get_relation() returns None in
this case, causing anomaly tests to fail with 'relation "none" does
not exist'.

Fall back to constructing a Relation from graph node coordinates when
the physical catalog lookup returns None but the node exists in the
dbt graph. This allows the generated SQL to reference the correct
(deferred) schema.

Co-Authored-By: Noy Arie <noyarie1992@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c73e6dd-b857-481e-ba62-43f6fc21ee1a

📥 Commits

Reviewing files that changed from the base of the PR and between cb00d62 and fdcb302.

📒 Files selected for processing (1)
  • macros/utils/graph/get_elementary_relation.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • macros/utils/graph/get_elementary_relation.sql

📝 Walkthrough

Walkthrough

The macro captures the adapter relation lookup into a relation variable and returns it if present. If the lookup returns none and invocation indicates deferral while an identifier_node exists, the macro returns an api.Relation.create(...) constructed from resolved database, schema, and identifier alias.

Changes

Relation Lookup Fallback

Layer / File(s) Summary
Conditional return and fallback relation construction
macros/utils/graph/get_elementary_relation.sql
The macro assigns the adapter.get_relation result to a local relation and returns it only when not none. If the relation is missing but an identifier_node exists and deferral is active (defer or favor_state), it returns a constructed api.Relation.create(elementary_database, elementary_schema, identifier_alias).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A lookup that wandered and couldn't be found,
Now returns a relation when deferance is sound.
Database, schema, alias stitched tight,
The macro constructs order from absent sight.
Hooray — no more undefined SQL tonight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: handle dbt deferral in get_elementary_relation' directly and clearly describes the main change: adding deferral handling to the get_elementary_relation macro.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1778682933-fix-get-elementary-relation-deferral

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

👋 @NoyaArie
Thank you for raising your pull request.
Please make sure to add tests and document all user-facing changes.
You can do this by editing the docs files in the elementary repository.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +33 to +41
{% if identifier_node %}
{% do return(
api.Relation.create(
database=elementary_database,
schema=elementary_schema,
identifier=identifier_alias,
)
) %}
{% endif %}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Fallback api.Relation.create() breaks truthiness-based existence checks in all callers

When adapter.get_relation() returns None (table doesn't physically exist) but identifier_node is present in the graph, the new fallback constructs a synthetic relation via api.Relation.create(). Since Elementary models are always in the dbt graph once the package is installed, identifier_node is always truthy for them (see macros/utils/graph/get_node.sql). This means get_elementary_relation() now never returns None for any installed Elementary model, even when the underlying table hasn't been materialized yet.

This breaks the contract that numerous callers depend on — they use truthiness of the return value to guard against operating on non-existent tables:

Affected callers that rely on a falsy return
  • macros/edr/dbt_artifacts/upload_dbt_artifacts.sql:24: {% if elementary.get_elementary_relation(artifacts_model) %} — guard bypassed, attempts upload to non-existent table
  • macros/edr/dbt_artifacts/upload_dbt_artifacts.sql:49-52: {% if not artifacts_hash_relation %} — guard bypassed, runs SELECT on non-existent table
  • macros/edr/tests/test_execution_sla.sql:196: {% if not run_results_relation %} — error handling bypassed
  • macros/edr/tests/on_run_end/handle_tests_results.sql:176: {% if not target_relation %} raise_missing... — missing-model detection disabled
  • macros/edr/tests/on_run_end/handle_tests_results.sql:246: same as above for schema_columns_snapshot
  • All upload_dbt_*.sql files use {% if execute and relation %} guards that are now bypassed

For example, on a partial dbt run --select some_model (not including elementary models) or any scenario where the elementary tables don't yet exist, callers will proceed to execute SQL (INSERT/SELECT) against tables that don't exist, causing runtime database errors. The fallback should be gated on actual deferral being active, rather than unconditionally applied whenever the graph node exists.

Prompt for agents
The fallback in get_elementary_relation.sql (lines 33-41) creates a synthetic relation via api.Relation.create() whenever identifier_node exists and adapter.get_relation() returns None. Since identifier_node is always truthy for installed Elementary models (they're always in the dbt graph), this means the function never returns None for any elementary model, even when the physical table doesn't exist.

This breaks all callers that use truthiness checks on the return value to detect non-existent tables (e.g. upload_dbt_artifacts.sql:24, handle_tests_results.sql:176, test_execution_sla.sql:196, and all upload_dbt_*.sql files).

Possible fixes:
1. Gate the fallback on dbt deferral being active. Check if dbt's defer/favor-state flags are set (e.g., via flags or var) before applying the fallback.
2. Only apply the fallback when the node's database/schema differ from the current target, which would indicate deferral is remapping coordinates.
3. If neither is feasible, update all callers to separately verify table existence (e.g., via adapter.get_relation()) rather than relying on this function's return value. But this would be a much larger change.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in fdcb302. The fallback is now gated on invocation_args_dict.defer or invocation_args_dict.favor_state being truthy, so it only fires when dbt deferral is actually active. Without deferral, get_elementary_relation() returns None as before, preserving the existing contract for all callers.

Only construct a synthetic Relation when dbt deferral is actually
active (invocation_args_dict.defer or favor_state). Without this
guard, the fallback would fire for any installed Elementary model
whose table doesn't exist yet (e.g. partial dbt run), breaking
callers that rely on a None return to detect missing tables.

Co-Authored-By: Noy Arie <noyarie1992@gmail.com>
@NoyaArie NoyaArie merged commit 8e1fc0b into master May 14, 2026
26 of 30 checks passed
@NoyaArie NoyaArie deleted the devin/1778682933-fix-get-elementary-relation-deferral branch May 14, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants