Packages
ecto
3.8.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/repo/queryable.ex
defmodule Ecto.Repo.Queryable do
@moduledoc false
alias Ecto.Queryable
alias Ecto.Query
alias Ecto.Query.Planner
alias Ecto.Query.SelectExpr
import Ecto.Query.Planner, only: [attach_prefix: 2]
require Ecto.Query
def all(name, queryable, tuplet) do
query =
queryable
|> Ecto.Queryable.to_query()
|> Ecto.Query.Planner.ensure_select(true)
execute(:all, name, query, tuplet) |> elem(1)
end
def stream(_name, queryable, {adapter_meta, opts}) do
%{adapter: adapter, cache: cache, repo: repo} = adapter_meta
query =
queryable
|> Ecto.Queryable.to_query()
|> Ecto.Query.Planner.ensure_select(true)
{query, opts} = repo.prepare_query(:stream, query, opts)
query = attach_prefix(query, opts)
{query_meta, prepared, params} = Planner.query(query, :all, cache, adapter, 0)
case query_meta do
%{select: nil} ->
adapter_meta
|> adapter.stream(query_meta, prepared, params, opts)
|> Stream.flat_map(fn {_, nil} -> [] end)
%{select: select, preloads: preloads} ->
%{
assocs: assocs,
preprocess: preprocess,
postprocess: postprocess,
take: take,
from: from
} = select
if preloads != [] or assocs != [] do
raise Ecto.QueryError, query: query, message: "preloads are not supported on streams"
end
preprocessor = preprocessor(from, preprocess, adapter)
stream = adapter.stream(adapter_meta, query_meta, prepared, params, opts)
postprocessor = postprocessor(from, postprocess, take, adapter)
stream
|> Stream.flat_map(fn {_, rows} -> rows end)
|> Stream.map(preprocessor)
|> Stream.map(postprocessor)
end
end
def get(name, queryable, id, opts) do
one(name, query_for_get(queryable, id), opts)
end
def get!(name, queryable, id, opts) do
one!(name, query_for_get(queryable, id), opts)
end
def get_by(name, queryable, clauses, opts) do
one(name, query_for_get_by(queryable, clauses), opts)
end
def get_by!(name, queryable, clauses, opts) do
one!(name, query_for_get_by(queryable, clauses), opts)
end
def reload(name, [head | _] = structs, opts) when is_list(structs) do
results = all(name, query_for_reload(structs), opts)
[pk] = head.__struct__.__schema__(:primary_key)
for struct <- structs do
struct_pk = Map.fetch!(struct, pk)
Enum.find(results, &Map.fetch!(&1, pk) == struct_pk)
end
end
def reload(name, struct, opts) do
one(name, query_for_reload([struct]), opts)
end
def reload!(name, [head | _] = structs, opts) when is_list(structs) do
query = query_for_reload(structs)
results = all(name, query, opts)
[pk] = head.__struct__.__schema__(:primary_key)
for struct <- structs do
struct_pk = Map.fetch!(struct, pk)
Enum.find(results, &Map.fetch!(&1, pk) == struct_pk) || raise "could not reload #{inspect(struct)}, maybe it doesn't exist or was deleted"
end
end
def reload!(name, struct, opts) do
query = query_for_reload([struct])
one!(name, query, opts)
end
def aggregate(name, queryable, aggregate, opts) do
one!(name, query_for_aggregate(queryable, aggregate), opts)
end
def aggregate(name, queryable, aggregate, field, opts) do
one!(name, query_for_aggregate(queryable, aggregate, field), opts)
end
def exists?(name, queryable, opts) do
queryable =
Query.exclude(queryable, :select)
|> Query.exclude(:preload)
|> Query.exclude(:order_by)
|> Query.exclude(:distinct)
|> Query.select(1)
|> Query.limit(1)
|> rewrite_combinations()
case all(name, queryable, opts) do
[1] -> true
[] -> false
end
end
defp rewrite_combinations(%{combinations: []} = query), do: query
defp rewrite_combinations(%{combinations: combinations} = query) do
combinations = Enum.map(combinations, fn {type, query} ->
{type, query |> Query.exclude(:select) |> Query.select(1)}
end)
%{query | combinations: combinations}
end
def one(name, queryable, tuplet) do
case all(name, queryable, tuplet) do
[one] -> one
[] -> nil
other -> raise Ecto.MultipleResultsError, queryable: queryable, count: length(other)
end
end
def one!(name, queryable, tuplet) do
case all(name, queryable, tuplet) do
[one] -> one
[] -> raise Ecto.NoResultsError, queryable: queryable
other -> raise Ecto.MultipleResultsError, queryable: queryable, count: length(other)
end
end
def update_all(name, queryable, [], tuplet) do
update_all(name, queryable, tuplet)
end
def update_all(name, queryable, updates, tuplet) do
query = Query.from(queryable, update: ^updates)
update_all(name, query, tuplet)
end
defp update_all(name, queryable, tuplet) do
query = Ecto.Queryable.to_query(queryable)
execute(:update_all, name, query, tuplet)
end
def delete_all(name, queryable, tuplet) do
query = Ecto.Queryable.to_query(queryable)
execute(:delete_all, name, query, tuplet)
end
@doc """
Load structs from query.
"""
def struct_load!([{field, type} | types], [value | values], acc, all_nil?, struct, adapter) do
all_nil? = all_nil? and value == nil
value = load!(type, value, field, struct, adapter)
struct_load!(types, values, [{field, value} | acc], all_nil?, struct, adapter)
end
def struct_load!([], values, _acc, true, _struct, _adapter) do
{nil, values}
end
def struct_load!([], values, acc, false, struct, _adapter) do
{Map.merge(struct, Map.new(acc)), values}
end
## Helpers
defp execute(operation, name, query, {adapter_meta, opts} = tuplet) do
%{adapter: adapter, cache: cache, repo: repo} = adapter_meta
{query, opts} = repo.prepare_query(operation, query, opts)
query = attach_prefix(query, opts)
{query_meta, prepared, params} = Planner.query(query, operation, cache, adapter, 0)
case query_meta do
%{select: nil} ->
adapter.execute(adapter_meta, query_meta, prepared, params, opts)
%{select: select, sources: sources, preloads: preloads} ->
%{
preprocess: preprocess,
postprocess: postprocess,
take: take,
assocs: assocs,
from: from
} = select
preprocessor = preprocessor(from, preprocess, adapter)
{count, rows} = adapter.execute(adapter_meta, query_meta, prepared, params, opts)
postprocessor = postprocessor(from, postprocess, take, adapter)
{count,
rows
|> Ecto.Repo.Assoc.query(assocs, sources, preprocessor)
|> Ecto.Repo.Preloader.query(name, preloads, take, postprocessor, tuplet)}
end
end
defp preprocessor({_, {:source, {source, schema}, prefix, types}}, preprocess, adapter) do
struct = Ecto.Schema.Loader.load_struct(schema, prefix, source)
fn row ->
{entry, rest} = struct_load!(types, row, [], false, struct, adapter)
preprocess(rest, preprocess, entry, adapter)
end
end
defp preprocessor({_, from}, preprocess, adapter) do
fn row ->
{entry, rest} = process(row, from, nil, adapter)
preprocess(rest, preprocess, entry, adapter)
end
end
defp preprocessor(:none, preprocess, adapter) do
fn row ->
preprocess(row, preprocess, nil, adapter)
end
end
defp preprocess(row, [], _from, _adapter) do
row
end
defp preprocess(row, [source | sources], from, adapter) do
{entry, rest} = process(row, source, from, adapter)
[entry | preprocess(rest, sources, from, adapter)]
end
defp postprocessor({:any, _}, postprocess, _take, adapter) do
fn [from | row] ->
row |> process(postprocess, from, adapter) |> elem(0)
end
end
defp postprocessor({:map, _}, postprocess, take, adapter) do
fn [from | row] ->
row |> process(postprocess, to_map(from, take), adapter) |> elem(0)
end
end
defp postprocessor(:none, postprocess, _take, adapter) do
fn row -> row |> process(postprocess, nil, adapter) |> elem(0) end
end
defp process(row, {:source, :from}, from, _adapter) do
{from, row}
end
defp process(row, {:source, {source, schema}, prefix, types}, _from, adapter) do
struct = Ecto.Schema.Loader.load_struct(schema, prefix, source)
struct_load!(types, row, [], true, struct, adapter)
end
defp process(row, {:merge, left, right}, from, adapter) do
{left, row} = process(row, left, from, adapter)
{right, row} = process(row, right, from, adapter)
data =
case {left, right} do
{%{__struct__: s}, %{__struct__: s}} ->
Map.merge(left, right)
{%{__struct__: left_struct}, %{__struct__: right_struct}} ->
raise ArgumentError,
"cannot merge structs of different types, " <>
"got: #{inspect(left_struct)} and #{inspect(right_struct)}"
{%{__struct__: name}, %{}} ->
for {key, _} <- right, not Map.has_key?(left, key) do
raise ArgumentError, "struct #{inspect(name)} does not have the key #{inspect(key)}"
end
Map.merge(left, right)
{%{}, %{}} ->
Map.merge(left, right)
{%{}, nil} ->
left
{_, %{}} ->
raise ArgumentError,
"cannot merge because the left side is not a map, got: #{inspect(left)}"
{%{}, _} ->
raise ArgumentError,
"cannot merge because the right side is not a map, got: #{inspect(right)}"
end
{data, row}
end
defp process(row, {:struct, struct, data, args}, from, adapter) do
case process(row, data, from, adapter) do
{%{__struct__: ^struct} = data, row} ->
process_update(data, args, row, from, adapter)
{data, _row} ->
raise BadStructError, struct: struct, term: data
end
end
defp process(row, {:struct, struct, args}, from, adapter) do
{fields, row} = process_kv(args, row, from, adapter)
case Map.merge(struct.__struct__(), Map.new(fields)) do
%{__meta__: %Ecto.Schema.Metadata{state: state} = metadata} = struct
when state != :loaded ->
{Map.replace!(struct, :__meta__, %{metadata | state: :loaded}), row}
map ->
{map, row}
end
end
defp process(row, {:map, data, args}, from, adapter) do
{data, row} = process(row, data, from, adapter)
process_update(data, args, row, from, adapter)
end
defp process(row, {:map, args}, from, adapter) do
{args, row} = process_kv(args, row, from, adapter)
{Map.new(args), row}
end
defp process(row, {:list, args}, from, adapter) do
process_args(args, row, from, adapter)
end
defp process(row, {:tuple, args}, from, adapter) do
{args, row} = process_args(args, row, from, adapter)
{List.to_tuple(args), row}
end
defp process([value | row], {:value, :any}, _from, _adapter) do
{value, row}
end
defp process([value | row], {:value, type}, _from, adapter) do
{load!(type, value, nil, nil, adapter), row}
end
defp process(row, value, _from, _adapter)
when is_binary(value) or is_number(value) or is_atom(value) do
{value, row}
end
defp process_update(data, args, row, from, adapter) do
{args, row} = process_kv(args, row, from, adapter)
data = Enum.reduce(args, data, fn {key, value}, acc -> %{acc | key => value} end)
{data, row}
end
defp process_args(args, row, from, adapter) do
Enum.map_reduce(args, row, fn arg, row ->
process(row, arg, from, adapter)
end)
end
defp process_kv(kv, row, from, adapter) do
Enum.map_reduce(kv, row, fn {key, value}, row ->
{key, row} = process(row, key, from, adapter)
{value, row} = process(row, value, from, adapter)
{{key, value}, row}
end)
end
@compile {:inline, load!: 5}
defp load!(type, value, field, struct, adapter) do
case Ecto.Type.adapter_load(adapter, type, value) do
{:ok, value} ->
value
:error ->
field = field && " for field #{inspect(field)}"
struct = struct && " in #{inspect(struct)}"
raise ArgumentError,
"cannot load `#{inspect(value)}` as type #{inspect(type)}#{field}#{struct}"
end
end
defp to_map(nil, _fields) do
nil
end
defp to_map(value, fields) when is_list(value) do
Enum.map(value, &to_map(&1, fields))
end
defp to_map(value, fields) do
for field <- fields, into: %{} do
case field do
{k, v} -> {k, to_map(Map.fetch!(value, k), List.wrap(v))}
k -> {k, Map.fetch!(value, k)}
end
end
end
defp query_for_get(_queryable, nil) do
raise ArgumentError, "cannot perform Ecto.Repo.get/2 because the given value is nil"
end
defp query_for_get(queryable, id) do
query = Queryable.to_query(queryable)
schema = assert_schema!(query)
case schema.__schema__(:primary_key) do
[pk] ->
Query.from(x in query, where: field(x, ^pk) == ^id)
pks ->
raise ArgumentError,
"Ecto.Repo.get/2 requires the schema #{inspect(schema)} " <>
"to have exactly one primary key, got: #{inspect(pks)}"
end
end
defp query_for_get_by(queryable, clauses) do
Query.where(queryable, [], ^Enum.to_list(clauses))
end
defp query_for_reload([head| _] = structs) do
assert_structs!(structs)
schema = head.__struct__
prefix = head.__meta__.prefix
case schema.__schema__(:primary_key) do
[pk] ->
keys = Enum.map(structs, &get_pk!(&1, pk))
query = Query.from(x in schema, where: field(x, ^pk) in ^keys)
%{query | prefix: prefix}
pks ->
raise ArgumentError,
"Ecto.Repo.reload/2 requires the schema #{inspect(schema)} " <>
"to have exactly one primary key, got: #{inspect(pks)}"
end
end
defp query_for_aggregate(queryable, aggregate) do
query =
case prepare_for_aggregate(queryable) do
%{distinct: nil, limit: nil, offset: nil, combinations: []} = query ->
%{query | order_bys: []}
query ->
query
|> Query.subquery()
|> Queryable.Ecto.SubQuery.to_query()
end
select = %SelectExpr{expr: {aggregate, [], []}, file: __ENV__.file, line: __ENV__.line}
%{query | select: select}
end
defp query_for_aggregate(queryable, aggregate, field) do
ast = field(0, field)
query =
case prepare_for_aggregate(queryable) do
%{distinct: nil, limit: nil, offset: nil, combinations: []} = query ->
%{query | order_bys: []}
query ->
select = %SelectExpr{expr: ast, file: __ENV__.file, line: __ENV__.line}
%{query | select: select}
|> Query.subquery()
|> Queryable.Ecto.SubQuery.to_query()
end
select = %SelectExpr{expr: {aggregate, [], [ast]}, file: __ENV__.file, line: __ENV__.line}
%{query | select: select}
end
defp prepare_for_aggregate(queryable) do
case %{Queryable.to_query(queryable) | preloads: [], assocs: []} do
%{group_bys: [_ | _]} = query ->
raise Ecto.QueryError, message: "cannot aggregate on query with group_by", query: query
%{} = query ->
query
end
end
defp field(ix, field) when is_integer(ix) and is_atom(field) do
{{:., [], [{:&, [], [ix]}, field]}, [], []}
end
defp assert_schema!(%{from: %{source: {_source, schema}}}) when schema != nil, do: schema
defp assert_schema!(query) do
raise Ecto.QueryError,
query: query,
message: "expected a from expression with a schema"
end
defp assert_structs!([head | _] = structs) when is_list(structs) do
unless Enum.all?(structs, &schema?/1) do
raise ArgumentError, "expected a struct or a list of structs, received #{inspect(structs)}"
end
unless Enum.all?(structs, &(&1.__struct__ == head.__struct__)) do
raise ArgumentError, "expected an homogenous list, received different struct types"
end
:ok
end
defp schema?(%{__meta__: _}), do: true
defp schema?(_), do: false
defp get_pk!(struct, pk) do
struct
|> Map.fetch!(pk)
|> case do
nil ->
raise ArgumentError, "Ecto.Repo.reload/2 expects existent structs, found a `nil` primary key"
key ->
key
end
end
end