Skip to content

Commit b888e7b

Browse files
committed
perf: only calculate per-path aggregates once while joining
1 parent e9e9aca commit b888e7b

1 file changed

Lines changed: 63 additions & 16 deletions

File tree

‎lib/join.ex‎

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,34 @@ defmodule AshSql.Join do
8282
parent_query,
8383
no_inner_join?
8484
) do
85+
do_join_all_relationships(
86+
query,
87+
filter,
88+
opts,
89+
relationship_paths,
90+
path,
91+
source,
92+
sort?,
93+
join_filters,
94+
parent_query,
95+
no_inner_join?,
96+
nil
97+
)
98+
end
99+
100+
defp do_join_all_relationships(
101+
query,
102+
filter,
103+
opts,
104+
relationship_paths,
105+
path,
106+
source,
107+
sort?,
108+
join_filters,
109+
parent_query,
110+
no_inner_join?,
111+
aggregates_by_path
112+
) do
85113
no_inner_join? = left_join_only?(query, opts, no_inner_join?)
86114

87115
case join_parent_paths(query, filter, relationship_paths) do
@@ -108,6 +136,12 @@ defmodule AshSql.Join do
108136
{updated_query, relationship_paths ++ parent_relationship_paths}
109137
end
110138

139+
aggregates_by_path =
140+
aggregates_by_path ||
141+
if Enum.any?(relationship_paths, &match?({_join_type, [_ | _]}, &1)) do
142+
aggregates_by_path(filter)
143+
end
144+
111145
Enum.reduce_while(relationship_paths, {:ok, query}, fn
112146
{_join_type, []}, {:ok, query} ->
113147
{:cont, {:ok, query}}
@@ -144,7 +178,7 @@ defmodule AshSql.Join do
144178
# We can't reuse joins if we're adding filters/have a separate parent binding
145179
if is_nil(join_filters) && is_nil(parent_query) && binding do
146180
full_path = Enum.map(current_path, & &1.name)
147-
used_aggregates = Ash.Filter.used_aggregates(filter, full_path)
181+
used_aggregates = Map.get(aggregates_by_path, full_path, [])
148182

149183
case AshSql.Aggregate.add_aggregates(
150184
query,
@@ -155,14 +189,18 @@ defmodule AshSql.Join do
155189
{query.__ash_bindings__.resource, full_path}
156190
) do
157191
{:ok, query} ->
158-
case join_all_relationships(
192+
case do_join_all_relationships(
159193
query,
160194
filter,
161195
opts,
162196
[{join_type, rest_rels}],
163197
current_path,
164198
source,
165-
sort?
199+
sort?,
200+
nil,
201+
nil,
202+
false,
203+
aggregates_by_path
166204
) do
167205
{:ok, query} ->
168206
{:cont, {:ok, query}}
@@ -181,14 +219,14 @@ defmodule AshSql.Join do
181219
Enum.map(path, & &1.name),
182220
current_join_type,
183221
source,
184-
filter,
222+
aggregates_by_path,
185223
sort?,
186224
join_filters[Enum.map(current_path, & &1.name)]
187225
) do
188226
{:ok, joined_query, _binding} ->
189227
joined_query_with_distinct = add_distinct(relationship, join_type, joined_query)
190228

191-
case join_all_relationships(
229+
case do_join_all_relationships(
192230
joined_query_with_distinct,
193231
filter,
194232
opts,
@@ -207,7 +245,9 @@ defmodule AshSql.Join do
207245
Enum.map(path, & &1.name) ++ [relationship.name]
208246
)
209247
end
210-
)
248+
),
249+
false,
250+
aggregates_by_path
211251
) do
212252
{:ok, query} ->
213253
{:cont, {:ok, query}}
@@ -227,6 +267,13 @@ defmodule AshSql.Join do
227267
end
228268
end
229269

270+
defp aggregates_by_path(filter) do
271+
filter
272+
|> Ash.Filter.used_aggregates(:*, true)
273+
|> Enum.group_by(&(&1.relationship_path || []), & &1.attribute)
274+
|> Map.new(fn {path, aggregates} -> {path, Enum.uniq(aggregates)} end)
275+
end
276+
230277
@doc false
231278
def join_relationship_path(query, resource, relationship_path, join_filters) do
232279
relationships = relationship_path_to_relationships(resource, relationship_path)
@@ -241,7 +288,7 @@ defmodule AshSql.Join do
241288
path,
242289
:left,
243290
resource,
244-
nil,
291+
%{},
245292
false,
246293
join_filters[full_path]
247294
) do
@@ -1190,7 +1237,7 @@ defmodule AshSql.Join do
11901237
path,
11911238
kind,
11921239
source,
1193-
filter,
1240+
aggregates_by_path,
11941241
sort?,
11951242
apply_filter
11961243
) do
@@ -1201,7 +1248,7 @@ defmodule AshSql.Join do
12011248

12021249
query = AshSql.Bindings.add_binding(query, binding_data)
12031250

1204-
used_aggregates = Ash.Filter.used_aggregates(filter, full_path)
1251+
used_aggregates = Map.get(aggregates_by_path, full_path, [])
12051252

12061253
with {:ok, relationship_destination} <-
12071254
related_subquery(relationship, query,
@@ -1273,7 +1320,7 @@ defmodule AshSql.Join do
12731320
path,
12741321
kind,
12751322
source,
1276-
filter,
1323+
aggregates_by_path,
12771324
sort?,
12781325
apply_filter
12791326
) do
@@ -1298,7 +1345,7 @@ defmodule AshSql.Join do
12981345
path,
12991346
kind,
13001347
source,
1301-
filter,
1348+
aggregates_by_path,
13021349
sort?,
13031350
apply_filter,
13041351
used_parent_aggregates,
@@ -1316,7 +1363,7 @@ defmodule AshSql.Join do
13161363

13171364
binding_data = %{type: kind, path: full_path, source: source}
13181365

1319-
used_aggregates = Ash.Filter.used_aggregates(filter, full_path)
1366+
used_aggregates = Map.get(aggregates_by_path, full_path, [])
13201367

13211368
query =
13221369
query
@@ -1425,7 +1472,7 @@ defmodule AshSql.Join do
14251472
path,
14261473
kind,
14271474
source,
1428-
filter,
1475+
aggregates_by_path,
14291476
_sort?,
14301477
apply_filter
14311478
) do
@@ -1436,7 +1483,7 @@ defmodule AshSql.Join do
14361483

14371484
query = AshSql.Bindings.add_binding(query, binding_data)
14381485

1439-
used_aggregates = Ash.Filter.used_aggregates(filter, full_path)
1486+
used_aggregates = Map.get(aggregates_by_path, full_path, [])
14401487

14411488
binding_kinds =
14421489
case kind do
@@ -1633,7 +1680,7 @@ defmodule AshSql.Join do
16331680
path,
16341681
kind,
16351682
source,
1636-
filter,
1683+
aggregates_by_path,
16371684
sort?,
16381685
apply_filter,
16391686
used_parent_aggregates,
@@ -1650,7 +1697,7 @@ defmodule AshSql.Join do
16501697

16511698
binding_data = %{type: kind, path: full_path, source: source}
16521699

1653-
used_aggregates = Ash.Filter.used_aggregates(filter, full_path)
1700+
used_aggregates = Map.get(aggregates_by_path, full_path, [])
16541701

16551702
binding_kinds =
16561703
case kind do

0 commit comments

Comments
 (0)