Skip to content

Commit

Permalink
fix: properly expand resource calculation/aggregates in fields
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Jul 16, 2024
1 parent 0eb2ee8 commit 855ee13
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 19 deletions.
51 changes: 40 additions & 11 deletions lib/aggregate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,26 @@ defmodule AshSql.Aggregate do
first_relationship
)

{field, acc} = AshSql.Expr.dynamic_expr(query, ref, query.__ash_bindings__, false)
{field, acc} = AshSql.Expr.dynamic_expr(query, ref, Map.put(query.__ash_bindings__, :location, :aggregate), false)

related =
Ash.Resource.Info.related(
query.__ash_bindings__.resource,
relationship_path
)

# field =
# with {type, constraints} <- get_type(related, aggregate.field),
# type when not is_nil(type) <-
# query.__ash_bindings__.sql_behaviour.parameterized_type(
# type,
# constraints
# ) do
# Ecto.Query.dynamic([], type(^field, ^type))
# else
# _ ->
# field
# end

has_sort? = has_sort?(aggregate.query)

Expand All @@ -1420,10 +1439,7 @@ defmodule AshSql.Aggregate do
AshSql.Sort.sort(
query,
sort,
Ash.Resource.Info.related(
query.__ash_bindings__.resource,
relationship_path
),
related,
relationship_path,
binding,
:return
Expand All @@ -1442,18 +1458,18 @@ defmodule AshSql.Aggregate do
if aggregate.include_nil? do
{:ok, expr} =
Ash.Query.Function.Fragment.casted_new(
[
"array_agg(#{distinct}? ORDER BY #{question_marks} FILTER (WHERE ? IS NOT NULL))",
field
] ++
sort_expr ++ [field]
["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr
)

expr
else
{:ok, expr} =
Ash.Query.Function.Fragment.casted_new(
["array_agg(#{distinct}? ORDER BY #{question_marks})", field] ++ sort_expr
[
"array_agg(#{distinct}? ORDER BY #{question_marks}) FILTER (WHERE ? IS NOT NULL)",
field
] ++
sort_expr ++ [field]
)

expr
Expand Down Expand Up @@ -1758,4 +1774,17 @@ defmodule AshSql.Aggregate do
aggregate.field
end
end


if field do
related = Ash.Resource.Info.related(resource, relationship_path)
{field_type, constraints} = get_type(related, field)
Ash.Query.Aggregate.kind_to_type(kind, field_type, constraints)
else
Ash.Query.Aggregate.kind_to_type(kind, nil, nil)
end

%{type: type, constraints: constraints} ->
{type, constraints}
end
end
9 changes: 1 addition & 8 deletions lib/expr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1324,13 +1324,6 @@ defmodule AshSql.Expr do
type = bindings.sql_behaviour.parameterized_type(aggregate.type, aggregate.constraints)
validate_type!(query, type, ref)

type =
if type && aggregate.kind == :list do
{:array, type}
else
type
end

coalesced =
if is_nil(aggregate.default_value) do
expr
Expand Down Expand Up @@ -1851,7 +1844,7 @@ defmodule AshSql.Expr do

defp default_dynamic_expr(query, value, bindings, embedded?, acc, type)
when is_map(value) and not is_struct(value) do
if bindings[:location] == :update && Ash.Expr.expr?(value) do
if bindings[:location] in [:update, :aggregate] && Ash.Expr.expr?(value) do
elements =
value
|> Enum.flat_map(fn {key, list_item} ->
Expand Down
80 changes: 80 additions & 0 deletions lib/sort.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,86 @@ defmodule AshSql.Sort do
binding \\ 0,
type \\ :window
) do
sort =
Enum.map(sort, fn
{key, val} when is_atom(key) ->
case Ash.Resource.Info.field(resource, key) do
%Ash.Resource.Calculation{calculation: {module, opts}} = calculation ->
{:ok, calculation} =
Ash.Query.Calculation.new(
calculation.name,
module,
opts,
calculation.type,
calculation.constraints
)

calculation =
Ash.Actions.Read.add_calc_context(
calculation,
query.__ash_bindings__.context[:private][:actor],
query.__ash_bindings__.context[:private][:authorize?],
query.__ash_bindings__.context[:private][:tenant],
query.__ash_bindings__.context[:private][:tracer],
nil
)

{calculation, val}

%Ash.Resource.Aggregate{} = aggregate ->
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)

read_action =
aggregate.read_action ||
Ash.Resource.Info.primary_action!(
related,
:read
).name

with %{valid?: true} = aggregate_query <- Ash.Query.for_read(related, read_action),
%{valid?: true} = aggregate_query <-
Ash.Query.build(aggregate_query,
filter: aggregate.filter,
sort: aggregate.sort
),
{:ok, agg} <-
Ash.Query.Aggregate.new(
resource,
aggregate.name,
aggregate.kind,
path: aggregate.relationship_path,
query: aggregate_query,
field: aggregate.field,
default: aggregate.default,
filterable?: aggregate.filterable?,
type: aggregate.type,
sortable?: aggregate.filterable?,
include_nil?: aggregate.include_nil?,
constraints: aggregate.constraints,
implementation: aggregate.implementation,
uniq?: aggregate.uniq?,
read_action:
aggregate.read_action ||
Ash.Resource.Info.primary_action!(
Ash.Resource.Info.related(resource, aggregate.relationship_path),
:read
).name,
authorize?: aggregate.authorize?
) do
{agg, val}
else
%{errors: errors} -> raise Ash.Error.to_ash_error(errors)
{:error, error} -> raise Ash.Error.to_ash_error(error)
end

_ ->
{key, val}
end

{key, val} ->
{key, val}
end)

used_aggregates =
Enum.flat_map(sort, fn
{%Ash.Query.Calculation{} = calculation, _} ->
Expand Down

0 comments on commit 855ee13

Please sign in to comment.