mirror of
https://github.com/ash-project/ash_postgres.git
synced 2024-09-20 05:23:18 +12:00
improvement: identities w/ calculations and where clauses in upserts
This commit is contained in:
parent
0db1b29c23
commit
97e538bf63
4 changed files with 90 additions and 16 deletions
|
@ -1721,6 +1721,7 @@ defmodule AshPostgres.DataLayer do
|
||||||
:conflict_target,
|
:conflict_target,
|
||||||
conflict_target(
|
conflict_target(
|
||||||
resource,
|
resource,
|
||||||
|
options[:identity],
|
||||||
options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)
|
options[:upsert_keys] || Ash.Resource.Info.primary_key(resource)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -2469,7 +2470,7 @@ defmodule AshPostgres.DataLayer do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def upsert(resource, changeset, keys \\ nil) do
|
def upsert(resource, changeset, keys, identity) do
|
||||||
if AshPostgres.DataLayer.Info.manage_tenant_update?(resource) do
|
if AshPostgres.DataLayer.Info.manage_tenant_update?(resource) do
|
||||||
{:error, "Cannot currently upsert a resource that owns a tenant"}
|
{:error, "Cannot currently upsert a resource that owns a tenant"}
|
||||||
else
|
else
|
||||||
|
@ -2491,6 +2492,7 @@ defmodule AshPostgres.DataLayer do
|
||||||
single?: true,
|
single?: true,
|
||||||
upsert?: true,
|
upsert?: true,
|
||||||
tenant: changeset.tenant,
|
tenant: changeset.tenant,
|
||||||
|
identity: identity,
|
||||||
upsert_keys: keys,
|
upsert_keys: keys,
|
||||||
upsert_fields: upsert_fields,
|
upsert_fields: upsert_fields,
|
||||||
return_records?: true
|
return_records?: true
|
||||||
|
@ -2504,23 +2506,73 @@ defmodule AshPostgres.DataLayer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp conflict_target(resource, keys) do
|
defp conflict_target(resource, identity, keys) do
|
||||||
if Ash.Resource.Info.base_filter(resource) do
|
identity_where =
|
||||||
|
case identity do
|
||||||
|
%{name: name, where: where} when not is_nil(where) ->
|
||||||
|
AshPostgres.DataLayer.Info.identity_where_to_sql(resource, name) ||
|
||||||
|
raise(
|
||||||
|
"Must provide an entry for :#{identity.name} in `postgres.identity_wheres_to_sql` to use it as an upsert_identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
base_filter_sql =
|
base_filter_sql =
|
||||||
|
case Ash.Resource.Info.base_filter(resource) do
|
||||||
|
nil ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
_base_filter ->
|
||||||
AshPostgres.DataLayer.Info.base_filter_sql(resource) ||
|
AshPostgres.DataLayer.Info.base_filter_sql(resource) ||
|
||||||
raise """
|
raise """
|
||||||
Cannot use upserts with resources that have a base_filter without also adding `base_filter_sql` in the postgres section.
|
Cannot use upserts with resources that have a base_filter without also adding `base_filter_sql` in the postgres section.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sources =
|
|
||||||
Enum.map(keys, fn key ->
|
|
||||||
~s("#{Ash.Resource.Info.attribute(resource, key).source || key}")
|
|
||||||
end)
|
|
||||||
|
|
||||||
{:unsafe_fragment, "(" <> Enum.join(sources, ", ") <> ") WHERE (#{base_filter_sql})"}
|
|
||||||
else
|
|
||||||
keys
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
where =
|
||||||
|
case {base_filter_sql, identity_where} do
|
||||||
|
{nil, nil} ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
{base_filter_sql, nil} ->
|
||||||
|
" WHERE (#{base_filter_sql})"
|
||||||
|
|
||||||
|
{nil, identity_where} ->
|
||||||
|
" WHERE (#{identity_where})"
|
||||||
|
|
||||||
|
{base_filter_sql, identity_where} ->
|
||||||
|
" WHERE ((#{base_filter_sql}) AND (#{identity_where}))"
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_nil(where) && Enum.all?(keys, &Ash.Resource.Info.attribute(resource, &1)) do
|
||||||
|
keys
|
||||||
|
else
|
||||||
|
sources = sources_to_sql(resource, keys)
|
||||||
|
{:unsafe_fragment, "(" <> Enum.join(sources, ", ") <> ")#{where}"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sources_to_sql(resource, keys) do
|
||||||
|
Enum.map(keys, fn key ->
|
||||||
|
case Ash.Resource.Info.field(resource, key) do
|
||||||
|
%Ash.Resource.Attribute{source: source, name: name} ->
|
||||||
|
~s("#{source || name}")
|
||||||
|
|
||||||
|
%Ash.Resource.Calculation{name: name} ->
|
||||||
|
if sql = AshPostgres.DataLayer.Info.calculations_to_sql(name) do
|
||||||
|
"(" <> sql <> ")"
|
||||||
|
else
|
||||||
|
raise ArgumentError,
|
||||||
|
"Calculation #{inspect(key)} used in `AshPostgres.DataLayer` conflict target must have its sql defined in `calculations_to_sql`"
|
||||||
|
end
|
||||||
|
|
||||||
|
_other ->
|
||||||
|
raise ArgumentError,
|
||||||
|
"Unsupported field #{inspect(key)} used in `AshPostgres.DataLayer` conflict target"
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_defaults(resource) do
|
defp update_defaults(resource) do
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -162,7 +162,7 @@ defmodule AshPostgres.MixProject do
|
||||||
# Run "mix help deps" to learn about dependencies.
|
# Run "mix help deps" to learn about dependencies.
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:ash, ash_version("~> 3.0 and >= 3.0.7")},
|
{:ash, ash_version("~> 3.0 and >= 3.0.13")},
|
||||||
{:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.4")},
|
{:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.4")},
|
||||||
{:ecto_sql, "~> 3.9"},
|
{:ecto_sql, "~> 3.9"},
|
||||||
{:ecto, "~> 3.9"},
|
{:ecto, "~> 3.9"},
|
||||||
|
|
2
mix.lock
2
mix.lock
|
@ -43,7 +43,7 @@
|
||||||
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
|
||||||
"sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"},
|
"sourceror": {:hex, :sourceror, "1.3.0", "70ab9e8bf6df085a1effba4b49ad621b7153b065f69ef6cdb82e6088f2026029", [:mix], [], "hexpm", "1794c3ceeca4eb3f9437261721e4d9cbf846d7c64c7aee4f64062b18d5ce1eac"},
|
||||||
"spark": {:hex, :spark, "2.2.1", "b4ec56c99b3a750f7db0d2651fc59dc59c4002a5e0d658dde968fc69bd451136", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a6052f08ebe1ef152f86489151401210755bd81a9f0cbd737dd50819c65a0fc1"},
|
"spark": {:hex, :spark, "2.2.1", "b4ec56c99b3a750f7db0d2651fc59dc59c4002a5e0d658dde968fc69bd451136", [:mix], [{:igniter, ">= 0.1.7 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a6052f08ebe1ef152f86489151401210755bd81a9f0cbd737dd50819c65a0fc1"},
|
||||||
"spitfire": {:hex, :spitfire, "0.1.1", "249c8ea38d9e313e636670f2f692df5158b231e5986cbd13390498741d33cc0b", [:mix], [], "hexpm", "13782a1f902bfa52f5058d71752f4aa78c6056667a66bda5804480b7355e3aa9"},
|
"spitfire": {:hex, :spitfire, "0.1.2", "49b85d59c170d671e7e49649f62f6fe0771743a61bc42bd7a203f98f322d99e2", [:mix], [], "hexpm", "21f04cf02df601d75e1551e393ee5c927e3986fbb7598f3e59f71d4ca544fd9b"},
|
||||||
"splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"},
|
"splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"},
|
||||||
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
|
||||||
"stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"},
|
"stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"},
|
||||||
|
|
|
@ -42,4 +42,26 @@ defmodule AshPostgres.Test.UniqueIdentityTest do
|
||||||
assert new_post.id == post.id
|
assert new_post.id == post.id
|
||||||
assert new_post.price == 10
|
assert new_post.price == 10
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "a unique constraint can be used to upsert when backed by a calculation" do
|
||||||
|
post =
|
||||||
|
Post
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
title: "title",
|
||||||
|
uniq_if_contains_foo: "abcfoodef",
|
||||||
|
price: 10
|
||||||
|
})
|
||||||
|
|> Ash.create!()
|
||||||
|
|
||||||
|
new_post =
|
||||||
|
Post
|
||||||
|
|> Ash.Changeset.for_create(:create, %{
|
||||||
|
title: "title2",
|
||||||
|
uniq_if_contains_foo: "abcfoodef"
|
||||||
|
})
|
||||||
|
|> Ash.create!(upsert?: true, upsert_identity: :uniq_if_contains_foo)
|
||||||
|
|
||||||
|
assert new_post.id == post.id
|
||||||
|
assert new_post.price == 10
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue