Skip to content

Instantly share code, notes, and snippets.

@manji602
Created September 16, 2016 02:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manji602/5b8cf30ed3bf55f5bfd9de380c760a38 to your computer and use it in GitHub Desktop.
Save manji602/5b8cf30ed3bf55f5bfd9de380c760a38 to your computer and use it in GitHub Desktop.
Ecto.Query.where/2 の処理を追ってみました

User |> where(id: ^user_id) を実行しようとすると、 Ecto.Query.where で内部で以下のような実装になっていました。

Ecto.Query.where/2

  defmacro where(query, binding \\ [], expr) do
    Filter.build(:where, query, binding, expr, __CALLER__)
  end

expr[id: ^user_id] が入っています。

Ecto.Query.Builder.Filter.build/5

def build(kind, query, binding, expr, env) do
  binding        = Builder.escape_binding(binding)
  {expr, params} = escape(kind, expr, binding, env)
  ...

exprがescapeされているようです。

Ecto.Query.Builder.Filter.escape/4

def escape(kind, expr, vars, env) when is_list(expr) do
  {parts, params} =
    Enum.map_reduce(expr, %{}, fn
      {field, nil}, _acc ->
        Builder.error! "nil given for #{inspect field}. Comparison with nil is forbidden as it is unsafe. " <>
                       "Instead write a query with is_nil/1, for example: is_nil(s.#{field})"
      {field, value}, acc when is_atom(field) ->
        {value, params} = Builder.escape(value, {0, field}, acc, vars, env)
        ...

fieldidvalue^user_id が入ります。

Ecto.Query.Builder.escape/5

# param interpolation
def escape({:^, _, [arg]}, type, params, _vars, _env) do
  index  = Map.size(params)
  params = Map.put(params, index, {arg, type})
  expr   = {:{}, [], [:^, [], [index]]}
  {expr, params}
end

^user_id{:^, _, [arg]}, として受け取っています。 この表記は内部表現と呼ばれるもので、コンソール上で以下のように確認できました。

iex(19)> quote do: ^user_id
{:^, [], [{:user_id, [], Elixir}]}
iex(20)> quote do: ^(1 + 2)
{:^, [], [{:+, [context: Elixir, import: Kernel], [1, 2]}]}

これにより、^ がピン演算子として評価されていそうです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment