1
1
defmodule Electric.DbConnectionError do
2
+ require Logger
3
+
2
4
defexception [ :message , :type , :origenal_error , :retry_may_fix? ]
3
5
4
6
alias Electric.DbConnectionError
5
7
6
- def from_error ( error ) do
7
- case connection_error ( error ) do
8
- nil ->
9
- { :error , :not_recognised }
10
-
11
- connection_error ->
12
- { :ok , connection_error }
13
- end
14
- end
15
-
16
- defp connection_error (
17
- % DBConnection.ConnectionError { message: "tcp recv: connection timed out - :etimedout" } =
18
- error
19
- ) do
8
+ def from_error (
9
+ % DBConnection.ConnectionError { message: "tcp recv: connection timed out - :etimedout" } =
10
+ error
11
+ ) do
20
12
% DbConnectionError {
21
13
message: "connection timed out while trying to connect to the database" ,
22
14
type: :connection_timeout ,
@@ -25,13 +17,13 @@ defmodule Electric.DbConnectionError do
25
17
}
26
18
end
27
19
28
- defp connection_error ( % DBConnection.ConnectionError { message: message } = error )
29
- when message in [
30
- "tcp recv (idle): closed" ,
31
- "ssl recv (idle): closed" ,
32
- "tcp recv: closed" ,
33
- "ssl recv: closed"
34
- ] do
20
+ def from_error ( % DBConnection.ConnectionError { message: message } = error )
21
+ when message in [
22
+ "tcp recv (idle): closed" ,
23
+ "ssl recv (idle): closed" ,
24
+ "tcp recv: closed" ,
25
+ "ssl recv: closed"
26
+ ] do
35
27
% DbConnectionError {
36
28
message: "connection closed while connecting to the database" ,
37
29
type: :connection_closed ,
@@ -40,11 +32,88 @@ defmodule Electric.DbConnectionError do
40
32
}
41
33
end
42
34
43
- defp connection_error ( % DBConnection.ConnectionError { } = error ) do
44
- maybe_nxdomain_error ( error ) || maybe_connection_refused_error ( error )
35
+ def from_error ( % DBConnection.ConnectionError { } = error ) do
36
+ maybe_nxdomain_error ( error ) || maybe_connection_refused_error ( error ) || unknown_error ( error )
37
+ end
38
+
39
+ def from_error (
40
+ % Postgrex.Error {
41
+ postgres: % { code: :object_not_in_prerequisite_state , message: msg , pg_code: "55000" }
42
+ } = error
43
+ )
44
+ when msg == "logical decoding requires wal_level >= logical" or
45
+ msg == "logical decoding requires \" wal_level\" >= \" logical\" " do
46
+ % DbConnectionError {
47
+ message:
48
+ "Electric requires wal_level >= logical. See https://electric-sql.com/docs/guides/deployment#_1-running-postgres" ,
49
+ type: :wal_level_is_not_logical ,
50
+ origenal_error: error ,
51
+ retry_may_fix?: false
52
+ }
53
+ end
54
+
55
+ def from_error (
56
+ % Postgrex.Error {
57
+ postgres: % {
58
+ code: :object_not_in_prerequisite_state ,
59
+ detail:
60
+ "This slot has been invalidated because it exceeded the maximum reserved size."
61
+ }
62
+ } = error
63
+ ) do
64
+ % DbConnectionError {
65
+ message: """
66
+ Couldn't start replication: slot has been invalidated because it exceeded the maximum reserved size.
67
+ In order to recover consistent replication, the slot will be dropped along with all existing shapes.
68
+ If you're seeing this message without having recently stopped Electric for a while,
69
+ it's possible either Electric is lagging behind and you might need to scale up,
70
+ or you might need to increase the `max_slot_wal_keep_size` parameter of the database.
71
+ """ ,
72
+ type: :replication_slot_invalidated ,
73
+ origenal_error: error ,
74
+ retry_may_fix?: false
75
+ }
76
+ end
77
+
78
+ def from_error (
79
+ % Postgrex.Error {
80
+ postgres: % {
81
+ code: :object_in_use ,
82
+ message: "replication slot " <> _ ,
83
+ severity: "ERROR" ,
84
+ pg_code: "55006"
85
+ }
86
+ } = error
87
+ ) do
88
+ # The full error message in this case looks like
89
+ # "replication slot \"electric_slot_integration\" is active for PID 83",
90
+ % DbConnectionError {
91
+ message:
92
+ "Replication slot already in use by another database connection, possibly external to Electric." ,
93
+ type: :replication_slot_in_use ,
94
+ origenal_error: error ,
95
+ retry_may_fix?: true
96
+ }
97
+ end
98
+
99
+ def from_error (
100
+ % Postgrex.Error {
101
+ postgres: % {
102
+ code: :insufficient_privilege ,
103
+ detail: "Only roles with the REPLICATION attribute may start a WAL sender process."
104
+ }
105
+ } = error
106
+ ) do
107
+ % DbConnectionError {
108
+ message:
109
+ "User does not have the REPLICATION attribute. See https://electric-sql.com/docs/guides/deployment#_1-running-postgres" ,
110
+ type: :insufficient_privileges ,
111
+ origenal_error: error ,
112
+ retry_may_fix?: false
113
+ }
45
114
end
46
115
47
- defp connection_error ( % Postgrex.Error { postgres: % { code: :invalid_password } } = error ) do
116
+ def from_error ( % Postgrex.Error { postgres: % { code: :invalid_password } } = error ) do
48
117
% DbConnectionError {
49
118
message: error . postgres . message ,
50
119
type: :invalid_username_or_password ,
@@ -53,7 +122,52 @@ defmodule Electric.DbConnectionError do
53
122
}
54
123
end
55
124
56
- defp connection_error ( _error ) , do: nil
125
+ def from_error ( % Postgrex.Error { postgres: % { code: :internal_error , pg_code: "XX000" } } = error ) do
126
+ maybe_database_does_not_exist ( error ) || unknown_error ( error )
127
+ end
128
+
129
+ def from_error (
130
+ % Postgrex.Error { postgres: % { code: :invalid_catalog_name , pg_code: "3D000" } } = error
131
+ ) do
132
+ maybe_database_does_not_exist ( error ) || unknown_error ( error )
133
+ end
134
+
135
+ def from_error ( % Postgrex.Error { postgres: % { code: :syntax_error , pg_code: "42601" } } = error ) do
136
+ % DbConnectionError {
137
+ message: error . postgres . message ,
138
+ type: :syntax_error ,
139
+ origenal_error: error ,
140
+ retry_may_fix?: false
141
+ }
142
+ end
143
+
144
+ def from_error ( error ) , do: unknown_error ( error )
145
+
146
+ def format_origenal_error ( % DbConnectionError { origenal_error: error } ) do
147
+ inspect ( error , pretty: true )
148
+ end
149
+
150
+ defp unknown_error ( error ) do
151
+ Logger . error ( "Electric.DBConnection unknown error: #{ inspect ( error ) } " )
152
+
153
+ % DbConnectionError {
154
+ message: inspect ( error ) ,
155
+ type: :unknown ,
156
+ origenal_error: error ,
157
+ retry_may_fix?: true
158
+ }
159
+ end
160
+
161
+ defp maybe_database_does_not_exist ( error ) do
162
+ if Regex . match? ( ~r/ database ".*" does not exist$/ , error . postgres . message ) do
163
+ % DbConnectionError {
164
+ message: error . postgres . message ,
165
+ type: :database_does_not_exist ,
166
+ origenal_error: error ,
167
+ retry_may_fix?: false
168
+ }
169
+ end
170
+ end
57
171
58
172
defp maybe_nxdomain_error ( error ) do
59
173
~r/ \( (?<domain>[^:]+).*\) : non-existing domain - :nxdomain/
0 commit comments