🧠 Résumé du concept :
Quand tu migres un projet dbt vers un autre data warehouse, tu rencontres forcément des différences de syntaxe SQL, de types de données, ou de comportements spécifiques.
👉 La meilleure façon de gérer cela, c’est d’utiliser des macros spécifiques à l’adapter, avec des fallbacks (retours par défaut).
✅ Objectif : avoir un code SQL unique et maintenable, mais qui s’adapte automatiquement au data warehouse utilisé.
🔧 Problèmes courants lors d’une migration :
| Problème | Exemple |
|---|---|
| Fonction différente | CURRENT_TIMESTAMP() (Snowflake) vs CURRENT_DATETIME() (BigQuery) |
| Type de données | VARCHAR vs STRING |
| Syntaxe MERGE | Redshift n’a pas MERGE, BigQuery oui |
| Gestion des identifiants | UPPER_CASE vs lower_case vs backticks |
🛠 Solution : adapter-specific macros with fallbacks
✅ Étape 1 — Créer une macro “générique”
Dans macros/utils/get_current_timestamp.sql :
{% macro get_current_timestamp() %}
{% if execute %}
{% set macro_name = adapter.dispatch('get_current_timestamp', 'utils') %}
{{ return(macro_name()) }}
{% endif %}
{% endmacro %}
👉 Cette macro appelle dynamiquement une version spécifique au warehouse via adapter.dispatch().
✅ Étape 2 — Fournir des implémentations spécifiques
Pour Snowflake :
-- macros/utils/get_current_timestamp__snowflake.sql
{% macro utils__get_current_timestamp() %}
CURRENT_TIMESTAMP()
{% endmacro %}
Pour BigQuery :
-- macros/utils/get_current_timestamp__bigquery.sql
{% macro utils__get_current_timestamp() %}
CURRENT_DATETIME()
{% endmacro %}
Fallback générique :
-- macros/utils/get_current_timestamp__default.sql
{% macro utils__get_current_timestamp() %}
now()
{% endmacro %}
✅ dbt choisira automatiquement la bonne macro en fonction de l’adapter actif (dbt-snowflake, dbt-bigquery, etc.). Si aucun n’est défini, il prend __default.
💡 Avantages
| Avantage | Détail |
|---|---|
| ✅ Réutilisable | Ton code {{ get_current_timestamp() }} ne change jamais |
| ✅ Lisible | Le SQL reste propre et court |
| ✅ Sécurisé | Tu réduis le risque d’erreurs lors de migration |
| ✅ Extensible | Tu peux ajouter d’autres macros par warehouse à tout moment |
🎯 Exemple complet d’utilisation dans un modèle :
-- models/transactions.sql
SELECT
id,
total,
{{ get_current_timestamp() }} AS loaded_at
FROM {{ source('api', 'transactions') }}
Peu importe l’entrepôt (Snowflake, BigQuery…), le SQL final sera correctement généré.
📌 En résumé
| Élément | Explication |
|---|---|
adapter.dispatch() | dbt choisit automatiquement une version spécifique selon le warehouse |
__snowflake, __bigquery | fichiers macros nommés selon le dialecte |
__default | fallback si aucun spécifique n’est trouvé |
| ✅ Objectif | Centraliser les différences, simplifier les migrations, maintenir un code unique |
🧠 Objectif de adapter.dispatch()
adapter.dispatch() sert à appeler une macro différente selon l’adapter utilisé (ex: dbt-snowflake, dbt-bigquery, dbt-postgres, etc.).
Il permet d’écrire un appel générique dans ton code, comme :
{{ adapter.dispatch('ma_macro', 'mon_namespace')() }}
Mais dbt le transformera en appel de macro spécifique à l’adapter :
mon_namespace__ma_macro__snowflake()si l’adapter est Snowflakemon_namespace__ma_macro__bigquery()si BigQuery- etc.
- et s’il ne trouve rien de spécifique, il prend
mon_namespace__ma_macro__default()
🔍 Comment ça marche exactement ?
Imaginons ce code dans ta macro :
{% set macro_name = adapter.dispatch('get_current_timestamp', 'utils') %}
{{ macro_name() }}
Voici les étapes internes de dbt :
Étape 1 : Recherche de l’adapter actif
dbt regarde dans ton profiles.yml la clé :
type: snowflake
Et comprend donc que tu utilises l’adapter snowflake.
Étape 2 : dbt cherche une macro nommée (ordre de priorité) :
utils__get_current_timestamp__snowflakeutils__get_current_timestamp__default
Et il prend la première trouvée.
Étape 3 : Appel de la macro
Une fois le bon nom trouvé, la fonction dispatch() retourne un pointeur vers la macro, que tu peux appeler comme une fonction normale :
{{ macro_name() }}
Tu peux aussi le chaîner directement :
{{ adapter.dispatch('get_current_timestamp', 'utils')() }}
📁 Convention de nommage des macros dispatchables
Exemple complet de structure de fichiers :
macros/
utils/
get_current_timestamp.sql <-- macro générique (wrapper)
get_current_timestamp__snowflake.sql <-- version spécifique à Snowflake
get_current_timestamp__bigquery.sql <-- version spécifique à BigQuery
get_current_timestamp__default.sql <-- fallback générique
Et dans chaque fichier spécifique :
-- macros/utils/get_current_timestamp__snowflake.sql
{% macro utils__get_current_timestamp() %}
CURRENT_TIMESTAMP()
{% endmacro %}
💡 À noter
- Le nom complet de la macro doit être prefixé par le namespace (
utils__ici) - Le nom du fichier n’est pas aussi important que le nom exact de la macro
- Le namespace est le dossier où tu places ta macro (par convention, mais tu peux le redéfinir)
🔧 Cas d’erreur ou fallback
Si tu appelles :
adapter.dispatch('cast_boolean', 'types')()
Et que tu n’as ni types__cast_boolean__snowflake ni types__cast_boolean__default, dbt te renverra une erreur de compilation du type :
Macro 'types__cast_boolean__snowflake' is not defined
✅ En résumé
| Élément | Rôle |
|---|---|
adapter.dispatch('macro_name', 'namespace') | Choisit dynamiquement la bonne macro |
| Recherche | namespace__macro_name__adapter, puis __default |
| Fallback | Si aucun adapter trouvé, prend __default |
| Usage | Permet d’écrire du code dbt portable et multi-warehouse |