Rewrite query into "pipe-based" syntax

In phx_gen_auth there is a query that does a join and then selects only the joined record. A simplified version of it is:

from token in UserToken, join: user in assoc(token, :user), select: user

and this works all rainbows and unicorns. However when I try to translate it into “pipe-based” syntax:

UserToken |> join(:inner, [user_token], user in assoc(user_token, :user))

so far so good, the query translates to an equivalent JOIN SQL. But then comes the select part where I keep getting stuck at. A hint how to SELECT the joined record using “pipe-based” syntax would be appreciated

Using positional binding:

UserToken
|> join(:inner, [user_token], user in assoc(user_token, :user))
|> select([user_token, user], user)

and using named binding:

UserToken
|> join(:inner, [user_token], user in assoc(user_token, :user), as: :user)
|> select([user: user], user)
4 Likes

This should work

UserToken
|> join(:inner, [user_token], user in assoc(user_token, :user))
|> select([user_token, user], user)

or

UserToken
|> join(:inner, [user_token], user in assoc(user_token, :user))
|> select([..., user], user)  # The ... is interesting

When using named binding:

UserToken
|> join(:inner, [user_token], user in assoc(user_token, :user), as: :user)
|> select([user: user], user)

By the way, here’s my experimental query builder without schema (just for fun): GitHub - Aetherus/endo

2 Likes

Thanks a lot!

Now that’s interesting… could you please elaborate a bit on what the three dots represent? Yes, it works - I just checked :wink:

Update:
OK, found it: “Similarly, if you are interested only in the last binding (or the last bindings) in a query, you can use ... to specify “all bindings before” and match on the last one.”

1 Like

The triple dots in the example above means “I don’t care how many tables are joined. Just give me the last one.”

You can also write

select(query, [a, b, ..., y, z], [a.foo, z.bar])

if you’ve joined more than 4 tables. The first is bound to a, the second to b, the last to z, and the one before it to y.

2 Likes

Actually, the ... is a valid identifier!

You can use it as a variable, like

... = :foo
IO.inspect(...)  # prints :foo and returns :foo

You can also define a function named ..., like

defmodule Foo do
  def ..., do: "Haha"
end

Foo."..."()  #=> "Haha"

When calling the triple dot function, you have to wrap it in a pair of quotation marks.

I found this during the period of implementing my own query builder. I tried to quote it, and found

iex> quote do: foo
{:foo, [], Elixir}

iex> quote do: ...
{:..., [], Elixir}

See? They are the same!

1 Like