Client 2Adding Error Checking
As mentioned earlier, libpqxx
Because these exception classes are ultimately derived from std::exception , each provides a member function named what() that returns a description of the error that occurred. In addition, the sql_error class includes a member function named query() that returns the text of the query that produced the error. Other Exceptions Thrown by libpqxxAn sql_error exception is thrown when the PostgreSQL server reports an error, but libpqxx throws other exception types when client-side errors occur (or when an error occurs in the client/server protocol):
Listing 10.3 shows a simple client that establishes a connection to a server and then exits, this time reporting error messages in a more meaningful manner. Listing 10.3. client2.cc
1 /* client2.cpp */
2
3 #include <stdlib.h> // Required for exit()
4 #include <iostream> // Required for cerr
5 #include <pqxx/pqxx> // libpqxx definitions
6
7 using namespace pqxx;
8 using namespace std;
9
10 int main( int argc, const char * argv[] )
11 {
12
13 try
14 {
15 connection myConnection( argc > 1 ? argv[1] : "" );
16
17 myConnection.activate();
18 }
19 catch( runtime_error & e )
20 {
21 cerr << "Connection failed: " << e.what();
22 exit( EXIT_FAILURE );
23 }
24 catch( exception & e )
25 {
26 cerr << e.what();
27 exit( EXIT_FAILURE );
28 }
29 catch( ... )
30 {
31 cerr << "Unknown exception caught" << endl;
32 exit( EXIT_FAILURE );
33 }
34
35 exit( EXIT_SUCCESS );
36
37 }
client2.cc
is nearly identical to
client1.cc
we've added two new
catch
clauses to distinguish between
runtime_error
exceptions, other standard exceptions (all derived from
std::exception
), and unknown exceptions. The handler for
runtime_error
exceptions (lines 1923) displays a meaningful error message followed by the text of the exception, obtained by calling
e.what()
. The handler for
exception
-derived exceptions (line 2428) is a bit more genericit simply displays the text message carried within the
exception
object. The catch-all handler (lines 2933) displays a less
In general, the handler for a runtime_error can be called with a broken_connection object, an sql_error object, an in_doubt_error object, or any other object derived from runtime_error (in client2.cc , the runtime_error handler will never receive an sql_error or in_doubt_error because the code simply connects to a server and exits). Of course, you could write a separate handler for each class derived from runtime_error if you needed to do something other than report the text of the error message. A broken_connection is often fatal, but you can continue processing after an sql_error exception). Listing 10.4 shows a bonus function that returns the demangled name of a type (just in case you want to include the exception type in your error messages). This function works when compiled with the GCC g++ compiler and many other compilers that produce code compatible with the Itanium C++ ABI (application binary interface) described at http://www.codesourcery.com/cxx-abi/abi.html. Listing 10.4. The demangled_name() Function
#include <typeinfo>
#include <string>
#include <cxxabi.h> // C++ name demangler functions
using namespace std;
string demangled_name( type_info const & typeinfo )
{
int status;
char * name = abi::__cxa_demangle( typeinfo.name(), NULL, NULL, &status );
if( name )
{
string result( name );
free( name );
return( result );
}
else
{
return( "can't demangle typename" );
}
}
To print the name of an exception type, call demangled_name() like this:
...
catch( runtime_error & e )
{
cerr << "Caught exception of type " << demangled_name( typeid( e )) << endl;
cerr << "Connection failed: " << e.what();
exit( EXIT_FAILURE );
}
...
Handling Informational/Warning Messages with Notice Processor Objects
libpqxx throws an exception whenever an error is
test=# CREATE TABLE myTestTable test-# ( test(# pkey INTEGER primary key, test(# value INTEGER test(# ); the psql client displays two messages:
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit
index "mytesttable_pkey" for table "mytesttable"
CREATE TABLE
The first message is a notice ; the second message simply shows that the CREATE TABLE command completed successfully. A notice is an informational message or a warning message that the server sends to the client outside the normal metadata/data channel. There is an important but subtle difference between an error and a notice: An error interrupts the normal flow of a program (that is, the program cannot continue until the error is handled in some way); a notice is simply extra information you can ignore if you're not interested. Another, more important, distinction is that an error aborts the current transaction, whereas a notice does not. The PostgreSQL server defines six error severities (plus a few more used for logging debugging information):
libpqxx reports
ERROR
,
FATAL
, and
PANIC
messages by throwing an exception. libpqxx delivers
INFO
,
NOTICE
, and
WARNING
messages to a
noticer
object. The libpqxx
noticer
class is very simple: It defines a single member function,
operator()
, that takes a null-
Don't confuse notice with notification . Notifications are generated by the NOTIFY command and are handled (in libpqxx applications) by trigger objects. We cover NOTIFY , LISTEN , and trigger objects later in the section titled "LISTEN/NOTIFY." To create your own notice processor, simply derive a class from pqxx::noticer , override the operator() member function with your own code, create an object of the new class, and assign the object to your connection . Listing 10.5 shows an implementation of a noticer -derived class that simply echoes any messages to the std::cerr stream. Listing 10.5. myNoticer Class Definition
class myNoticer : public pqxx::noticer
{
public:
virtual void operator()( const char message[] ) throw()
{
cerr << message;
}
};
Notice that you don't have to append any end-of-line
auto_ptr< pqxx::noticer >
pqxx::connection_base::set_noticer( auto_ptr< noticer > N ) throw ()
When you see a function that requires an
auto_ptr<>
argument (as opposed to a simple pointer), you know that the function will take ownership of the object pointed to. The use of
auto_ptr<>
has two important consequences for how you manage
noticer
objects in your own application. First, after you've attached a
noticer
object to a
connection
, you can forget about itthe
connection
object automatically deletes its
noticer
when the
connection
is
...
static void attachNoticer( connection & conn )
{
noticer * noticeProcessor = new myNoticer;
conn.set_noticer( auto_ptr<noticer>( noticeProcessor ));
}
...
The connection_base::set_noticer() function returns an auto_ptr<> to the connection's previous notice processor (if any). If you capture the return value in another auto_ptr<> , you own the old noticer , meaning you decide when to delete the old noticer . If you don't capture the old noticer , it's automatically delete d when your function returns because the implicit auto_ptr<> created for the return value goes out of scope at the end of your function. libpqxx offers two ways to detach a noticer from a connection. First, you can replace a connection 's noticer by creating a new noticer object and calling set_noticer() with the new object (and wrapping it in an auto_ptr<> ). Second, you can detach a noticer without replacing it with a new one by calling set_noticer( auto_ptr<noticer>( NULL )) . If you want to send a message to your own client application, you could retrieve a pointer to a connection 's noticer by calling the connection_base::get_noticer() function, but libpqxx offers a better way. The connection_base::process_notice() function routes a message through the connection 's current noticer . process_notice() comes in two flavors: connection_base::process_notice( const char message[] ) throw() connection_base::process_notice( const std::string & message ) throw() The first flavor expects a null-terminated C-style string, and the second expects a reference to a std::string object. Any notices you generate in your own application should end with a newline character, just to be consistent with the messages generated by libpqxx (or by the PostgreSQL server). Why would you want to send your own messages through process_notice() ? Because libpqxx routes all informational messages through a notice processor, you can use the same mechanism to route debugging information or progress messages to the same destination. That way, any debug messages you generate in your own application are interspersed with informational messages generated by PostgreSQL. Now that you know how to connect to a database server, catch error conditions, and intercept informational messages generated by the server, it's time to get to the interesting stuff: query processing. |