Skip to content

Commit 1813043

Browse files
casperisfinesodabrew
authored andcommitted
More specific exception classes (#911)
* Simplify Mysql2::Error construction * Implement more specific exception classes * Raise specialized exception for connection and timeout errors
1 parent d8daa9f commit 1813043

File tree

5 files changed

+54
-18
lines changed

5 files changed

+54
-18
lines changed

ext/mysql2/client.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include "mysql_enc_name_to_ruby.h"
1616

1717
VALUE cMysql2Client;
18-
extern VALUE mMysql2, cMysql2Error;
18+
extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError;
1919
static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
2020
static VALUE sym_no_good_index_used, sym_no_index_used, sym_query_was_slow;
2121
static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;
@@ -660,7 +660,7 @@ static VALUE do_query(void *args) {
660660
retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);
661661

662662
if (retval == 0) {
663-
rb_raise(cMysql2Error, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
663+
rb_raise(cMysql2TimeoutError, "Timeout waiting for a response from the last query. (waited %d seconds)", FIX2INT(read_timeout));
664664
}
665665

666666
if (retval < 0) {

ext/mysql2/mysql2_ext.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#include <mysql2_ext.h>
22

3-
VALUE mMysql2, cMysql2Error;
3+
VALUE mMysql2, cMysql2Error, cMysql2TimeoutError;
44

55
/* Ruby Extension initializer */
66
void Init_mysql2() {
77
mMysql2 = rb_define_module("Mysql2");
88
cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
9+
cMysql2TimeoutError = rb_const_get(cMysql2Error, rb_intern("TimeoutError"));
910

1011
init_mysql2_client();
1112
init_mysql2_result();

ext/mysql2/statement.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <mysql2_ext.h>
22

33
VALUE cMysql2Statement;
4-
extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate;
4+
extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError, cBigDecimal, cDateTime, cDate;
55
static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
66
static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
77

lib/mysql2/error.rb

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,60 @@ class Error < StandardError
66
replace: '?'.freeze,
77
}.freeze
88

9+
ConnectionError = Class.new(Error)
10+
TimeoutError = Class.new(Error)
11+
12+
CODES = {
13+
1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
14+
15+
1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
16+
1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
17+
1152 => ConnectionError, # ER_ABORTING_CONNECTION
18+
1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
19+
1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
20+
1155 => ConnectionError, # ER_NET_FCNTL_ERROR
21+
1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
22+
1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
23+
1158 => ConnectionError, # ER_NET_READ_ERROR
24+
1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
25+
1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
26+
1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
27+
28+
2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
29+
2002 => ConnectionError, # CR_CONNECTION_ERROR
30+
2003 => ConnectionError, # CR_CONN_HOST_ERROR
31+
2004 => ConnectionError, # CR_IPSOCK_ERROR
32+
2005 => ConnectionError, # CR_UNKNOWN_HOST
33+
2006 => ConnectionError, # CR_SERVER_GONE_ERROR
34+
2007 => ConnectionError, # CR_VERSION_ERROR
35+
2009 => ConnectionError, # CR_WRONG_HOST_INFO
36+
2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
37+
2013 => ConnectionError, # CR_SERVER_LOST
38+
2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
39+
2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
40+
2027 => ConnectionError, # CR_MALFORMED_PACKET
41+
2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
42+
2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
43+
2049 => ConnectionError, # CR_UNUSED_1
44+
}.freeze
45+
946
attr_reader :error_number, :sql_state
1047

1148
# Mysql gem compatibility
1249
alias errno error_number
1350
alias error message
1451

15-
def initialize(msg)
16-
@server_version ||= nil
52+
def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
53+
@server_version = server_version
54+
@error_number = error_number
55+
@sql_state = sql_state ? sql_state.encode(ENCODE_OPTS) : nil
1756

1857
super(clean_message(msg))
1958
end
2059

2160
def self.new_with_args(msg, server_version, error_number, sql_state)
22-
err = allocate
23-
err.instance_variable_set('@server_version', server_version)
24-
err.instance_variable_set('@error_number', error_number)
25-
err.instance_variable_set('@sql_state', sql_state.encode(ENCODE_OPTS))
26-
err.send(:initialize, msg)
27-
err
61+
error_class = CODES.fetch(error_number, self)
62+
error_class.new(msg, server_version, error_number, sql_state)
2863
end
2964

3065
private

spec/mysql2/client_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
end
1818
end
1919

20-
it "should raise an exception upon connection failure" do
20+
it "should raise a Mysql::Error::ConnectionError upon connection failure" do
2121
expect do
2222
# The odd local host IP address forces the mysql client library to
2323
# use a TCP socket rather than a domain socket.
2424
new_client('host' => '127.0.0.2', 'port' => 999999)
25-
end.to raise_error(Mysql2::Error)
25+
end.to raise_error(Mysql2::Error::ConnectionError)
2626
end
2727

2828
it "should raise an exception on create for invalid encodings" do
@@ -559,7 +559,7 @@ def run_gc
559559
client = new_client(read_timeout: 0)
560560
expect do
561561
client.query('SELECT SLEEP(0.1)')
562-
end.to raise_error(Mysql2::Error)
562+
end.to raise_error(Mysql2::Error::TimeoutError)
563563
end
564564

565565
# XXX this test is not deterministic (because Unix signal handling is not)
@@ -918,10 +918,10 @@ def run_gc
918918
end
919919
end
920920

921-
it "should raise a Mysql2::Error exception upon connection failure" do
921+
it "should raise a Mysql2::Error::ConnectionError exception upon connection failure due to invalid credentials" do
922922
expect do
923-
new_client(host: "localhost", username: 'asdfasdf8d2h', password: 'asdfasdfw42')
924-
end.to raise_error(Mysql2::Error)
923+
new_client(host: 'localhost', username: 'asdfasdf8d2h', password: 'asdfasdfw42')
924+
end.to raise_error(Mysql2::Error::ConnectionError)
925925

926926
expect do
927927
new_client(DatabaseCredentials['root'])

0 commit comments

Comments
 (0)