dbt: adapter-specific macros with fallbacks provides

🧠 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èmeExemple
Fonction différenteCURRENT_TIMESTAMP() (Snowflake) vs CURRENT_DATETIME() (BigQuery)
Type de donnéesVARCHAR vs STRING
Syntaxe MERGERedshift n’a pas MERGE, BigQuery oui
Gestion des identifiantsUPPER_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

AvantageDétail
RéutilisableTon code {{ get_current_timestamp() }} ne change jamais
LisibleLe SQL reste propre et court
SécuriséTu réduis le risque d’erreurs lors de migration
ExtensibleTu 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émentExplication
adapter.dispatch()dbt choisit automatiquement une version spécifique selon le warehouse
__snowflake, __bigqueryfichiers macros nommés selon le dialecte
__defaultfallback si aucun spécifique n’est trouvé
✅ ObjectifCentraliser 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 Snowflake
  • mon_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é) :

  1. utils__get_current_timestamp__snowflake
  2. utils__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émentRôle
adapter.dispatch('macro_name', 'namespace')Choisit dynamiquement la bonne macro
Recherchenamespace__macro_name__adapter, puis __default
FallbackSi aucun adapter trouvé, prend __default
UsagePermet d’écrire du code dbt portable et multi-warehouse

Leave a Reply

Your email address will not be published. Required fields are marked *