From 0b7817e420eb8e345c6d78d7731459514bba0599 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Thu, 4 Mar 2021 22:50:12 -0500 Subject: [PATCH] fix: add back in inner join detection logic --- lib/data_layer.ex | 130 ++++++++++++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/lib/data_layer.ex b/lib/data_layer.ex index de28bb9..ba516ff 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -661,68 +661,96 @@ defmodule AshPostgres.DataLayer do }) end + @known_inner_join_operators [ + Eq, + GreaterThan, + GreaterThanOrEqual, + In, + IsNil, + LessThanOrEqual, + LessThan, + NotEq + ] + |> Enum.map(&Module.concat(Ash.Query.Operator, &1)) + + @known_inner_join_functions [ + Ago, + Contains, + IsNil + ] + |> Enum.map(&Module.concat(Ash.Query.Function, &1)) + + @known_inner_join_predicates @known_inner_join_functions ++ @known_inner_join_operators + # For consistency's sake, this logic was removed. # We can revisit it sometime though. - defp can_inner_join?(_, _), do: false - # defp can_inner_join?(path, expr, seen_an_or? \\ false) + 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, %{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, 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: :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?(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?( - # path, - # %Not{expression: %BooleanExpression{op: :or, left: left, right: right}}, - # seen_an_or? - # ) do - # can_inner_join?( - # path, - # %BooleanExpression{ - # op: :and, - # left: %Not{expression: left}, - # right: %Not{expression: right} - # }, - # # We count `not` as having seen an `or` to be conservative. - # # The reason for this is that `id = 1 or not(id = 1)` - # true - # ) - # end + defp can_inner_join?( + path, + %Not{expression: %BooleanExpression{op: :or, left: left, right: right}}, + seen_an_or? + ) do + can_inner_join?( + path, + %BooleanExpression{ + op: :and, + left: %Not{expression: left}, + right: %Not{expression: right} + }, + seen_an_or? + ) + end - # defp can_inner_join?(path, %Not{expression: expression}, seen_an_or?) do - # can_inner_join?(path, expression, seen_an_or?) - # end + defp can_inner_join?(path, %Not{expression: expression}, seen_an_or?) do + can_inner_join?(path, expression, seen_an_or?) + end - # defp can_inner_join?( - # search_path, - # %{__operator__?: true, left: %Ref{relationship_path: relationship_path}}, - # seen_an_or? - # ) - # when search_path == relationship_path do - # not seen_an_or? - # 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, - # %{__function__?: true, arguments: arguments}, - # seen_an_or? - # ) 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?( + 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?(_, _, _), do: true + 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 @impl true def add_aggregate(query, aggregate, _resource) do