AshPostgres update only nested property value of jsonb column?

If I have large nested maps in jsonb columns and I just want to update one value, can ash_postgres update the value only without sending the whole JSON of the map again?

Can it use jsonb_set or concatenation like the example from these answers?

Yes.

change atomic_update(:attribute, expr(fragment("")))

or

in a custom change using Ash.Changeset.atomic_update

I get this error trying to use expr and fragment.

“Type Ash.Type.Map does not support atomic updates with expressions”

    attribute :data, :map do
      allow_nil? true
      public? true
    end

    update :update do
      ...
      change atomic_update(:data, expr(fragment("data || jsonb_build_object('status', 'test')")))
      change atomic_update(:data, expr(fragment("'test'")))
      change atomic_update(:data, expr(fragment("")))
    end

error #=> %Ash.Error.Invalid{
  changeset: "#Changeset<>",
  errors: [
    %Ash.Error.Changes.InvalidChanges{
      fields: nil,
      message: "Cannot atomically update MyApp.Schemas.Entity.data: Type `Ash.Type.Map` does not support atomic updates with expressions",
      validation: nil,
      value: nil,
      splode: Ash.Error,
      bread_crumbs: [],
      vars: [],
      path: [],
      stacktrace: #Splode.Stacktrace<>,
      class: :invalid
    }
  ]
}

We need to document this better, but you can wrap the expression in {:atomic, expr(...)} to indicate that you’re handing any necessary type casting etc.

I don’t get the error with cast_atomic?: false.

change atomic_update(:data, expr(fragment("data || jsonb_build_object('status', 'test')")), cast_atomic?: false)

{:atomic, expr(...)} worked. Is it different from cast_atomic?: false?

I actually forgot that we had cast_atomic?: false :slight_smile: They are equivalent.