@@ -5,8 +5,11 @@ defmodule Ecto.Integration.ConstraintsTest do
55 alias Ecto.Integration.PoolRepo
66
77 defmodule CustomConstraintHandler do
8+ @ behaviour Ecto.Adapters.SQL.Constraint
9+
810 @ quotes ~w( " ' `)
911
12+ @ impl Ecto.Adapters.SQL.Constraint
1013 # An example of a custom handler a user might write
1114 def to_constraints ( % MyXQL.Error { mysql: % { name: :ER_SIGNAL_EXCEPTION } , message: message } , opts ) do
1215 # Assumes this is the only use-case of `ER_SIGNAL_EXCEPTION` the user has implemented custom errors for
@@ -41,7 +44,7 @@ defmodule Ecto.Integration.ConstraintsTest do
4144 end
4245 end
4346
44- defmodule ConstraintMigration do
47+ defmodule CheckConstraintMigration do
4548 use Ecto.Migration
4649
4750 @ table table ( :constraints_test )
@@ -52,7 +55,7 @@ defmodule Ecto.Integration.ConstraintsTest do
5255 end
5356 end
5457
55- defmodule ProcedureEmulatingConstraintMigration do
58+ defmodule TriggerEmulatingConstraintMigration do
5659 use Ecto.Migration
5760
5861 @ table_name :constraints_test
@@ -70,11 +73,14 @@ defmodule Ecto.Integration.ConstraintsTest do
7073 drop_triggers ( @ table_name )
7174 end
7275
76+ # FOR EACH ROW, not a great example performance-wise,
77+ # but demonstrates the feature
7378 defp trigger_sql ( table_name , before_type ) do
7479 ~s"""
7580 CREATE TRIGGER #{ table_name } _#{ String . downcase ( before_type ) } _overlap
7681 BEFORE #{ String . upcase ( before_type ) }
77- ON #{ table_name } FOR EACH ROW
82+ ON #{ table_name }
83+ FOR EACH ROW
7884 BEGIN
7985 DECLARE v_rowcount INT;
8086 DECLARE v_msg VARCHAR(200);
@@ -112,7 +118,6 @@ defmodule Ecto.Integration.ConstraintsTest do
112118 ExUnit.CaptureLog . capture_log ( fn ->
113119 num = @ base_migration + System . unique_integer ( [ :positive ] )
114120 up ( PoolRepo , num , ConstraintTableMigration , log: false )
115- up ( PoolRepo , num + 1 , ProcedureEmulatingConstraintMigration , log: false )
116121 end )
117122
118123 :ok
@@ -121,8 +126,9 @@ defmodule Ecto.Integration.ConstraintsTest do
121126 @ tag :create_constraint
122127 test "check constraint" do
123128 num = @ base_migration + System . unique_integer ( [ :positive ] )
129+
124130 ExUnit.CaptureLog . capture_log ( fn ->
125- :ok = up ( PoolRepo , num , ConstraintMigration , log: false )
131+ :ok = up ( PoolRepo , num , CheckConstraintMigration , log: false )
126132 end )
127133
128134 # When the changeset doesn't expect the db error
@@ -131,9 +137,7 @@ defmodule Ecto.Integration.ConstraintsTest do
131137 exception =
132138 assert_raise Ecto.ConstraintError ,
133139 ~r/ constraint error when attempting to insert struct/ ,
134- fn ->
135- PoolRepo . insert ( changeset )
136- end
140+ fn -> PoolRepo . insert ( changeset ) end
137141
138142 assert exception . message =~ "\" positive_price\" (check_constraint)"
139143 assert exception . message =~ "The changeset has not defined any constraint."
@@ -184,7 +188,14 @@ defmodule Ecto.Integration.ConstraintsTest do
184188 assert is_integer ( result . id )
185189 end
186190
191+ @ tag :constraint_handler
187192 test "custom handled constraint" do
193+ num = @ base_migration + System . unique_integer ( [ :positive ] )
194+
195+ ExUnit.CaptureLog . capture_log ( fn ->
196+ :ok = up ( PoolRepo , num , TriggerEmulatingConstraintMigration , log: false )
197+ end )
198+
188199 changeset = Ecto.Changeset . change ( % Constraint { } , from: 0 , to: 10 )
189200 { :ok , item } = PoolRepo . insert ( changeset )
190201
@@ -211,42 +222,67 @@ defmodule Ecto.Integration.ConstraintsTest do
211222 |> Ecto.Changeset . exclusion_constraint ( :from )
212223 |> PoolRepo . insert ( )
213224 end
225+
214226 assert exception . message =~ "\" cannot_overlap\" (exclusion_constraint)"
215227
216228 # When the changeset does expect the db error, but doesn't give a custom message
217229 { :error , changeset } =
218230 overlapping_changeset
219231 |> Ecto.Changeset . exclusion_constraint ( :from , name: :cannot_overlap )
220232 |> PoolRepo . insert ( )
221- assert changeset . errors == [ from: { "violates an exclusion constraint" , [ constraint: :exclusion , constraint_name: "cannot_overlap" ] } ]
233+
234+ assert changeset . errors == [
235+ from:
236+ { "violates an exclusion constraint" ,
237+ [ constraint: :exclusion , constraint_name: "cannot_overlap" ] }
238+ ]
239+
222240 assert changeset . data . __meta__ . state == :built
223241
224242 # When the changeset does expect the db error and gives a custom message
225243 { :error , changeset } =
226244 overlapping_changeset
227- |> Ecto.Changeset . exclusion_constraint ( :from , name: :cannot_overlap , message: "must not overlap" )
245+ |> Ecto.Changeset . exclusion_constraint ( :from ,
246+ name: :cannot_overlap ,
247+ message: "must not overlap"
248+ )
228249 |> PoolRepo . insert ( )
229- assert changeset . errors == [ from: { "must not overlap" , [ constraint: :exclusion , constraint_name: "cannot_overlap" ] } ]
230- assert changeset . data . __meta__ . state == :built
231250
251+ assert changeset . errors == [
252+ from:
253+ { "must not overlap" , [ constraint: :exclusion , constraint_name: "cannot_overlap" ] }
254+ ]
255+
256+ assert changeset . data . __meta__ . state == :built
232257
233258 # When the changeset does expect the db error, but a different handler is used
234259 exception =
235260 assert_raise MyXQL.Error , fn ->
236261 overlapping_changeset
237262 |> Ecto.Changeset . exclusion_constraint ( :from , name: :cannot_overlap )
238- |> PoolRepo . insert ( constraint_handler: Ecto.Adapters.MyXQL.Connection )
263+ |> PoolRepo . insert (
264+ constraint_handler: { Ecto.Adapters.MyXQL.Connection , :to_constraints , [ ] }
265+ )
239266 end
267+
240268 assert exception . message =~ "Overlapping values for key 'constraints_test.cannot_overlap'"
241269
242270 # When custom error is coming from an UPDATE
243271 overlapping_update_changeset = Ecto.Changeset . change ( item , from: 0 , to: 9 )
244272
245273 { :error , changeset } =
246274 overlapping_update_changeset
247- |> Ecto.Changeset . exclusion_constraint ( :from , name: :cannot_overlap , message: "must not overlap" )
275+ |> Ecto.Changeset . exclusion_constraint ( :from ,
276+ name: :cannot_overlap ,
277+ message: "must not overlap"
278+ )
248279 |> PoolRepo . insert ( )
249- assert changeset . errors == [ from: { "must not overlap" , [ constraint: :exclusion , constraint_name: "cannot_overlap" ] } ]
280+
281+ assert changeset . errors == [
282+ from:
283+ { "must not overlap" , [ constraint: :exclusion , constraint_name: "cannot_overlap" ] }
284+ ]
285+
250286 assert changeset . data . __meta__ . state == :loaded
251287 end
252288end
0 commit comments