Packages
ash_sql
0.1.1-rc.12
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.93
0.2.92
0.2.91
0.2.90
0.2.89
0.2.88
0.2.87
0.2.86
0.2.85
0.2.84
0.2.83
0.2.82
0.2.81
0.2.80
0.2.79
0.2.78
0.2.77
0.2.76
0.2.75
0.2.74
0.2.73
0.2.72
0.2.71
0.2.70
0.2.69
0.2.68
0.2.67
0.2.66
0.2.65
0.2.64
0.2.63
0.2.62
0.2.61
0.2.60
0.2.59
0.2.58
0.2.57
0.2.56
0.2.55
0.2.54
0.2.53
0.2.52
0.2.51
0.2.50
0.2.49
0.2.48
0.2.47
0.2.46
0.2.45
0.2.44
0.2.43
0.2.42
0.2.41
0.2.40
0.2.39
0.2.38
0.2.37
0.2.36
0.2.35
0.2.34
0.2.33
0.2.32
0.2.31
0.2.30
0.2.29
0.2.28
0.2.27
0.2.26
0.2.25
0.2.24
0.2.23
0.2.22
0.2.21
0.2.20
0.2.19
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
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.3
0.1.2
0.1.1-rc.20
0.1.1-rc.19
0.1.1-rc.18
0.1.1-rc.17
0.1.1-rc.16
0.1.1-rc.15
0.1.1-rc.14
0.1.1-rc.13
0.1.1-rc.12
0.1.1-rc.11
0.1.1-rc.10
0.1.1-rc.9
0.1.1-rc.8
0.1.1-rc.7
0.1.1-rc.6
0.1.1-rc.5
0.1.1-rc.4
0.1.1-rc.3
0.1.1-rc.2
0.1.1-rc.1
0.1.1-rc.0
Shared utilities for ecto-based sql data layers.
Current section
Files
Jump to
Current section
Files
lib/calculation.ex
defmodule AshSql.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, calculations, resource, source_binding, select? \\ false)
def add_calculations(query, [], _, _, _select?), do: {:ok, query}
def add_calculations(query, calculations, resource, source_binding, select?) do
{:ok, query} =
AshSql.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 AshSql.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 =
query.__ash_bindings__.sql_behaviour.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} =
AshSql.Expr.dynamic_expr(
query,
expression,
query.__ash_bindings__,
false,
type
)
expr =
if type do
query.__ash_bindings__.sql_behaviour.type_expr(expr, type)
else
expr
end
{[{calculation.load, calculation.name, expr} | list],
AshSql.Expr.merge_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