improvement: support ash main upsert_condition logic

This commit is contained in:
Zach Daniel 2024-09-03 18:02:14 -04:00
parent 7285b3382e
commit 2015aa658c
4 changed files with 82 additions and 1 deletions

View file

@ -1930,7 +1930,8 @@ defmodule AshPostgres.DataLayer do
end)
end
defp get_source_for_upsert_field(field, resource) do
@doc false
def get_source_for_upsert_field(field, resource) do
case Ash.Resource.Info.attribute(resource, field) do
%{source: source} when not is_nil(source) ->
source

View file

@ -41,6 +41,33 @@ defmodule AshPostgres.SqlImplementation do
{:ok, Ecto.Query.dynamic(fragment("'[]'::jsonb")), acc}
end
def expr(
query,
%Ash.Query.UpsertConflict{attribute: attribute},
_bindings,
_embedded?,
acc,
_type
) do
query.__ash_bindings__.resource
{:ok,
Ecto.Query.dynamic(
[],
fragment(
"EXCLUDED.?",
literal(
^to_string(
AshPostgres.DataLayer.get_source_for_upsert_field(
attribute,
query.__ash_bindings__.resource
)
)
)
)
), acc}
end
def expr(query, %AshPostgres.Functions.Binding{}, _bindings, _embedded?, acc, _type) do
binding =
AshSql.Bindings.get_binding(

View file

@ -2,6 +2,8 @@ defmodule AshPostgres.BulkCreateTest do
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Post, Record}
import Ash.Expr
describe "bulk creates" do
test "bulk creates insert each input" do
Ash.bulk_create!([%{title: "fred"}, %{title: "george"}], Post, :create)
@ -109,6 +111,51 @@ defmodule AshPostgres.BulkCreateTest do
end)
end
test "bulk upsert skips with upsert_condition" do
assert [
{:ok, %{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10}},
{:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20}},
{:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}}
] =
Ash.bulk_create!(
[
%{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10},
%{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20},
%{title: "herbert", uniq_if_contains_foo: "3", price: 30}
],
Post,
:create,
return_stream?: true,
return_records?: true
)
|> Enum.sort_by(fn {:ok, result} -> result.title end)
assert [
{:ok, %{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000}},
{:ok, %{title: "herbert", uniq_if_contains_foo: "3", price: 30}}
] =
Ash.bulk_create!(
[
%{title: "fredfoo", uniq_if_contains_foo: "1foo", price: 10},
%{title: "georgefoo", uniq_if_contains_foo: "2foo", price: 20_000},
%{title: "herbert", uniq_if_contains_foo: "3", price: 30}
],
Post,
:upsert_with_no_filter,
return_stream?: true,
upsert_condition: expr(price != upsert_conflict(:price)),
return_errors?: true,
return_records?: true
)
|> Enum.sort_by(fn
{:ok, result} ->
result.title
_ ->
nil
end)
end
# confirmed that this doesn't work because it can't. An upsert must map to a potentially successful insert.
# leaving this test here for posterity
# test "bulk creates can upsert with id" do

View file

@ -278,6 +278,12 @@ defmodule AshPostgres.Test.Post do
end)
end
create :upsert_with_no_filter do
upsert?(true)
upsert_identity(:uniq_if_contains_foo)
upsert_fields([:price])
end
update :set_title_from_author do
change(atomic_update(:title, expr(author.first_name)))
end