diff --git a/config/config.exs b/config/config.exs index 6861abc5..2a17cfd8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -25,6 +25,8 @@ if Mix.env() == :test do config :ash_postgres, :ash_domains, [AshPostgres.Test.Domain] + config :ash, :custom_expressions, [AshPostgres.Expressions.TrigramWordSimilarity] + config :ash_postgres, AshPostgres.TestRepo, username: "postgres", database: "ash_postgres_test", diff --git a/mix.lock b/mix.lock index 73c0f0c5..b6cfe550 100644 --- a/mix.lock +++ b/mix.lock @@ -20,7 +20,7 @@ "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.3", "38c6e381b8281b86e2911fa39bea4eab2d171c86d7428786566891efb73b68c3", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a81cb6c6a2a026a4d48cb9a2e1dfca203f9283a3a70aa0c7bc171970c44f23f8"}, "glob_ex": {:hex, :glob_ex, "0.1.10", "d819a368637495a5c1962ef34f48fe4e9a09032410b96ade5758f2cd1cc5fcde", [:mix], [], "hexpm", "c75357e57d71c85ef8ef7269b6e787dce3f0ff71e585f79a90e4d5477c532b90"}, - "igniter": {:hex, :igniter, "0.3.76", "ff283416402f4d1ef3f79ab57d38aac08389b3768fc81da03795ce5347f1167f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "4692f874f969dc4856167469a3415a5a57a362314f37e1cdb14431316a74e896"}, + "igniter": {:hex, :igniter, "0.4.1", "74a6a376340755120a4c48949014ac09e95d1ffd918e73aea25fe8a4e405243e", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c25a41acb4bb719ef1f0f847e9b999c995e3edf85ec631a404d5e035fea9c335"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, @@ -36,7 +36,7 @@ "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, - "sourceror": {:hex, :sourceror, "1.7.0", "62c34f4e3a109d837edd652730219b6332745e0ec7db34d5d350a5cdf30b376a", [:mix], [], "hexpm", "3dd2b1bd780fd0df48089f48480a54fd065bf815b63ef8046219d7784e7435f3"}, + "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, "spark": {:hex, :spark, "2.2.35", "1c0bb30f340151eca24164885935de39e6ada4010555f444c813d0488990f8f3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f242d6385c287389034a0e146d8f025b5c9ab777f1ae5cf0fdfc9209db6ae748"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "splode": {:hex, :splode, "0.2.7", "ed042fa9bd8fe7b66dd0a0faabdb97352058420d90cd1c7c1537f609deb7ef6d", [:mix], [], "hexpm", "267f1f51d5a5ac988cda0649498294844988c5086916fed5a8aff297d69a2059"}, diff --git a/test/custom_expression_test.exs b/test/custom_expression_test.exs new file mode 100644 index 00000000..e88737ad --- /dev/null +++ b/test/custom_expression_test.exs @@ -0,0 +1,17 @@ +defmodule AshPostgres.Test.CustomExpressionTest do + use AshPostgres.RepoCase, async: false + + test "unique constraint errors are properly caught" do + Ash.create!(AshPostgres.Test.Profile, %{description: "foo"}) + + assert [_] = + AshPostgres.Test.Profile + |> Ash.Query.for_read(:by_indirectly_matching_description, %{term: "fop"}) + |> Ash.read!() + + assert [_] = + AshPostgres.Test.Profile + |> Ash.Query.for_read(:by_directly_matching_description, %{term: "fop"}) + |> Ash.read!() + end +end diff --git a/test/load_test.exs b/test/load_test.exs index dd9b0e9d..9261e084 100644 --- a/test/load_test.exs +++ b/test/load_test.exs @@ -1,6 +1,16 @@ defmodule AshPostgres.Test.LoadTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Author, Comment, Post, Record, StatefulPostFollower, TempEntity, User} + + alias AshPostgres.Test.{ + Author, + Comment, + Post, + PostFollower, + Record, + StatefulPostFollower, + TempEntity, + User + } require Ash.Query @@ -130,6 +140,32 @@ defmodule AshPostgres.Test.LoadTest do assert length(post.active_followers) == 2 end + test "many_to_many loads work with filter on the join relationship via the parent" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "a"}) + |> Ash.create!() + + for i <- 1..2 do + user = + User + |> Ash.Changeset.for_create(:create, %{name: "user#{i}", is_active: true}) + |> Ash.create!() + + PostFollower + |> Ash.Changeset.for_create(:create, %{order: i, post_id: post.id, follower_id: user.id}) + |> Ash.create!() + end + + [post] = + Post + |> Ash.Query.for_read(:read, %{}) + |> Ash.Query.load(:first_3_followers) + |> Ash.read!() + + assert length(post.first_3_followers) == 2 + end + test "many_to_many loads work when nested" do source_post = Post diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 7d2764cb..db11f897 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -524,6 +524,15 @@ defmodule AshPostgres.Test.Post do destination_attribute_on_join_resource: :follower_id ) + many_to_many(:first_3_followers, AshPostgres.Test.User, + public?: true, + through: AshPostgres.Test.PostFollower, + join_relationship: :first_three_followers_assoc, + source_attribute_on_join_resource: :post_id, + destination_attribute_on_join_resource: :follower_id, + filter: expr(parent(first_three_followers_assoc.order) <= 3) + ) + many_to_many(:stateful_followers, AshPostgres.Test.User, public?: true, through: AshPostgres.Test.StatefulPostFollower, diff --git a/test/support/resources/profile.ex b/test/support/resources/profile.ex index 0455f9b9..862887c4 100644 --- a/test/support/resources/profile.ex +++ b/test/support/resources/profile.ex @@ -19,6 +19,30 @@ defmodule AshPostgres.Test.Profile do default_accept(:*) defaults([:create, :read, :update, :destroy]) + + read :by_indirectly_matching_description do + argument :term, :string do + allow_nil?(false) + end + + filter(expr(calc_word_similarity(term: ^arg(:term)) > 0.2)) + end + + read :by_directly_matching_description do + argument :term, :string do + allow_nil?(false) + end + + filter(expr(trigram_word_similarity(description, ^arg(:term)) > 0.2)) + end + end + + calculations do + calculate :calc_word_similarity, + :float, + expr(trigram_word_similarity(description, ^arg(:term))) do + argument(:term, :string, allow_nil?: false) + end end relationships do diff --git a/test/support/trigram_word_similarity.ex b/test/support/trigram_word_similarity.ex new file mode 100644 index 00000000..4b7d0bfb --- /dev/null +++ b/test/support/trigram_word_similarity.ex @@ -0,0 +1,14 @@ +defmodule AshPostgres.Expressions.TrigramWordSimilarity do + @moduledoc false + use Ash.CustomExpression, + name: :trigram_word_similarity, + arguments: [[:string, :string]], + # setting to true does not seem to change the behaviour observed in this ticket + predicate?: false + + def expression(data_layer, [left, right]) when data_layer in [AshPostgres.DataLayer] do + {:ok, expr(fragment("word_similarity(?, ?)", ^left, ^right))} + end + + def expression(_data_layer, _args), do: :unknown +end