I have a working Ecto.Multi transaction, with several bank-transaction-like constraints, that several vales can’t be negative… this works, but when the constraints are violated, I’m using update_all not update, so using queries not changesets, and my changesets know about these constraints, but I don’t see how to add an on_conflict or other constraint awareness to an Ecto.Multi query based transaction…
I could rescue the error, but that doesn’t seem right…code is on this commit and like I said, it works on happy path, but when I intentionally do a big illegal transaction I’d expect a way to get an {:error, reason} tuple or something out, and instead I get a big fat error, and it looks like update_all doesn’t accept on_conflict /conflict_target options like I might use otherwise (would be OK with an on_conflict: :nothing
and getting an error from transaction for now, FWIW)
3 Likes
I had a chance to ask Chris McCord about this after his talk at NYC last week, and his advise was to move each update_all operation into a function and cal with Ecto.Multi.run, and put the rescue clause inside the run function, so the function returns :ok/:error tuple with error coming from a rescue of the database error. For now I just threw the whole transaction into a spawn block so that a crash doesn’t take down the channel doing the transaction, and since I effectively want on_conflict: :nothing for now, this achieves it… if I want to notify client of the errror later (which I may, as it is the client will have optimistically assumed success, but in my testing so far it’s pretty self-correcting because of the data I send back on other events) I plan to refactor to Chris’s suggested Multi.run plan, or maybe even as an experiment sooner if I have time. Just thought I’d capture here in case anyone else comes across the issue and finds this looking for advice! (and/or for future me if I forget!)
4 Likes
I faced a similar situation and implemented the following. Sort of what like Chris suggested. update_transactions_status/3
is called from Ecto.Multi.run
.
defp update_transactions_status(_multi, ids, status) do
target_count = Enum.count(ids)
{count, nil} =
Repo.update_all (from t in Transaction, where: t.id in ^ids),
[set: [status: status, updated_at: NaiveDateTime.utc_now()]]
if target_count == count do
{:ok, count}
else
{:error, count}
end
4 Likes