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 *