[boost::system] Is the library abstraction general enough?
Hi, all I'm currently building an asynchronous MySQL client API based on Boost.Asio, whose error handling is based on Boost.System. And thus, I wrote some code to wrap MySQL error codes and error messages with Boost.System. While writing the code, I found that the library doesn't provide a general enough abstraction. As the document says, there should always be only one instance of each error_category class. And each error_code instance communicates with its error_category through only the error number. Then here is the problem. MySQL error code is a property of a single MySQL connection. The mysql_errno() and mysql_error() functions both require a MYSQL* connection handle as parameter to retrieve the last error number and error message of the given MySQL connection. And the error messages provided by MySQL API are actually printf format strings. The mysql_error() function fills the format strings with information related to the MYSQL* handle. But in Boost.System, I've got no idea how to pass the connection handle to the error_category::message() function so that a proper error message can be retrieved. Is there any way to fix this problem? Or the library itself is just not general enough? You see, not all error numbers can be directly mapped to a well defined error message string. Cheers Cheng
On Fri, 20 Feb 2009 09:36:53 +0100, Lian Cheng
[...]Then here is the problem. MySQL error code is a property of a single MySQL connection. The mysql_errno() and mysql_error() functions both require a MYSQL* connection handle as parameter to retrieve the last error number and error message of the given MySQL connection. And the error messages provided by MySQL API are actually printf format strings. The mysql_error() function fills the format strings with information related to the MYSQL* handle. But in Boost.System, I've got no idea how to pass the connection handle to the error_category::message() function so that a proper error message can be retrieved.
Isn't there a list of error numbers and error strings MySQL uses? I ask as calling error_category::message() should also return an error string even if no MySQL connection is used? Boris
Boris Schaeling:
On Fri, 20 Feb 2009 09:36:53 +0100, Lian Cheng
wrote: Isn't there a list of error numbers and error strings MySQL uses? I ask as calling error_category::message() should also return an error string even if no MySQL connection is used?
Unfortunately, no. As I've mentioned, the error strings MySQL uses are printf format strings, such as: Using unsupported buffer type: %d (parameter: %d) And you always need an active MySQL connection handle to obtain the full error message by calling mysql_error(). And you can never get the message back once you close the connection: MYSQL* conn = mysql_init( 0 ); // do something causes an error. mysql_close( conn ); printf( "error: %s", mysql_error( conn ) ); // Messed up.
Boris
On Mon, Feb 23, 2009 at 2:33 AM, Lian Cheng
Boris Schaeling:
On Fri, 20 Feb 2009 09:36:53 +0100, Lian Cheng
wrote: Isn't there a list of error numbers and error strings MySQL uses? I ask as calling error_category::message() should also return an error string even if no MySQL connection is used?
Unfortunately, no. As I've mentioned, the error strings MySQL uses are printf format strings, such as:
Using unsupported buffer type: %d (parameter: %d)
And you always need an active MySQL connection handle to obtain the full error message by calling mysql_error(). And you can never get the message back once you close the connection:
MYSQL* conn = mysql_init( 0 ); // do something causes an error. mysql_close( conn ); printf( "error: %s", mysql_error( conn ) ); // Messed up.
Boost Exception can be used to transport anything in an exception
object. You can transport error codes as well as error messages:
#include
Thanks a lot for the recipe! But the asynchronous MySQL API I'm working on is based on Asio, which uses Boost.System for error handling. Boost.System provides a unified method to handle errors with both traditional error code paradigm and the exception paradigm. Thus, there may be code like this: connector.async_query( "SHOW DATABASES", handle_query ); void handle_query( boost::system::error_code const& ec ) { if( ec ) { std::cerr << boost::format( "Query error: %1% - %2%" ) % ec.value() % ec.message() << std::endl; } else { // ... } } The ec.message() call above is redirected to the message() method of the user defined error_category class (amysql::client_category in my case) with an error code (ONLY!). Currently, I use a boost::thread_specific_ptrstd::string to store the last MySQL error message, and in the amysql::client_category::message() method, the error code parameter is ignored, and just return the thread specific last error message string. The pitfall here is that an error_code object should never be stored for later usage, since the thread specific last error message would be changed whenever a new error occurred. I think Boost.System should allow user to pass into a function object instead of a single error code to the error_category::message() function, so that user can have better control on error message generation policies. Cheers Cheng Emil Dotchevski:
On Mon, Feb 23, 2009 at 2:33 AM, Lian Cheng
wrote: On Fri, 20 Feb 2009 09:36:53 +0100, Lian Cheng
wrote: Isn't there a list of error numbers and error strings MySQL uses? I ask as calling error_category::message() should also return an error string even if no MySQL connection is used? Unfortunately, no. As I've mentioned, the error strings MySQL uses are
Boris Schaeling: printf format strings, such as:
Using unsupported buffer type: %d (parameter: %d)
And you always need an active MySQL connection handle to obtain the full error message by calling mysql_error(). And you can never get the message back once you close the connection:
MYSQL* conn = mysql_init( 0 ); // do something causes an error. mysql_close( conn ); printf( "error: %s", mysql_error( conn ) ); // Messed up.
Boost Exception can be used to transport anything in an exception object. You can transport error codes as well as error messages:
#include
typedef boost::error_info
mysql_error_code; typedef boost::error_info mysql_error_string; struct mysql_err: std::exception, boost::exception { }; .... if( int error=mysql_query(conn,....) ) throw mysql_err() << mysql_error_code(error) << mysql_error_string(mysql_error(conn));
Alternatively, if you use shared_ptr to manage the lifetime of the MYSQL connection, as in:
boost::shared_ptr<MYSQL> conn( mysql_init(0), mysql_close );
you can use an exception object to keep the connection afloat:
typedef boost::error_info
mysql_error_code; typedef boost::error_info mysql_conn; struct mysql_err: std::exception, boost::exception { }; .... if( int error=mysql_query(conn,....) ) throw mysql_err() << mysql_error_code(error) << mysql_conn(conn);
Now, when you catch mysql_err, you can talk to mysql to get the error message:
catch( mysql_err & x ) { boost::shared_ptr<MYSQL> conn=*boost::get_error_info
(x); char const * msg=mysql_error(conn.get()); .... } Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On Mon, Feb 23, 2009 at 10:38 PM, Lian Cheng
Thanks a lot for the recipe! But the asynchronous MySQL API I'm working on is based on Asio, which uses Boost.System for error handling. Boost.System provides a unified method to handle errors with both traditional error code paradigm and the exception paradigm.
I am not familiar with Boost Asio, but as long as it reports errors by throwing exceptions through boost::throw_exception (which most Boost libraries do), you can use a modified version of the recipe I posted. This is because any exception emitted by boost::throw_exception is guaranteed to derive boost::exception, which lets you intercept it and inject information relevant to the failure, even if that information wasn't initially stored by the code reporting the error. See "Adding of Arbitrary Data to Active Exception Objects" in http://www.boost.org/doc/libs/1_38_0/libs/exception/doc/tutorial_transportin....
Thus, there may be code like this: <snipped> I think Boost.System should allow user to pass into a function object instead of a single error code to the error_category::message() function, so that user can have better control on error message generation policies.
The question is where do you draw the line? There are use cases for which a function object wouldn't be enough. In fact, what data has to be stored in an error reporting object (such as boost::system::error_code or an exception) cannot be determined by the designers of the library that reports the error, because the user context in which the error occurs is not known but is very much relevant. For example consider an XML parser that detects and reports a parsing error: in general, it doesn't know if the XML it parses comes from a file or from somewhere else, but if it comes from a file, the file name is certainly relevant to the failure. This common problem was the motivation for Boost Exception and has lead to a design that lets users store anything at all into exception objects. Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
participants (3)
-
Boris Schaeling
-
Emil Dotchevski
-
Lian Cheng