Advanced Jinja Techniques
Dispatch Architecture
Architecture Diagram
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā JINJA DISPATCH ARCHITECTURE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā DISPATCH FLOW ā ā
ā ā ā ā
ā ā Macro Call: {{ adapter.dispatch('macro_name', 'package') }} ā ā
ā ā ā ā
ā ā 1. Check project-level override ā ā
ā ā 2. Check package-level implementation ā ā
ā ā 3. Check adapter-specific implementation ā ā
ā ā 4. Use default implementation ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā ā
ā ā¼ ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā DISPATCH EXAMPLE ā ā
ā ā ā ā
ā ā {% macro safe_cast(column, type) %} ā ā
ā ā {% set macro = adapter.dispatch('safe_cast', 'my_package') ā ā
ā ā (column, type) %} ā ā
ā ā {{ return(macro) }} ā ā
ā ā {% endmacro %} ā ā
ā ā ā ā
ā ā Implementations: ā ā
ā ā āāā default__safe_cast (fallback) ā ā
ā ā āāā snowflake__safe_cast (Snowflake-specific) ā ā
ā ā āāā bigquery__safe_cast (BigQuery-specific) ā ā
ā ā āāā redshift__safe_cast (Redshift-specific) ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Adapter Pattern
Architecture Diagram
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ADAPTER PATTERN IMPLEMENTATION ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā ADAPTER HIERARCHY ā ā
ā ā ā ā
ā ā āāāāāāāāāāāāāāāā ā ā
ā ā ā BaseAdapter ā ā ā
ā ā ā (dbt-core) ā ā ā
ā ā āāāāāāāā¬āāāāāāāā ā ā
ā ā ā ā ā
ā ā āāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāā ā ā
ā ā ā ā ā ā ā
ā ā ā¼ ā¼ ā¼ ā ā
ā ā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā ā ā
ā ā ā Snowflake ā ā BigQuery ā ā Redshift ā ā ā
ā ā ā Adapter ā ā Adapter ā ā Adapter ā ā ā
ā ā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā ā
ā ā¼ ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā MACRO DISPATCH ā ā
ā ā ā ā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā
ā ā ā safe_cast(column, type) ā ā ā
ā ā ā ā ā ā
ā ā ā Snowflake: safe_cast({{ column }} as {{ type }}) ā ā ā
ā ā ā BigQuery: safe_cast({{ column }} as {{ type }}) ā ā ā
ā ā ā Redshift: cast({{ column }} as {{ type }}) ā ā ā
ā ā ā Default: cast({{ column }} as {{ type }}) ā ā ā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Custom Macro Architecture
Architecture Diagram
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā CUSTOM MACRO ARCHITECTURE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā MACRO LIBRARY STRUCTURE ā ā
ā ā ā ā
ā ā macros/ ā ā
ā ā āāā general/ ā ā
ā ā ā āāā string_utils.sql ā ā
ā ā ā āāā date_utils.sql ā ā
ā ā ā āāā math_utils.sql ā ā
ā ā āāā cross_db/ ā ā
ā ā ā āāā safe_cast.sql ā ā
ā ā ā āāā date_trunc.sql ā ā
ā ā ā āāā concat.sql ā ā
ā ā āāā schema_tests/ ā ā
ā ā ā āāā test_unique.sql ā ā
ā ā ā āāā test_not_null.sql ā ā
ā ā āāā generate/ ā ā
ā ā āāā create_table.sql ā ā
ā ā āāā merge.sql ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā ā
ā ā¼ ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā MACRO USAGE PATTERNS ā ā
ā ā ā ā
ā ā 1. Direct call: {{ macro_name(args) }} ā ā
ā ā 2. Return value: {{ return(value) }} ā ā
ā ā 3. Call macro: {% call macro() %}...{% endcall %} ā ā
ā ā 4. Import: {% import 'macros/utils.sql' as utils %} ā ā
ā ā 5. From import: {% from 'macros/utils.sql' import safe_cast %} ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Detailed Explanation
Advanced Jinja techniques in dbt enable sophisticated data transformation patterns, cross-database compatibility, and reusable code libraries.
Dispatch Pattern
The dispatch pattern allows macros to have database-specific implementations:
- Base implementation: Default fallback
- Adapter-specific: Database-optimized versions
- Package-level: Shared across projects
- Project-level: Custom overrides
Adapter Functions
dbt provides adapter functions for database-specific operations:
adapter.dispatch(): Route to correct implementationadapter.resolve(): Resolve model referencesadapter.get_relation(): Get database objectsadapter.create_schema(): Create database schemasadapter.drop_relation(): Drop database objects
Macro Libraries
Organize macros into reusable libraries:
- General utilities: String, date, math functions
- Cross-database: Database-agnostic functions
- Schema tests: Reusable test definitions
- Generation macros: Code generation utilities
Advanced Patterns
- Recursive macros: Process hierarchical data
- Conditional logic: Dynamic SQL based on conditions
- Loop constructs: Process collections efficiently
- Context manipulation: Modify compilation context
Code Examples
Cross-Database Dispatch
-- macros/cross_db/safe_cast.sql
{% macro safe_cast(column, type) %}
{% set macro = adapter.dispatch('safe_cast', 'dbt_utils')(column, type) %}
{{ return(macro) }}
{% endmacro %}
{% macro default__safe_cast(column, type) %}
cast({{ column }} as {{ type }})
{% endmacro %}
{% macro snowflake__safe_cast(column, type) %}
safe_cast({{ column }} as {{ type }})
{% endmacro %}
{% macro bigquery__safe_cast(column, type) %}
safe_cast({{ column }} as {{ type }})
{% endmacro %}
{% macro redshift__safe_cast(column, type) %}
case
when {{ column }} ~ '^[0-9]+\.?[0-9]*$' then cast({{ column }} as {{ type }})
else null
end
{% endmacro %}
Advanced Macro with Call Block
-- macros/generate_merge.sql
{% macro generate_merge(target, source, unique_key, update_columns, insert_columns) %}
{% call statement('merge') %}
merge into {{ target }} as target
using {{ source }} as source
on {{ unique_key }}
when matched then update set
{% for col in update_columns %}
{{ col }} = source.{{ col }}
{% if not loop.last %},{% endif %}
{% endfor %}
when not matched then insert (
{{ insert_columns | join(', ') }}
)
values (
{% for col in insert_columns %}
source.{{ col }}
{% if not loop.last %},{% endif %}
{% endfor %}
)
{% endcall %}
{% endmacro %}
Recursive Macro for Hierarchical Data
-- macros/generate_recursive_cte.sql
{% macro recursive_cte(cte_name, base_query, recursive_query, max_depth=10) %}
with recursive {{ cte_name }} as (
{{ base_query }}
union all
select
{% for col in base_columns %}
{{ cte_name }}_next.{{ col }}
{% if not loop.last %},{% endif %}
{% endfor %}
from {{ cte_name }}
inner join (
{{ recursive_query }}
) {{ cte_name }}_next
on {{ cte_name }}.id = {{ cte_name }}_next.parent_id
where {{ cte_name }}.depth < {{ max_depth }}
)
select * from {{ cte_name }}
{% endmacro %}
Dynamic Column Generation
-- macros/generate_pivot.sql
{% macro pivot(source, group_by_columns, pivot_column, value_column, agg='sum') %}
{% set pivot_values = run_query(
"select distinct " ~ pivot_column ~ " from " ~ source ~ " order by 1"
).columns[0].values() %}
select
{{ group_by_columns | join(', ') }},
{% for value in pivot_values %}
{{ agg }}(case when {{ pivot_column }} = '{{ value }}' then {{ value_column }} end) as {{ value_column }}_{{ value | replace(' ', '_') | lower }}
{% if not loop.last %},{% endif %}
{% endfor %}
from {{ source }}
group by {{ group_by_columns | join(', ') }}
{% endmacro %}
Custom Test Macro
-- macros/schema_tests/test_freshness.sql
{% test freshness(model, column_name, interval, datepart) %}
with source_data as (
select
max({{ column_name }}) as last_record,
{{ dbt_utils.current_timestamp() }} as current_time
from {{ model }}
),
validation as (
select
last_record,
current_time,
{{ dbt_utils.datediff(
"last_record",
"current_time",
datepart
)}} as time_diff
from source_data
)
select *
from validation
where time_diff > {{ interval }}
{% endtest %}
Advanced Dispatch with Package Override
-- macros/cross_db/date_trunc.sql
{% macro date_trunc(datepart, date) %}
{% set macro = adapter.dispatch('date_trunc', 'dbt_utils')(datepart, date) %}
{{ return(macro) }}
{% endmacro %}
{% macro default__date_trunc(datepart, date) %}
date_trunc({{ datepart }}, {{ date }})
{% endmacro %}
{% macro snowflake__date_trunc(datepart, date) %}
date_trunc({{ datepart }}, {{ date }})
{% endmacro %}
{% macro bigquery__date_trunc(datepart, date) %}
date_trunc({{ date }}, {{ datepart }})
{% endmacro %}
{% macro redshift__date_trunc(datepart, date) %}
date_trunc({{ datepart }}, {{ date }})
{% endmacro %}
{% macro postgres__date_trunc(datepart, date) %}
date_trunc({{ datepart }}, {{ date }})
{% endmacro %}
Performance Metrics
| Pattern | Compilation Time | Use Case |
|---|---|---|
| Direct call | O(1) | Simple macros |
| Dispatch | O(n) | Cross-database |
| Recursive | O(depth) | Hierarchical data |
| Dynamic columns | O(rows) | Pivot operations |
| Import | O(1) | Code reuse |
Best Practices
- Use dispatch for cross-database compatibility
- Organize macros into logical directories
- Document macros with clear descriptions
- Test macros with different inputs
- Use return() to return values from macros
- Leverage call blocks for complex operations
- Import strategically to avoid circular dependencies
- Use adapter functions for database-specific operations