Perform database constraint operations in changeset

I have a changeset that has check_constraint and foreign_key_constraint but I don’t want to use Repo.insert() immediately. How can I trigger the check_constraint and foreign_key_constraint changesets without using Repo.insert() after? Because changeset.valid? only works for cast and validate_required.

@spec changeset(%User{}, map) :: %Ecto.Changeset{}
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :age, :master_id])
    |> validate_required([:name, :age, :master_id])
    |> check_constraint(:age,
      name: :age_must_be_positive,
    )
    |> foreign_key_constraint(:property_id, message: "master id does not exist")
  end

No Repo.insert() yet

User.changeset(%User{}, %{name: "John", age: 18, master_id: 33})

Is there any ecto function that can trigger the constraint not using Repo.insert()?

I don’t think so, as constraints are checked at db level…

BTW your age>0 constraint could be done by a custom validation.

But how would You solve unique constraints without querying the DB?

Just found a solution. I’ll just use validate_change/3 https://hexdocs.pm/ecto/Ecto.Changeset.html#validate_change/3

validate_change(changeset, :master_id, fn :master_id, master_id  ->
  if MyApp.master_exists?(id) do
    [master_id: "master id already exists"]
  else
    []
  end
end

If this hits the db it‘ll be prone to race conditions. The id might exist when doing the validation, but no longer exist when you do your insert.

Good point.