Skip to content

Commit db12e07

Browse files
authored
upserts: offer guidance when :replace_all or :replace_all_except produce an empty-list of fields to replace (#4623)
1 parent 5c6c65e commit db12e07

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

lib/ecto/repo/schema.ex

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -914,11 +914,19 @@ defmodule Ecto.Repo.Schema do
914914
# since the values don't change and this allows postgres to
915915
# possibly perform a HOT optimization: https://www.postgresql.org/docs/current/storage-hot.html
916916
to_remove = List.wrap(conflict_target)
917-
{{replace_all_fields!(:replace_all, schema, to_remove), [], conflict_target}, []}
917+
replace = replace_all_fields!(:replace_all, schema, to_remove)
918+
919+
if replace == [], do: raise(ArgumentError, "empty list of fields to update, use the `:replace` option instead")
920+
921+
{{replace, [], conflict_target}, []}
918922

919923
{:replace_all_except, fields} ->
920924
to_remove = List.wrap(conflict_target) ++ fields
921-
{{replace_all_fields!(:replace_all_except, schema, to_remove), [], conflict_target}, []}
925+
replace = replace_all_fields!(:replace_all_except, schema, to_remove)
926+
927+
if replace == [], do: raise(ArgumentError, "empty list of fields to update, use the `:replace` option instead")
928+
929+
{{replace, [], conflict_target}, []}
922930

923931
[_ | _] = on_conflict ->
924932
from = if schema, do: {source, schema}, else: source

test/ecto/repo_test.exs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ defmodule Ecto.RepoTest do
181181
end
182182
end
183183

184+
defmodule MySchemaOneField do
185+
use Ecto.Schema
186+
187+
@primary_key false
188+
schema "my_schema" do
189+
field :n, :integer
190+
end
191+
end
192+
184193
test "defines child_spec/1" do
185194
assert TestRepo.child_spec([]) == %{
186195
id: TestRepo,
@@ -1929,12 +1938,34 @@ defmodule Ecto.RepoTest do
19291938
assert_received {:insert, %{source: "my_schema", on_conflict: {^fields, [], [:id]}}}
19301939
end
19311940

1941+
test "raises on empty-list of fields to update when :replace_all_except is given" do
1942+
msg = "empty list of fields to update, use the `:replace` option instead"
1943+
1944+
assert_raise ArgumentError, msg, fn ->
1945+
TestRepo.insert(%MySchema{id: 1},
1946+
on_conflict: {:replace_all_except, [:array, :map, :z, :y, :x]},
1947+
conflict_target: [:id]
1948+
)
1949+
end
1950+
end
1951+
19321952
test "excludes conflict target from :replace_all" do
19331953
fields = [:map, :array, :z, :yyy, :x]
19341954
TestRepo.insert(%MySchema{id: 1}, on_conflict: :replace_all, conflict_target: [:id])
19351955
assert_received {:insert, %{source: "my_schema", on_conflict: {^fields, [], [:id]}}}
19361956
end
19371957

1958+
test "raises on empty-list of fields to update when :replace_all is given" do
1959+
msg = "empty list of fields to update, use the `:replace` option instead"
1960+
1961+
assert_raise ArgumentError, msg, fn ->
1962+
TestRepo.insert(%MySchemaOneField{n: 1},
1963+
on_conflict: :replace_all,
1964+
conflict_target: [:n]
1965+
)
1966+
end
1967+
end
1968+
19381969
test "converts keyword list into query" do
19391970
TestRepo.insert(%MySchema{id: 1}, on_conflict: [set: [x: "123", y: "456"]])
19401971
assert_received {:insert, %{source: "my_schema", on_conflict: {query, ["123", "456"], []}}}

0 commit comments

Comments
 (0)