Current section

Files

Jump to
ecto lib ecto repo queryable.ex
Raw

lib/ecto/repo/queryable.ex

defmodule Ecto.Repo.Queryable do
# The module invoked by user defined repos
# for query related functionality.
@moduledoc false
alias Ecto.Queryable
alias Ecto.Query.Planner
require Ecto.Query
@doc """
Implementation for `Ecto.Repo.all/2`
"""
def all(repo, adapter, queryable, opts) when is_list(opts) do
execute(:all, repo, adapter, queryable, opts) |> elem(1)
end
@doc """
Implementation for `Ecto.Repo.get/3`
"""
def get(repo, adapter, queryable, id, opts) do
one(repo, adapter, query_for_get(repo, queryable, id), opts)
end
@doc """
Implementation for `Ecto.Repo.get!/3`
"""
def get!(repo, adapter, queryable, id, opts) do
one!(repo, adapter, query_for_get(repo, queryable, id), opts)
end
def get_by(repo, adapter, queryable, clauses, opts) do
one(repo, adapter, query_for_get_by(repo, queryable, clauses), opts)
end
def get_by!(repo, adapter, queryable, clauses, opts) do
one!(repo, adapter, query_for_get_by(repo, queryable, clauses), opts)
end
@doc """
Implementation for `Ecto.Repo.one/2`
"""
def one(repo, adapter, queryable, opts) do
case all(repo, adapter, queryable, opts) do
[one] -> one
[] -> nil
other -> raise Ecto.MultipleResultsError, queryable: queryable, count: length(other)
end
end
@doc """
Implementation for `Ecto.Repo.one!/2`
"""
def one!(repo, adapter, queryable, opts) do
case all(repo, adapter, queryable, opts) do
[one] -> one
[] -> raise Ecto.NoResultsError, queryable: queryable
other -> raise Ecto.MultipleResultsError, queryable: queryable, count: length(other)
end
end
@doc """
Runtime callback for `Ecto.Repo.update_all/3`
"""
def update_all(repo, adapter, queryable, [], opts) when is_list(opts) do
update_all(repo, adapter, queryable, opts)
end
def update_all(repo, adapter, queryable, updates, opts) when is_list(opts) do
query = Ecto.Query.from q in queryable, update: ^updates
update_all(repo, adapter, query, opts)
end
defp update_all(repo, adapter, queryable, opts) do
execute(:update_all, repo, adapter, queryable, opts)
end
@doc """
Implementation for `Ecto.Repo.delete_all/2`
"""
def delete_all(repo, adapter, queryable, opts) when is_list(opts) do
execute(:delete_all, repo, adapter, queryable, opts)
end
## Helpers
def execute(operation, repo, adapter, queryable, opts) when is_list(opts) do
{meta, prepared, params} =
queryable
|> Queryable.to_query()
|> Planner.query(operation, repo, adapter)
if meta.select do
preprocess = preprocess(meta.prefix, meta.sources, adapter)
{count, rows} = adapter.execute(repo, meta, prepared, params, preprocess, opts)
{count,
rows
|> Ecto.Repo.Assoc.query(meta.assocs, meta.sources)
|> Ecto.Repo.Preloader.query(repo, meta.preloads, meta.assocs, postprocess(meta.select))}
else
adapter.execute(repo, meta, prepared, params, nil, opts)
end
end
defp preprocess(prefix, sources, adapter) do
&preprocess(&1, &2, prefix, &3, sources, adapter)
end
defp preprocess({:&, _, [ix]}, value, prefix, context, sources, adapter) do
{source, model} = elem(sources, ix)
Ecto.Schema.__load__(model, prefix, source, context, value, &adapter.load/2)
end
defp preprocess({{:., _, [{:&, _, [_]}, _]}, meta, []}, value, _prefix, _context, _sources, adapter) do
case Keyword.fetch(meta, :ecto_type) do
{:ok, type} -> load!(type, value, adapter)
:error -> value
end
end
defp preprocess(%Ecto.Query.Tagged{tag: tag}, value, _prefix, _context, _sources, adapter) do
load!(tag, value, adapter)
end
defp preprocess(_key, value, _prefix, _context, _sources, _adapter) do
value
end
defp load!(type, value, adapter) do
case adapter.load(type, value) do
{:ok, value} -> value
:error -> raise ArgumentError, "cannot load `#{inspect value}` as type #{inspect type}"
end
end
defp postprocess(%{expr: expr, fields: fields}) do
# The planner always put the from as the first
# entry in the query, avoiding fetching it multiple
# times even if it appears multiple times in the query.
# So we always need to handle it specially.
from? = match?([{:&, _, [0]}|_], fields)
&postprocess(&1, expr, from?)
end
defp postprocess(row, expr, true),
do: transform_row(expr, hd(row), tl(row)) |> elem(0)
defp postprocess(row, expr, false),
do: transform_row(expr, nil, row) |> elem(0)
defp transform_row({:{}, _, list}, from, values) do
{result, values} = transform_row(list, from, values)
{List.to_tuple(result), values}
end
defp transform_row({left, right}, from, values) do
{[left, right], values} = transform_row([left, right], from, values)
{{left, right}, values}
end
defp transform_row({:%{}, _, pairs}, from, values) do
Enum.reduce pairs, {%{}, values}, fn({key, value}, {map, values_acc}) ->
{value, new_values} = transform_row(value, from, values_acc)
{Map.put(map, key, value), new_values}
end
end
defp transform_row(list, from, values) when is_list(list) do
Enum.map_reduce(list, values, &transform_row(&1, from, &2))
end
defp transform_row({:&, _, [0]}, from, values) do
{from, values}
end
defp transform_row(_, _from, values) do
[value|values] = values
{value, values}
end
defp query_for_get(repo, _queryable, nil) do
raise ArgumentError, "cannot perform #{inspect repo}.get/2 because the given value is nil"
end
defp query_for_get(_repo, queryable, id) do
query = Queryable.to_query(queryable)
model = assert_model!(query)
primary_key = primary_key_field!(model)
Ecto.Query.from(x in query, where: field(x, ^primary_key) == ^id)
end
defp query_for_get_by(_repo, queryable, clauses) do
Ecto.Query.where(queryable, [], ^Enum.to_list(clauses))
end
defp assert_model!(query) do
case query.from do
{_source, model} when model != nil ->
model
_ ->
raise Ecto.QueryError,
query: query,
message: "expected a from expression with a model"
end
end
defp primary_key_field!(model) when is_atom(model) do
case model.__schema__(:primary_key) do
[field] -> field
_ -> raise Ecto.NoPrimaryKeyFieldError, model: model
end
end
end