mirror of
https://github.com/ash-project/ash_postgres.git
synced 2024-09-20 05:23:18 +12:00
37cc01957d
* WIP * chore: fix mix.lock merge issues * improvement: upgrade to 3.0 * chore: remove `repo.to_tenant` * chore: continue removal of unnecessary helper * chore: use `Ash.ToTenant`
165 lines
4.7 KiB
Elixir
165 lines
4.7 KiB
Elixir
defmodule AshPostgres.Calculation do
|
|
@moduledoc false
|
|
|
|
require Ecto.Query
|
|
|
|
@next_calculation_names Enum.reduce(0..999, %{}, fn i, acc ->
|
|
Map.put(acc, :"calculation_#{i}", :"calculation_#{i + 1}")
|
|
end)
|
|
|
|
def add_calculations(query, [], _, _, _select?), do: {:ok, query}
|
|
|
|
def add_calculations(query, calculations, resource, source_binding, select?) do
|
|
query = AshPostgres.DataLayer.default_bindings(query, resource)
|
|
|
|
{:ok, query} =
|
|
AshPostgres.Join.join_all_relationships(
|
|
query,
|
|
%Ash.Filter{
|
|
resource: resource,
|
|
expression: Enum.map(calculations, &elem(&1, 1))
|
|
},
|
|
left_only?: true
|
|
)
|
|
|
|
aggregates =
|
|
calculations
|
|
|> Enum.flat_map(fn {calculation, expression} ->
|
|
expression
|
|
|> Ash.Filter.used_aggregates([])
|
|
|> Enum.map(&Map.put(&1, :context, calculation.context))
|
|
end)
|
|
|> Enum.uniq()
|
|
|
|
{query, calculations} =
|
|
Enum.reduce(
|
|
calculations,
|
|
{query, []},
|
|
fn {calculation, expression}, {query, calculations} ->
|
|
if is_atom(calculation.name) do
|
|
{query, [{calculation, expression} | calculations]}
|
|
else
|
|
{query, name} = use_calculation_name(query, calculation.name)
|
|
|
|
{query, [{%{calculation | name: name}, expression} | calculations]}
|
|
end
|
|
end
|
|
)
|
|
|
|
case AshPostgres.Aggregate.add_aggregates(
|
|
query,
|
|
aggregates,
|
|
query.__ash_bindings__.resource,
|
|
false,
|
|
source_binding
|
|
) do
|
|
{:ok, query} ->
|
|
if select? do
|
|
query =
|
|
if query.select do
|
|
query
|
|
else
|
|
Ecto.Query.select_merge(query, %{})
|
|
end
|
|
|
|
{dynamics, query} =
|
|
Enum.reduce(calculations, {[], query}, fn {calculation, expression}, {list, query} ->
|
|
type =
|
|
AshPostgres.Types.parameterized_type(
|
|
calculation.type,
|
|
Map.get(calculation, :constraints, [])
|
|
)
|
|
|
|
expression =
|
|
Ash.Actions.Read.add_calc_context_to_filter(
|
|
expression,
|
|
calculation.context.actor,
|
|
calculation.context.authorize?,
|
|
calculation.context.tenant,
|
|
calculation.context.tracer,
|
|
nil
|
|
)
|
|
|
|
{expr, acc} =
|
|
AshPostgres.Expr.dynamic_expr(
|
|
query,
|
|
expression,
|
|
query.__ash_bindings__,
|
|
false,
|
|
type
|
|
)
|
|
|
|
expr =
|
|
if type do
|
|
Ecto.Query.dynamic(type(^expr, ^type))
|
|
else
|
|
expr
|
|
end
|
|
|
|
{[{calculation.load, calculation.name, expr} | list],
|
|
AshPostgres.DataLayer.merge_expr_accumulator(query, acc)}
|
|
end)
|
|
|
|
{:ok, add_calculation_selects(query, dynamics)}
|
|
else
|
|
{:ok, query}
|
|
end
|
|
|
|
{:error, error} ->
|
|
{:error, error}
|
|
end
|
|
end
|
|
|
|
def next_calculation_name(i) do
|
|
@next_calculation_names[i] ||
|
|
raise Ash.Error.Framework.AssumptionFailed,
|
|
message: """
|
|
All 1000 static names for calculations have been used in a single query.
|
|
Congratulations, this means that you have gone so wildly beyond our imagination
|
|
of how much can fit into a single quer. Please file an issue and we will raise the limit.
|
|
"""
|
|
end
|
|
|
|
defp use_calculation_name(query, aggregate_name) do
|
|
{%{
|
|
query
|
|
| __ash_bindings__: %{
|
|
query.__ash_bindings__
|
|
| current_calculation_name:
|
|
next_calculation_name(query.__ash_bindings__.current_calculation_name),
|
|
calculation_names:
|
|
Map.put(
|
|
query.__ash_bindings__.calculation_names,
|
|
aggregate_name,
|
|
query.__ash_bindings__.current_calculation_name
|
|
)
|
|
}
|
|
}, query.__ash_bindings__.current_calculation_name}
|
|
end
|
|
|
|
defp add_calculation_selects(query, dynamics) do
|
|
{in_calculations, in_body} =
|
|
Enum.split_with(dynamics, fn {load, _name, _dynamic} -> is_nil(load) end)
|
|
|
|
calcs =
|
|
in_body
|
|
|> Map.new(fn {load, _, dynamic} ->
|
|
{load, dynamic}
|
|
end)
|
|
|
|
calcs =
|
|
if Enum.empty?(in_calculations) do
|
|
calcs
|
|
else
|
|
Map.put(
|
|
calcs,
|
|
:calculations,
|
|
Map.new(in_calculations, fn {_, name, dynamic} ->
|
|
{name, dynamic}
|
|
end)
|
|
)
|
|
end
|
|
|
|
Ecto.Query.select_merge(query, ^calcs)
|
|
end
|
|
end
|