Skip to content

Commit

Permalink
improvement: add simple_join_first_aggregates option
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Jun 12, 2023
1 parent 29815eb commit 0e6bcd0
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 14 deletions.
1 change: 1 addition & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ spark_locals_without_parens = [
reference: 2,
repo: 1,
schema: 1,
simple_join_first_aggregates: 1,
skip_unique_indexes: 1,
statement: 1,
statement: 2,
Expand Down
21 changes: 16 additions & 5 deletions lib/aggregate.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ defmodule AshPostgres.Aggregate do
first_can_join? =
case aggregates do
[aggregate] ->
single_path?(resource, aggregate.relationship_path)
optimizable_first_aggregate?(resource, aggregate)

_ ->
false
Expand Down Expand Up @@ -501,7 +501,7 @@ defmodule AshPostgres.Aggregate do

defp can_group_kind?(aggregate, resource) do
if aggregate.kind == :first do
if array_type?(resource, aggregate) || single_path?(resource, aggregate.relationship_path) do
if array_type?(resource, aggregate) || optimizable_first_aggregate?(resource, aggregate) do
false
else
true
Expand All @@ -511,6 +511,18 @@ defmodule AshPostgres.Aggregate do
end
end

@doc false
def optimizable_first_aggregate?(resource, %{
name: name,
kind: :first,
relationship_path: relationship_path
}) do
name in AshPostgres.DataLayer.Info.simple_join_first_aggregates(resource) &&
single_path?(resource, relationship_path)
end

def optimizable_first_aggregate?(_, _), do: false

defp array_type?(resource, aggregate) do
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)

Expand Down Expand Up @@ -939,10 +951,9 @@ defmodule AshPostgres.Aggregate do
Ecto.Query.select_merge(query, ^%{aggregate_name => casted})
end

@doc false
def single_path?(_, []), do: true
defp single_path?(_, []), do: true

def single_path?(resource, [relationship | rest]) do
defp single_path?(resource, [relationship | rest]) do
relationship = Ash.Resource.Info.relationship(resource, relationship)
relationship.type == :belongs_to && single_path?(relationship.destination, rest)
end
Expand Down
12 changes: 12 additions & 0 deletions lib/data_layer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ defmodule AshPostgres.DataLayer do
doc:
"A raw sql version of the base_filter, e.g `representative = true`. Required if trying to create a unique constraint on a resource with a base_filter"
],
simple_join_first_aggregates: [
type: {:list, :atom},
default: [],
doc: """
A list of `:first` type aggregate names that can be joined to using a simple join.
This is used in the relatively rare case that you have a `:first` aggregate that uses
a `has_many` or `many_to_many` relationship in its path, but your `filter` statement ensures
that there is only one result. In these cases, we can use a more optimized version of
computing the aggregate value.
"""
],
skip_unique_indexes: [
type: {:custom, __MODULE__, :validate_skip_unique_indexes, []},
default: false,
Expand Down
4 changes: 4 additions & 0 deletions lib/data_layer/info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ defmodule AshPostgres.DataLayer.Info do
Extension.get_opt(resource, [:postgres], :table, nil, true)
end

def simple_join_first_aggregates(resource) do
Extension.get_opt(resource, [:postgres], :simple_join_first_aggregates, [])
end

@doc "The configured schema for a resource"
def schema(resource) do
Extension.get_opt(resource, [:postgres], :schema, nil, true)
Expand Down
3 changes: 1 addition & 2 deletions lib/expr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,7 @@ defmodule AshPostgres.Expr do
related = Ash.Resource.Info.related(query.__ash_bindings__.resource, ref.relationship_path)

first_optimized_aggregate? =
aggregate.kind == :first &&
AshPostgres.Aggregate.single_path?(related, aggregate.relationship_path)
AshPostgres.Aggregate.optimizable_first_aggregate?(related, aggregate)

{ref_binding, field_name} =
if first_optimized_aggregate? do
Expand Down
9 changes: 4 additions & 5 deletions lib/join.ex
Original file line number Diff line number Diff line change
Expand Up @@ -744,11 +744,10 @@ defmodule AshPostgres.Join do
relationship_destination =
used_aggregates
|> Enum.reject(fn aggregate ->
aggregate.kind == :first &&
AshPostgres.Aggregate.single_path?(
relationship.destination,
aggregate.relationship_path
)
AshPostgres.Aggregate.optimizable_first_aggregate?(
relationship.destination,
aggregate
)
end)
|> case do
[] ->
Expand Down
4 changes: 2 additions & 2 deletions lib/sort.ex
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ defmodule AshPostgres.Sort do
aggregate = Ash.Resource.Info.aggregate(resource, sort)

{binding, sort} =
if aggregate && aggregate.kind == :first &&
AshPostgres.Aggregate.single_path?(resource, aggregate.relationship_path) do
if aggregate &&
AshPostgres.Aggregate.optimizable_first_aggregate?(resource, aggregate) do
{AshPostgres.Join.get_binding(
resource,
aggregate.relationship_path,
Expand Down

0 comments on commit 0e6bcd0

Please sign in to comment.