Packages
ecto
3.3.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/from.ex
defmodule Ecto.Query.Builder.From do
@moduledoc false
alias Ecto.Query.Builder
@doc """
Handles from expressions.
The expressions may either contain an `in` expression or not.
The right side is always expected to Queryable.
## Examples
iex> escape(quote(do: MySchema), __ENV__)
{quote(do: MySchema), []}
iex> escape(quote(do: p in posts), __ENV__)
{quote(do: posts), [p: 0]}
iex> escape(quote(do: p in {"posts", MySchema}), __ENV__)
{quote(do: {"posts", MySchema}), [p: 0]}
iex> escape(quote(do: [p, q] in posts), __ENV__)
{quote(do: posts), [p: 0, q: 1]}
iex> escape(quote(do: [_, _] in abc), __ENV__)
{quote(do: abc), [_: 0, _: 1]}
iex> escape(quote(do: other), __ENV__)
{quote(do: other), []}
iex> escape(quote(do: x() in other), __ENV__)
** (Ecto.Query.CompileError) binding list should contain only variables or `{as, var}` tuples, got: x()
"""
@spec escape(Macro.t(), Macro.Env.t()) :: {Macro.t(), Keyword.t()}
def escape({:in, _, [var, query]}, env) do
Builder.escape_binding(query, List.wrap(var), env)
end
def escape(query, _env) do
{query, []}
end
@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(), Macro.Env.t(), atom, String.t | nil, nil | String.t | [String.t]) ::
{Macro.t(), Keyword.t(), non_neg_integer | nil}
def build(query, env, as, prefix, maybe_hints) do
hints = List.wrap(maybe_hints)
unless Enum.all?(hints, &is_binary/1) do
Builder.error!(
"`hints` must be a compile time string or list of strings, " <>
"got: `#{Macro.to_string(maybe_hints)}`"
)
end
unless is_atom(as) do
Builder.error!("`as` must be a compile time atom, got: `#{Macro.to_string(as)}`")
end
unless is_binary(prefix) or is_nil(prefix) do
Builder.error!("`prefix` must be a compile time string, got: `#{Macro.to_string(prefix)}`")
end
{query, binds} = escape(query, env)
case expand_from(query, env) do
schema when is_atom(schema) ->
# Get the source at runtime so no unnecessary compile time
# dependencies between modules are added
source = quote(do: unquote(schema).__schema__(:source))
prefix = prefix || quote(do: unquote(schema).__schema__(:prefix))
{query(prefix, source, schema, as, hints), binds, 1}
source when is_binary(source) ->
# When a binary is used, there is no schema
{query(prefix, source, nil, as, hints), binds, 1}
{source, schema} when is_binary(source) and is_atom(schema) ->
prefix = prefix || quote(do: unquote(schema).__schema__(:prefix))
{query(prefix, source, schema, as, hints), binds, 1}
_other ->
quoted = quote do
Ecto.Query.Builder.From.apply(unquote(query), unquote(length(binds)), unquote(as), unquote(prefix), unquote(hints))
end
{quoted, binds, nil}
end
end
defp query(prefix, source, schema, as, hints) do
aliases = if as, do: [{as, 0}], else: []
from_fields = [source: {source, schema}, as: as, prefix: prefix, hints: hints]
query_fields = [
from: {:%, [], [Ecto.Query.FromExpr, {:%{}, [], from_fields}]},
aliases: {:%{}, [], aliases}
]
{:%, [], [Ecto.Query, {:%{}, [], query_fields}]}
end
defp expand_from({left, right}, env) do
{left, Macro.expand(right, env)}
end
defp expand_from(other, env) do
Macro.expand(other, env)
end
@doc """
The callback applied by `build/2` to build the query.
"""
@spec apply(Ecto.Queryable.t(), non_neg_integer, atom, String.t | nil, [String.t]) :: Ecto.Query.t()
def apply(query, binds, as, prefix, hints) do
query =
query
|> Ecto.Queryable.to_query()
|> maybe_apply_as(as)
|> maybe_apply_prefix(prefix)
|> maybe_apply_hints(hints)
check_binds(query, binds)
query
end
defp maybe_apply_as(query, nil), do: query
defp maybe_apply_as(%{from: %{as: from_as}}, as) when not is_nil(from_as) do
Builder.error!(
"can't apply alias `#{inspect(as)}`, binding in `from` is already aliased to `#{inspect(from_as)}`"
)
end
defp maybe_apply_as(%{from: from, aliases: aliases} = query, as) do
if Map.has_key?(aliases, as) do
Builder.error!("alias `#{inspect(as)}` already exists")
else
%{query | aliases: Map.put(aliases, as, 0), from: %{from | as: as}}
end
end
defp maybe_apply_prefix(query, nil), do: query
defp maybe_apply_prefix(query, prefix) do
update_in query.from.prefix, fn
nil ->
prefix
from_prefix ->
Builder.error!(
"can't apply prefix `#{inspect(prefix)}`, `from` is already prefixed to `#{inspect(from_prefix)}`"
)
end
end
defp maybe_apply_hints(query, []), do: query
defp maybe_apply_hints(query, hints), do: update_in(query.from.hints, &(&1 ++ hints))
defp check_binds(query, count) do
if count > 1 and count > Builder.count_binds(query) do
Builder.error!(
"`from` in query expression specified #{count} " <>
"binds but query contains #{Builder.count_binds(query)} binds"
)
end
end
end