From 52302046e1b7643bf4c360ae640636db83efa5fe Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 24 Oct 2023 12:25:23 -0400 Subject: [PATCH] fix: properly join to related references in relationship filters --- lib/join.ex | 63 ++++++++++++++++++++++++++++++---- mix.lock | 2 +- test/filter_test.exs | 6 ++++ test/support/resources/post.ex | 4 +++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/lib/join.ex b/lib/join.ex index 525915ef..dda3ce05 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -46,12 +46,12 @@ defmodule AshPostgres.Join do other end) |> Ash.Filter.relationship_paths() - |> to_joins(filter) + |> to_joins(filter, query.__ash_bindings__.resource) true -> filter |> Ash.Filter.relationship_paths() - |> to_joins(filter) + |> to_joins(filter, query.__ash_bindings__.resource) end Enum.reduce_while(relationship_paths, {:ok, query}, fn @@ -128,25 +128,45 @@ defmodule AshPostgres.Join do end) end - defp to_joins(paths, filter) do + defp to_joins(paths, filter, resource) do paths |> Enum.map(fn path -> if can_inner_join?(path, filter) do {:inner, AshPostgres.Join.relationship_path_to_relationships( - filter.resource, + resource, path )} else {:left, AshPostgres.Join.relationship_path_to_relationships( - filter.resource, + resource, path )} end end) end + # defp expand_join_paths(joins) do + # Enum.flat_map(joins, fn {type, path} -> + # path + # |> sub_paths() + # |> Enum.map(&add_relationship_filter_paths/1) + # end) + # end + + # defp add_relationship_filter_paths(path) do + # last = List.last(path) + # prefix = :lists.droplast(path) + + # end + + # defp sub_paths(path) do + # Enum.map(1..Enum.count(path), fn i -> + # Enum.take(path, i) + # end) + # end + def relationship_path_to_relationships(resource, path, acc \\ []) def relationship_path_to_relationships(_resource, [], acc), do: Enum.reverse(acc) @@ -304,7 +324,6 @@ defmodule AshPostgres.Join do AshPostgres.Expr.dynamic_expr(query, filter, bindings, true) end - {:ok, query} = join_all_relationships(query, filter) from(row in query, where: ^dynamic) end @@ -609,6 +628,22 @@ defmodule AshPostgres.Join do }) |> AshPostgres.DataLayer.add_binding(binding_data) + {:ok, related_filter} = + Ash.Filter.hydrate_refs( + relationship.filter, + %{ + resource: relationship.destination, + aggregates: %{}, + calculations: %{}, + public?: false + } + ) + + related_filter = + Ash.Filter.move_to_relationship_path(related_filter, full_path) + + {:ok, query} = join_all_relationships(query, related_filter) + used_calculations = Ash.Filter.used_calculations( filter, @@ -745,6 +780,22 @@ defmodule AshPostgres.Join do query = AshPostgres.DataLayer.add_binding(query, binding_data) + {:ok, related_filter} = + Ash.Filter.hydrate_refs( + relationship.filter, + %{ + resource: relationship.destination, + aggregates: %{}, + calculations: %{}, + public?: false + } + ) + + related_filter = + Ash.Filter.move_to_relationship_path(related_filter, full_path) + + {:ok, query} = join_all_relationships(query, related_filter) + used_calculations = Ash.Filter.used_calculations( filter, diff --git a/mix.lock b/mix.lock index 89d0eb0f..d15eae8a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "2.15.18", "abc793ce246cde5b68a881a4fb74237498f375dd146bb997a03bd91b96e1900f", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cb33816af2b924db832ee36aefc7d17019bd822793ece44f7356780496a72dd7"}, + "ash": {:hex, :ash, "2.15.20", "0702181ca817ab1cad5e3ccb53851c56a9c8ec0a010780ad7eb881a997151e38", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.47 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efa3afe081ed22f1c7d6dc45de13b8b80f8ab831ebaa255108aad11cacd23b13"}, "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, diff --git a/test/filter_test.exs b/test/filter_test.exs index 64f62898..649f4555 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -969,4 +969,10 @@ defmodule AshPostgres.FilterTest do |> Api.read!() end end + + test "can reference related items from a relationship expression" do + Post + |> Ash.Query.filter(comments_with_high_rating.title == "foo") + |> Api.read!() + end end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 6be836d3..4f3cc66f 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -127,6 +127,10 @@ defmodule AshPostgres.Test.Post do manual(AshPostgres.Test.Post.CommentsContainingTitle) end + has_many :comments_with_high_rating, AshPostgres.Test.Comment do + filter(expr(ratings.score > 5)) + end + has_many(:ratings, AshPostgres.Test.Rating, destination_attribute: :resource_id, relationship_context: %{data_layer: %{table: "post_ratings"}}