Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More specific exception classes #870

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ext/mysql2/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#include "mysql_enc_name_to_ruby.h"

VALUE cMysql2Client;
extern VALUE mMysql2, cMysql2Error;
extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError;
static VALUE sym_id, sym_version, sym_header_version, sym_async, sym_symbolize_keys, sym_as, sym_array, sym_stream;
static ID intern_brackets, intern_merge, intern_merge_bang, intern_new_with_args;

Expand Down Expand Up @@ -650,7 +650,7 @@ static VALUE do_query(void *args) {
retval = rb_wait_for_single_fd(async_args->fd, RB_WAITFD_IN, tvp);

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

if (retval < 0) {
Expand Down
3 changes: 2 additions & 1 deletion ext/mysql2/mysql2_ext.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include <mysql2_ext.h>

VALUE mMysql2, cMysql2Error;
VALUE mMysql2, cMysql2Error, cMysql2TimeoutError;

/* Ruby Extension initializer */
void Init_mysql2() {
mMysql2 = rb_define_module("Mysql2");
cMysql2Error = rb_const_get(mMysql2, rb_intern("Error"));
cMysql2TimeoutError = rb_const_get(cMysql2Error, rb_intern("TimeoutError"));

init_mysql2_client();
init_mysql2_result();
Expand Down
2 changes: 1 addition & 1 deletion ext/mysql2/statement.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <mysql2_ext.h>

VALUE cMysql2Statement;
extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate;
extern VALUE mMysql2, cMysql2Error, cMysql2TimeoutError, cBigDecimal, cDateTime, cDate;
static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s;
static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
#ifndef HAVE_RB_BIG_CMP
Expand Down
55 changes: 47 additions & 8 deletions lib/mysql2/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,64 @@ class Error < StandardError
:replace => '?'.freeze,
}.freeze

ConnectionError = Class.new(Error)
TimeoutError = Class.new(Error)

CODES = {
1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
}

[
1044, # ER_DBACCESS_DENIED_ERROR
1045, # ER_ACCESS_DENIED_ERROR
1152, # ER_ABORTING_CONNECTION
1153, # ER_NET_PACKET_TOO_LARGE
1154, # ER_NET_READ_ERROR_FROM_PIPE
1155, # ER_NET_FCNTL_ERROR
1156, # ER_NET_PACKETS_OUT_OF_ORDER
1157, # ER_NET_UNCOMPRESS_ERROR
1158, # ER_NET_READ_ERROR
1159, # ER_NET_READ_INTERRUPTED
1160, # ER_NET_ERROR_ON_WRITE
1161, # ER_NET_WRITE_INTERRUPTED

2001, # CR_SOCKET_CREATE_ERROR
2002, # CR_CONNECTION_ERROR
2003, # CR_CONN_HOST_ERROR
2004, # CR_IPSOCK_ERROR
2005, # CR_UNKNOWN_HOST
2006, # CR_SERVER_GONE_ERROR
2007, # CR_VERSION_ERROR
2009, # CR_WRONG_HOST_INFO
2012, # CR_SERVER_HANDSHAKE_ERR
2013, # CR_SERVER_LOST
2020, # CR_NET_PACKET_TOO_LARGE
2026, # CR_SSL_CONNECTION_ERROR
2027, # CR_MALFORMED_PACKET
2047, # CR_CONN_UNKNOW_PROTOCOL
2048, # CR_INVALID_CONN_HANDLE
2049, # CR_UNUSED_1
].each { |c| CODES[c] = ConnectionError }

CODES.freeze

attr_reader :error_number, :sql_state

# Mysql gem compatibility
alias_method :errno, :error_number
alias_method :error, :message

def initialize(msg)
@server_version ||= nil
def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
@server_version = server_version
@error_number = error_number
@sql_state = sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state

super(clean_message(msg))
end

def self.new_with_args(msg, server_version, error_number, sql_state)
err = allocate
err.instance_variable_set('@server_version', server_version)
err.instance_variable_set('@error_number', error_number)
err.instance_variable_set('@sql_state', sql_state.respond_to?(:encode) ? sql_state.encode(ENCODE_OPTS) : sql_state)
err.send(:initialize, msg)
err
error_class = CODES.fetch(error_number, self)
error_class.new(msg, server_version, error_number, sql_state)
end

private
Expand Down
10 changes: 5 additions & 5 deletions spec/mysql2/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
end
end

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

it "should raise an exception on create for invalid encodings" do
Expand Down Expand Up @@ -538,7 +538,7 @@ def run_gc
client = new_client(:read_timeout => 0)
expect {
client.query('SELECT SLEEP(0.1)')
}.to raise_error(Mysql2::Error)
}.to raise_error(Mysql2::Error::TimeoutError)
end

# XXX this test is not deterministic (because Unix signal handling is not)
Expand Down Expand Up @@ -918,10 +918,10 @@ def run_gc
end
end

it "should raise a Mysql2::Error exception upon connection failure" do
it "should raise a Mysql2::Error::ConnectionError exception upon connection failure due to invalid credentials" do
expect {
new_client(:host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42')
}.to raise_error(Mysql2::Error)
}.to raise_error(Mysql2::Error::ConnectionError)

expect {
new_client(DatabaseCredentials['root'])
Expand Down