🕌

dbt macro tips advent calendar 2022 day 18 - hookの実装のdeep-dive

2022/12/18に公開

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

先日は tableの実装から persist_docsの実装を覗いてみました。
今度はhook の実装を覗いてみましょう。

hookの実装を覗いてみよう

hookはどうやら run_hooks というmacroで実装されてるようです。

https://github.com/dbt-labs/dbt-core/blob/0544b085439b3a635b8ce56adbf56d8e7c7e6839/core/dbt/include/global_project/macros/materializations/models/table/table.sql#L24-L27

このmacroの中身を覗いてみましょう。

https://github.com/dbt-labs/dbt-core/blob/0544b085439b3a635b8ce56adbf56d8e7c7e6839/core/dbt/include/global_project/macros/materializations/hooks.sql#L1-L15

何やら気になる記述がいくつかありますね。

render(hook.get('sql')) というこの記述、どうやらhookに書かれたSQLをJinjaテンプレートとして解釈して描画しているようです。

さて、ではこのrenderという実態は一体何処にあるのでしょうか?
探っていくと、次のコードにたどり着きます。

https://github.com/dbt-labs/dbt-core/blob/0544b085439b3a635b8ce56adbf56d8e7c7e6839/core/dbt/context/providers.py#L784-L787

Pythonのコードですね。同じ階層にあるREADME.mdを読んでみましょう。

https://github.com/dbt-labs/dbt-core/blob/0544b085439b3a635b8ce56adbf56d8e7c7e6839/core/dbt/context/README.md

なんと、こちらはコンテキストの実装が書かれたPythonコードなのでした。
コンテキストとはdbtの実行の最中にJinjaテンプレートの描画を行うときに使うもので、設定やbuiltinのmacroやら何やらたくさん書かれているものです。

つまり、コンテキストの謎を深ぼっていくと、どこで何が使えるのか?という理解が進むわけなのです。

さて、話は戻して、render() というのは、ProviderContextに紐付いているコンテキストmethodのようです。この ProviderContext というのは MacroContext, ModelContext, TestContext の親コンテキストということらしいです。つまり、render()は我々が触る範疇では基本的にはどこでも使えるということですね。 (ただし、yamlは除く)

よし、render() というのはわかった。 selectattr などはJinjaのfilter関数だ。

https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.selectattr

lengthとかも同様だ。 callは前に見た。
あと不明なのは、いきなり呼び出し側で参照されている pre_hooks, post_hooks だろう。
コレは一体何なんだ!

https://github.com/dbt-labs/dbt-core/blob/0544b085439b3a635b8ce56adbf56d8e7c7e6839/core/dbt/context/providers.py#L1296-L1315

答えは、 ModelContextのコンテキスト変数でした。

中身を見てみると、 modelのconfigのpre_hookやpost_hookをそれぞれto_dictしたものようです。
この辺はPythonコードですね。
では、この self.model.config が何者なのかを探りましょう。

https://github.com/dbt-labs/dbt-core/blob/0544b085439b3a635b8ce56adbf56d8e7c7e6839/core/dbt/context/providers.py#L1297

self.model は どうやら ManifestNode というものらしいですね。

そうなんです。pre_hooksやpost_hooksというのは実は深く深くたどっていくとマニフェスト由来の値が入っているのです。

此処から先をたどっていくとPythonのコードのもっと奥深くに潜ることになるので、一旦割愛して結論だけいうと

{
	"pre_hooks": [
		{
			"sql: "SELECT ...",
			"transaction": false
		},
		{
			"sql: "SELECT ...",
			"transaction": true
		}
	]
}

な感じの内容が手に入ります。

ここまで、わかればなんとなくhookがどのように動いているか?というのは見えてきたのではないでしょうか。

ところで、ここで覚えておいてほしいのは

  • コンテキストなる概念がある。 その実装は pythonのコードを読めばなんとなく
  • コンテキストにはドキュメントにはされていない render() なるメソッドがあったりする。

ということです。


19日目は続けて、コンテキストの中にある便利なものを紹介します。

Discussion