improvement: support :no_rollback and return_query/2 callback

This commit is contained in:
Zach Daniel 2023-12-29 21:43:15 -05:00
parent b600b23803
commit 44761e7e3f
11 changed files with 69 additions and 25 deletions

View file

@ -903,7 +903,7 @@ defmodule Ash.Actions.Create.Bulk do
must_return_records_for_changes?, must_return_records_for_changes?,
tenant: opts[:tenant] tenant: opts[:tenant]
}) })
|> Ash.Actions.Helpers.rollback_if_in_transaction(nil) |> Ash.Actions.Helpers.rollback_if_in_transaction(resource, nil)
|> Enum.flat_map(fn |> Enum.flat_map(fn
{:ok, result} -> {:ok, result} ->
[result] [result]
@ -927,7 +927,7 @@ defmodule Ash.Actions.Create.Bulk do
tracer: opts[:tracer], tracer: opts[:tracer],
api: api api: api
}) })
|> Ash.Actions.Helpers.rollback_if_in_transaction(nil) |> Ash.Actions.Helpers.rollback_if_in_transaction(resource, nil)
case result do case result do
{:ok, result} -> {:ok, result} ->
@ -965,7 +965,7 @@ defmodule Ash.Actions.Create.Bulk do
tenant: opts[:tenant] tenant: opts[:tenant]
} }
) )
|> Ash.Actions.Helpers.rollback_if_in_transaction(nil) |> Ash.Actions.Helpers.rollback_if_in_transaction(resource, nil)
else else
[changeset] = batch [changeset] = batch
upsert? = opts[:upsert?] || action.upsert? || false upsert? = opts[:upsert?] || action.upsert? || false

View file

@ -337,7 +337,10 @@ defmodule Ash.Actions.Create do
opts[:upsert?] -> opts[:upsert?] ->
changeset.resource changeset.resource
|> Ash.DataLayer.upsert(changeset, upsert_keys) |> Ash.DataLayer.upsert(changeset, upsert_keys)
|> Ash.Actions.Helpers.rollback_if_in_transaction(changeset) |> Ash.Actions.Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset) |> add_tenant(changeset)
|> manage_relationships(api, changeset, |> manage_relationships(api, changeset,
actor: opts[:actor], actor: opts[:actor],
@ -348,7 +351,10 @@ defmodule Ash.Actions.Create do
true -> true ->
changeset.resource changeset.resource
|> Ash.DataLayer.create(changeset) |> Ash.DataLayer.create(changeset)
|> Ash.Actions.Helpers.rollback_if_in_transaction(changeset) |> Ash.Actions.Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset) |> add_tenant(changeset)
|> manage_relationships(api, changeset, |> manage_relationships(api, changeset,
actor: opts[:actor], actor: opts[:actor],

View file

@ -182,7 +182,7 @@ defmodule Ash.Actions.Destroy do
else else
changeset.resource changeset.resource
|> Ash.DataLayer.destroy(changeset) |> Ash.DataLayer.destroy(changeset)
|> Ash.Actions.Helpers.rollback_if_in_transaction(changeset) |> Ash.Actions.Helpers.rollback_if_in_transaction(changeset.resource, changeset)
|> case do |> case do
:ok -> :ok ->
{:ok, data} = Ash.Changeset.apply_attributes(changeset, force?: true) {:ok, data} = Ash.Changeset.apply_attributes(changeset, force?: true)

View file

@ -3,23 +3,28 @@ defmodule Ash.Actions.Helpers do
require Logger require Logger
require Ash.Flags require Ash.Flags
def rollback_if_in_transaction({:error, error}, changeset) do def rollback_if_in_transaction({:error, error}, resource, changeset) do
if Ash.DataLayer.in_transaction?(changeset.resource) do if Ash.DataLayer.in_transaction?(resource) do
if changeset do case changeset do
Ash.DataLayer.rollback(changeset.resource, Ash.Changeset.add_error(changeset, error)) %Ash.Changeset{} = changeset ->
else Ash.DataLayer.rollback(resource, Ash.Changeset.add_error(changeset, error))
Ash.DataLayer.rollback(changeset.resource, Ash.Error.to_error_class(error))
%Ash.Query{} = query ->
Ash.DataLayer.rollback(resource, Ash.Query.add_error(query, error))
_ ->
Ash.DataLayer.rollback(resource, Ash.Error.to_error_class(error))
end end
else else
{:error, error} {:error, error}
end end
end end
def rollback_if_in_transaction({:error, :no_rollback, error}, _changeset) do def rollback_if_in_transaction({:error, :no_rollback, error}, _, _changeset) do
{:error, error} {:error, error}
end end
def rollback_if_in_transaction(success, _), do: success def rollback_if_in_transaction(success, _, _), do: success
def validate_calculation_load!(%{__struct__: Ash.Query}, module) do def validate_calculation_load!(%{__struct__: Ash.Query}, module) do
raise """ raise """

View file

@ -1405,7 +1405,10 @@ defmodule Ash.Actions.Read do
]) ])
|> Map.put(:context, ash_query.context) |> Map.put(:context, ash_query.context)
|> Ash.Query.set_context(%{action: ash_query.action}) |> Ash.Query.set_context(%{action: ash_query.action})
|> Ash.Query.data_layer_query(only_validate_filter?: true), |> Ash.Query.data_layer_query(
only_validate_filter?: true,
run_return_query?: false
),
{:ok, filter} <- {:ok, filter} <-
filter_with_related( filter_with_related(
Enum.map(filter_requests, & &1.path), Enum.map(filter_requests, & &1.path),
@ -1450,7 +1453,11 @@ defmodule Ash.Actions.Read do
ash_query.resource ash_query.resource
), ),
{:ok, query} <- {:ok, query} <-
Ash.DataLayer.sort(query, ash_query.sort, ash_query.resource), Ash.DataLayer.sort(
query,
ash_query.sort,
ash_query.resource
),
{:ok, query} <- {:ok, query} <-
Ash.DataLayer.distinct_sort(query, ash_query.distinct_sort, ash_query.resource), Ash.DataLayer.distinct_sort(query, ash_query.distinct_sort, ash_query.resource),
{:ok, query} <- {:ok, query} <-
@ -2947,6 +2954,7 @@ defmodule Ash.Actions.Read do
else else
query query
|> Ash.DataLayer.run_query(resource) |> Ash.DataLayer.run_query(resource)
|> Helpers.rollback_if_in_transaction(ash_query.resource, ash_query)
|> Helpers.select(ash_query) |> Helpers.select(ash_query)
|> Helpers.load_runtime_types(ash_query, load_attributes?) |> Helpers.load_runtime_types(ash_query, load_attributes?)
end end

