2021-12-21 16:19:24 +13:00
|
|
|
defmodule AshPostgres.Join do
|
|
|
|
@moduledoc false
|
|
|
|
import Ecto.Query, only: [from: 2, subquery: 1]
|
|
|
|
|
|
|
|
alias Ash.Query.{BooleanExpression, Not, Ref}
|
|
|
|
|
|
|
|
@known_inner_join_operators [
|
|
|
|
Eq,
|
|
|
|
GreaterThan,
|
|
|
|
GreaterThanOrEqual,
|
|
|
|
In,
|
|
|
|
LessThanOrEqual,
|
|
|
|
LessThan,
|
|
|
|
NotEq
|
|
|
|
]
|
|
|
|
|> Enum.map(&Module.concat(Ash.Query.Operator, &1))
|
|
|
|
|
|
|
|
@known_inner_join_functions [
|
|
|
|
Ago,
|
|
|
|
Contains
|
|
|
|
]
|
|
|
|
|> Enum.map(&Module.concat(Ash.Query.Function, &1))
|
|
|
|
|
|
|
|
@known_inner_join_predicates @known_inner_join_functions ++ @known_inner_join_operators
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
def join_all_relationships(
|
|
|
|
query,
|
|
|
|
filter,
|
|
|
|
relationship_paths \\ nil,
|
|
|
|
path \\ [],
|
|
|
|
source \\ nil,
|
|
|
|
use_root_query_bindings? \\ false
|
|
|
|
) do
|
2021-12-21 16:19:24 +13:00
|
|
|
relationship_paths =
|
|
|
|
relationship_paths ||
|
|
|
|
filter
|
|
|
|
|> Ash.Filter.relationship_paths()
|
|
|
|
|> Enum.map(fn path ->
|
|
|
|
if can_inner_join?(path, filter) do
|
|
|
|
{:inner, AshPostgres.Join.relationship_path_to_relationships(filter.resource, path)}
|
|
|
|
else
|
|
|
|
{:left, AshPostgres.Join.relationship_path_to_relationships(filter.resource, path)}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
Enum.reduce_while(relationship_paths, {:ok, query}, fn
|
|
|
|
{_join_type, []}, {:ok, query} ->
|
|
|
|
{:cont, {:ok, query}}
|
|
|
|
|
|
|
|
{join_type, [relationship | rest_rels]}, {:ok, query} ->
|
|
|
|
source = source || relationship.source
|
|
|
|
|
|
|
|
current_path = path ++ [relationship]
|
|
|
|
|
|
|
|
current_join_type =
|
|
|
|
case join_type do
|
|
|
|
{:aggregate, _name, _agg} when rest_rels != [] ->
|
|
|
|
:left
|
|
|
|
|
|
|
|
other ->
|
|
|
|
other
|
|
|
|
end
|
|
|
|
|
|
|
|
if has_binding?(source, Enum.reverse(current_path), query, current_join_type) do
|
|
|
|
{:cont, {:ok, query}}
|
|
|
|
else
|
|
|
|
case join_relationship(
|
|
|
|
query,
|
|
|
|
relationship,
|
|
|
|
Enum.map(path, & &1.name),
|
|
|
|
current_join_type,
|
|
|
|
source,
|
2022-02-12 10:06:51 +13:00
|
|
|
filter,
|
|
|
|
use_root_query_bindings?
|
2021-12-21 16:19:24 +13:00
|
|
|
) do
|
|
|
|
{:ok, joined_query} ->
|
2022-02-15 11:44:17 +13:00
|
|
|
joined_query_with_distinct =
|
|
|
|
if use_root_query_bindings? do
|
|
|
|
joined_query
|
|
|
|
else
|
|
|
|
add_distinct(relationship, join_type, joined_query)
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
|
|
|
case join_all_relationships(
|
|
|
|
joined_query_with_distinct,
|
|
|
|
filter,
|
|
|
|
[{join_type, rest_rels}],
|
|
|
|
current_path,
|
2022-02-12 10:06:51 +13:00
|
|
|
source,
|
|
|
|
use_root_query_bindings?
|
2021-12-21 16:19:24 +13:00
|
|
|
) do
|
|
|
|
{:ok, query} ->
|
|
|
|
{:cont, {:ok, query}}
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:halt, {:error, error}}
|
|
|
|
end
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:halt, {:error, error}}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
def relationship_path_to_relationships(resource, path, acc \\ [])
|
|
|
|
def relationship_path_to_relationships(_resource, [], acc), do: Enum.reverse(acc)
|
|
|
|
|
|
|
|
def relationship_path_to_relationships(resource, [relationship | rest], acc) do
|
|
|
|
relationship = Ash.Resource.Info.relationship(resource, relationship)
|
|
|
|
|
|
|
|
relationship_path_to_relationships(relationship.destination, rest, [relationship | acc])
|
|
|
|
end
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
def maybe_get_resource_query(
|
|
|
|
resource,
|
|
|
|
relationship,
|
|
|
|
root_query,
|
|
|
|
path \\ [],
|
|
|
|
use_root_query_bindings? \\ false
|
|
|
|
) do
|
2021-12-21 16:19:24 +13:00
|
|
|
resource
|
2022-02-12 10:06:51 +13:00
|
|
|
|> Ash.Query.new(nil, base_filter?: false)
|
|
|
|
|> Ash.Query.set_context(root_query.__ash_bindings__.context)
|
2021-12-21 16:19:24 +13:00
|
|
|
|> Ash.Query.set_context(relationship.context)
|
|
|
|
|> case do
|
|
|
|
%{valid?: true} = query ->
|
2022-02-12 10:06:51 +13:00
|
|
|
ash_query = query
|
|
|
|
|
2021-12-21 16:19:24 +13:00
|
|
|
initial_query = %{
|
|
|
|
AshPostgres.DataLayer.resource_to_query(resource, nil)
|
|
|
|
| prefix: Map.get(root_query, :prefix)
|
|
|
|
}
|
|
|
|
|
2022-02-10 05:49:19 +13:00
|
|
|
case Ash.Query.data_layer_query(query,
|
|
|
|
initial_query: initial_query
|
|
|
|
) do
|
|
|
|
{:ok, query} ->
|
2022-02-15 11:44:17 +13:00
|
|
|
query =
|
|
|
|
query
|
|
|
|
|> do_base_filter(
|
|
|
|
root_query,
|
|
|
|
ash_query,
|
|
|
|
resource,
|
|
|
|
path,
|
|
|
|
use_root_query_bindings?
|
|
|
|
)
|
|
|
|
|> do_relationship_filter(
|
|
|
|
relationship.filter,
|
|
|
|
root_query,
|
|
|
|
ash_query,
|
|
|
|
resource,
|
|
|
|
path,
|
|
|
|
use_root_query_bindings?
|
|
|
|
)
|
|
|
|
|
|
|
|
{:ok, query}
|
2022-02-10 05:49:19 +13:00
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
|
|
|
query ->
|
|
|
|
{:error, query}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-02-15 11:44:17 +13:00
|
|
|
defp do_relationship_filter(query, nil, _, _, _, _, _), do: query
|
|
|
|
|
|
|
|
defp do_relationship_filter(
|
|
|
|
query,
|
|
|
|
relationship_filter,
|
|
|
|
root_query,
|
|
|
|
ash_query,
|
|
|
|
resource,
|
|
|
|
path,
|
|
|
|
use_root_query_bindings?
|
|
|
|
) do
|
|
|
|
filter =
|
|
|
|
resource
|
|
|
|
|> Ash.Filter.parse!(
|
|
|
|
relationship_filter,
|
|
|
|
ash_query.aggregates,
|
|
|
|
ash_query.calculations,
|
|
|
|
ash_query.context
|
|
|
|
)
|
|
|
|
|
|
|
|
dynamic =
|
2022-05-22 18:12:07 +12:00
|
|
|
AshPostgres.Expr.dynamic_expr(
|
|
|
|
root_query,
|
|
|
|
Ash.Filter.move_to_relationship_path(filter, path),
|
|
|
|
root_query.__ash_bindings__,
|
|
|
|
true
|
|
|
|
)
|
2022-02-15 11:44:17 +13:00
|
|
|
|
|
|
|
{:ok, query} = join_all_relationships(query, filter, nil, [], nil, use_root_query_bindings?)
|
|
|
|
from(row in query, where: ^dynamic)
|
|
|
|
end
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
defp do_base_filter(query, root_query, ash_query, resource, path, use_root_query_bindings?) do
|
|
|
|
case Ash.Resource.Info.base_filter(resource) do
|
|
|
|
nil ->
|
|
|
|
query
|
|
|
|
|
|
|
|
filter ->
|
|
|
|
filter =
|
|
|
|
resource
|
|
|
|
|> Ash.Filter.parse!(
|
|
|
|
filter,
|
|
|
|
ash_query.aggregates,
|
|
|
|
ash_query.calculations,
|
|
|
|
ash_query.context
|
|
|
|
)
|
|
|
|
|
|
|
|
dynamic =
|
|
|
|
if use_root_query_bindings? do
|
|
|
|
filter = Ash.Filter.move_to_relationship_path(filter, path)
|
|
|
|
|
|
|
|
AshPostgres.Expr.dynamic_expr(root_query, filter, root_query.__ash_bindings__, true)
|
|
|
|
else
|
|
|
|
AshPostgres.Expr.dynamic_expr(query, filter, query.__ash_bindings__, true)
|
|
|
|
end
|
|
|
|
|
|
|
|
from(row in query, where: ^dynamic)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-12-21 16:19:24 +13:00
|
|
|
def set_join_prefix(join_query, query, resource) do
|
|
|
|
if Ash.Resource.Info.multitenancy_strategy(resource) == :context do
|
2022-05-23 10:30:20 +12:00
|
|
|
%{join_query | prefix: query.prefix || AshPostgres.schema(resource) || "public"}
|
2021-12-21 16:19:24 +13:00
|
|
|
else
|
|
|
|
%{
|
|
|
|
join_query
|
2022-05-23 10:30:20 +12:00
|
|
|
| prefix:
|
|
|
|
AshPostgres.schema(resource) || AshPostgres.repo(resource).config()[:default_prefix] ||
|
|
|
|
"public"
|
2021-12-21 16:19:24 +13:00
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(path, expr, seen_an_or? \\ false)
|
|
|
|
|
|
|
|
defp can_inner_join?(path, %{expression: expr}, seen_an_or?),
|
|
|
|
do: can_inner_join?(path, expr, seen_an_or?)
|
|
|
|
|
|
|
|
defp can_inner_join?(_path, expr, _seen_an_or?) when expr in [nil, true, false], do: true
|
|
|
|
|
|
|
|
defp can_inner_join?(path, %BooleanExpression{op: :and, left: left, right: right}, seen_an_or?) do
|
|
|
|
can_inner_join?(path, left, seen_an_or?) || can_inner_join?(path, right, seen_an_or?)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(path, %BooleanExpression{op: :or, left: left, right: right}, _) do
|
|
|
|
can_inner_join?(path, left, true) && can_inner_join?(path, right, true)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(
|
|
|
|
_,
|
|
|
|
%Not{},
|
|
|
|
_
|
|
|
|
) do
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(
|
|
|
|
search_path,
|
|
|
|
%struct{__operator__?: true, left: %Ref{relationship_path: relationship_path}},
|
|
|
|
seen_an_or?
|
|
|
|
)
|
|
|
|
when search_path == relationship_path and struct in @known_inner_join_predicates do
|
|
|
|
not seen_an_or?
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(
|
|
|
|
search_path,
|
|
|
|
%struct{__operator__?: true, right: %Ref{relationship_path: relationship_path}},
|
|
|
|
seen_an_or?
|
|
|
|
)
|
|
|
|
when search_path == relationship_path and struct in @known_inner_join_predicates do
|
|
|
|
not seen_an_or?
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(
|
|
|
|
search_path,
|
|
|
|
%struct{__function__?: true, arguments: arguments},
|
|
|
|
seen_an_or?
|
|
|
|
)
|
|
|
|
when struct in @known_inner_join_predicates do
|
|
|
|
if Enum.any?(arguments, &match?(%Ref{relationship_path: ^search_path}, &1)) do
|
|
|
|
not seen_an_or?
|
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp can_inner_join?(_, _, _), do: false
|
|
|
|
|
|
|
|
defp has_binding?(resource, path, query, {:aggregate, _, _}),
|
|
|
|
do: has_binding?(resource, path, query, :aggregate)
|
|
|
|
|
|
|
|
defp has_binding?(resource, candidate_path, %{__ash_bindings__: _} = query, type) do
|
|
|
|
Enum.any?(query.__ash_bindings__.bindings, fn
|
|
|
|
{_, %{path: path, source: source, type: ^type}} ->
|
|
|
|
Ash.SatSolver.synonymous_relationship_paths?(resource, path, candidate_path, source)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
false
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp has_binding?(_, _, _, _), do: false
|
|
|
|
|
|
|
|
defp add_distinct(relationship, join_type, joined_query) do
|
|
|
|
if relationship.cardinality == :many and join_type == :left && !joined_query.distinct do
|
|
|
|
from(row in joined_query,
|
|
|
|
distinct: ^Ash.Resource.Info.primary_key(relationship.destination)
|
|
|
|
)
|
|
|
|
else
|
|
|
|
joined_query
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
defp join_relationship(
|
|
|
|
query,
|
|
|
|
relationship,
|
|
|
|
path,
|
|
|
|
join_type,
|
|
|
|
source,
|
|
|
|
filter,
|
|
|
|
use_root_query_bindings?
|
|
|
|
) do
|
2021-12-21 16:19:24 +13:00
|
|
|
case Map.get(query.__ash_bindings__.bindings, path) do
|
|
|
|
%{type: existing_join_type} when join_type != existing_join_type ->
|
|
|
|
raise "unreachable?"
|
|
|
|
|
|
|
|
nil ->
|
2022-02-12 10:06:51 +13:00
|
|
|
do_join_relationship(
|
|
|
|
query,
|
|
|
|
relationship,
|
|
|
|
path,
|
|
|
|
join_type,
|
|
|
|
source,
|
|
|
|
filter,
|
|
|
|
use_root_query_bindings?
|
|
|
|
)
|
2021-12-21 16:19:24 +13:00
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:ok, query}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp do_join_relationship(
|
|
|
|
query,
|
|
|
|
%{type: :many_to_many} = relationship,
|
|
|
|
path,
|
|
|
|
kind,
|
|
|
|
source,
|
2022-02-12 10:06:51 +13:00
|
|
|
filter,
|
|
|
|
use_root_query_bindings?
|
2021-12-21 16:19:24 +13:00
|
|
|
) do
|
|
|
|
join_relationship = Ash.Resource.Info.relationship(source, relationship.join_relationship)
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
join_path =
|
|
|
|
Enum.reverse([
|
|
|
|
String.to_existing_atom(to_string(relationship.name) <> "_join_assoc") | path
|
|
|
|
])
|
|
|
|
|
|
|
|
full_path = Enum.reverse([relationship.name | path])
|
|
|
|
|
|
|
|
initial_ash_bindings = query.__ash_bindings__
|
|
|
|
|
|
|
|
binding_data =
|
|
|
|
case kind do
|
|
|
|
{:aggregate, name, _agg} ->
|
|
|
|
%{type: :aggregate, name: name, path: full_path, source: source}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
%{type: kind, path: full_path, source: source}
|
|
|
|
end
|
|
|
|
|
|
|
|
additional_binding? =
|
|
|
|
case kind do
|
|
|
|
{:aggregate, _, _subquery} ->
|
|
|
|
false
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
query =
|
|
|
|
case kind do
|
|
|
|
{:aggregate, _, _subquery} ->
|
|
|
|
additional_bindings =
|
|
|
|
if additional_binding? do
|
|
|
|
1
|
|
|
|
else
|
|
|
|
0
|
|
|
|
end
|
|
|
|
|
|
|
|
query
|
|
|
|
|> AshPostgres.DataLayer.add_binding(binding_data, additional_bindings)
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
query
|
|
|
|
|> AshPostgres.DataLayer.add_binding(%{
|
|
|
|
path: join_path,
|
|
|
|
type: :left,
|
|
|
|
source: source
|
|
|
|
})
|
|
|
|
|> AshPostgres.DataLayer.add_binding(binding_data)
|
|
|
|
end
|
|
|
|
|
2021-12-21 16:19:24 +13:00
|
|
|
with {:ok, relationship_through} <-
|
2022-02-12 10:06:51 +13:00
|
|
|
maybe_get_resource_query(
|
|
|
|
relationship.through,
|
|
|
|
join_relationship,
|
|
|
|
query,
|
|
|
|
join_path,
|
|
|
|
use_root_query_bindings?
|
|
|
|
),
|
2021-12-21 16:19:24 +13:00
|
|
|
{:ok, relationship_destination} <-
|
2022-02-12 10:06:51 +13:00
|
|
|
maybe_get_resource_query(
|
|
|
|
relationship.destination,
|
|
|
|
relationship,
|
|
|
|
query,
|
|
|
|
use_root_query_bindings?
|
|
|
|
) do
|
2021-12-21 16:19:24 +13:00
|
|
|
relationship_through =
|
|
|
|
relationship_through
|
|
|
|
|> Ecto.Queryable.to_query()
|
|
|
|
|> set_join_prefix(query, relationship.through)
|
|
|
|
|
|
|
|
relationship_destination =
|
|
|
|
relationship_destination
|
|
|
|
|> Ecto.Queryable.to_query()
|
|
|
|
|> set_join_prefix(query, relationship.destination)
|
|
|
|
|
|
|
|
binding_kind =
|
|
|
|
case kind do
|
|
|
|
{:aggregate, _, _} ->
|
|
|
|
:left
|
|
|
|
|
|
|
|
other ->
|
|
|
|
other
|
|
|
|
end
|
|
|
|
|
|
|
|
current_binding =
|
2022-02-12 10:06:51 +13:00
|
|
|
Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} ->
|
2021-12-21 16:19:24 +13:00
|
|
|
if data.type == binding_kind && data.path == Enum.reverse(path) do
|
|
|
|
binding
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
used_calculations =
|
|
|
|
Ash.Filter.used_calculations(
|
|
|
|
filter,
|
|
|
|
relationship.destination,
|
2022-02-12 10:06:51 +13:00
|
|
|
full_path
|
2021-12-21 16:19:24 +13:00
|
|
|
)
|
|
|
|
|
|
|
|
used_aggregates =
|
2022-02-12 10:06:51 +13:00
|
|
|
filter
|
|
|
|
|> AshPostgres.Aggregate.used_aggregates(relationship, used_calculations, full_path)
|
|
|
|
|> Enum.map(fn aggregate ->
|
|
|
|
%{aggregate | load: aggregate.name}
|
|
|
|
end)
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
relationship_destination
|
|
|
|
|> AshPostgres.Aggregate.add_aggregates(used_aggregates, relationship.destination)
|
2021-12-21 16:19:24 +13:00
|
|
|
|> case do
|
|
|
|
{:ok, relationship_destination} ->
|
|
|
|
relationship_destination =
|
|
|
|
case used_aggregates do
|
|
|
|
[] ->
|
|
|
|
relationship_destination
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
subquery(relationship_destination)
|
|
|
|
end
|
|
|
|
|
|
|
|
case kind do
|
2022-02-12 10:06:51 +13:00
|
|
|
{:aggregate, _, subquery} ->
|
|
|
|
subquery =
|
|
|
|
AshPostgres.Aggregate.agg_subquery_for_lateral_join(
|
|
|
|
current_binding,
|
|
|
|
query,
|
|
|
|
subquery,
|
|
|
|
relationship
|
|
|
|
)
|
|
|
|
|
|
|
|
{:ok,
|
|
|
|
from([{row, current_binding}] in query,
|
|
|
|
left_lateral_join: through in ^subquery,
|
|
|
|
as: ^initial_ash_bindings.current
|
|
|
|
)}
|
2022-01-25 11:59:31 +13:00
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
:inner ->
|
2021-12-21 16:19:24 +13:00
|
|
|
{:ok,
|
2022-02-12 10:06:51 +13:00
|
|
|
from([{row, current_binding}] in query,
|
|
|
|
join: through in ^relationship_through,
|
|
|
|
as: ^initial_ash_bindings.current,
|
|
|
|
on:
|
|
|
|
field(row, ^relationship.source_field) ==
|
|
|
|
field(through, ^relationship.source_field_on_join_table),
|
|
|
|
join: destination in ^relationship_destination,
|
2022-02-19 16:30:12 +13:00
|
|
|
as: ^(initial_ash_bindings.current + 1),
|
2022-02-12 10:06:51 +13:00
|
|
|
on:
|
|
|
|
field(destination, ^relationship.destination_field) ==
|
|
|
|
field(through, ^relationship.destination_field_on_join_table)
|
|
|
|
)}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:ok,
|
2022-02-12 10:06:51 +13:00
|
|
|
from([{row, current_binding}] in query,
|
|
|
|
left_join: through in ^relationship_through,
|
|
|
|
as: ^initial_ash_bindings.current,
|
|
|
|
on:
|
|
|
|
field(row, ^relationship.source_field) ==
|
|
|
|
field(through, ^relationship.source_field_on_join_table),
|
|
|
|
left_join: destination in ^relationship_destination,
|
|
|
|
as: ^(initial_ash_bindings.current + 1),
|
|
|
|
on:
|
|
|
|
field(destination, ^relationship.destination_field) ==
|
|
|
|
field(through, ^relationship.destination_field_on_join_table)
|
|
|
|
)}
|
2021-12-21 16:19:24 +13:00
|
|
|
end
|
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
defp do_join_relationship(
|
|
|
|
query,
|
|
|
|
relationship,
|
|
|
|
path,
|
|
|
|
kind,
|
|
|
|
source,
|
|
|
|
filter,
|
|
|
|
use_root_query_bindings?
|
|
|
|
) do
|
|
|
|
full_path = Enum.reverse([relationship.name | path])
|
|
|
|
initial_ash_bindings = query.__ash_bindings__
|
|
|
|
|
|
|
|
binding_data =
|
|
|
|
case kind do
|
|
|
|
{:aggregate, name, _agg} ->
|
|
|
|
%{type: :aggregate, name: name, path: full_path, source: source}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
%{type: kind, path: full_path, source: source}
|
|
|
|
end
|
|
|
|
|
|
|
|
query = AshPostgres.DataLayer.add_binding(query, binding_data)
|
|
|
|
|
|
|
|
case maybe_get_resource_query(
|
|
|
|
relationship.destination,
|
|
|
|
relationship,
|
|
|
|
query,
|
|
|
|
full_path,
|
|
|
|
use_root_query_bindings?
|
|
|
|
) do
|
2021-12-21 16:19:24 +13:00
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
|
|
|
|
|
|
|
{:ok, relationship_destination} ->
|
|
|
|
relationship_destination =
|
|
|
|
relationship_destination
|
|
|
|
|> Ecto.Queryable.to_query()
|
|
|
|
|> set_join_prefix(query, relationship.destination)
|
|
|
|
|
|
|
|
binding_kind =
|
|
|
|
case kind do
|
|
|
|
{:aggregate, _, _} ->
|
|
|
|
:left
|
|
|
|
|
|
|
|
other ->
|
|
|
|
other
|
|
|
|
end
|
|
|
|
|
|
|
|
current_binding =
|
2022-02-12 10:06:51 +13:00
|
|
|
Enum.find_value(initial_ash_bindings.bindings, 0, fn {binding, data} ->
|
2021-12-21 16:19:24 +13:00
|
|
|
if data.type == binding_kind && data.path == Enum.reverse(path) do
|
|
|
|
binding
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
used_calculations =
|
|
|
|
Ash.Filter.used_calculations(
|
|
|
|
filter,
|
|
|
|
relationship.destination,
|
2022-02-12 10:06:51 +13:00
|
|
|
full_path
|
2021-12-21 16:19:24 +13:00
|
|
|
)
|
|
|
|
|
|
|
|
used_aggregates =
|
2022-02-12 10:06:51 +13:00
|
|
|
filter
|
|
|
|
|> AshPostgres.Aggregate.used_aggregates(relationship, used_calculations, full_path)
|
|
|
|
|> Enum.map(fn aggregate ->
|
|
|
|
%{aggregate | load: aggregate.name}
|
|
|
|
end)
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
relationship_destination
|
|
|
|
|> AshPostgres.Aggregate.add_aggregates(used_aggregates, relationship.destination)
|
2021-12-21 16:19:24 +13:00
|
|
|
|> case do
|
|
|
|
{:ok, relationship_destination} ->
|
|
|
|
relationship_destination =
|
|
|
|
case used_aggregates do
|
|
|
|
[] ->
|
|
|
|
relationship_destination
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
subquery(relationship_destination)
|
|
|
|
end
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
case kind do
|
|
|
|
{:aggregate, _, subquery} ->
|
|
|
|
subquery =
|
|
|
|
AshPostgres.Aggregate.agg_subquery_for_lateral_join(
|
|
|
|
current_binding,
|
|
|
|
query,
|
|
|
|
subquery,
|
|
|
|
relationship
|
2021-12-21 16:19:24 +13:00
|
|
|
)
|
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
{:ok,
|
|
|
|
from([{row, current_binding}] in query,
|
|
|
|
left_lateral_join: destination in ^subquery,
|
|
|
|
as: ^initial_ash_bindings.current
|
|
|
|
)}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
:inner ->
|
|
|
|
{:ok,
|
|
|
|
from([{row, current_binding}] in query,
|
|
|
|
join: destination in ^relationship_destination,
|
|
|
|
as: ^initial_ash_bindings.current,
|
|
|
|
on:
|
|
|
|
field(row, ^relationship.source_field) ==
|
|
|
|
field(destination, ^relationship.destination_field)
|
|
|
|
)}
|
2021-12-21 16:19:24 +13:00
|
|
|
|
2022-02-12 10:06:51 +13:00
|
|
|
_ ->
|
|
|
|
{:ok,
|
|
|
|
from([{row, current_binding}] in query,
|
|
|
|
left_join: destination in ^relationship_destination,
|
|
|
|
as: ^initial_ash_bindings.current,
|
|
|
|
on:
|
|
|
|
field(row, ^relationship.source_field) ==
|
|
|
|
field(destination, ^relationship.destination_field)
|
|
|
|
)}
|
|
|
|
end
|
2021-12-21 16:19:24 +13:00
|
|
|
|
|
|
|
{:error, error} ->
|
|
|
|
{:error, error}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|