Possible bug in `Ash.Actions.Update.Bulk.do_run/3`
Describe the bug
When using ash_archival, Ash.Actions.Update.Bulk.do_run/8 calls Ash.Actions.Read.Stream.stream_strategy/3. If the strategy returned is :keyset, do_atomic_batches/6 is executed, which only applies the :atomic strategy. However, the action does not support :atomic, causing a failure.
https://github.com/ash-project/ash/blob/c675101316222205b3e3f2a16602763d89ec48e1/lib/ash/actions/update/bulk.ex#L1087
It seems that when Ash.Actions.Read.Stream.stream_strategy/3 returns :keyset, the :stream strategy should be applied instead.
To Reproduce
I failed to reproduce it in my playground project.
Expected behavior
When Ash.Actions.Read.Stream.stream_strategy/3 returns :keyset, the :stream strategy should be applied instead of enforcing :atomic, ensuring that the requested operation does not fail.
Runtime
- Elixir version: 1.18.1
- Erlang version: 27
- OS: mac
- Ash version: 3.4.60
- any related extension versions
- ash_archival: 1.1.1
Additional context
@nallwhy its hard to reason about what we might do about this w/o a reproduction, or at least some information about how or why it failed. "it fails" is not enough information.
This is an error message example. I don’t think there’s any reason for only atomic to remain in strategies.
I understand that it might not be easy to spot the bug just from this information. However, looking at the code, I think it is strange to enforce atomic batch when a stream strategy is available.
** (Ash.Error.Invalid)
Bread Crumbs:
> Returned from bulk update: Bard.Resources.UserOrg.destroy
> Exception raised in: Bard.Resources.User.destroy
Invalid Error
* Bard.Resources.UserOrg.destroy had no matching bulk strategy that could be used.
Requested strategies: [:atomic]
Could not use `:stream`: Not in requested strategies
Could not use `:atomic_batches`: Not in requested strategies
Could not use `:atomic`: cannot atomically update a query if it has `before_action` hooks
(ash 3.4.64) lib/ash/error/invalid/no_matching_bulk_strategy.ex:5: Ash.Error.Invalid.NoMatchingBulkStrategy.exception/1
(ash 3.4.64) lib/ash/actions/update/bulk.ex:1135: Ash.Actions.Update.Bulk.do_run/8
(ash 3.4.64) lib/ash/actions/update/bulk.ex:479: Ash.Actions.Update.Bulk.run/6
(ash 3.4.64) lib/ash/actions/update/bulk.ex:159: Ash.Actions.Update.Bulk.run/6
(ash 3.4.64) lib/ash/actions/update/bulk.ex:1186: anonymous fn/8 in Ash.Actions.Update.Bulk.do_atomic_batches/6
(elixir 1.18.1) lib/stream.ex:626: anonymous fn/4 in Stream.map/2
(elixir 1.18.1) lib/stream.ex:1761: anonymous fn/3 in Enumerable.Stream.reduce/3
(elixir 1.18.1) lib/stream.ex:302: Stream.after_chunk_while/2
(elixir 1.18.1) lib/stream.ex:1790: Enumerable.Stream.do_done/2
(elixir 1.18.1) lib/stream.ex:1773: Enumerable.Stream.do_each/4
(elixir 1.18.1) lib/stream.ex:703: Stream.run/1
(ash 3.4.64) lib/ash/actions/update/bulk.ex:1329: Ash.Actions.Update.Bulk.run_batches/5
(ash 3.4.64) lib/ash/actions/update/bulk.ex:479: Ash.Actions.Update.Bulk.run/6
(ash 3.4.64) lib/ash/actions/update/bulk.ex:159: Ash.Actions.Update.Bulk.run/6
(ash 3.4.64) lib/ash.ex:2569: Ash.bulk_destroy!/4
(elixir 1.18.1) lib/enum.ex:987: Enum."-each/2-lists^foreach/1-0-"/2
(ash_archival 1.1.1) lib/ash_archival/resource/changes/archive_related.ex:8: anonymous fn/3 in AshArchival.Resource.Changes.ArchiveRelated.change/3
(ash 3.4.64) lib/ash/changeset/changeset.ex:4079: anonymous fn/2 in Ash.Changeset.run_after_actions/3
(elixir 1.18.1) lib/enum.ex:4964: Enumerable.List.reduce/3
(elixir 1.18.1) lib/enum.ex:2600: Enum.reduce_while/3
(ash 3.4.64) lib/ash/changeset/changeset.ex:3557: anonymous fn/3 in Ash.Changeset.with_hooks/3
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1400: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
(db_connection 2.7.0) lib/db_connection.ex:1756: DBConnection.run_transaction/4
(ash 3.4.64) lib/ash/changeset/changeset.ex:3555: anonymous fn/3 in Ash.Changeset.with_hooks/3
(ash 3.4.64) lib/ash/changeset/changeset.ex:3699: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
(ash 3.4.64) lib/ash/changeset/changeset.ex:3536: Ash.Changeset.with_hooks/3
(ash 3.4.64) lib/ash/actions/update/update.ex:423: Ash.Actions.Update.commit/3
(ash 3.4.64) lib/ash/actions/update/update.ex:303: Ash.Actions.Update.do_run/4
(ash 3.4.64) lib/ash/actions/update/update.ex:247: Ash.Actions.Update.run/4
(ash 3.4.64) lib/ash.ex:2828: Ash.destroy/2
(elixir 1.18.1) src/elixir.erl:386: :elixir.eval_external_handler/3
(stdlib 6.1) erl_eval.erl:904: :erl_eval.do_apply/7
(elixir 1.18.1) src/elixir.erl:364: :elixir.eval_forms/4
(elixir 1.18.1) lib/module/parallel_checker.ex:120: Module.ParallelChecker.verify/1
(iex 1.18.1) lib/iex/evaluator.ex:336: IEx.Evaluator.eval_and_inspect/3
(iex 1.18.1) lib/iex/evaluator.ex:310: IEx.Evaluator.eval_and_inspect_parsed/3
(iex 1.18.1) lib/iex/evaluator.ex:299: IEx.Evaluator.parse_eval_inspect/4
(iex 1.18.1) lib/iex/evaluator.ex:189: IEx.Evaluator.loop/1
(iex 1.18.1) lib/iex/evaluator.ex:34: IEx.Evaluator.init/5
(stdlib 6.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
(ash 3.4.64) lib/ash.ex:2589: Ash.bulk_destroy!/4
(elixir 1.18.1) lib/enum.ex:987: Enum."-each/2-lists^foreach/1-0-"/2
(ash_archival 1.1.1) lib/ash_archival/resource/changes/archive_related.ex:8: anonymous fn/3 in AshArchival.Resource.Changes.ArchiveRelated.change/3
(ash 3.4.64) lib/ash/changeset/changeset.ex:4079: anonymous fn/2 in Ash.Changeset.run_after_actions/3
(elixir 1.18.1) lib/enum.ex:4964: Enumerable.List.reduce/3
(elixir 1.18.1) lib/enum.ex:2600: Enum.reduce_while/3
(ash 3.4.64) lib/ash/changeset/changeset.ex:3557: anonymous fn/3 in Ash.Changeset.with_hooks/3
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1400: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
(db_connection 2.7.0) lib/db_connection.ex:1756: DBConnection.run_transaction/4
(ash 3.4.64) lib/ash/changeset/changeset.ex:3555: anonymous fn/3 in Ash.Changeset.with_hooks/3
(ash 3.4.64) lib/ash/changeset/changeset.ex:3699: anonymous fn/2 in Ash.Changeset.transaction_hooks/2
(ash 3.4.64) lib/ash/changeset/changeset.ex:3536: Ash.Changeset.with_hooks/3
(ash 3.4.64) lib/ash/actions/update/update.ex:423: Ash.Actions.Update.commit/3
(ash 3.4.64) lib/ash/actions/update/update.ex:303: Ash.Actions.Update.do_run/4
(ash 3.4.64) lib/ash/actions/update/update.ex:247: Ash.Actions.Update.run/4
(ash 3.4.64) lib/ash.ex:2828: Ash.destroy/2
iex:5: (file)
Something definitely seems strange there. From what I can tell, ash_archival allows all strategies, and I can't find a reason we'd be resetting it or altering it. You're using ash_postgres right?
I've spent about an hour trying to piece it together. Ultimate w/o a reproduction I think I'm dead in the water. Sorry 😓