fix: simplify aggregate bindings & calculation reference building

This commit is contained in:
Zach Daniel 2023-11-22 14:07:18 -05:00
parent 179cee4b24
commit 5cf9affff4
2 changed files with 38 additions and 120 deletions

View file

@ -111,6 +111,15 @@ defmodule AshPostgres.Aggregate do
{:cont, {:ok, query, [{aggregate.load, aggregate.name, exists} | dynamics]}}
true ->
root_data_path =
case root_data do
{_, path} ->
path
_ ->
[]
end
with {:ok, agg_root_query} <-
AshPostgres.Join.maybe_get_resource_query(
first_relationship.destination,
@ -166,7 +175,8 @@ defmodule AshPostgres.Aggregate do
first_relationship,
relationship_path,
aggregates,
source_binding
source_binding,
root_data_path
) do
if select? do
new_dynamics =
@ -459,7 +469,8 @@ defmodule AshPostgres.Aggregate do
%{manual: {module, opts}} = first_relationship,
_relationship_path,
aggregates,
source_binding
source_binding,
root_data_path
) do
field = first_relationship.destination_attribute
@ -496,7 +507,7 @@ defmodule AshPostgres.Aggregate do
AshPostgres.DataLayer.add_binding(
query,
%{
path: [],
path: root_data_path,
type: :aggregate,
aggregates: aggregates
}
@ -510,7 +521,8 @@ defmodule AshPostgres.Aggregate do
first_relationship,
_relationship_path,
aggregates,
source_binding
source_binding,
root_data_path
) do
join_relationship_struct = Ash.Resource.Info.relationship(source, join_relationship)
@ -562,7 +574,7 @@ defmodule AshPostgres.Aggregate do
AshPostgres.DataLayer.add_binding(
query,
%{
path: [],
path: root_data_path,
type: :aggregate,
aggregates: aggregates
}
@ -575,7 +587,8 @@ defmodule AshPostgres.Aggregate do
first_relationship,
_relationship_path,
aggregates,
source_binding
source_binding,
root_data_path
) do
field = first_relationship.destination_attribute
@ -604,7 +617,7 @@ defmodule AshPostgres.Aggregate do
AshPostgres.DataLayer.add_binding(
query,
%{
path: [],
path: root_data_path,
type: :aggregate,
aggregates: aggregates
}

View file

@ -765,8 +765,7 @@ defmodule AshPostgres.Expr do
query,
%Ref{
attribute: %Ash.Query.Calculation{} = calculation,
relationship_path: [],
resource: resource
relationship_path: relationship_path
} = type_expr,
bindings,
embedded?,
@ -781,6 +780,7 @@ defmodule AshPostgres.Expr do
)
validate_type!(query, type, type_expr)
resource = Ash.Resource.Info.related(bindings.resource, relationship_path)
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
@ -792,6 +792,12 @@ defmodule AshPostgres.Expr do
}
) do
{:ok, expression} ->
expression =
Ash.Filter.move_to_relationship_path(
expression,
relationship_path
)
expression =
Ash.Actions.Read.add_calc_context_to_filter(
expression,
@ -840,18 +846,6 @@ defmodule AshPostgres.Expr do
)
end
defp do_dynamic_expr(
_query,
%Ref{
attribute: %Ash.Resource.Calculation{} = calculation
},
_bindings,
_embedded?,
_type
) do
raise "cannot build expression from resource calculation! #{calculation.name}"
end
defp do_dynamic_expr(
query,
%Ref{attribute: %Ash.Query.Aggregate{} = aggregate} = ref,
@ -876,7 +870,12 @@ defmodule AshPostgres.Expr do
{ref_binding, field_name, value} =
if first_optimized_aggregate? do
ref = %{ref | relationship_path: ref.relationship_path ++ aggregate.relationship_path}
ref = %{
ref
| attribute: %Ash.Resource.Attribute{name: :fake},
relationship_path: ref.relationship_path ++ aggregate.relationship_path
}
ref_binding = ref_binding(ref, bindings)
if is_nil(ref_binding) do
@ -912,19 +911,14 @@ defmodule AshPostgres.Expr do
ref_binding = ref_binding(ref, bindings)
if is_nil(ref_binding) do
IO.inspect(ref)
IO.inspect(bindings)
raise "Error while building reference: #{inspect(ref)}"
end
{ref_binding, aggregate.name, nil}
end
ref_binding =
if ref.relationship_path == [] || first_optimized_aggregate? do
ref_binding
else
ref_binding + 1
end
expr =
if value do
value
@ -964,85 +958,6 @@ defmodule AshPostgres.Expr do
end
end
defp do_dynamic_expr(
query,
%Ref{
attribute: %Ash.Query.Calculation{} = calculation,
relationship_path: relationship_path
} = ref,
bindings,
embedded?,
_type
) do
binding_to_replace =
Enum.find_value(bindings.bindings, fn {i, binding} ->
if binding.path == relationship_path do
i
end
end)
if is_nil(binding_to_replace) do
raise """
Error building calculation reference: #{inspect(relationship_path)} is not available in bindings.
In reference: #{ref}
"""
end
temp_bindings =
bindings.bindings
|> Map.delete(0)
|> Map.update!(binding_to_replace, &Map.merge(&1, %{path: [], type: :root}))
type =
AshPostgres.Types.parameterized_type(
calculation.type,
Map.get(calculation, :constraints, [])
)
validate_type!(query, type, ref)
case Ash.Filter.hydrate_refs(
calculation.module.expression(calculation.opts, calculation.context),
%{
resource: ref.resource,
aggregates: %{},
calculations: %{},
public?: false
}
) do
{:ok, hydrated} ->
hydrated =
Ash.Actions.Read.add_calc_context_to_filter(
hydrated,
calculation.context[:actor],
calculation.context[:authorize?],
calculation.context[:tenant],
calculation.context[:tracer]
)
expr =
do_dynamic_expr(
query,
Ash.Filter.update_aggregates(hydrated, fn aggregate, _ ->
%{aggregate | relationship_path: []}
end),
%{bindings | bindings: temp_bindings},
embedded?,
type
)
if type do
Ecto.Query.dynamic(type(^expr, ^type))
else
expr
end
_ ->
raise "Failed to hydrate references for #{inspect(ref.resource)} in #{inspect(calculation.module.expression(calculation.opts, calculation.context))}"
end
end
defp do_dynamic_expr(
query,
%Type{arguments: [arg1, arg2, constraints]},
@ -1511,11 +1426,12 @@ defmodule AshPostgres.Expr do
end
defp ref_binding(
%{attribute: %Ash.Query.Aggregate{name: name}, relationship_path: []},
%{attribute: %Ash.Query.Aggregate{name: name}, relationship_path: relationship_path},
bindings
) do
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.type == :aggregate &&
data.path == relationship_path &&
Enum.any?(data.aggregates, &(&1.name == name)) && binding
end)
end
@ -1531,17 +1447,6 @@ defmodule AshPostgres.Expr do
end)
end
defp ref_binding(%{attribute: %Ash.Query.Aggregate{}} = ref, bindings) do
Enum.find_value(bindings.bindings, fn {binding, data} ->
data.type in [:inner, :left, :root] &&
Ash.SatSolver.synonymous_relationship_paths?(
bindings.resource,
data.path,
ref.relationship_path
) && binding
end)
end
defp do_get_path(
query,
%GetPath{arguments: [left, right], embedded?: pred_embedded?} = get_path,