improvement: inform users about postgres incompatibility with multidimensional arrays

This commit is contained in:
Zach Daniel 2022-12-01 20:24:49 -05:00
parent f088601cf9
commit 52fcba79ac
5 changed files with 80 additions and 37 deletions

View file

@ -416,7 +416,8 @@ defmodule AshPostgres.DataLayer do
sections: @sections, sections: @sections,
transformers: [ transformers: [
AshPostgres.Transformers.VerifyRepo, AshPostgres.Transformers.VerifyRepo,
AshPostgres.Transformers.EnsureTableOrPolymorphic AshPostgres.Transformers.EnsureTableOrPolymorphic,
AshPostgres.Transformers.PreventMultidimensionalArrayAggregates
] ]
@doc false @doc false

View file

@ -0,0 +1,51 @@
defmodule AshPostgres.Transformers.PreventMultidimensionalArrayAggregates do
@moduledoc "Prevents at compile time certain aggregates that are unsupported by `AshPostgres`"
use Spark.Dsl.Transformer
alias Spark.Dsl.Transformer
def after_compile?, do: true
def transform(dsl) do
resource = Transformer.get_persisted(dsl, :module)
dsl
|> Ash.Resource.Info.aggregates()
|> Stream.filter(&(&1.kind in [:list, :first]))
|> Stream.filter(& &1.field)
|> Enum.each(fn aggregate ->
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
type = Ash.Resource.Info.attribute(related, aggregate.field).type
case type do
{:array, _} ->
raise Spark.Error.DslError,
module: resource,
path: [:aggregates, aggregate.name],
message: """
Aggregate not supported.
Aggregate #{inspect(resource)}.#{aggregate.name} is not supported, because its type is `#{aggregate.kind}`, and the destination attribute is an array.
Postgres does not support multidimensional arrays with differing lengths internally. In the future we may be able to remove this restriction
for the `:first` type aggregate, but likely never for `:list`. In the meantime, you will have to use a custom calculation to get this data.
"""
_ ->
:ok
end
end)
repo = Transformer.get_option(dsl, [:postgres], :repo)
cond do
match?({:error, _}, Code.ensure_compiled(repo)) ->
{:error, "Could not find repo module #{repo}"}
repo.__adapter__() != Ecto.Adapters.Postgres ->
{:error, "Expected a repo using the postgres adapter `Ecto.Adapters.Postgres`"}
true ->
{:ok, dsl}
end
end
end

View file

@ -3,6 +3,8 @@ defmodule AshPostgres.Transformers.VerifyRepo do
use Spark.Dsl.Transformer use Spark.Dsl.Transformer
alias Spark.Dsl.Transformer alias Spark.Dsl.Transformer
def after_compile?, do: true
def transform(dsl) do def transform(dsl) do
repo = Transformer.get_option(dsl, [:postgres], :repo) repo = Transformer.get_option(dsl, [:postgres], :repo)

View file

@ -1,6 +1,6 @@
defmodule AshPostgres.AggregateTest do defmodule AshPostgres.AggregateTest do
use AshPostgres.RepoCase, async: false use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, Comment, Post, Rating, Author, Profile} alias AshPostgres.Test.{Api, Comment, Post, Rating}
require Ash.Query require Ash.Query
@ -236,43 +236,36 @@ defmodule AshPostgres.AggregateTest do
|> Ash.Query.sort(:first_comment) |> Ash.Query.sort(:first_comment)
|> Api.read_one!() |> Api.read_one!()
end end
test "on an array composite type" do
author =
Author
|> Ash.Changeset.for_create(:create, %{badges: [:author_of_the_year]})
|> Api.create!()
profile =
Profile
|> Ash.Changeset.for_create(:create)
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove)
|> Api.create!()
assert %{author_badges: [:author_of_the_year]} =
Profile
|> Ash.Query.filter(id == ^profile.id)
|> Ash.Query.load([:author_badges])
|> Api.read_one!()
end end
test "on an empty array composite type" do test "can't define multidimensional array aggregate types" do
author = assert_raise Spark.Error.DslError, ~r/Aggregate not supported/, fn ->
Author defmodule Foo do
|> Ash.Changeset.for_create(:create, %{badges: []}) @moduledoc false
|> Api.create!() use Ash.Resource,
data_layer: AshPostgres.DataLayer
profile = postgres do
Profile table("profile")
|> Ash.Changeset.for_create(:create) repo(AshPostgres.TestRepo)
|> Ash.Changeset.manage_relationship(:author, author, type: :append_and_remove) end
|> Api.create!()
assert %{author_badges: []} = attributes do
Profile uuid_primary_key(:id, writable?: true)
|> Ash.Query.filter(id == ^profile.id) end
|> Ash.Query.load([:author_badges])
|> Api.read_one!() actions do
defaults([:create, :read, :update, :destroy])
end
relationships do
belongs_to(:author, AshPostgres.Test.Author)
end
aggregates do
first(:author_badges, :author, :badges)
end
end
end end
end end

View file

@ -21,8 +21,4 @@ defmodule AshPostgres.Test.Profile do
relationships do relationships do
belongs_to(:author, AshPostgres.Test.Author) belongs_to(:author, AshPostgres.Test.Author)
end end
aggregates do
first(:author_badges, :author, :badges)
end
end end