Skip to content

Commit 0f023cf

Browse files
committed
remove MyRepo.to_constraints/3, simplify constraint handler tests
1 parent 6b99c3d commit 0f023cf

5 files changed

Lines changed: 54 additions & 381 deletions

File tree

integration_test/myxql/constraints_test.exs

Lines changed: 26 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,7 @@ defmodule Ecto.Integration.ConstraintsTest do
44
import Ecto.Migrator, only: [up: 4]
55
alias Ecto.Integration.PoolRepo
66

7-
defmodule CustomConstraintHandler do
8-
@quotes ~w(" ' `)
9-
10-
# An example of a custom handler a user might write.
11-
# Handles custom MySQL signal exceptions from triggers,
12-
# falling back to the default handler.
13-
def to_constraints(%MyXQL.Error{mysql: %{name: :ER_SIGNAL_EXCEPTION}, message: message}, opts) do
14-
# Assumes this is the only use-case of `ER_SIGNAL_EXCEPTION` the user has implemented custom errors for
15-
with [_, quoted] <- :binary.split(message, "Overlapping values for key "),
16-
[_, index | _] <- :binary.split(quoted, @quotes, [:global]) do
17-
[exclusion: strip_source(index, opts[:source])]
18-
else
19-
_ -> []
20-
end
21-
end
22-
23-
def to_constraints(err, opts) do
24-
# Falls back to default `ecto_sql` handler for all others
25-
Ecto.Adapters.MyXQL.Connection.to_constraints(err, opts)
26-
end
27-
28-
defp strip_source(name, nil), do: name
29-
defp strip_source(name, source), do: String.trim_leading(name, "#{source}.")
30-
end
31-
32-
defmodule ConstraintTableMigration do
7+
defmodule ConstraintMigration do
338
use Ecto.Migration
349

3510
@table table(:constraints_test)
@@ -40,67 +15,12 @@ defmodule Ecto.Integration.ConstraintsTest do
4015
add :from, :integer
4116
add :to, :integer
4217
end
43-
end
44-
end
45-
46-
defmodule CheckConstraintMigration do
47-
use Ecto.Migration
4818

49-
@table table(:constraints_test)
50-
51-
def change do
5219
# Only valid after MySQL 8.0.19
5320
create constraint(@table.name, :positive_price, check: "price > 0")
5421
end
5522
end
5623

57-
defmodule TriggerEmulatingConstraintMigration do
58-
use Ecto.Migration
59-
60-
@table_name :constraints_test
61-
62-
def up do
63-
insert_trigger_sql = trigger_sql(@table_name, "INSERT")
64-
update_trigger_sql = trigger_sql(@table_name, "UPDATE")
65-
66-
drop_triggers(@table_name)
67-
repo().query!(insert_trigger_sql)
68-
repo().query!(update_trigger_sql)
69-
end
70-
71-
def down do
72-
drop_triggers(@table_name)
73-
end
74-
75-
# FOR EACH ROW, not a great example performance-wise,
76-
# but demonstrates the feature
77-
defp trigger_sql(table_name, before_type) do
78-
~s"""
79-
CREATE TRIGGER #{table_name}_#{String.downcase(before_type)}_overlap
80-
BEFORE #{String.upcase(before_type)}
81-
ON #{table_name}
82-
FOR EACH ROW
83-
BEGIN
84-
DECLARE v_rowcount INT;
85-
DECLARE v_msg VARCHAR(200);
86-
87-
SELECT COUNT(*) INTO v_rowcount FROM #{table_name}
88-
WHERE (NEW.from <= `to` AND NEW.to >= `from`);
89-
90-
IF v_rowcount > 0 THEN
91-
SET v_msg = CONCAT('Overlapping values for key \\'#{table_name}.cannot_overlap\\'');
92-
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = v_msg, MYSQL_ERRNO = 1644;
93-
END IF;
94-
END;
95-
"""
96-
end
97-
98-
defp drop_triggers(table_name) do
99-
repo().query!("DROP TRIGGER IF EXISTS #{table_name}_insert_overlap")
100-
repo().query!("DROP TRIGGER IF EXISTS #{table_name}_update_overlap")
101-
end
102-
end
103-
10424
defmodule Constraint do
10525
use Ecto.Integration.Schema
10626

@@ -116,27 +36,20 @@ defmodule Ecto.Integration.ConstraintsTest do
11636
setup_all do
11737
ExUnit.CaptureLog.capture_log(fn ->
11838
num = @base_migration + System.unique_integer([:positive])
119-
up(PoolRepo, num, ConstraintTableMigration, log: false)
39+
up(PoolRepo, num, ConstraintMigration, log: false)
12040
end)
12141

12242
:ok
12343
end
12444

12545
@tag :create_constraint
12646
test "check constraint" do
127-
num = @base_migration + System.unique_integer([:positive])
128-
129-
ExUnit.CaptureLog.capture_log(fn ->
130-
:ok = up(PoolRepo, num, CheckConstraintMigration, log: false)
131-
end)
132-
13347
# When the changeset doesn't expect the db error
13448
changeset = Ecto.Changeset.change(%Constraint{}, price: -10)
135-
13649
exception =
137-
assert_raise Ecto.ConstraintError,
138-
~r/constraint error when attempting to insert struct/,
139-
fn -> PoolRepo.insert(changeset) end
50+
assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
51+
PoolRepo.insert(changeset)
52+
end
14053

14154
assert exception.message =~ "\"positive_price\" (check_constraint)"
14255
assert exception.message =~ "The changeset has not defined any constraint."
@@ -147,98 +60,43 @@ defmodule Ecto.Integration.ConstraintsTest do
14760
changeset
14861
|> Ecto.Changeset.check_constraint(:price, name: :positive_price)
14962
|> PoolRepo.insert()
150-
151-
assert changeset.errors == [
152-
price: {"is invalid", [constraint: :check, constraint_name: "positive_price"]}
153-
]
154-
63+
assert changeset.errors == [price: {"is invalid", [constraint: :check, constraint_name: "positive_price"]}]
15564
assert changeset.data.__meta__.state == :built
15665

15766
# When the changeset does expect the db error and gives a custom message
15867
changeset = Ecto.Changeset.change(%Constraint{}, price: -10)
159-
16068
{:error, changeset} =
16169
changeset
162-
|> Ecto.Changeset.check_constraint(:price,
163-
name: :positive_price,
164-
message: "price must be greater than 0"
165-
)
70+
|> Ecto.Changeset.check_constraint(:price, name: :positive_price, message: "price must be greater than 0")
16671
|> PoolRepo.insert()
167-
168-
assert changeset.errors == [
169-
price:
170-
{"price must be greater than 0",
171-
[constraint: :check, constraint_name: "positive_price"]}
172-
]
173-
72+
assert changeset.errors == [price: {"price must be greater than 0", [constraint: :check, constraint_name: "positive_price"]}]
17473
assert changeset.data.__meta__.state == :built
17574

17675
# When the change does not violate the check constraint
17776
changeset = Ecto.Changeset.change(%Constraint{}, price: 10, from: 100, to: 200)
178-
179-
{:ok, result} =
77+
{:ok, changeset} =
18078
changeset
181-
|> Ecto.Changeset.check_constraint(:price,
182-
name: :positive_price,
183-
message: "price must be greater than 0"
184-
)
79+
|> Ecto.Changeset.check_constraint(:price, name: :positive_price, message: "price must be greater than 0")
18580
|> PoolRepo.insert()
186-
187-
assert is_integer(result.id)
81+
assert is_integer(changeset.id)
18882
end
18983

190-
@tag :constraint_handler
191-
test "custom handled constraint" do
192-
num = @base_migration + System.unique_integer([:positive])
193-
194-
ExUnit.CaptureLog.capture_log(fn ->
195-
:ok = up(PoolRepo, num, TriggerEmulatingConstraintMigration, log: false)
196-
end)
197-
198-
constraint_handler = &CustomConstraintHandler.to_constraints/2
199-
200-
changeset = Ecto.Changeset.change(%Constraint{}, from: 0, to: 10)
201-
{:ok, item} = PoolRepo.insert(changeset)
202-
203-
non_overlapping_changeset = Ecto.Changeset.change(%Constraint{}, from: 11, to: 12)
204-
{:ok, _} = PoolRepo.insert(non_overlapping_changeset)
205-
206-
overlapping_changeset = Ecto.Changeset.change(%Constraint{}, from: 9, to: 12)
207-
208-
# Custom handler converts the trigger error into a constraint
209-
{:error, changeset} =
210-
overlapping_changeset
211-
|> Ecto.Changeset.exclusion_constraint(:from, name: :cannot_overlap)
212-
|> PoolRepo.insert(constraint_handler: constraint_handler)
213-
214-
assert changeset.errors == [
215-
from:
216-
{"violates an exclusion constraint",
217-
[constraint: :exclusion, constraint_name: "cannot_overlap"]}
218-
]
219-
220-
assert changeset.data.__meta__.state == :built
221-
222-
# Without the custom handler, the default handler doesn't recognize
223-
# the custom signal, so the error is raised as-is
224-
assert_raise MyXQL.Error, fn ->
225-
overlapping_changeset
226-
|> Ecto.Changeset.exclusion_constraint(:from, name: :cannot_overlap)
227-
|> PoolRepo.insert()
84+
@tag :create_constraint
85+
test "custom :constraint_handler option" do
86+
parent = self()
87+
custom_handler = fn _err, _opts ->
88+
send(parent, :custom_handler_called)
89+
[exclusion: "positive_price"]
22890
end
22991

230-
# Custom handler also works on UPDATE
231-
{:error, changeset} =
232-
Ecto.Changeset.change(item, from: 0, to: 9)
233-
|> Ecto.Changeset.exclusion_constraint(:from, name: :cannot_overlap)
234-
|> PoolRepo.update(constraint_handler: constraint_handler)
235-
236-
assert changeset.errors == [
237-
from:
238-
{"violates an exclusion constraint",
239-
[constraint: :exclusion, constraint_name: "cannot_overlap"]}
240-
]
92+
changeset =
93+
%Constraint{}
94+
|> Ecto.Changeset.change(price: -10)
95+
|> Ecto.Changeset.exclusion_constraint(:price, name: :positive_price)
24196

242-
assert changeset.data.__meta__.state == :loaded
97+
{:error, changeset} = PoolRepo.insert(changeset, constraint_handler: custom_handler)
98+
assert_received :custom_handler_called
99+
assert changeset.errors == [price: {"violates an exclusion constraint", [constraint: :exclusion, constraint_name: "positive_price"]}]
100+
assert changeset.data.__meta__.state == :built
243101
end
244-
end
102+
end

integration_test/myxql/test_helper.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ _ = Ecto.Adapters.MyXQL.storage_down(TestRepo.config())
8484
:ok = Ecto.Adapters.MyXQL.storage_up(TestRepo.config())
8585

8686
{:ok, _pid} = TestRepo.start_link()
87-
8887
{:ok, _pid} = PoolRepo.start_link()
8988

9089
%{rows: [[version]]} = TestRepo.query!("SELECT @@version", [])

0 commit comments

Comments
 (0)