đ§ 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__snowflake
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Ă©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 |