Packages
ecto
1.0.3
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/query/builder/join.ex
defmodule Ecto.Query.Builder.Join do
@moduledoc false
alias Ecto.Query.Builder
alias Ecto.Query.JoinExpr
@doc """
Escapes a join expression (not including the `on` expression).
It returns a tuple containing the binds, the on expression (if available)
and the association expression.
## Examples
iex> escape(quote(do: x in "foo"), [], __ENV__)
{:x, {"foo", nil}, nil, %{}}
iex> escape(quote(do: "foo"), [], __ENV__)
{:_, {"foo", nil}, nil, %{}}
iex> escape(quote(do: x in Sample), [], __ENV__)
{:x, {nil, {:__aliases__, [alias: false], [:Sample]}}, nil, %{}}
iex> escape(quote(do: x in {"foo", Sample}), [], __ENV__)
{:x, {"foo", {:__aliases__, [alias: false], [:Sample]}}, nil, %{}}
iex> escape(quote(do: x in {"foo", :sample}), [], __ENV__)
{:x, {"foo", :sample}, nil, %{}}
iex> escape(quote(do: c in assoc(p, :comments)), [p: 0], __ENV__)
{:c, nil, {0, :comments}, %{}}
iex> escape(quote(do: x in fragment("foo")), [], __ENV__)
{:x, {:{}, [], [:fragment, [], [raw: "foo"]]}, nil, %{}}
"""
@spec escape(Macro.t, Keyword.t, Macro.Env.t) :: {[atom], Macro.t | nil, Macro.t | nil, %{}}
def escape({:in, _, [{var, _, context}, expr]}, vars, env)
when is_atom(var) and is_atom(context) do
{_, expr, assoc, params} = escape(expr, vars, env)
{var, expr, assoc, params}
end
def escape({:fragment, _, [_|_]} = expr, vars, env) do
{expr, params} = Builder.escape(expr, :any, %{}, vars, env)
{:_, expr, nil, params}
end
def escape({:__aliases__, _, _} = module, _vars, _env) do
{:_, {nil, module}, nil, %{}}
end
def escape(string, _vars, _env) when is_binary(string) do
{:_, {string, nil}, nil, %{}}
end
def escape({string, {:__aliases__, _, _} = module}, _vars, _env) when is_binary(string) do
{:_, {string, module}, nil, %{}}
end
def escape({string, atom}, _vars, _env) when is_binary(string) and is_atom(atom) do
{:_, {string, atom}, nil, %{}}
end
def escape({:assoc, _, [{var, _, context}, field]}, vars, _env)
when is_atom(var) and is_atom(context) do
var = Builder.find_var!(var, vars)
field = Builder.quoted_field!(field)
{:_, nil, {var, field}, %{}}
end
def escape({:^, _, [expr]}, _vars, _env) do
{:_, quote(do: Ecto.Query.Builder.Join.join!(unquote(expr))), nil, %{}}
end
def escape(join, _vars, _env) do
Builder.error! "malformed join `#{Macro.to_string(join)}` in query expression"
end
@doc """
Called at runtime to check dynamic joins.
"""
def join!(expr) when is_atom(expr),
do: {nil, expr}
def join!(expr) when is_binary(expr),
do: {expr, nil}
def join!({source, module}) when is_binary(source) and is_atom(module),
do: {source, module}
def join!(expr),
do: Builder.error!("expected join to be a string, atom or {string, atom}, got: `#{inspect expr}`")
@doc """
Builds a quoted expression.
The quoted expression should evaluate to a query at runtime.
If possible, it does all calculations at compile time to avoid
runtime work.
"""
@spec build(Macro.t, atom, [Macro.t], Macro.t, Macro.t, Macro.t, Macro.Env.t) ::
{Macro.t, Keyword.t, non_neg_integer | nil}
def build(query, qual, binding, expr, on, count_bind, env) do
binding = Builder.escape_binding(binding)
{join_bind, join_expr, join_assoc, join_params} = escape(expr, binding, env)
join_params = Builder.escape_params(join_params)
qual = validate_qual(qual)
validate_bind(join_bind, binding)
if join_bind != :_ and !count_bind do
# If count_bind is not an integer, make it a variable.
# The variable is the getter/setter storage.
count_bind = quote(do: count_bind)
count_setter = quote(do: unquote(count_bind) = Builder.count_binds(query))
end
if on && join_assoc do
Builder.error! "cannot specify `on` on `#{qual}_join` when using association join, " <>
"add extra clauses with `where` instead"
end
binding = binding ++ [{join_bind, count_bind}]
join_on = escape_on(on || true, binding, env)
join =
quote do
%JoinExpr{qual: unquote(qual), source: unquote(join_expr),
on: unquote(join_on), assoc: unquote(join_assoc),
file: unquote(env.file), line: unquote(env.line),
params: unquote(join_params)}
end
if is_integer(count_bind) do
count_bind = count_bind + 1
quoted = Builder.apply_query(query, __MODULE__, [join], env)
else
count_bind = quote(do: unquote(count_bind) + 1)
quoted =
quote do
query = Ecto.Queryable.to_query(unquote(query))
unquote(count_setter)
%{query | joins: query.joins ++ [unquote(join)]}
end
end
{quoted, binding, count_bind}
end
def apply(query, expr) do
query = Ecto.Queryable.to_query(query)
%{query | joins: query.joins ++ [expr]}
end
defp escape_on(on, binding, env) do
{on, params} = Builder.escape(on, :boolean, %{}, binding, env)
params = Builder.escape_params(params)
quote do: %Ecto.Query.QueryExpr{
expr: unquote(on),
params: unquote(params),
line: unquote(env.line),
file: unquote(env.file)}
end
defp validate_qual(qual) when is_atom(qual) do
qual!(qual)
end
defp validate_qual(qual) do
quote(do: Ecto.Query.Builder.Join.qual!(unquote(qual)))
end
defp validate_bind(bind, all) do
if bind != :_ and bind in all do
Builder.error! "variable `#{bind}` is already defined in query"
end
end
@qualifiers [:inner, :left, :right, :full]
@doc """
Called at runtime to check dynamic qualifier.
"""
def qual!(qual) when qual in @qualifiers, do: qual
def qual!(qual) do
Builder.error! "invalid join qualifier `#{inspect qual}`, accepted qualifiers are: " <>
Enum.map_join(@qualifiers, ", ", &"`#{inspect &1}`")
end
end