From e2bd3c5aecc0bcd32399c1239e7f6df5c4a602a0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Mon, 7 Feb 2022 16:48:36 -0500 Subject: [PATCH] improvement: support access syntax --- .github/workflows/elixir.yml | 2 +- lib/expr.ex | 33 ++++++++++- .../migration_generator.ex | 2 +- mix.exs | 2 +- mix.lock | 5 +- .../test_repo/authors/20220207205146.json | 57 +++++++++++++++++++ .../20220207205146_migrate_resources2.exs | 21 +++++++ test/calculation_test.exs | 13 +++++ test/filter_test.exs | 39 ++++++++++++- test/support/resources/author.ex | 2 + test/support/resources/bio.ex | 9 +++ 11 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 priv/resource_snapshots/test_repo/authors/20220207205146.json create mode 100644 priv/test_repo/migrations/20220207205146_migrate_resources2.exs create mode 100644 test/support/resources/bio.ex diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 6673455..bbc3f63 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -18,7 +18,7 @@ jobs: matrix: otp: ["23"] elixir: ["1.11.0"] - ash: ["master", "1.50.16"] + ash: ["master", "1.50.19"] pg_version: ["9.6", "11"] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/lib/expr.ex b/lib/expr.ex index 2d8b8d5..e98aee7 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -4,7 +4,7 @@ defmodule AshPostgres.Expr do alias Ash.Filter alias Ash.Query.{BooleanExpression, Not, Ref} alias Ash.Query.Operator.IsNil - alias Ash.Query.Function.{Ago, Contains, If} + alias Ash.Query.Function.{Ago, Contains, GetPath, If} alias AshPostgres.Functions.{Fragment, TrigramSimilarity, Type} require Ecto.Query @@ -88,6 +88,33 @@ defmodule AshPostgres.Expr do Ecto.Query.dynamic(datetime_add(^DateTime.utc_now(), ^left * -1, ^to_string(right))) end + defp do_dynamic_expr( + query, + %GetPath{arguments: [left, right], embedded?: pred_embedded?}, + bindings, + embedded?, + type + ) do + path = Enum.map(right, &to_string/1) + + do_dynamic_expr( + query, + %Fragment{ + embedded?: pred_embedded?, + arguments: [ + raw: "(", + expr: left, + raw: " #> ", + expr: path, + raw: ")" + ] + }, + bindings, + embedded?, + type + ) + end + defp do_dynamic_expr( query, %Contains{arguments: [left, %Ash.CiString{} = right], embedded?: pred_embedded?}, @@ -125,9 +152,9 @@ defmodule AshPostgres.Expr do %Fragment{ embedded?: pred_embedded?, arguments: [ - raw: "strpos(", + raw: "strpos((", expr: left, - raw: ", ", + raw: "), ", expr: right, raw: ") > 0" ] diff --git a/lib/migration_generator/migration_generator.ex b/lib/migration_generator/migration_generator.ex index cd3210c..1353090 100644 --- a/lib/migration_generator/migration_generator.ex +++ b/lib/migration_generator/migration_generator.ex @@ -1521,7 +1521,7 @@ defmodule AshPostgres.MigrationGenerator do def get_snapshots(resource, all_resources) do Code.ensure_compiled!(AshPostgres.repo(resource)) - + if AshPostgres.polymorphic?(resource) do all_resources |> Enum.flat_map(&Ash.Resource.Info.relationships/1) diff --git a/mix.exs b/mix.exs index 6e36804..03c8f7e 100644 --- a/mix.exs +++ b/mix.exs @@ -97,7 +97,7 @@ defmodule AshPostgres.MixProject do {:ecto, github: "elixir-ecto/ecto", branch: "master", override: true}, {:jason, "~> 1.0"}, {:postgrex, ">= 0.0.0"}, - {:ash, ash_version("~> 1.50 and >= 1.50.16")}, + {:ash, ash_version("~> 1.50 and >= 1.50.19")}, {:git_ops, "~> 2.4.5", only: :dev}, {:ex_doc, "~> 0.22", only: :dev, runtime: false}, {:ex_check, "~> 0.11.0", only: :dev}, diff --git a/mix.lock b/mix.lock index 20f0386..92b69fe 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "1.50.16", "11fb1d4f761b9e9cf3e6d5f5910a330381cb0c5e35b574d207f20f06afbb9faa", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "47813d5da2f209fc98f55a793777ec9f24a02dcd386898d3716ed919ac212818"}, + "ash": {:hex, :ash, "1.50.19", "83d7c9ad840602bb0b02aa06545cdac7fa4cc6b09ad0510d2cbc2454466125ae", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.9", [hex: :sourceror, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "04a0c442a98efc4774e55fbfa46cde5769ad22efb56e288e712edde9c9b7a90a"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, @@ -21,7 +21,7 @@ "excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "getopt": {:hex, :getopt, "1.0.1", "c73a9fa687b217f2ff79f68a3b637711bb1936e712b521d8ce466b29cbf7808a", [:rebar3], [], "hexpm", "53e1ab83b9ceb65c9672d3e7a35b8092e9bdc9b3ee80721471a161c10c59959c"}, - "gettext": {:hex, :gettext, "0.19.0", "6909d61b38bb33339558f128f8af5913d5d5fe304a770217bf352b1620fb7ec4", [:mix], [], "hexpm", "3f7a274f52ebda9bb6655dfeda3d6b0dc4537ae51ce41dcccc7f73ca7379ad5e"}, + "gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.4.5", "185a724dfde3745edd22f7571d59c47a835cf54ded67e9ccbc951920b7eec4c2", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e323a5b01ad53bc8c19c3a444be3e61ed7803ecd2e95530446ae9327d0143ecc"}, "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"}, @@ -38,6 +38,7 @@ "postgrex": {:hex, :postgrex, "0.16.1", "f94628a32c571266f53cd1e5fca705e626e2417bf1eee6f868985d14e874160a", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6b225df32c857b9430619dbe30200a7ae664e23415a771ae9209396ee8eeee64"}, "providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"}, "sobelow": {:hex, :sobelow, "0.10.2", "00e91208046d3b434f9f08779fe0ca7c6d6595b7fa33b289e792dffa6dde8081", [:mix], [], "hexpm", "e30fc994330cf6f485c1c4f2fb7c4b2d403557d0e101c6e5329fd17a58e55a7e"}, + "sourceror": {:hex, :sourceror, "0.10.0", "a6a85d8acc874df59bedb7497374f8f7517e4d54a5805da903ff31aaee14a1a7", [:mix], [], "hexpm", "ddd94d23d96ac949696616eb89cecac977dfed7ecdfac6bbd3f83617e4daea28"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, "timex": {:hex, :timex, "3.7.6", "502d2347ec550e77fdf419bc12d15bdccd31266bb7d925b30bf478268098282f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a296327f79cb1ec795b896698c56e662ed7210cc9eb31f0ab365eb3a62e2c589"}, diff --git a/priv/resource_snapshots/test_repo/authors/20220207205146.json b/priv/resource_snapshots/test_repo/authors/20220207205146.json new file mode 100644 index 0000000..3bb1c01 --- /dev/null +++ b/priv/resource_snapshots/test_repo/authors/20220207205146.json @@ -0,0 +1,57 @@ +{ + "attributes": [ + { + "allow_nil?": false, + "default": "fragment(\"uuid_generate_v4()\")", + "generated?": false, + "name": "id", + "primary_key?": true, + "references": null, + "size": null, + "type": "uuid" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "name": "first_name", + "primary_key?": false, + "references": null, + "size": null, + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "name": "last_name", + "primary_key?": false, + "references": null, + "size": null, + "type": "text" + }, + { + "allow_nil?": true, + "default": "nil", + "generated?": false, + "name": "bio", + "primary_key?": false, + "references": null, + "size": null, + "type": "map" + } + ], + "base_filter": null, + "check_constraints": [], + "custom_indexes": [], + "has_create_action": true, + "hash": "19AEE20A027B64FBA25A86974080E72C7B5B4FBB85005325F2F36BA580E32346", + "identities": [], + "multitenancy": { + "attribute": null, + "global": null, + "strategy": null + }, + "repo": "Elixir.AshPostgres.TestRepo", + "table": "authors" +} \ No newline at end of file diff --git a/priv/test_repo/migrations/20220207205146_migrate_resources2.exs b/priv/test_repo/migrations/20220207205146_migrate_resources2.exs new file mode 100644 index 0000000..c91e285 --- /dev/null +++ b/priv/test_repo/migrations/20220207205146_migrate_resources2.exs @@ -0,0 +1,21 @@ +defmodule AshPostgres.TestRepo.Migrations.MigrateResources2 do + @moduledoc """ + Updates resources based on their most recent snapshots. + + This file was autogenerated with `mix ash_postgres.generate_migrations` + """ + + use Ecto.Migration + + def up do + alter table(:authors) do + add :bio, :map + end + end + + def down do + alter table(:authors) do + remove :bio + end + end +end \ No newline at end of file diff --git a/test/calculation_test.exs b/test/calculation_test.exs index 2e0c035..3cc6153 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -59,6 +59,19 @@ defmodule AshPostgres.CalculationTest do |> Api.read!() end + test "calculations can refer to embedded attributes" do + author = + Author + |> Ash.Changeset.for_create(:create, %{bio: %{title: "Mr.", bio: "Bones"}}) + |> Api.create!() + + assert %{title: "Mr."} = + Author + |> Ash.Query.filter(id == ^author.id) + |> Ash.Query.load(:title) + |> Api.read_one!() + end + test "calculations can be used in related filters" do post = Post diff --git a/test/filter_test.exs b/test/filter_test.exs index 7c10e70..0f5de67 100644 --- a/test/filter_test.exs +++ b/test/filter_test.exs @@ -1,6 +1,6 @@ defmodule AshPostgres.FilterTest do use AshPostgres.RepoCase, async: false - alias AshPostgres.Test.{Api, Comment, Post} + alias AshPostgres.Test.{Api, Author, Comment, Post} require Ash.Query @@ -263,6 +263,43 @@ defmodule AshPostgres.FilterTest do end end + describe "accessing embeds" do + setup do + Author + |> Ash.Changeset.for_create(:create, bio: %{title: "Dr.", bio: "Strange"}) + |> Api.create!() + + Author + |> Ash.Changeset.for_create(:create, + bio: %{title: "Highlander", bio: "There can be only one."} + ) + |> Api.create!() + + :ok + end + + test "works using simple equality" do + assert [%{bio: %{title: "Dr."}}] = + Author + |> Ash.Query.filter(bio[:title] == "Dr.") + |> Api.read!() + end + + test "works using an expression" do + assert [%{bio: %{title: "Highlander"}}] = + Author + |> Ash.Query.filter(contains(type(bio[:bio], :string), "only one.")) + |> Api.read!() + end + + test "calculations that use embeds can be filtered on" do + assert [%{bio: %{title: "Dr."}}] = + Author + |> Ash.Query.filter(title == "Dr.") + |> Api.read!() + end + end + describe "basic expressions" do test "basic expressions work" do Post diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex index e18fee4..7983653 100644 --- a/test/support/resources/author.ex +++ b/test/support/resources/author.ex @@ -12,9 +12,11 @@ defmodule AshPostgres.Test.Author do uuid_primary_key(:id, writable?: true) attribute(:first_name, :string) attribute(:last_name, :string) + attribute(:bio, AshPostgres.Test.Bio) end calculations do + calculate(:title, :string, expr(bio[:title])) calculate(:full_name, :string, expr(first_name <> " " <> last_name)) calculate( diff --git a/test/support/resources/bio.ex b/test/support/resources/bio.ex new file mode 100644 index 0000000..a677f24 --- /dev/null +++ b/test/support/resources/bio.ex @@ -0,0 +1,9 @@ +defmodule AshPostgres.Test.Bio do + @moduledoc false + use Ash.Resource, data_layer: :embedded + + attributes do + attribute(:title, :string) + attribute(:bio, :string) + end +end