From 46744c5d4a072c27eef57aad88c242eaa74898c7 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 1 Jul 2021 15:28:27 -0400 Subject: [PATCH] fix: properly coalesce aggregate values --- lib/data_layer.ex | 133 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 25 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index c930df9..0fdebd2 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -996,13 +996,54 @@ defmodule AshPostgres.DataLayer do end {order, sort}, {:ok, query} -> - binding = + expr = case Map.fetch(query.__ash_bindings__.aggregates, sort) do {:ok, binding} -> - binding + aggregate = + Ash.Resource.Info.aggregate(resource, sort) || + raise "No such aggregate for query aggregate #{inspect(sort)}" + + {:ok, field_type} = + if aggregate.field do + related = Ash.Resource.Info.related(resource, aggregate.relationship_path) + + attr = Ash.Resource.Info.attribute(related, aggregate.field) + + if attr && related do + {:ok, attr.type} + else + {:ok, nil} + end + else + {:ok, nil} + end + + default_value = Ash.Query.Aggregate.default_value(aggregate.kind) + + if is_nil(default_value) do + {{:., [], [{:&, [], [binding]}, sort]}, [], []} + else + if field_type do + {:coalesce, [], + [ + {{:., [], [{:&, [], [binding]}, sort]}, [], []}, + {:type, [], + [ + default_value, + Ash.Type.ecto_type(field_type) + ]} + ]} + else + {:coalesce, [], + [ + {{:., [], [{:&, [], [binding]}, sort]}, [], []}, + default_value + ]} + end + end :error -> - 0 + {{:., [], [{:&, [], [0]}, sort]}, [], []} end new_query = @@ -1011,7 +1052,7 @@ defmodule AshPostgres.DataLayer do sort_expr = %Ecto.Query.QueryExpr{ expr: [ - {order, {{:., [], [{:&, [], [binding]}, sort]}, [], []}} + {order, expr} ] } @@ -1570,8 +1611,7 @@ defmodule AshPostgres.DataLayer do end end - defp add_subquery_aggregate_select(query, %{kind: kind} = aggregate, _resource) - when kind in [:first, :list] do + defp add_subquery_aggregate_select(query, %{kind: :first} = aggregate, _resource) do query = default_bindings(query, aggregate.resource) key = aggregate.field type = Ash.Type.ecto_type(aggregate.type) @@ -1623,26 +1663,28 @@ defmodule AshPostgres.DataLayer do {[], field} end - casted = - if kind == :first do - {:type, [], - [ - {:fragment, [], - [ - raw: "(", - expr: filtered, - raw: ")[1]" - ]}, - type - ]} + value = + {:fragment, [], + [ + raw: "(", + expr: filtered, + raw: ")[1]" + ]} + + with_default = + if aggregate.default_value do + {:coalesce, [], [value, {:type, [], [aggregate.default_value, type]}]} else - {:type, [], - [ - filtered, - {:array, type} - ]} + value end + casted = + {:type, [], + [ + with_default, + type + ]} + new_expr = {:merge, [], [query.select.expr, {:%{}, [], [{aggregate.name, casted}]}]} %{query | select: %{query.select | expr: new_expr, params: params}} @@ -1700,7 +1742,14 @@ defmodule AshPostgres.DataLayer do {[], field} end - cast = {:type, [], [filtered, {:array, type}]} + with_default = + if aggregate.default_value do + {:coalesce, [], [filtered, {:type, [], [aggregate.default_value, type]}]} + else + filtered + end + + cast = {:type, [], [with_default, {:array, type}]} new_expr = {:merge, [], [query.select.expr, {:%{}, [], [{aggregate.name, cast}]}]} @@ -1730,7 +1779,14 @@ defmodule AshPostgres.DataLayer do {[], field} end - cast = {:type, [], [filtered, type]} + with_default = + if aggregate.default_value do + {:coalesce, [], [filtered, {:type, [], [aggregate.default_value, type]}]} + else + filtered + end + + cast = {:type, [], [with_default, type]} new_expr = {:merge, [], [query.select.expr, {:%{}, [], [{aggregate.name, cast}]}]} @@ -2585,6 +2641,33 @@ defmodule AshPostgres.DataLayer do end end + defp do_filter_to_expr( + %Ref{attribute: %Ash.Query.Aggregate{} = aggregate} = ref, + bindings, + params, + _embedded?, + _type + ) do + expr = {{:., [], [{:&, [], [ref_binding(ref, bindings)]}, aggregate.name]}, [], []} + type = Ash.Type.ecto_type(aggregate.type) + + type = + if aggregate.kind == :list do + {:array, type} + else + type + end + + with_default = + if aggregate.default_value do + {:coalesce, [], [expr, {:type, [], [aggregate.default_value, type]}]} + else + expr + end + + {params, with_default} + end + defp do_filter_to_expr( %Ref{attribute: %{name: name}} = ref, bindings,