ash_postgres icon indicating copy to clipboard operation
ash_postgres copied to clipboard

The changeset does not define a constraint for a custom unique index

Open smt116 opened this issue 2 years ago • 1 comments

Describe the bug

The following custom unique index:

postgres do
  # ...

  custom_indexes do
    index(["provider", "provider_id"], unique: true, where: "archived_at IS NULL and provider_id IS NOT NULL")
  end
end

Does not add a constraint on a changeset which results in the exception when inserting a duplicated record:

** (Ecto.ConstraintError) constraint error when attempting to insert struct:

    * table_provider_provider_id_index (unique_constraint)

If you would like to stop this constraint violation from raising an
exception and instead add it as an error to your changeset, please
call `unique_constraint/3` on your changeset with the constraint
`:name` as an option.

The changeset defined the following constraints:

    * table_user_id_fkey (foreign_key_constraint)
    * table_pkey (unique_constraint)

    (ecto 3.8.4) lib/ecto/repo/schema.ex:783: anonymous fn/4 in Ecto.Repo.Schema.constraints_to_errors/3
    (elixir 1.14.0) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (ecto 3.8.4) lib/ecto/repo/schema.ex:768: Ecto.Repo.Schema.constraints_to_errors/3
    (ecto 3.8.4) lib/ecto/repo/schema.ex:749: Ecto.Repo.Schema.apply/4
    (ecto 3.8.4) lib/ecto/repo/schema.ex:367: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
    (ash_postgres 1.0.0-rc.5) lib/data_layer.ex:934: AshPostgres.DataLayer.create/2
    (ash 2.0.0-rc.7) lib/ash/actions/create.ex:378: anonymous fn/8 in Ash.Actions.Create.as_requests/5
    (ash 2.0.0-rc.7) lib/ash/changeset/changeset.ex:1463: Ash.Changeset.run_around_actions/2
    (ash 2.0.0-rc.7) lib/ash/actions/create.ex:313: anonymous fn/8 in Ash.Actions.Create.as_requests/5
    (ash 2.0.0-rc.7) lib/ash/engine/request.ex:1041: Ash.Engine.Request.do_try_resolve_local/4
    (ash 2.0.0-rc.7) lib/ash/engine/request.ex:281: Ash.Engine.Request.do_next/1
    (ash 2.0.0-rc.7) lib/ash/engine/request.ex:210: Ash.Engine.Request.next/1
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:610: Ash.Engine.advance_request/2
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:535: Ash.Engine.fully_advance_request/2
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:469: Ash.Engine.do_run_iteration/2
    (elixir 1.14.0) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:218: Ash.Engine.run_to_completion/1
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:167: Ash.Engine.do_run/2
    (ash 2.0.0-rc.7) lib/ash/engine/engine.ex:88: anonymous fn/2 in Ash.Engine.run/2
    (ecto_sql 3.8.3) lib/ecto/adapters/sql.ex:1222: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4

Expected behavior All custom unique indexes should add a constraint to the changeset.

** Runtime

  • Elixir version 1.14.0
  • Erlang version 25.0.4
  • ash 2.0.0-rc.7
  • ash_admin 0.6.0-rc.1
  • ash_graphql 0.20.0-rc.1
  • ash_phoenix 1.0.0-rc.0
  • ash_postgres 1.0.0-rc.5

smt116 avatar Sep 29 '22 08:09 smt116

🤔 yeah, this is interesting. There is a way to add custom unique indexes to be checked in the changeset. Specifically:

postgres do
  unique_index_names {[:provider, :provider_id], "...name of constraint"}
end

But we could likely derive these for custom constraints. The only thing is that we need to have atoms for the fields instead of strings, and we don't necessarily want to assume that every key provided to a custom index already exists as an atom.

So, to do this automatically we'd need to

  1. make custom_indexes take a list of atom for fields instead of strings
  2. add those to the list of unique constraint names to check for.

zachdaniel avatar Sep 30 '22 20:09 zachdaniel

Fixed in: 059837651d2cee1b717113363b5b9b45a5cbe0a5

zachdaniel avatar Nov 25 '22 19:11 zachdaniel