How do Ecto bindings work?

How does this work?

query = from u in "users",
          where: u.age > 18,
          select: u.name

I get that from is a macro, which essentially takes two arguments:

query = from(u in "users",
          [where: u.age > 18,
          select: u.name])

which makes it more mysterious. u in "users" what does it do? The keyword list has u.age, where does the u come from? How is throwing u not defined exception?

The macro based syntax is also suprising:

"users"
|> where([u], u.age > 18)
|> select([:name])

How does where([u], u.age > 18) not fail with u not defined?

Elixir is not directly using what you wrote for the ecto query, but ecto transforms the AST of your code into something that makes sense to elixir.

1 Like

A macro doesn’t execute the code passed in, It gets an AST of the code and it can then do whatever it wants. I haven’t looked at how Ecto actually does this, I am assuming that it gets a hold of the AST and transforms it into a SQL query based on the operators that are being used and then it executes the code. One way to inspect it is by looking at the struct that was returned:

iex(7)> q = from(u in User, where: u.id == 3, order_by: [asc: :id]) |> Map.from_struct
%{
  assocs: [],
  distinct: nil,
  from: {"users", UA.Accounts.User},
  group_bys: [],
  havings: [],
  joins: [],
  limit: nil,
  lock: nil,
  offset: nil,
  order_bys: [
    %Ecto.Query.QueryExpr{
      expr: [asc: {{:., [], [{:&, [], [0]}, :id]}, [], []}],
      file: "iex",
      line: 7,
      params: []
    }
  ],
  prefix: nil,
  preloads: [],
  select: nil,
  sources: nil,
  updates: [],
  wheres: [
    %Ecto.Query.BooleanExpr{
      expr: {:==, [],
       [
         {{:., [], [{:&, [], [0]}, :id]}, [], []},
         %Ecto.Query.Tagged{tag: nil, type: {0, :id}, value: 3}
       ]},
      file: "iex",
      line: 7,
      op: :and,
      params: []
    }
  ]
}

Here is an example that should give you more clarith around macros, the expression 1 + 2 is never evaluated:

defmodule M do
  defmacro add({:+, _, [op1, op2]}) do
    quote do
      "Add #{unquote(op1)} and #{unquote(op2)} yourself!"
    end
  end
end

defmodule Test do
  def test do
    require M
    M.add(1 + 3)
  end
end

Test.test() |> IO.puts
# => Add 1 and 3 yourself!
2 Likes