@@ -37,6 +37,9 @@ defmodule Ecto.Adapters.SQL do
3737 * `to_sql(type, query)` -
3838 shortcut for `Ecto.Adapters.SQL.to_sql/3`
3939
40+ * `to_constraints(exception, opts, error_opts)` -
41+ shortcut for `Ecto.Adapters.SQL.to_constraints/4`
42+
4043 Generally speaking, you must invoke those functions directly from
4144 your repository, for example: `MyApp.Repo.query("SELECT true")`.
4245 You can also invoke them directly from `Ecto.Adapters.SQL`, but
@@ -393,6 +396,45 @@ defmodule Ecto.Adapters.SQL do
393396 {"SELECT p.id, p.title, p.inserted_at, p.created_at FROM posts as p", []}
394397 """
395398
399+ # TODO - add docs here and/or somewhere for how to pass `constraint_handler` per call
400+ @ to_constraints_doc """
401+ Handles adapter-specific exceptions, converting them to
402+ the corresponding contraint errors.
403+
404+ The constraints are in the keyword list and must return the
405+ constraint type, like `:unique`, and the constraint name as
406+ a string, for example:
407+
408+ [unique: "posts_title_index"]
409+
410+ Returning an empty list signifies the error does not come
411+ from any constraint, and should continue with the default
412+ exception handling path (i.e. raise or further handling).
413+
414+ ## Options
415+ * `:constraint_handler` - A module, function, and list of arguments (`mfargs`)
416+
417+ The `:constraint_handler` option defaults to the adapter's connection module.
418+ For the built-in adapters this would be:
419+
420+ * `Ecto.Adapters.Postgres.Connection.to_constraints/2`
421+ * `Ecto.Adapters.MyXQL.Connection.to_constraints/2`
422+ * `Ecto.Adapters.Tds.Connection.to_constraints/2`
423+
424+ See `Ecto.Adapters.SQL.Constraint` if you need to fully
425+ customize the handling of constraints for all operations.
426+
427+ ## Examples
428+
429+ # Postgres
430+ iex> MyRepo.to_constraints(%Postgrex.Error{code: :unique, constraint: "posts_title_index"}, [])
431+ [unique: "posts_title_index"]
432+
433+ # MySQL
434+ iex> MyRepo.to_constraints(%MyXQL.Error{mysql: %{name: :ER_CHECK_CONSTRAINT_VIOLATED}, message: "Check constraint 'positive_price' is violated."}, [])
435+ [check: "positive_price"]
436+ """
437+
396438 @ explain_doc """
397439 Executes an EXPLAIN statement or similar for the given query according to its kind and the
398440 adapter in the given repository.
@@ -668,11 +710,22 @@ defmodule Ecto.Adapters.SQL do
668710 sql_call ( adapter_meta , :query_many , [ sql ] , params , opts )
669711 end
670712
671- @ doc false
672- def to_constraints ( adapter_meta , opts , err , err_opts ) do
713+ @ doc @ to_constraints_doc
714+ @ spec to_constraints (
715+ pid ( ) | Ecto.Repo . t ( ) | Ecto.Adapter . adapter_meta ( ) ,
716+ exception :: Exception . t ( ) ,
717+ options :: Keyword . t ( ) ,
718+ error_options :: Keyword . t ( )
719+ ) :: Keyword . t ( )
720+ def to_constraints ( repo , err , opts , err_opts ) when is_atom ( repo ) or is_pid ( repo ) do
721+ to_constraints ( Ecto.Adapter . lookup_meta ( repo ) , err , opts , err_opts )
722+ end
723+
724+ def to_constraints ( adapter_meta , err , opts , err_opts ) do
673725 % { constraint_handler: constraint_handler } = adapter_meta
674- constraint_handler = Keyword . get ( opts , :constraint_handler ) || constraint_handler
675- constraint_handler . to_constraints ( err , err_opts )
726+ { constraint_mod , fun , args } = Keyword . get ( opts , :constraint_handler ) || constraint_handler
727+ args = [ err , err_opts | args ]
728+ apply ( constraint_mod , fun , args )
676729 end
677730
678731 defp sql_call ( adapter_meta , callback , args , params , opts ) do
@@ -794,6 +847,7 @@ defmodule Ecto.Adapters.SQL do
794847 query_many_doc = @ query_many_doc
795848 query_many_bang_doc = @ query_many_bang_doc
796849 to_sql_doc = @ to_sql_doc
850+ to_constraints_doc = @ to_constraints_doc
797851 explain_doc = @ explain_doc
798852 disconnect_all_doc = @ disconnect_all_doc
799853
@@ -833,6 +887,16 @@ defmodule Ecto.Adapters.SQL do
833887 Ecto.Adapters.SQL . to_sql ( operation , get_dynamic_repo ( ) , queryable )
834888 end
835889
890+ @ doc unquote ( to_constraints_doc )
891+ @ spec to_constraints (
892+ exception :: Exception . t ( ) ,
893+ options :: Keyword . t ( ) ,
894+ error_options :: Keyword . t ( )
895+ ) :: Keyword . t ( )
896+ def to_constraints ( err , opts , err_opts ) do
897+ Ecto.Adapters.SQL . to_constraints ( get_dynamic_repo ( ) , err , opts , err_opts )
898+ end
899+
836900 @ doc unquote ( explain_doc )
837901 @ spec explain ( :all | :update_all | :delete_all , Ecto.Queryable . t ( ) , opts :: Keyword . t ( ) ) ::
838902 String . t ( ) | Exception . t ( ) | list ( map )
@@ -890,7 +954,9 @@ defmodule Ecto.Adapters.SQL do
890954 """
891955 end
892956
893- constraint_handler = Keyword . get ( config , :constraint_handler , connection )
957+ constraint_handler =
958+ Keyword . get ( config , :constraint_handler , { connection , :to_constraints , [ ] } )
959+
894960 stacktrace = Keyword . get ( config , :stacktrace )
895961 telemetry_prefix = Keyword . fetch! ( config , :telemetry_prefix )
896962 telemetry = { config [ :repo ] , log , telemetry_prefix ++ [ :query ] }
@@ -1200,7 +1266,7 @@ defmodule Ecto.Adapters.SQL do
12001266 operation: operation
12011267
12021268 { :error , err } ->
1203- case to_constraints ( adapter_meta , opts , err , source: source ) do
1269+ case to_constraints ( adapter_meta , err , opts , source: source ) do
12041270 [ ] -> raise_sql_call_error ( err )
12051271 constraints -> { :invalid , constraints }
12061272 end
0 commit comments