Even when the logic is identical, the optimal SQL and functions differ per adapter. dbt's macro dispatch automatically routes a call to the macro implementation matching the active adapter at runtime.
This article covers the basics of naming conventions (default__, snowflake__, etc.) and lookup order (dispatch configuration), through to real-world overrides, validation, and anti-patterns, with an eye on the topics commonly tested on the exam.
adapter.dispatch delegates from a wrapper macro to an implementation macro tuned for the active adapter. Callers use a single macro name while, behind the scenes, dbt picks between snowflake__..., bigquery__..., default__..., and so on.
The default lookup order follows the dispatch settings in your project. When unset, dbt generally searches your project first and then the package that owns the wrapper, looking for the adapter-prefixed macro (e.g. snowflake__macro) and falling back to default__macro when none is found.
| Mechanism | Characteristics | Typical use cases |
|---|---|---|
| adapter.dispatch | Automatically picks the best implementation per adapter | Cross-adapter package support and distributing shared macros |
| default__ implementation | Safe backward-compatible fallback when no adapter-specific implementation exists | Generic logic that can be written in ANSI SQL |
| Project override (dispatch) | Lets you swap out an external package's implementation locally | Temporary bug workarounds and dialect tuning |
Minimal wrapper and implementation
{% macro my_pkg.my_macro(col) %}\n {%- set impl = adapter.dispatch('my_macro', 'my_pkg') -%}\n {{ impl(col) }}\n{% endmacro %}\n\n{# Implementations: inside the same package (my_pkg) #}\n{% macro my_pkg.default__my_macro(col) %}\n upper({{ col }}) {# Generic example that runs on any DWH #}\n{% endmacro %}\n\n{% macro my_pkg.snowflake__my_macro(col) %}\n upper({{ col }})::string {# Example of Snowflake-specific tuning #}\n{% endmacro %}Implementation macro names follow the shape "prefix__original_macro_name". The prefix is the adapter type (snowflake, bigquery, redshift, postgres, spark, databricks, etc.) or default. The wrapper itself has no prefix and uses adapter.dispatch with the macro name and the package name.
Lookup order is controlled via dispatch in dbt_project.yml. Set macro_namespace to the package that owns the wrapper, and use search_order to set the package search priority. Most projects put their own project at the head to allow overrides, followed by the original package.
adapter.dispatch lookup flow
dispatch configuration example (dbt_project.yml)
dispatch:\n - macro_namespace: my_pkg\n search_order: ['my_project', 'my_pkg']\n\n# Lookup order from wrapper my_pkg.my_macro:\n# 1) my_project.snowflake__my_macro → fallback to my_project.default__my_macro\n# 2) my_pkg.snowflake__my_macro → fallback to my_pkg.default__my_macroWhen you need to temporarily adjust the behavior of an external package macro, put your project at the head of search_order and define an implementation macro with the same name to override it. The benefit is that you can maintain just the diff without forking the package itself.
Even when overriding, keep the name unchanged. Example: if the source package has my_pkg.snowflake__my_macro, define snowflake__my_macro under your own project's namespace as well.
Project-level override example
{# packages.yml #}\npackages:\n - package: my_org\n version: 1.0.0\n\n{# dbt_project.yml #}\ndispatch:\n - macro_namespace: my_org\n search_order: ['my_project', 'my_org']\n\n{# Project side (override): models/macros/snowflake__my_macro.sql #}\n{% macro my_project.snowflake__my_macro(col) %}\n to_varchar(upper({{ col }})) {# Fine-grained override for Snowflake #}\n{% endmacro %}To see which implementation was resolved, write a small macro that logs the result via run-operation. Including adapter.type or target.type alongside makes it easier to match against the execution environment.
In CI, run sample models against multiple profiles (e.g. snowflake, bigquery) to verify that the same wrapper resolves to different implementations per environment as intended.
Log which implementation was chosen
{% macro debug_my_macro_resolution() %}\n {# Wrapper name and namespace #}\n {% set m = adapter.dispatch('my_macro', 'my_pkg') %}\n {% do log('adapter: ' ~ target.type, info=True) %}\n {# The macro object itself usually contains the name when stringified #}\n {% do log('resolved macro: ' ~ (m|string), info=True) %}\n{% endmacro %}\n\n# Example: dbt run-operation debug_my_macro_resolutionSwitching SQL with if-branches on adapter.type scales poorly, is hard to maintain, and is unsuitable for package distribution. With dispatch, adding support for a new adapter is as simple as adding another implementation macro.
If the wrapper's namespace and the package name passed to dispatch do not match, the intended implementation will not be found—either silently falling through to default or erroring out unresolved.
Refactoring from raw branching to dispatch
{# Bad example #}\n{% macro my_macro(col) %}\n {% if target.type == 'snowflake' %}\n {{ return('upper(' ~ col ~ ')::string') }}\n {% elif target.type == 'bigquery' %}\n {{ return('cast(upper(' ~ col ~ ') as string)') }}\n {% else %}\n {{ return('upper(' ~ col ~ ')') }}\n {% endif %}\n{% endmacro %}\n\n{# Good example: wrapper + implementations #}\n{% macro my_pkg.my_macro(col) %}\n {% set impl = adapter.dispatch('my_macro', 'my_pkg') %}\n {{ impl(col) }}\n{% endmacro %}\n\n{% macro my_pkg.default__my_macro(col) %} upper({{ col }}) {% endmacro %}\n{% macro my_pkg.bigquery__my_macro(col) %} cast(upper({{ col }}) as string) {% endmacro %}\n{% macro my_pkg.snowflake__my_macro(col) %} upper({{ col }})::string {% endmacro %}Open-source Spark and Databricks often share similar SQL dialects, but function names, data types, and DDL behavior can differ in practice. Provide both spark__ and databricks__ prefixes as needed and optimize each with minimal diffs, rather than collapsing one into the default.
When building an external package, start by hardening default__, then incrementally add minimal implementations for the major adapters (snowflake/bigquery/redshift/postgres/spark/databricks). Leaving project-level search_order overrides as an escape hatch makes day-to-day operations much easier.
Skeleton for minimal multi-adapter implementations
{% macro util_pkg.normalize_text(col) %}\n {% set impl = adapter.dispatch('normalize_text', 'util_pkg') %}\n {{ impl(col) }}\n{% endmacro %}\n\n{% macro util_pkg.default__normalize_text(col) %}\n trim(lower({{ col }}))\n{% endmacro %}\n\n{% macro util_pkg.bigquery__normalize_text(col) %}\n trim(lower(cast({{ col }} as string)))\n{% endmacro %}\n\n{% macro util_pkg.databricks__normalize_text(col) %}\n trim(lower({{ col }})) {# Add normalization functions as needed #}\n{% endmacro %}Analytics Engineer
問題 1
Given the configuration and implementations below, with Snowflake as the target, which implementation is selected when {{ my_pkg.my_macro('name') }} is called?
正解: A
With dispatch set to macro_namespace: my_pkg and search_order: ['my_project', 'my_pkg'], the lookup order is: 1) my_project.snowflake__my_macro → my_project.default__my_macro if absent, then 2) my_pkg.snowflake__my_macro → my_pkg.default__my_macro if absent. On a Snowflake target, if the first one (my_project.snowflake__my_macro) exists, it is selected.
What is the lookup order when dispatch is not configured?
Generally, dbt searches your own project first, then the package that owns the wrapper. Within each, it looks for the adapter-prefixed macro (e.g. snowflake__) and falls back to default__ if that is missing. For deterministic control, declare dispatch explicitly in dbt_project.yml.
Do the wrapper and its implementations need to live in the same package?
Implementations (default__, snowflake__, etc.) are defined against the namespace the wrapper belongs to. When distributing an external package, ship the implementations inside that package; for project-level overrides, place your own project at the head of search_order.
Should I provide Spark or Databricks implementations (or both)?
They are largely compatible but have meaningful differences, so providing both spark__ and databricks__ prefixes separately is safest. If you cover only one, run CI against both targets to catch unintended default fallbacks or dialect mismatches.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
dbt Models: SQL-Defined Transformation Units (2026)
Model fundamentals — SELECT-based definitions, naming, refs,...
dbt Analytics Engineering Exam: Complete Guide (2026)
Pass the AE Certification — scope, weighting, sample questio...
dbt Cloud vs dbt Core: Feature & Cost Comparison (2026)
Honest comparison of dbt Cloud vs. dbt Core — IDE, scheduler...
dbt Project Structure: models/seeds/macros Layout (2026)
Recommended dbt project layout — models, seeds, macros, snap...
dbt_project.yml Explained: Every Config (2026)
Every dbt_project.yml setting that matters — paths, vars, ma...