Current section

Files

Jump to
ecto3_mnesia lib ecto adapters mnesia qlc.ex
Raw

lib/ecto/adapters/mnesia/qlc.ex

defmodule Ecto.Adapters.Mnesia.Qlc do
@moduledoc false
require Qlc
alias Ecto.Adapters.Mnesia.Record
alias Ecto.Adapters.Mnesia.Table
alias Ecto.Query.BooleanExpr
alias Ecto.Query.QueryExpr
alias Ecto.Query.SelectExpr
@order_mapping %{
asc: :ascending,
desc: :descending
}
@spec query(%SelectExpr{} | :all, any(), list(tuple())) ::
(list() -> (params :: list() -> query_handle :: :qlc.query_handle()))
def query(select, joins, sources) do
select = select(select, sources)
fn
[%BooleanExpr{}] = wheres ->
fn params ->
context = %{sources: sources, params: params}
qualifiers = qualifiers(wheres, context)
joins = joins(joins, context)
comprehension =
[select, Enum.join(joins, ", "), Enum.join(qualifiers, ", ")]
|> Enum.reject(fn component -> String.length(component) == 0 end)
|> Enum.join(", ")
Qlc.q("[#{comprehension}]", [])
end
filters ->
fn params ->
context = %{sources: sources, params: params}
qualifiers = qualifiers(filters, context)
joins = joins(joins, context)
comprehension =
[select, Enum.join(joins, ", "), Enum.join(qualifiers, ", ")]
|> Enum.reject(fn component -> String.length(component) == 0 end)
|> Enum.join(", ")
Qlc.q("[#{comprehension}]", [])
end
end
end
@spec sort(list(%QueryExpr{}), %SelectExpr{}, list(tuple())) ::
(query_handle :: :qlc.query_handle() -> query_handle :: :qlc.query_handle())
def sort([], _select, _sources) do
fn query -> query end
end
def sort(order_bys, select, sources) do
fn query ->
Enum.reduce(order_bys, query, fn
%QueryExpr{expr: expr}, query1 ->
Enum.reduce(expr, query1, fn {order, field_expr}, query2 ->
field = field(field_expr, sources)
field_index = Enum.find_index(fields(select, sources), fn e -> e == field end)
Qlc.keysort(query2, field_index, order: @order_mapping[order])
end)
end)
end
end
@spec answers(limit :: %QueryExpr{} | nil, offset :: %QueryExpr{} | nil) ::
(query_handle :: :qlc.query_handle(), context :: Keyword.t() -> list(tuple()))
def answers(limit, offset) do
fn query, context ->
limit = unbind_limit(limit, context)
offset = unbind_offset(offset, context)
cursor = Qlc.cursor(query)
if offset > 0 do
:qlc.next_answers(cursor.c, offset)
end
:qlc.next_answers(cursor.c, limit)
|> :qlc.e()
end
end
defp unbind_limit(nil, _context), do: :all_remaining
defp unbind_limit(%QueryExpr{expr: {:^, [], [param_index]}}, context) do
Enum.at(context[:params], param_index)
end
defp unbind_limit(%QueryExpr{expr: limit}, _context) when is_integer(limit), do: limit
defp unbind_offset(nil, _context), do: 0
defp unbind_offset(%QueryExpr{expr: {:^, [], [param_index]}}, context) do
Enum.at(context[:params], param_index)
end
defp unbind_offset(%QueryExpr{expr: offset}, _context) when is_integer(offset), do: offset
defp select(select, sources) do
fields = fields(select, sources)
"{#{Enum.join(fields, ", ")}} || " <>
(Enum.map(sources, fn {table_name, _schema} = source ->
"#{record_pattern(source)} <- mnesia:table('#{table_name}')"
end)
|> Enum.join(", "))
end
defp fields(%SelectExpr{fields: fields}, sources) do
Enum.flat_map(sources, fn _source ->
Enum.map(fields, &field(&1, sources))
|> Enum.reject(&is_nil(&1))
end)
end
defp fields(:all, [{_table_name, schema} = source | _t]) do
schema.__schema__(:fields)
|> Enum.map(&Record.Attributes.to_erl_var(&1, source))
end
defp fields(_, [{_table_name, schema} = source | _t]) do
schema.__schema__(:fields)
|> Enum.map(&Record.Attributes.to_erl_var(&1, source))
end
defp field({{_, _, [{:&, [], [source_index]}, field]}, [], []}, sources) do
case Enum.at(sources, source_index) do
source -> Record.Attributes.to_erl_var(field, source)
end
end
defp qualifiers(wheres, context) do
wheres
|> Enum.map(fn
%BooleanExpr{expr: expr} -> expr
{field, value} -> {field, value}
end)
|> Enum.map(&to_qlc(&1, context))
end
defp joins(joins, context) do
joins
|> Enum.map(fn %{on: %{expr: expr}} -> expr end)
|> Enum.map(&to_qlc(&1, context))
end
defp record_pattern(source) do
"{#{Enum.join(record_pattern_attributes(source), ", ")}}"
end
defp record_pattern_attributes({table_name, _schema} = source) do
Table.attributes(table_name)
|> Enum.map(fn attribute -> Record.Attributes.to_erl_var(attribute, source) end)
|> List.insert_at(0, "Schema")
end
defp to_qlc(true, _context), do: "true"
defp to_qlc({field, value}, %{sources: [source]}) do
erl_var = Record.Attributes.to_erl_var(field, source)
"#{erl_var} == #{to_erl(value)}"
end
defp to_qlc(
{:and, [], [a, b]},
context
),
do: "(#{to_qlc(a, context)} andalso #{to_qlc(b, context)})"
defp to_qlc(
{:or, [], [a, b]},
context
),
do: "(#{to_qlc(a, context)} orelse #{to_qlc(b, context)})"
defp to_qlc(
{:is_nil, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}]},
context
) do
source = Enum.at(context[:sources], source_index)
erl_var = Record.Attributes.to_erl_var(field, source)
"#{erl_var} == nil"
end
defp to_qlc(
{:in, [],
[{{:., [], [{:&, [], [source_index]}, field]}, [], []}, {:^, [], [index, length]}]},
context
) do
values = Enum.slice(context[:params], index, length)
to_qlc({:in, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}, values]}, context)
end
defp to_qlc(
{:in, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}, values]},
context
)
when is_list(values) do
source = Enum.at(context[:sources], source_index)
erl_var = Record.Attributes.to_erl_var(field, source)
"lists:member(#{erl_var}, [#{to_erl(values) |> Enum.join(", ")}])"
end
defp to_qlc(
{op, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}, {:^, [], [index]}]},
context
) do
value = Enum.at(context[:params], index)
to_qlc({op, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}, value]}, context)
end
defp to_qlc(
{op, [],
[
{{:., [], [{:&, [], [key_source_index]}, key_field]}, [], []},
{{:., [], [{:&, [], [value_source_index]}, value_field]}, [], []}
]},
context
) do
key_source = Enum.at(context[:sources], key_source_index)
value_source = Enum.at(context[:sources], value_source_index)
erl_var = Record.Attributes.to_erl_var(key_field, key_source)
value = Record.Attributes.to_erl_var(value_field, value_source)
"#{erl_var} #{op} #{value}"
end
defp to_qlc(
{:!=, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}, value]},
context
) do
source = Enum.at(context[:sources], source_index)
erl_var = Record.Attributes.to_erl_var(field, source)
value = to_erl(value)
"#{erl_var} =/= #{value}"
end
defp to_qlc(
{op, [], [{{:., [], [{:&, [], [source_index]}, field]}, [], []}, value]},
context
) do
source = Enum.at(context[:sources], source_index)
erl_var = Record.Attributes.to_erl_var(field, source)
value = to_erl(value)
"#{erl_var} #{op} #{value}"
end
defp to_erl(values) when is_list(values), do: Enum.map(values, &to_erl(&1))
defp to_erl(value) when is_binary(value), do: inspect(value, binaries: :as_binaries)
defp to_erl(value), do: value
end