🐕

dbt macro tips advent calendar 2022 day 15 - macro override

2022/12/15に公開

便利なデータ変換ツールである dbt の中のmacroに関するtipsを書いていく dbt macro tips Advent Calendar 2022 15日目です。

本日裏側で、dbt Advent Calender 2022の15日目の記事を書いている予定です。そちらではCustom Materializationについて書いているので良ければそちらもご覧ください。

macro override

いままで、macroを書く方法について書いてきましたが、今回は少し毛色が違います。
dbtを使い始めたばかりの人で、よくよく最初に気になるのはcustom schemaの挙動です。

例えば、profileのtarget.schemaを public に設定していて

{{ config(schema='marketing') }}

このように、configでschemaを指定したときに、marketing というスキーマに作成されるのかと思いきや、dbtのデフォルトの挙動では public_marketing となります。

これは、dbtの標準的な思想が schemaで環境を分けるという思想だからですね。

https://docs.getdbt.com/docs/collaborate/environments

Targets offer the flexibility to decide how to implement your separate environments – whether you want to use separate schemas, databases, or entirely different clusters altogether! We recommend using different schemas within one data warehouse to separate your environments. This is the easiest to set up, and is the most cost effective solution in a modern cloud-based data stack.

要は、1つのデータウェアハウスに複数の異なるスキーマで環境を分離して使うのが一番セットアップが簡単で費用対効果が高いでしょ!? という話なんですね。

とは言うものの、databaseごと分離したほうがメリットのある運用もありますし、運用の事情は様々です。
セキュリティレベル、ターゲットのDWH製品の性質etc...

そんな中で、1つの挙動しかできず、必ずprofileで指定したschemaがprefixとしてつくという挙動が困るということはあるあるでしょう。

そのように、デフォルトのdbtの挙動で不都合なことがあるから、挙動を書き換えたい。
コレを実現する話がmacro overrideという話になります。

https://docs.getdbt.com/docs/build/custom-schemas

custom-schemasの実例を見ると

{% macro generate_schema_name(custom_schema_name, node) -%}
    {{ generate_schema_name_for_env(custom_schema_name, node) }}
{%- endmacro %}

というふうに書かれています。

もう少し具体例を出すと、custom schemaで指定した通りのものにする例は以下のようになります。

{% macro generate_schema_name(custom_schema_name, node) -%}
    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}
        {{ default_schema }}
    {%- else -%}
        {{ custom_schema_name | trim }}
    {%- endif -%}
{%- endmacro %}

なぜ、これで挙動が書き換わるかというと、macroの呼び出し順序が関係しています。
dbtがmacroを呼び出そうとする際、root package(自分たちが作ってるdbtのproject)が一番最初に検索されます。そのため、dbtのbuiltinされている標準的なシステムマクロを呼び出す際に、同名のmacroが存在していたら、builtinのものを無視して自分たちで定義したmacroを呼び出すことになります。

ここで、面白い例を一つ出してみましょう。

macro/overrides.sql
{%- macro ref() %}
    {%- do log('call ref('~varargs~') from '~this, info=True) %}
    {{ return(builtins.ref(*varargs)) }}
{%- endmacro %}

これで実行してみると以下のようになります。

$ dbt build
03:53:56  Running with dbt=1.3.1
03:53:56  Unable to do partial parsing because a project config has changed
03:53:57  call ref(('my_first_dbt_model',)) from "postgres"."dev"."my_second_dbt_model"
03:53:58  Found 2 models, 0 tests, 0 snapshots, 0 analyses, 290 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics
03:53:58  
03:53:58  Concurrency: 4 threads (target='dev')
03:53:58  
03:53:58  1 of 2 START sql table model dev.my_first_dbt_model ............................ [RUN]
03:53:58  1 of 2 OK created sql table model dev.my_first_dbt_model ....................... [SELECT 2 in 0.15s]
03:53:58  2 of 2 START sql view model dev.my_second_dbt_model ............................ [RUN]
03:53:58  call ref(('my_first_dbt_model',)) from "postgres"."dev"."my_second_dbt_model"
03:53:58  2 of 2 OK created sql view model dev.my_second_dbt_model ....................... [CREATE VIEW in 0.09s]
03:53:58  
03:53:58  Finished running 1 table model, 1 view model in 0 hours 0 minutes and 0.49 seconds (0.49s).
03:53:58  
03:53:58  Completed successfully
03:53:58  
03:53:58  Done. PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2

そう、ref macroですら書き換え可能なのです。
この例は
https://docs.getdbt.com/reference/dbt-jinja-functions/builtins

こちらのbuiltinsの説明に載っているものを少し改造した例となっています。


16日目はdispachの話をします。

Discussion