Packages
ecto
2.2.2
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
import Kernel, except: [apply: 2]
defmodule Ecto.Query.Builder.Join do
@moduledoc false
alias Ecto.Query.Builder
alias Ecto.Query.{JoinExpr, QueryExpr}
@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({:subquery, _, [expr]}, _vars, _env) do
{:_, quote(do: Ecto.Query.subquery(unquote(expr))), nil, %{}}
end
def escape({:subquery, _, [expr, opts]}, _vars, _env) do
{:_, quote(do: Ecto.Query.subquery(unquote(expr), unquote(opts))), nil, %{}}
end
def escape({:fragment, _, [_|_]} = expr, vars, env) do
{expr, {params, :acc}} = Builder.escape(expr, :any, {%{}, :acc}, 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
ensure_field!(field)
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
case Macro.expand(join, env) do
^join ->
Builder.error! "malformed join `#{Macro.to_string(join)}` in query expression"
join ->
escape(join, vars, env)
end
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: Ecto.Queryable.to_query(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
{query, binding} = Builder.escape_binding(query, binding)
{join_bind, join_source, join_assoc, join_params} = escape(expr, binding, env)
join_params = Builder.escape_params(join_params)
qual = validate_qual(qual)
validate_bind(join_bind, binding)
{count_bind, query} =
if join_bind != :_ and !count_bind do
# If count_bind is not available,
# we need to compute the amount of binds at runtime
query =
quote do
query = Ecto.Queryable.to_query(unquote(query))
join_count = Builder.count_binds(query)
query
end
{quote(do: join_count), query}
else
{count_bind, query}
end
binding = binding ++ [{join_bind, count_bind}]
next_bind =
if is_integer(count_bind) do
count_bind + 1
else
quote(do: unquote(count_bind) + 1)
end
query = build_on(on || true, query, binding, count_bind, qual,
join_source, join_assoc, join_params, env)
{query, binding, next_bind}
end
def build_on({:^, _, [var]}, query, _binding, count_bind,
join_qual, join_source, join_assoc, join_params, env) do
quote do
query = unquote(query)
Ecto.Query.Builder.Join.join!(query, unquote(var), unquote(count_bind),
unquote(join_qual), unquote(join_source), unquote(join_assoc),
unquote(join_params), unquote(env.file), unquote(env.line))
end
end
def build_on(on, query, binding, count_bind,
join_qual, join_source, join_assoc, join_params, env) do
{on_expr, on_params} = Ecto.Query.Builder.Filter.escape(:on, on, count_bind, binding, env)
on_params = Builder.escape_params(on_params)
join =
quote do
%JoinExpr{qual: unquote(join_qual), source: unquote(join_source),
assoc: unquote(join_assoc), file: unquote(env.file),
line: unquote(env.line), params: unquote(join_params),
on: %QueryExpr{expr: unquote(on_expr), params: unquote(on_params),
line: unquote(env.line), file: unquote(env.file)}}
end
Builder.apply_query(query, __MODULE__, [join], env)
end
@doc """
Applies the join expression to the query.
"""
def apply(%Ecto.Query{joins: joins} = query, expr) do
%{query | joins: joins ++ [expr]}
end
def apply(query, expr) do
apply(Ecto.Queryable.to_query(query), expr)
end
@doc """
Called at runtime to build a join.
"""
def join!(query, expr, count_bind, join_qual, join_source, join_assoc, join_params, file, line) do
{on_expr, on_params, on_file, on_line} =
Ecto.Query.Builder.Filter.filter!(:on, query, expr, count_bind, file, line)
join = %JoinExpr{qual: join_qual, source: join_source, assoc: join_assoc,
file: file, line: line, params: join_params,
on: %QueryExpr{expr: on_expr, params: on_params,
line: on_line, file: on_file}}
apply(query, join)
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, :inner_lateral, :left, :left_lateral, :right, :full, :cross]
@doc """
Called at runtime to check dynamic qualifier.
"""
def qual!(qual) when qual in @qualifiers, do: qual
def qual!(qual) do
raise ArgumentError,
"invalid join qualifier `#{inspect qual}`, accepted qualifiers are: " <>
Enum.map_join(@qualifiers, ", ", &"`#{inspect &1}`")
end
defp ensure_field!({var, _, _}) when var != :^ do
Builder.error! "you passed the variable `#{var}` to `assoc/2`. Did you mean to pass the atom `:#{var}?`"
end
defp ensure_field!(_), do: true
end