Skip to content

Commit d46b9bd

Browse files
author
José Valim
committed
Introduce new multiple primary key error
1 parent 6a4a0d8 commit d46b9bd

File tree

3 files changed

+44
-34
lines changed

3 files changed

+44
-34
lines changed

integration_test/sql/sql.exs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,15 @@ defmodule Ecto.Integration.SQLTest do
4343
assert sql =~ "barebones"
4444
end
4545

46-
test "struct/7 raises when primary key is not unique" do
46+
test "raises when primary key is not unique on struct operation" do
4747
schema = %CorruptedPk{a: "abc"}
48-
for _ <- 1..10, do: TestRepo.insert!(schema)
48+
TestRepo.insert!(schema)
49+
TestRepo.insert!(schema)
50+
TestRepo.insert!(schema)
4951

50-
assert_raise Ecto.MultipleResultsError, ~s|expected at most one result but got 10 in query:\n\n| <>
51-
~s|from c in Ecto.Integration.CorruptedPk,\n| <>
52-
~s| where: c.a == ^\"abc\"\n|, fn ->
53-
TestRepo.get(CorruptedPk, "abc")
54-
end
55-
56-
assert %CorruptedPk{a: "abc"} = schema |> Ecto.Changeset.change(force: true) |> TestRepo.update!()
57-
58-
assert_raise Ecto.MultipleResultsError, fn ->
59-
TestRepo.delete!(schema)
60-
end
52+
assert_raise Ecto.MultiplePrimaryKeyError,
53+
~r|expected delete on corrupted_pk to return at most one entry but got 3 entries|,
54+
fn -> TestRepo.delete!(schema) end
6155
end
6256

6357
test "Repo.insert! escape" do

lib/ecto/adapters/sql.ex

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,22 @@ defmodule Ecto.Adapters.SQL do
8888
{kind, conflict_params, _} = on_conflict, returning, opts) do
8989
{fields, values} = :lists.unzip(params)
9090
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, returning)
91-
Ecto.Adapters.SQL.struct(repo, @conn, sql, values ++ conflict_params, kind, returning, opts)
91+
Ecto.Adapters.SQL.struct(repo, @conn, sql, {:insert, source, []}, values ++ conflict_params, kind, returning, opts)
9292
end
9393

9494
@doc false
95-
def update(repo, %{source: {prefix, source}}, fields, filter, returning, opts) do
95+
def update(repo, %{source: {prefix, source}}, fields, params, returning, opts) do
9696
{fields, values1} = :lists.unzip(fields)
97-
{filter, values2} = :lists.unzip(filter)
97+
{filter, values2} = :lists.unzip(params)
9898
sql = @conn.update(prefix, source, fields, filter, returning)
99-
Ecto.Adapters.SQL.struct(repo, @conn, sql, values1 ++ values2, :raise, returning, opts)
99+
Ecto.Adapters.SQL.struct(repo, @conn, sql, {:update, source, params}, values1 ++ values2, :raise, returning, opts)
100100
end
101101

102102
@doc false
103-
def delete(repo, %{source: {prefix, source}}, filter, opts) do
104-
{filter, values} = :lists.unzip(filter)
103+
def delete(repo, %{source: {prefix, source}}, params, opts) do
104+
{filter, values} = :lists.unzip(params)
105105
sql = @conn.delete(prefix, source, filter, [])
106-
Ecto.Adapters.SQL.struct(repo, @conn, sql, values, :raise, [], opts)
106+
Ecto.Adapters.SQL.struct(repo, @conn, sql, {:delete, source, params}, values, :raise, [], opts)
107107
end
108108

109109
## Transaction
@@ -551,16 +551,17 @@ defmodule Ecto.Adapters.SQL do
551551
end
552552

553553
@doc false
554-
def struct(repo, conn, sql, values, on_conflict, returning, opts) do
554+
def struct(repo, conn, sql, {operation, source, params}, values, on_conflict, returning, opts) do
555555
case query(repo, sql, values, fn x -> x end, opts) do
556556
{:ok, %{rows: nil, num_rows: 1}} ->
557557
{:ok, []}
558558
{:ok, %{rows: [values], num_rows: 1}} ->
559559
{:ok, Enum.zip(returning, values)}
560560
{:ok, %{num_rows: 0}} ->
561561
if on_conflict == :nothing, do: {:ok, []}, else: {:error, :stale}
562-
{:ok, %{rows: nil, num_rows: num_rows}} when num_rows > 1 ->
563-
raise Ecto.MultipleResultsError, sql_query: sql, count: num_rows
562+
{:ok, %{num_rows: num_rows}} when num_rows > 1 ->
563+
raise Ecto.MultiplePrimaryKeyError,
564+
source: source, params: params, count: num_rows, operation: operation
564565
{:error, err} ->
565566
case conn.to_constraints(err) do
566567
[] -> raise err

lib/ecto/exceptions.ex

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,22 +204,37 @@ defmodule Ecto.MultipleResultsError do
204204
defexception [:message]
205205

206206
def exception(opts) do
207-
sql_query = Keyword.get(opts, :sql_query)
207+
query = Keyword.fetch!(opts, :queryable) |> Ecto.Queryable.to_query
208208
count = Keyword.fetch!(opts, :count)
209-
query =
210-
if sql_query do
211-
sql_query
212-
else
213-
opts
214-
|> Keyword.fetch!(:queryable)
215-
|> Ecto.Queryable.to_query()
216-
|> Inspect.Ecto.Query.to_string()
217-
end
218209

219210
msg = """
220211
expected at most one result but got #{count} in query:
221212
222-
#{query}
213+
#{Inspect.Ecto.Query.to_string(query)}
214+
"""
215+
216+
%__MODULE__{message: msg}
217+
end
218+
end
219+
220+
defmodule Ecto.MultiplePrimaryKeyError do
221+
defexception [:message]
222+
223+
def exception(opts) do
224+
operation = Keyword.fetch!(opts, :operation)
225+
source = Keyword.fetch!(opts, :source)
226+
params = Keyword.fetch!(opts, :params)
227+
count = Keyword.fetch!(opts, :count)
228+
229+
msg = """
230+
expected #{operation} on #{source} to return at most one entry but got #{count} entries.
231+
232+
This typically means the field(s) set as primary_key in your schema/source
233+
are not enough to uniquely identify entries in the repository.
234+
235+
Those are the parameters sent to the repository:
236+
237+
#{inspect params}
223238
"""
224239

225240
%__MODULE__{message: msg}

0 commit comments

Comments
 (0)