View file

@ -272,7 +272,10 @@ defmodule Ash.Actions.Update do
changeset.resource changeset.resource
|> Ash.DataLayer.update(changeset) |> Ash.DataLayer.update(changeset)
|> Ash.Actions.Helpers.rollback_if_in_transaction(changeset) |> Ash.Actions.Helpers.rollback_if_in_transaction(
changeset.resource,
changeset
)
|> add_tenant(changeset) |> add_tenant(changeset)
|> manage_relationships(api, changeset, |> manage_relationships(api, changeset,
actor: opts[:actor], actor: opts[:actor],

View file

@ -1030,7 +1030,10 @@ defmodule Ash.Api do
|> Ash.Query.data_layer_query() |> Ash.Query.data_layer_query()
|> case do |> case do
{:ok, data_layer_query} -> {:ok, data_layer_query} ->
case Ash.DataLayer.run_query(data_layer_query, query.resource) do data_layer_query
|> Ash.DataLayer.run_query(query.resource)
|> Ash.Actions.Helpers.rollback_if_in_transaction(query.resource, query)
|> case do
{:ok, results} -> {:ok, results} ->
if Enum.count(results) == Enum.count(data) do if Enum.count(results) == Enum.count(data) do
{:ok, true} {:ok, true}
@ -1065,7 +1068,10 @@ defmodule Ash.Api do
|> Ash.Query.data_layer_query() |> Ash.Query.data_layer_query()
|> case do |> case do
{:ok, data_layer_query} -> {:ok, data_layer_query} ->
case Ash.DataLayer.run_query(data_layer_query, resource) do data_layer_query
|> Ash.DataLayer.run_query(resource)
|> Ash.Actions.Helpers.rollback_if_in_transaction(query.resource, query)
|> case do
{:ok, [_]} -> {:ok, [_]} ->
{:ok, true} {:ok, true}

View file

@ -378,13 +378,13 @@ defmodule Ash.DataLayer do
end end
@spec update(Ash.Resource.t(), Ash.Changeset.t()) :: @spec update(Ash.Resource.t(), Ash.Changeset.t()) ::
{:ok, Ash.Resource.record()} | {:error, term} {:ok, Ash.Resource.record()} | {:error, term} | {:error, :no_rollback, term}
def update(resource, changeset) do def update(resource, changeset) do
Ash.DataLayer.data_layer(resource).update(resource, changeset) Ash.DataLayer.data_layer(resource).update(resource, changeset)
end end
@spec create(Ash.Resource.t(), Ash.Changeset.t()) :: @spec create(Ash.Resource.t(), Ash.Changeset.t()) ::
{:ok, Ash.Resource.record()} | {:error, term} {:ok, Ash.Resource.record()} | {:error, term} | {:error, :no_rollback, term}
def create(resource, changeset) do def create(resource, changeset) do
Ash.DataLayer.data_layer(resource).create(resource, changeset) Ash.DataLayer.data_layer(resource).create(resource, changeset)
end end
@ -409,11 +409,13 @@ defmodule Ash.DataLayer do
:ok :ok
| {:ok, Enumerable.t(Ash.Resource.record())} | {:ok, Enumerable.t(Ash.Resource.record())}
| {:error, Ash.Error.t()} | {:error, Ash.Error.t()}
| {:error, :no_rollback, Ash.Error.t()}
def bulk_create(resource, changesets, options) do def bulk_create(resource, changesets, options) do
Ash.DataLayer.data_layer(resource).bulk_create(resource, changesets, options) Ash.DataLayer.data_layer(resource).bulk_create(resource, changesets, options)
end end
@spec destroy(Ash.Resource.t(), Ash.Changeset.t()) :: :ok | {:error, term} @spec destroy(Ash.Resource.t(), Ash.Changeset.t()) ::
:ok | {:error, term} | {:error, :no_rollback, term}
def destroy(resource, changeset) do def destroy(resource, changeset) do
Ash.DataLayer.data_layer(resource).destroy(resource, changeset) Ash.DataLayer.data_layer(resource).destroy(resource, changeset)
end end
@ -615,7 +617,7 @@ defmodule Ash.DataLayer do
end end
@spec run_query(data_layer_query(), central_resource :: Ash.Resource.t()) :: @spec run_query(data_layer_query(), central_resource :: Ash.Resource.t()) ::
{:ok, list(Ash.Resource.record())} | {:error, term} {:ok, list(Ash.Resource.record())} | {:error, term} | {:error, :no_rollback, term}
def run_query(query, central_resource) do def run_query(query, central_resource) do
Ash.DataLayer.data_layer(central_resource).run_query(query, central_resource) Ash.DataLayer.data_layer(central_resource).run_query(query, central_resource)
end end

View file

@ -919,6 +919,7 @@ defmodule Ash.Engine.Request do
{:ok, data_layer_query} -> {:ok, data_layer_query} ->
data_layer_query data_layer_query
|> Ash.DataLayer.run_query(request.resource) |> Ash.DataLayer.run_query(request.resource)
|> Ash.Actions.Helpers.rollback_if_in_transaction(request.resource, new_query)
|> case do |> case do
{:ok, results} -> {:ok, results} ->
pkey = Ash.Resource.Info.primary_key(request.resource) pkey = Ash.Resource.Info.primary_key(request.resource)
@ -971,6 +972,10 @@ defmodule Ash.Engine.Request do
{:ok, data_layer_query} -> {:ok, data_layer_query} ->
data_layer_query data_layer_query
|> Ash.DataLayer.run_query(request.resource) |> Ash.DataLayer.run_query(request.resource)
|> Ash.Actions.Helpers.rollback_if_in_transaction(
request.resource,
query_with_pkey_filter
)
|> case do |> case do
{:ok, []} -> {:ok, []} ->
{:error, {:error,

View file

@ -588,7 +588,8 @@ defmodule Ash.Query.Aggregate do
Ash.DataLayer.run_query( Ash.DataLayer.run_query(
data_layer_query, data_layer_query,
query.resource query.resource
) do )
|> Ash.Actions.Helpers.rollback_if_in_transaction(query.resource, query) do
loaded_aggregates = loaded_aggregates =
aggregates aggregates
|> Enum.map(& &1.load) |> Enum.map(& &1.load)

View file

@ -2627,7 +2627,7 @@ defmodule Ash.Query do
{:ok, query} <- {:ok, query} <-
Ash.DataLayer.offset(query, ash_query.offset, resource), Ash.DataLayer.offset(query, ash_query.offset, resource),
{:ok, query} <- Ash.DataLayer.lock(query, ash_query.lock, resource), {:ok, query} <- Ash.DataLayer.lock(query, ash_query.lock, resource),
{:ok, query} <- Ash.DataLayer.return_query(query, resource) do {:ok, query} <- maybe_return_query(query, resource, opts) do
if opts[:no_modify?] || !ash_query.action || !ash_query.action.modify_query do if opts[:no_modify?] || !ash_query.action || !ash_query.action.modify_query do
{:ok, query} {:ok, query}
else else
@ -2644,6 +2644,14 @@ defmodule Ash.Query do
end end
end end
defp maybe_return_query(query, resource, opts) do
if Keyword.get(opts, :run_return_query?, true) do
Ash.DataLayer.return_query(query, resource)
else
{:ok, query}
end
end
defp add_tenant(query, ash_query) do defp add_tenant(query, ash_query) do
with :context <- Ash.Resource.Info.multitenancy_strategy(ash_query.resource), with :context <- Ash.Resource.Info.multitenancy_strategy(ash_query.resource),
tenant when not is_nil(tenant) <- ash_query.tenant, tenant when not is_nil(tenant) <- ash_query.tenant,