Skip to content

fix: detect ref() calls nested in Jinja macro arguments - #92

Merged
eitsupi merged 2 commits into
mainfrom
fix/refs-in-macro-args
Jun 11, 2026
Merged

fix: detect ref() calls nested in Jinja macro arguments#92
eitsupi merged 2 commits into
mainfrom
fix/refs-in-macro-args

Conversation

@eitsupi

@eitsupi eitsupi commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Fixes #91

Problem

ref() calls passed as arguments to macros that minijinja cannot resolve (package macros such as shared_macros.import_cte(...) or dbt_utils.star(from=ref(...))) were not detected, producing incomplete lineage.

Two layers both dropped them:

  1. When rendering hits an unknown macro it fails, and the partial extraction (including refs already recorded from the macro arguments) was discarded.
  2. The regex fallback only matched ref() as a whole {{ ref('...') }} expression, so calls nested in macro arguments or {% set %} statements never matched.

Fix

  • Keep refs/sources recorded before a render failure instead of discarding them. minijinja evaluates call arguments before resolving the callee, so {{ unknown_macro(ref('a')) }} records ref('a') even though rendering fails. This also recovers dynamically built refs like ref('model_' ~ var('env')) that no regex can find.
  • Relax the regex fallback to find ref()/source() anywhere inside {{ ... }} and {% ... %} blocks, then merge it with the partial jinja result (deduplicated). {# #} comments and {% raw %} sections are excluded, text outside jinja blocks (e.g. SQL comments) is not scanned, and matches starting inside string literals are skipped so {{ log("see ref('x')") }} does not create a phantom edge.

Verification

  • 18 new unit tests covering the issue example, package/unknown macros, {% set %}, refs after the failure point, dynamic refs, string-literal and {% raw %} false-positive guards
  • End-to-end: a project using the exact import_cte pattern from the issue now shows all three upstream models in dlin graph

🤖 Generated with Claude Code

eitsupi and others added 2 commits June 11, 2026 15:19
When a model calls a macro that minijinja cannot resolve (e.g. a package
macro like shared_macros.import_cte or dbt_utils.star), rendering fails
and extraction fell back to a regex that only matched bare
{{ ref('...') }} expressions, dropping any ref()/source() nested in
macro arguments.

- Keep refs/sources recorded before the render failure instead of
  discarding the partial extraction (minijinja evaluates call arguments
  before resolving the callee, so they are valid)
- Relax the regex fallback to find ref()/source() anywhere inside
  {{ ... }} and {% ... %} blocks, excluding {# #} comments and
  {% raw %} sections, and merge it with the partial jinja result

This also fixes refs inside {% set %} statements being missed by the
regex fallback.

Fixes #91

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The block-scoped regex fallback scanned raw jinja block text, so
ref()/source() appearing inside a string literal (e.g. a log message
like {{ log("see ref('x')") }}) would create a phantom dependency.
Track quoted string literal spans per block, honoring backslash
escapes, and skip matches that start inside one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@eitsupi eitsupi changed the title fix: detect ref() calls nested in Jinja macro arguments fix: detect ref() calls nested in Jinja macro arguments Jun 11, 2026
@eitsupi
eitsupi requested a review from Copilot June 11, 2026 15:30

Copilot AI left a comment

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.

Pull request overview

This PR fixes incomplete dbt lineage extraction when ref()/source() calls appear inside macro arguments (especially namespaced/package macros that minijinja can’t resolve). It does this by preserving partial Jinja extraction results on render failure and enhancing the regex fallback to detect calls anywhere inside Jinja blocks.

Changes:

  • Preserve refs/sources/config recorded during a failed minijinja render and merge with regex results instead of discarding partial extraction.
  • Update regex fallback to scan only inside {{ ... }} / {% ... %} blocks (excluding {# #} and {% raw %}) and skip matches inside string literals.
  • Add unit tests covering nested macro args, {% set %} statements, raw blocks, and string-literal false-positive guards.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
crates/dlin-core/src/parser/sql.rs Improves regex fallback extraction (block-scoped scanning, raw/comment stripping, string-literal guarding) and merges regex results with partial Jinja extraction when rendering fails.
crates/dlin-core/src/parser/jinja.rs Changes Jinja extraction to return a structured outcome (partial extraction + completion flag) and makes merge logic reusable for combining partial + fallback results.
Comments suppressed due to low confidence (1)

crates/dlin-core/src/parser/sql.rs:320

  • extract_config_regex strips only {# ... #} comments, but not {% raw %}...{% endraw %} blocks. Since {% raw %} content is not evaluated by Jinja, a {{ config(...) }} inside a raw block would be incorrectly treated as real config when extract_all falls back to regex merging on render failures. Using the same inert-Jinja stripping as ref/source extraction avoids this false positive.
    extract_all(sql, macro_prefix).config
}

/// Regex fallback for extracting config() settings
fn extract_config_regex(sql: &str) -> SqlConfig {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@eitsupi
eitsupi merged commit 56d55ac into main Jun 11, 2026
12 checks passed
@eitsupi
eitsupi deleted the fix/refs-in-macro-args branch June 11, 2026 15:36
@eitsupi eitsupi mentioned this pull request Jun 11, 2026
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.

ref() calls inside Jinja macro arguments are not detected

2 participants