From c104e5c6330608fb3f2c725cea8031de1d7ca086 Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Mon, 27 Mar 2023 09:09:43 -0400 Subject: [PATCH] Raise all SystemCallErrors as Trilogy::Error. This allows syscall errors to be rescued generically by Trilogy::Error, or handled individually by consumers using their oriinal Errno classes. To make this work, we subclass all of the SystemCallErrors and include the base error module on each of them. I've also removed the constructors from the existing Errno class subclasses (Trilogy::TimeoutError, Trilogy::ConnectionClosedError, etc.). We never set an error_code on syscall errors, so the constructors are not needed. --- CHANGELOG.md | 1 + contrib/ruby/ext/trilogy-ruby/cext.c | 12 ++++++---- contrib/ruby/lib/trilogy.rb | 36 ++++++++++++++++------------ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f95d7b41..d6d6423f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). Trilogy client can now accept an `:encoding` option, which will tell the connection to use the specified encoding, and will ensure that outgoing query strings are transcoded appropriately. If no encoding is supplied, utf8mb4 is used by default. #64 + - All SystemCallErrors classified as `Trilogy::Error`. ## 2.3.0 diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index be44cf10..e591c2e4 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -20,14 +20,14 @@ VALUE Trilogy_CastError; static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError, Trilogy_ConnectionClosedError, Trilogy_ConnectionRefusedError, Trilogy_ConnectionResetError, - Trilogy_TimeoutError, Trilogy_Result; + Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result; static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout, id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count, id_ivar_affected_rows, id_ivar_fields, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password, id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key, id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result, - id_from_code, id_connection_options; + id_from_code, id_from_errno, id_connection_options; struct trilogy_ctx { trilogy_conn_t conn; @@ -97,8 +97,8 @@ static void trilogy_syserr_fail_str(int e, VALUE msg) } else if (e == ECONNRESET) { rb_raise(Trilogy_ConnectionResetError, "%" PRIsVALUE, msg); } else { - // TODO: All syserr should be wrapped. - rb_syserr_fail_str(e, msg); + VALUE exc = rb_funcall(Trilogy_SyscallError, id_from_errno, 2, INT2NUM(e), msg); + rb_exc_raise(exc); } } @@ -1099,6 +1099,9 @@ RUBY_FUNC_EXPORTED void Init_cext() Trilogy_Result = rb_const_get(Trilogy, rb_intern("Result")); rb_global_variable(&Trilogy_Result); + Trilogy_SyscallError = rb_const_get(Trilogy, rb_intern("SyscallError")); + rb_global_variable(&Trilogy_SyscallError); + Trilogy_CastError = rb_const_get(Trilogy, rb_intern("CastError")); rb_global_variable(&Trilogy_CastError); @@ -1130,6 +1133,7 @@ RUBY_FUNC_EXPORTED void Init_cext() id_multi_statement = rb_intern("multi_statement"); id_multi_result = rb_intern("multi_result"); id_from_code = rb_intern("from_code"); + id_from_errno = rb_intern("from_errno"); id_ivar_affected_rows = rb_intern("@affected_rows"); id_ivar_fields = rb_intern("@fields"); id_ivar_last_insert_id = rb_intern("@last_insert_id"); diff --git a/contrib/ruby/lib/trilogy.rb b/contrib/ruby/lib/trilogy.rb index b8f43885..97a0f720 100644 --- a/contrib/ruby/lib/trilogy.rb +++ b/contrib/ruby/lib/trilogy.rb @@ -15,6 +15,27 @@ module ConnectionError include Error end + # Trilogy may raise various syscall errors, which we treat as Trilogy::Errors. + class SyscallError + ERRORS = {} + + Errno.constants + .map { |c| Errno.const_get(c) }.uniq + .select { |c| c.is_a?(Class) && c < SystemCallError } + .each do |c| + errno_name = c.to_s.split('::').last + ERRORS[c::Errno] = const_set(errno_name, Class.new(c) { include Trilogy::Error }) + end + + ERRORS.freeze + + class << self + def from_errno(errno, message) + ERRORS[errno].new(message) + end + end + end + class BaseError < StandardError include Error @@ -43,29 +64,14 @@ class CastError < ClientError class TimeoutError < Errno::ETIMEDOUT include ConnectionError - - def initialize(error_message = nil, error_code = nil) - super - @error_code = error_code - end end class ConnectionRefusedError < Errno::ECONNREFUSED include ConnectionError - - def initialize(error_message = nil, error_code = nil) - super - @error_code = error_code - end end class ConnectionResetError < Errno::ECONNRESET include ConnectionError - - def initialize(error_message = nil, error_code = nil) - super - @error_code = error_code - end end # DatabaseError was replaced by ProtocolError, but we'll keep it around as an