Skip to content

Commit 6971ecd

Browse files
authored
Rework error codes (#134)
1 parent d656a7b commit 6971ecd

File tree

9 files changed

+89
-4568
lines changed

9 files changed

+89
-4568
lines changed

.github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
test:
1111
runs-on: ubuntu-16.04
1212
strategy:
13+
fail-fast: false
1314
matrix:
1415
db:
1516
- mysql:5.6

lib/myxql.ex

+58-22
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ defmodule MyXQL do
9696
the prepare statement count of your databases (such as using a dashboard or
9797
setting alarm handlers)
9898
99-
* `:disconnect_on_error_codes` - List of error code atoms that when encountered
99+
* `:disconnect_on_error_codes` - List of error code integers or atoms that when encountered
100100
will disconnect the connection. See "Disconnecting on Errors" section below for more
101101
information.
102102
@@ -129,11 +129,11 @@ defmodule MyXQL do
129129
iex> {:ok, pid} = MyXQL.start_link(after_connect: &MyXQL.query!(&1, "SET time_zone = '+00:00'"))
130130
{:ok, #PID<0.69.0>}
131131
132-
## Disconnecting on Errors
132+
## Disconnecting on errors
133133
134-
Sometimes the connection becomes unusable. For example, some services, such as AWS Aurora,
135-
support failover. This means the database you are currently connected to may suddenly become
136-
read-only, and an attempt to do any write operation, such as INSERT/UPDATE/DELETE will lead to
134+
Sometimes the connection becomes unusable. For example, services such as AWS Aurora support
135+
failover which means the database you are currently connected to may suddenly become
136+
read-only. An attempt to do any write operation, such as INSERT/UPDATE/DELETE will lead to
137137
errors such as:
138138
139139
** (MyXQL.Error) (1792) (ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION) Cannot execute statement in a READ ONLY transaction.
@@ -147,18 +147,35 @@ defmodule MyXQL do
147147
MyXQL automatically disconnects the connection on the following error codes and they don't have
148148
to be configured:
149149
150-
* `ER_MAX_PREPARED_STMT_COUNT_REACHED`
150+
* `:ER_MAX_PREPARED_STMT_COUNT_REACHED`
151151
152-
To convert error code number to error code name you can use `perror` command-line utility that
153-
ships with MySQL client installation, e.g.:
152+
You can pass error codes as integers too:
153+
154+
disconnect_on_error_codes: [1792]
155+
156+
## Error codes
157+
158+
MyXQL maintains a mapping of integers/atoms for commonly used errors. You can add additional
159+
ones by adding the following compile-time configuration:
160+
161+
config :myxql, :extra_error_codes, [
162+
{1048, :ER_BAD_NULL_ERROR}
163+
]
164+
165+
After adding the configuration, MyXQL needs to be recompiled. It can be done with:
166+
167+
$ mix deps.clean myxql --build
168+
169+
To convert error code integers to names you can use `perror` command-line utility that ships
170+
with MySQL client installation, e.g.:
154171
155172
bash$ perror 1792
156173
MySQL error code 1792 (ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION): Cannot execute statement in a READ ONLY transaction.
157174
158175
"""
159176
@spec start_link([start_option()]) :: {:ok, pid()} | {:error, MyXQL.Error.t()}
160177
def start_link(options) do
161-
ensure_deps_started!(options)
178+
options = ensure_valid_error_codes!(options)
162179
DBConnection.start_link(MyXQL.Connection, options)
163180
end
164181

@@ -526,9 +543,9 @@ defmodule MyXQL do
526543
Returns a supervisor child specification for a DBConnection pool.
527544
"""
528545
@spec child_spec([start_option()]) :: :supervisor.child_spec()
529-
def child_spec(opts) do
530-
ensure_deps_started!(opts)
531-
DBConnection.child_spec(MyXQL.Connection, opts)
546+
def child_spec(options) do
547+
options = ensure_valid_error_codes!(options)
548+
DBConnection.child_spec(MyXQL.Connection, options)
532549
end
533550

534551
@doc """
@@ -547,18 +564,37 @@ defmodule MyXQL do
547564

548565
## Helpers
549566

550-
defp ensure_deps_started!(opts) do
551-
if Keyword.get(opts, :ssl, false) and
552-
not List.keymember?(:application.which_applications(), :ssl, 0) do
553-
raise """
554-
SSL connection cannot be established because `:ssl` application is not started,
555-
you can add it to `:extra_applications` in your `mix.exs`:
567+
defp ensure_valid_error_codes!(opts) do
568+
default_error_codes = [
569+
:ER_MAX_PREPARED_STMT_COUNT_REACHED
570+
]
556571

557-
def application() do
558-
[extra_applications: [:ssl]]
572+
codes = default_error_codes ++ Keyword.get(opts, :disconnect_on_error_codes, [])
573+
574+
codes =
575+
for code <- codes do
576+
if is_integer(code) do
577+
code
578+
else
579+
integer = MyXQL.Protocol.ServerErrorCodes.name_to_code(code)
580+
581+
unless integer do
582+
raise """
583+
#{inspect(code)} is not a recognized error code
584+
585+
To solve this, you can either:
586+
587+
- pass an integer error code
588+
589+
- make it recognizable by adding it to `config :myxql, extra_error_codes: [...]`
590+
(See "Error codes" in MyXQL.start_link/1 documentation for more information).
591+
"""
559592
end
560593

561-
"""
562-
end
594+
integer
595+
end
596+
end
597+
598+
Keyword.put(opts, :disconnect_on_error_codes, codes)
563599
end
564600
end

lib/myxql/connection.ex

+4-11
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ defmodule MyXQL.Connection do
55
import MyXQL.Protocol.{Flags, Records}
66
alias MyXQL.{Client, Cursor, Query, Protocol, Result, TextQuery}
77

8-
@disconnect_on_error_codes [
9-
:ER_MAX_PREPARED_STMT_COUNT_REACHED
10-
]
11-
128
defstruct [
139
:client,
1410
cursors: %{},
@@ -26,15 +22,12 @@ defmodule MyXQL.Connection do
2622
ping_timeout = Keyword.get(opts, :ping_timeout, 15_000)
2723
config = Client.Config.new(opts)
2824

29-
disconnect_on_error_codes =
30-
@disconnect_on_error_codes ++ Keyword.get(opts, :disconnect_on_error_codes, [])
31-
3225
case Client.connect(config) do
3326
{:ok, %Client{} = client} ->
3427
state = %__MODULE__{
3528
client: client,
3629
prepare: prepare,
37-
disconnect_on_error_codes: disconnect_on_error_codes,
30+
disconnect_on_error_codes: Keyword.fetch!(opts, :disconnect_on_error_codes),
3831
ping_timeout: ping_timeout,
3932
queries: queries_new()
4033
}
@@ -348,7 +341,7 @@ defmodule MyXQL.Connection do
348341

349342
defp error(err_packet(code: code, message: message)) do
350343
name = Protocol.error_code_to_name(code)
351-
%MyXQL.Error{message: "(#{code}) (#{name}) " <> message, mysql: %{code: code, name: name}}
344+
%MyXQL.Error{mysql: %{code: code, name: name}, message: message}
352345
end
353346

354347
defp error(reason) do
@@ -385,9 +378,9 @@ defmodule MyXQL.Connection do
385378
end
386379

387380
defp maybe_disconnect(exception, state) do
388-
%MyXQL.Error{mysql: %{name: error_name}} = exception
381+
%MyXQL.Error{mysql: %{code: code}} = exception
389382

390-
if error_name in state.disconnect_on_error_codes do
383+
if code in state.disconnect_on_error_codes do
391384
{:disconnect, exception, state}
392385
else
393386
{:error, exception, state}

lib/myxql/error.ex

+10-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@ defmodule MyXQL.Error do
99
@type t :: %__MODULE__{
1010
connection_id: non_neg_integer() | nil,
1111
message: String.t(),
12-
mysql: %{code: integer(), name: atom()} | nil,
12+
mysql: %{code: integer(), name: atom() | nil} | nil,
1313
statement: iodata() | nil
1414
}
15+
16+
@impl true
17+
def message(%{mysql: %{code: code, name: nil}, message: message}) do
18+
"(#{code}) " <> message
19+
end
20+
21+
def message(%{mysql: %{code: code, name: name}, message: message}) do
22+
"(#{code}) (#{name})" <> message
23+
end
1524
end

0 commit comments

Comments
 (0)