同じロジックでも、アダプターごとに最適なSQLや関数は異なります。dbtのmacro dispatchは、実行時のアダプターに応じたマクロ実装へ自動ルーティングする仕組みです。
本記事では、命名規則(default__ / snowflake__ など)と検索順序(dispatch設定)の基本から、実務での上書き・検証・アンチパターンまでを、試験で問われやすい観点とともに整理します。
adapter.dispatchは、ある“ラッパー”マクロから、実行中のアダプターに最適化された“実装”マクロへ処理を委譲します。呼び出し側は1つのマクロ名に統一しつつ、裏側でsnowflake__...やbigquery__...、default__...といった実装を切り替えます。
既定の検索順序は、プロジェクト設定のdispatchに従います。未設定の場合は一般に「自プロジェクト」→「ラッパーの属するパッケージ」の順で探索し、まずアダプター名接頭辞付きマクロ(例: snowflake__macro)を探し、見つからなければdefault__macroにフォールバックします。
| 手段 | 特徴 | 代表的な適用例 |
|---|---|---|
| adapter.dispatch | アダプター別に最適実装を自動選択 | パッケージのクロスアダプター対応、共通マクロの配布 |
| default__実装 | アダプター別実装が無いときの安全な後方互換 | ANSI SQLで書ける汎用ロジック |
| プロジェクト上書き(dispatch) | 外部パッケージ実装をローカルで差し替え可能 | 一時的なバグ回避、方言チューニング |
最小構成のラッパーと実装
{% macro my_pkg.my_macro(col) %}\n {%- set impl = adapter.dispatch('my_macro', 'my_pkg') -%}\n {{ impl(col) }}\n{% endmacro %}\n\n{# 実装: 同一パッケージ(my_pkg)内 #}\n{% macro my_pkg.default__my_macro(col) %}\n upper({{ col }}) {# どのDWHでも動く汎用例 #}\n{% endmacro %}\n\n{% macro my_pkg.snowflake__my_macro(col) %}\n upper({{ col }})::string {# Snowflake最適化の一例 #}\n{% endmacro %}実装マクロ名は「接頭辞__本来のマクロ名」という形です。接頭辞はアダプター種別名(snowflake, bigquery, redshift, postgres, spark, databricks など)または default を使用します。ラッパーは接頭辞を付けず、adapter.dispatchで“マクロ名”と“パッケージ名”を指定します。
検索順序は dbt_project.yml の dispatch で制御します。macro_namespace にラッパーの属するパッケージ名を指定し、search_order でパッケージ探索の優先順位を決めます。多くのプロジェクトでは、まず自分のプロジェクトを先頭に置いて上書き可能にし、次に元のパッケージを指定します。
adapter.dispatchの探索フロー
dispatchの設定例(dbt_project.yml)
dispatch:\n - macro_namespace: my_pkg\n search_order: ['my_project', 'my_pkg']\n\n# ラッパー my_pkg.my_macro からの探索順:\n# 1) my_project.snowflake__my_macro -> 無ければ my_project.default__my_macro\n# 2) my_pkg.snowflake__my_macro -> 無ければ my_pkg.default__my_macro外部パッケージのマクロ動作を一時的に調整したい場合、search_orderの先頭に自プロジェクトを置き、同名の実装マクロを定義して上書きします。パッケージ本体をフォークせずに差分だけ保守できるのが利点です。
実装を上書きする場合も名前は変えません。例: 元パッケージが my_pkg.snowflake__my_macro を持つなら、プロジェクト側でも snowflake__my_macro を自プロジェクトの名前空間で定義します。
プロジェクトでの上書き例
{# 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{# プロジェクト側(上書き): models/macros/snowflake__my_macro.sql #}\n{% macro my_project.snowflake__my_macro(col) %}\n to_varchar(upper({{ col }})) {# Snowflake向けの細かな上書き #}\n{% endmacro %}実装がどれに解決されたかを把握するには、run-operationでログを出す簡易マクロを用意します。adapter.type や target.type を併記すると、実行環境との対応が確認しやすくなります。
CIでは複数プロファイル(例: snowflake, bigquery)でサンプルモデルを実行し、同じラッパーが環境ごとに異なる実装へ正しく切り替わるかを検証します。
どの実装が選ばれたかをログ出力
{% macro debug_my_macro_resolution() %}\n {# ラッパー名とネームスペース #}\n {% set m = adapter.dispatch('my_macro', 'my_pkg') %}\n {% do log('adapter: ' ~ target.type, info=True) %}\n {# マクロオブジェクト自体は文字列化で名前を含むことが多い #}\n {% do log('resolved macro: ' ~ (m|string), info=True) %}\n{% endmacro %}\n\n# 実行例: dbt run-operation debug_my_macro_resolutionif分岐でadapter.typeを判定してSQLを書き分けるのは、拡張性・保守性が低く、パッケージ配布にも不向きです。dispatchなら新しいアダプターが増えても実装マクロを足すだけで済みます。
ラッパーのネームスペースとdispatchに渡すパッケージ名が一致していないと、意図した実装が見つからずにdefaultへ落ちる、あるいは解決不能でエラーになります。
分岐ベタ書きからdispatchへのリファクタ
{# 悪い例 #}\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{# 良い例: ラッパー + 実装 #}\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 %}Spark系(オープンソースSpark)とDatabricksはSQL方言が近いケースが多い一方、実際の関数名やデータ型、DDLの挙動が異なることがあります。必要に応じて spark__ と databricks__ の両方を用意し、片方をdefaultに寄せるのではなく、それぞれ最小差分で最適化するのが安全です。
外部パッケージを作る場合は、まずdefault__を堅牢にし、主要アダプター(snowflake/bigquery/redshift/postgres/spark/databricks)に対する最小実装を段階的に追加します。プロジェクト側のsearch_orderで一時的なホットフィックスも可能にしておくと運用が楽になります。
複数アダプター最小実装の雛形
{% 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 }})) {# 必要に応じて正規化関数を追加 #}\n{% endmacro %}Analytics Engineer
問題 1
次の設定と実装がある。ターゲットは Snowflake。呼び出し {{ my_pkg.my_macro('name') }} 実行時に選ばれる実装はどれか。
正解: A
dispatch設定が macro_namespace: my_pkg, search_order: ['my_project', 'my_pkg'] の場合、探索は 1) my_project.snowflake__my_macro → 無ければ my_project.default__my_macro → 2) my_pkg.snowflake__my_macro → 無ければ my_pkg.default__my_macro の順。Snowflakeターゲットで先頭の my_project.snowflake__my_macro が存在すればそれが選択される。
dispatchを設定しない場合の探索順序は?
一般的には自プロジェクト→ラッパーの属するパッケージの順に探索され、まずアダプター接頭辞付き(例: snowflake__)、無ければdefault__が評価されます。確実な制御が必要ならdbt_project.ymlでdispatchを明示しましょう。
ラッパーと実装は同じパッケージに置く必要がありますか?
ラッパーの属する“ネームスペース”に対して実装(default__/snowflake__など)を定義します。外部パッケージを配布する場合は、そのパッケージ内に実装を揃え、プロジェクト上書きはsearch_orderで自プロジェクトを先頭にするのが通例です。
SparkとDatabricksのどちらの実装を用意すべき?
互換性が高い部分もありますが差異もあるため、両方の接頭辞(spark__/databricks__)を別々に用意するのが安全です。片方のみで代替する場合はCIで双方のターゲットを実行し、意図せぬdefaultフォールバックや構文差異がないか検証してください。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...