-
-
Notifications
You must be signed in to change notification settings - Fork 115
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Code of Conduct
- I agree to follow this project's Code of Conduct
AI Policy
- I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.
Versions
1.18.4 OTP 27
27.3.3
Operating system
macos
Current Behavior
Following up discussion on Elixir Discord. jsonb
(or map
) is the recommended type for storing list of embeds in Ecto. Naturally, I tried to do the same with Ash, but run into an error during update.
Reproduction
Here's a one file reproduction
Mix.install(
[
{:ash, "~> 3.0"},
{:ash_postgres, "~> 2.0"}
],
consolidate_protocols: false
)
Application.put_env(:myapp, MyRepo,
database: "mix_install_examples",
username: "postgres",
password: "postgres",
hostname: "localhost"
)
defmodule MyRepo do
use AshPostgres.Repo, otp_app: :myapp
end
defmodule Migration0 do
use Ecto.Migration
def change do
create table("profiles") do
add(:name, :string)
add(:identities, :jsonb)
add :inserted_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
end
end
end
defmodule Accounts.Profile.Identity do
use Ash.TypedStruct
typed_struct do
field :first_name, :string, allow_nil?: false
field :last_name, :string
end
end
defmodule Accounts.Profile do
use Ash.Resource,
domain: Accounts,
data_layer: AshPostgres.DataLayer
postgres do
table "profiles"
repo MyRepo
end
actions do
defaults [:read, :destroy, create: [:name, :identities], update: [:name, :identities]]
end
attributes do
integer_primary_key :id
attribute :name, :string
attribute :identities, {:array, Accounts.Profile.Identity}, default: []
timestamps()
end
end
defmodule Accounts do
use Ash.Domain, validate_config_inclusion?: false
resources do
resource Accounts.Profile do
define :all_profiles, action: :read
define :profile_by_id, action: :read, get_by: [:id]
define :create_profile, args: [:name], action: :create
define :update_profile, action: :update
define :delete_profile, action: :destroy
end
end
end
defmodule Main do
import Ecto.Query, warn: false
def main do
MyRepo.__adapter__().storage_down(MyRepo.config())
:ok = MyRepo.__adapter__().storage_up(MyRepo.config())
{:ok, _} = MyRepo.start_link([])
Ecto.Migrator.run(MyRepo, [{0, Migration0}], :up, all: true, log_migrations_sql: :info)
profile =
Accounts.Profile
|> Ash.Changeset.for_create(:create, %{name: "Joe Armstrong"})
|> Ash.create!()
profile.id
|> Accounts.update_profile!(%{identities: [%{first_name: "Foo", last_name: "Bar"}]})
|> IO.inspect()
end
end
Main.main()
error
elixir ash_postgres.exs
17:11:16.391 [info] == Running 0 Migration0.change/0 forward
17:11:16.393 [info] create table profiles
17:11:16.395 [info] QUERY OK db=2.0ms
CREATE TABLE "profiles" ("id" bigserial, "name" varchar(255), "identities" jsonb, "inserted_at" timestamp DEFAULT (now() AT TIME ZONE 'utc') NOT NULL, "updated_at" timestamp DEFAULT (now() AT TIME ZONE 'utc') NOT NULL, PRIMARY KEY ("id")) []
17:11:16.396 [info] == Migrated 0 in 0.0s
17:11:16.429 [debug] QUERY OK db=0.6ms idle=57.8ms
begin []
17:11:16.442 [debug] QUERY OK source="profiles" db=5.9ms
INSERT INTO "profiles" ("name","identities","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "updated_at","inserted_at","identities","name","id" ["Joe Armstrong", [], ~U[2025-09-09 00:11:16.426384Z], ~U[2025-09-09 00:11:16.426384Z]]
17:11:16.445 [debug] QUERY OK db=0.8ms
commit []
17:11:16.501 [debug] QUERY ERROR source="profiles" db=0.0ms queue=3.0ms idle=127.1ms
UPDATE "profiles" AS p0 SET "updated_at" = s1."__new_updated_at", "identities" = s1."__new_identities" FROM (SELECT sp0."id" AS "id", (CASE WHEN $1::jsonb[] != sp0."identities"::jsonb[] THEN $2::timestamp ELSE sp0."updated_at"::timestamp END)::timestamp AS "__new_updated_at", $3::jsonb[] AS "__new_identities" FROM "profiles" AS sp0 WHERE (sp0."id"::bigint = $4::bigint) LIMIT $5) AS s1 WHERE (p0."id" = s1."id") RETURNING p0."id", p0."name", p0."identities", p0."inserted_at", p0."updated_at" [[%Accounts.Profile.Identity{first_name: "Foo", last_name: "Bar"}], ~U[2025-09-09 00:11:16.495357Z], [%Accounts.Profile.Identity{first_name: "Foo", last_name: "Bar"}], 1, 1]
** (Ash.Error.Unknown)
Bread Crumbs:
> Returned from bulk query update: Accounts.Profile.update
Unknown Error
* ** (Postgrex.Error) ERROR 42846 (cannot_coerce) cannot cast type jsonb to jsonb[]
query: UPDATE "profiles" AS p0 SET "updated_at" = s1."__new_updated_at", "identities" = s1."__new_identities" FROM (SELECT sp0."id" AS "id", (CASE WHEN $1::jsonb[] != sp0."identities"::jsonb[] THEN $2::timestamp ELSE sp0."updated_at"::timestamp END)::timestamp AS "__new_updated_at", $3::jsonb[] AS "__new_identities" FROM "profiles" AS sp0 WHERE (sp0."id"::bigint = $4::bigint) LIMIT $5) AS s1 WHERE (p0."id" = s1."id") RETURNING p0."id", p0."name", p0."identities", p0."inserted_at", p0."updated_at"
(ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:1098: Ecto.Adapters.SQL.raise_sql_call_error/1
(ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:996: Ecto.Adapters.SQL.execute/6
(ecto 3.13.2) lib/ecto/repo/queryable.ex:241: Ecto.Repo.Queryable.execute/4
(ash_postgres 2.6.17) lib/data_layer.ex:1577: AshPostgres.DataLayer.update_query/4
(ash 3.5.39) lib/ash/actions/update/bulk.ex:608: Ash.Actions.Update.Bulk.do_atomic_update/5
(ash 3.5.39) lib/ash/actions/update/bulk.ex:293: Ash.Actions.Update.Bulk.run/6
(ash 3.5.39) lib/ash.ex:3320: Ash.bulk_update!/4
/Users/amartsinovich/Library/Caches/mix/installs/elixir-1.18.4-erts-15.2.6/617f01c5d311df92f70ed4380fa03884/deps/ash/lib/ash/code_interface.ex:1215: Accounts.update_profile!/3
(ash 3.5.39) lib/ash.ex:3340: Ash.bulk_update!/4
/Users/amartsinovich/Library/Caches/mix/installs/elixir-1.18.4-erts-15.2.6/617f01c5d311df92f70ed4380fa03884/deps/ash/lib/ash/code_interface.ex:1215: Accounts.update_profile!/3
ash_postgres.exs:99: Main.main/0
ash_postgres.exs:104: (file)
Expected Behavior
No response
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working