Me again!
I have a Person
resource that has a birthdate
field. I would like to have the column be a map
type because I want to allow a nil
year. My domain always requires an actor and I have some policies set up to ensure that the actor is the only one that can read, update, or destroy the Resource.
When I attempt to update the Resource’s birthdate
field with an appropriate actor, I get the following error:
17:43:20.715 [debug] QUERY ERROR source="people" db=0.0ms queue=2.8ms idle=1211.0ms
UPDATE "people" AS p0 SET "birthdate" = s1."__new_birthdate", "updated_at" = s1."__new_updated_at" FROM (SELECT $1::timestamp::timestamp AS "__new_updated_at", $2 AS "__new_birthdate", sp0."id" AS "id" FROM "people" AS sp0 LEFT OUTER JOIN "public"."people" AS sp1 ON sp0."user_id" = sp1."id" WHERE (sp0."id"::uuid = $3::uuid) AND ((CASE WHEN sp1."id"::uuid = $4::uuid THEN $5 ELSE ash_raise_error($6::jsonb) END))) AS s1 WHERE (p0."id" = s1."id") RETURNING p0."id", p0."name", p0."birthdate", p0."inserted_at", p0."updated_at", p0."user_id" [~U[2025-02-16 23:43:20.711984Z], %{month: 2, day: 4, year: 2000}, "f8af8361-bc73-4fe4-8891-1c7ee75c9cae", "202c002e-8bbc-45dd-89e0-623f07360d0d", true, "{\"input\":{\"authorizer\":\"Ash.Policy.Authorizer\"},\"exception\":\"Ash.Error.Forbidden.Placeholder\"}"]
** (Ash.Error.Unknown)
Bread Crumbs:
> Exception raised in bulk update: Playdate.People.Person.update
> Exception raised in: Playdate.People.Person.update
Unknown Error
* ** (Postgrex.Error) ERROR 42804 (datatype_mismatch) column "birthdate" is of type jsonb but expression is of type text
query: UPDATE "people" AS p0 SET "birthdate" = s1."__new_birthdate", "updated_at" = s1."__new_updated_at" FROM (SELECT $1::timestamp::timestamp AS "__new_updated_at", $2 AS "__new_birthdate", sp0."id" AS "id" FROM "people" AS sp0 LEFT OUTER JOIN "public"."people" AS sp1 ON sp0."user_id" = sp1."id" WHERE (sp0."id"::uuid = $3::uuid) AND ((CASE WHEN sp1."id"::uuid = $4::uuid THEN $5 ELSE ash_raise_error($6::jsonb) END))) AS s1 WHERE (p0."id" = s1."id") RETURNING p0."id", p0."name", p0."birthdate", p0."inserted_at", p0."updated_at", p0."user_id"
hint: You will need to rewrite or cast the expression.
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1096: Ecto.Adapters.SQL.raise_sql_call_error/1
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:994: Ecto.Adapters.SQL.execute/6
(ecto 3.12.5) lib/ecto/repo/queryable.ex:232: Ecto.Repo.Queryable.execute/4
(ash_postgres 2.5.3) lib/data_layer.ex:1503: AshPostgres.DataLayer.update_query/4
(ash 3.4.63) lib/ash/actions/update/bulk.ex:576: Ash.Actions.Update.Bulk.do_atomic_update/5
(ash 3.4.63) lib/ash/actions/update/bulk.ex:272: Ash.Actions.Update.Bulk.run/6
(ash 3.4.63) lib/ash/actions/update/update.ex:165: Ash.Actions.Update.run/4
(ash 3.4.63) lib/ash.ex:2736: Ash.update/3
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1096: Ecto.Adapters.SQL.raise_sql_call_error/1
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:994: Ecto.Adapters.SQL.execute/6
(ecto 3.12.5) lib/ecto/repo/queryable.ex:232: Ecto.Repo.Queryable.execute/4
(ash_postgres 2.5.3) lib/data_layer.ex:1503: AshPostgres.DataLayer.update_query/4
(ash 3.4.63) lib/ash/actions/update/bulk.ex:576: Ash.Actions.Update.Bulk.do_atomic_update/5
(ash 3.4.63) lib/ash/actions/update/bulk.ex:272: Ash.Actions.Update.Bulk.run/6
(ash 3.4.63) lib/ash/actions/update/update.ex:165: Ash.Actions.Update.run/4
iex:10: (file)
Based on the debug output, this led me to think, despite the Postgrex
error later, that it was actually an authorization error. If I change my authorization code to always allow an update, it passes and the update is made.
Here’s where it gets stranger. If I use defaults [:read, update: :*]
, the update passes, even with my more restrictive policy (this is what I would consider expected behaviour). If I have an actual update :update do ... end
block, the policy fails BUT ONLY WHEN UPDATING THE birthdate
MAP FIELD. I’ve tried both putting in all the fields explicity and the :*
version within the block and both fail. If I put in the always()
policy for action_type(:update)
, the update again succeeds even with the block-type update
.
Is there something I need to put in my update :update do ... end
block for that birthdate
field specifically? I am at a total loss here.
Here is my resource.
defmodule Playdate.People.Person do
use Ash.Resource,
otp_app: :playdate,
domain: Playdate.People,
authorizers: [Ash.Policy.Authorizer],
data_layer: AshPostgres.DataLayer
postgres do
table "people"
repo Playdate.Repo
end
actions do
defaults [:read]
# defaults [:read, update: :*] THIS ALWAYS SUCCEEDS
create :create do
accept [:birthdate, :name]
change relate_actor(:user)
end
read :list do
prepare build(load: [:date_of_latest_activity])
end
update :update do
accept :* # STRANGELY, THIS ALSO FAILS
# accept [:birthdate, :name] THIS FAILS
end
end
policies do
policy action_type(:create) do
authorize_if always()
end
# this allows the block-type update action to succeed
policy action_type(:update) do
authorize_if always()
end
# putting :update in here (instead of the above) only succeeds when I don't use the block-type update definition
# but it only fails when updating the `birthdate` field
policy action_type([:destroy, :read]) do
authorize_if relates_to_actor_via(:user)
end
end
attributes do
uuid_primary_key :id
attribute :birthdate, :map do
allow_nil? true
public? true
constraints fields: [
year: [
type: :integer,
allow_nil?: true
],
month: [
type: :integer,
allow_nil?: false,
constraints: [
min: 1,
max: 12
]
],
day: [
type: :integer,
allow_nil?: false,
constraints: [
min: 1,
max: 31
]
]
]
end
attribute :name, :string do
allow_nil? false
public? true
end
timestamps()
end
relationships do
belongs_to :user, Playdate.People.Person
has_many :activities, Playdate.People.Activity
end
aggregates do
first :date_of_latest_activity, :activities, :date do
sort date: :desc
public? true
end
end
end
I’m on latest ash (main
in fact) and all the related ash packages.