Ectoは、Elixirが標準として提供するDBへの接続ライブラリです。macroを学んだので、少しmacroを読んでみよう、ということで読んでみました。
ここでは、すべてEcto v0.16.0を元にしています。
Ecto.Queryの補完を見ればわかるのですが、この下にfromなどのSQL構文を持っています。
iex(1)> Ecto.Query. API Builder CompileError JoinExpr Planner QueryExpr SelectExpr Tagged __struct__/0 distinct/3 exclude/2 from/2 group_by/3 having/3 join/5 limit/3 lock/2 offset/3 order_by/3 preload/3 select/3 update/3 where/3
これらを使い、例えば、 Sample.User というリポジトリに含まれる要素に対してSQLを発行したい場合に以下のようにSQLを発行することができます。
iex> from(
users in Sampe.User,
where: users.count < 1,
select: users.user,
limit: 1)
|> Sample.Repo.all
[debug] SELECT v0."user" FROM "votes" AS v0 WHERE (v0."count" < 1) LIMIT 1 [] OK query=1.1ms
["user2"]
これらを読んでいこうとすると、まずは以下の fromに関するdefmacro に出会います。
defmacro from(expr, kw \\ []) do
このコードを読んでいくと、このmacroの先で幾つかの条件に分けれられた from が定義されています。
defp from([{type, expr}|t], env, count_bind, quoted, binds) when type in @binds dodefp from([{type, expr}|t], env, count_bind, quoted, binds) when type in @no_binds dodefp from([{join, expr}|t], env, count_bind, quoted, binds) when join in @joins do
ここで、whenにより type が @binds @no_bind @joins でそれぞれ処理が分けられていることがわかります。これは何かな?と追ってみると、
https://github.com/elixir-lang/ecto/blob/v0.16.0/lib/ecto/query.ex#L250
に以下のようにリストによる定義があります。
@binds [:where, :select, :distinct, :order_by, :group_by,
:having, :limit, :offset, :preload, :update]
@no_binds [:lock]
@joins [:join, :inner_join, :left_join, :right_join, :full_join]
推測するに、SQLで使われる表現を区分しているようですね。それらは、 lib/ecto/query.ex の同じ階層のコードを覗くと from と同じようにdefmacroで定義されていることがわかります。
つまるとこ、 コードがそのままSQL構文になるようにElixirの文法をmacroで拡張している のがこのqueryの箇所の役割のようです。
他にも幾つかwhenにより定義があるのですが、何も合致しない場合は以下が呼ばれるらしいですね。
defp from([], _env, _count_bind, quoted, _binds) do
もう少し読み進めてみます。それぞれのdefmacroの処理の中では xxx.build が呼ばれていることがわかります。これの先に escape などのQueryに対するエスケープ処理などが実装されています。
例えば、 where はこのコードの箇所の Filter.build が呼ばれます。Ecto.Query.Builder の escape とかはこちら、個別の定義の例えば Ecto.Query.Builder.Select なんかで呼ばれています。
ここまで読んでいると、Elixirは関数の定義の段階で その処理の前提条件 がコードに記述されているので良いですね。個人的に、「この関数は何に焦点を当てて読めば良いのか?」、ということに集中できるので読みやすいです。
ここら辺のショートカットになる関数も用意されているので、すべてがこの記述になるわけではないですが、Elixirの拡張としてmacroを使ったEctoの話でした。