Skip to content

Commit e0d4625

Browse files
committed
add check constraint error handling for MyXQL
1 parent ccb62ea commit e0d4625

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
defmodule Ecto.Integration.ConstraintsTest do
2+
use ExUnit.Case, async: true
3+
4+
import Ecto.Migrator, only: [up: 4]
5+
alias Ecto.Integration.PoolRepo
6+
7+
defmodule ConstraintMigration do
8+
use Ecto.Migration
9+
10+
@table table(:constraints_test)
11+
12+
def change do
13+
create @table do
14+
add :price, :integer
15+
add :from, :integer
16+
add :to, :integer
17+
end
18+
19+
execute(&positive_price_up/0, &positive_price_down/0)
20+
end
21+
22+
defp positive_price_up do
23+
# Only valid after MySQL 8.0.19
24+
repo().query!("ALTER TABLE #{@table.name} ADD CONSTRAINT positive_price CHECK (price > 0);", [], [log: :info])
25+
end
26+
27+
defp positive_price_down do
28+
# Only valid after MySQL 8.0.19
29+
repo().query!("ALTER TABLE #{@table.name} DROP CONSTRAINT positive_price;", [], [log: :info])
30+
end
31+
end
32+
33+
defmodule Constraint do
34+
use Ecto.Integration.Schema
35+
36+
schema "constraints_test" do
37+
field :price, :integer
38+
field :from, :integer
39+
field :to, :integer
40+
end
41+
end
42+
43+
@base_migration 2_000_000
44+
45+
setup_all do
46+
ExUnit.CaptureLog.capture_log(fn ->
47+
num = @base_migration + System.unique_integer([:positive])
48+
up(PoolRepo, num, ConstraintMigration, log: false)
49+
end)
50+
51+
:ok
52+
end
53+
54+
test "check constraint" do
55+
# When the changeset doesn't expect the db error
56+
changeset = Ecto.Changeset.change(%Constraint{}, price: -10)
57+
exception =
58+
assert_raise Ecto.ConstraintError, ~r/constraint error when attempting to insert struct/, fn ->
59+
PoolRepo.insert(changeset)
60+
end
61+
62+
assert exception.message =~ "\"positive_price\" (check_constraint)"
63+
assert exception.message =~ "The changeset has not defined any constraint."
64+
assert exception.message =~ "call `check_constraint/3`"
65+
66+
# When the changeset does expect the db error, but doesn't give a custom message
67+
{:error, changeset} =
68+
changeset
69+
|> Ecto.Changeset.check_constraint(:price, name: :positive_price)
70+
|> PoolRepo.insert()
71+
assert changeset.errors == [price: {"is invalid", [constraint: :check, constraint_name: "positive_price"]}]
72+
assert changeset.data.__meta__.state == :built
73+
74+
# When the changeset does expect the db error and gives a custom message
75+
changeset = Ecto.Changeset.change(%Constraint{}, price: -10)
76+
{:error, changeset} =
77+
changeset
78+
|> Ecto.Changeset.check_constraint(:price, name: :positive_price, message: "price must be greater than 0")
79+
|> PoolRepo.insert()
80+
assert changeset.errors == [price: {"price must be greater than 0", [constraint: :check, constraint_name: "positive_price"]}]
81+
assert changeset.data.__meta__.state == :built
82+
83+
# When the change does not violate the check constraint
84+
changeset = Ecto.Changeset.change(%Constraint{}, price: 10, from: 100, to: 200)
85+
{:ok, changeset} =
86+
changeset
87+
|> Ecto.Changeset.check_constraint(:price, name: :positive_price, message: "price must be greater than 0")
88+
|> PoolRepo.insert()
89+
assert is_integer(changeset.id)
90+
end
91+
end

lib/ecto/adapters/myxql/connection.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ if Code.ensure_loaded?(MyXQL) do
7878
end
7979
end
8080

81+
def to_constraints(%MyXQL.Error{mysql: %{name: :ER_CHECK_CONSTRAINT_VIOLATED}, message: message}, _opts) do
82+
with [_, quoted] <- :binary.split(message, ["Check constraint "]),
83+
[_, constraint | _] <- :binary.split(quoted, @quotes, [:global]) do
84+
[check: constraint]
85+
else
86+
_ -> []
87+
end
88+
end
89+
8190
def to_constraints(_, _),
8291
do: []
8392

0 commit comments

Comments
 (0)