module Mysql2 class Error < StandardError ENCODE_OPTS = { undef: :replace, invalid: :replace, replace: '?'.freeze, }.freeze ConnectionError = Class.new(Error) TimeoutError = Class.new(Error) CODES = { 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT 1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR 1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR 1152 => ConnectionError, # ER_ABORTING_CONNECTION 1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE 1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE 1155 => ConnectionError, # ER_NET_FCNTL_ERROR 1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER 1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR 1158 => ConnectionError, # ER_NET_READ_ERROR 1159 => ConnectionError, # ER_NET_READ_INTERRUPTED 1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE 1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED 2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR 2002 => ConnectionError, # CR_CONNECTION_ERROR 2003 => ConnectionError, # CR_CONN_HOST_ERROR 2004 => ConnectionError, # CR_IPSOCK_ERROR 2005 => ConnectionError, # CR_UNKNOWN_HOST 2006 => ConnectionError, # CR_SERVER_GONE_ERROR 2007 => ConnectionError, # CR_VERSION_ERROR 2009 => ConnectionError, # CR_WRONG_HOST_INFO 2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR 2013 => ConnectionError, # CR_SERVER_LOST 2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE 2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR 2027 => ConnectionError, # CR_MALFORMED_PACKET 2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL 2048 => ConnectionError, # CR_INVALID_CONN_HANDLE 2049 => ConnectionError, # CR_UNUSED_1 }.freeze attr_reader :error_number, :sql_state # Mysql gem compatibility alias errno error_number alias error message def initialize(msg, server_version = nil, error_number = nil, sql_state = nil) @server_version = server_version @error_number = error_number @sql_state = sql_state ? sql_state.encode(**ENCODE_OPTS) : nil super(clean_message(msg)) end def self.new_with_args(msg, server_version, error_number, sql_state) error_class = CODES.fetch(error_number, self) error_class.new(msg, server_version, error_number, sql_state) end private # In MySQL 5.5+ error messages are always constructed server-side as UTF-8 # then returned in the encoding set by the `character_set_results` system # variable. # # See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for # more context. # # Before MySQL 5.5 error message template strings are in whatever encoding # is associated with the error message language. # See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html # for more information. # # The issue is that the user-data inserted in the message could potentially # be in any encoding MySQL supports and is insert into the latin1, euckr or # koi8r string raw. Meaning there's a high probability the string will be # corrupt encoding-wise. # # See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for # more information. # # So in an attempt to make sure the error message string is always in a valid # encoding, we'll assume UTF-8 and clean the string of anything that's not a # valid UTF-8 character. # # Returns a valid UTF-8 string. def clean_message(message) if @server_version && @server_version > 50500 message.encode(**ENCODE_OPTS) else message.encode(Encoding::UTF_8, **ENCODE_OPTS) end end end end