Client 2—Adding Error Checking

As mentioned earlier, libpqxx follows the STL philosophy that an error condition should be reported by throwing an exception. libpqxx defines three exception types, all derived from std::runtime_error:

  • sql_error— Thrown when the PostgreSQL server reports an error

  • broken_connection— Thrown when the libpqxx library detects that the connection to the server has been broken

  • in_doubt_error— Thrown when you COMMIT a transaction but the connection to the server fails before libpqxx has received an acknowledgement that the COMMIT completed

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 libpqxx

An 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):

  • logic_error— Indicates a design error. Typically, a logic_error indicates a problem with the design of your client code, such as trying to COMMIT a transaction after you've already aborted it. A logic_error can also indicate a problem with the internal design of the libpqxx library. Think of a logic_error as some condition that “should never happen.” A user should never be able to induce a logic_error.

  • out_of_range— Thrown when you try to access a nonexistent element in an array (or array-like collection) or a string. libpqxx throws an out_of_range exception when you call result::at(), tuple::at(), result::column_name(), or binarystring::at() with an invalid index.

  • invalid_argument— Thrown when you call a libpqxx function with an invalid argument. For example, result:column_number(), which maps a column name to a its corresponding column number, throws an invalid_argument if you search for a column name that doesn't exist in the result set.

  • runtime_error— Sort of a catch-all for other errors that can occur in a libpqxx client application. Most notably, whenever libpqxx receives an unexpected result from the PostgreSQL server (or the client/server protocol stack), it translates the result into text form and throws a runtime_error to report the problem. You can retrieve the text of the PostgreSQL error message from the exception.

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 19–23) displays a meaningful error message followed by the text of the exception, obtained by calling e.what(). The handler for exception-derived exceptions (line 24–28) is a bit more generic—it simply displays the text message carried within the exception object. The catch-all handler (lines 29–33) displays a less satisfying message because the type of the exception is unknown.

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 detected, but the PostgreSQL server occasionally sends other messages that are not classified as errors. For example, when you type the following command into psql:

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):

  • INFO— Extra information you might be interested in but that implies no negative consequences. For example, if you define an unrecognized configuration option in your server configuration file, you'll see an INFO message stating that the option is unrecognized and therefore has no effect.

  • NOTICE— Extra information that tells you that a command has had (possibly) surprising, but not negative, consequences. For example, when you create a column defined as a PRIMARY KEY, the server produces a NOTICE stating that an index has automatically been created to support the primary key.

  • WARNING— Implies that a command succeeded but found a problem along the way. For instance, if you try to remove a user from a group and that user isn't a member of the group, you'll see a WARNING. Technically speaking, the command succeeds because, when it's finished, the user will not be a member of the group, but you'll see a WARNING to tell you that something unexpected occurred.

  • ERROR— A command has failed. When an ERROR is generated, the server aborts the current transaction. All further commands are ignored until you execute a ROLLBACK or COMMIT command. If you execute a COMMIT in an aborted transaction, the server automatically translates the command into a ROLLBACK. An error implies that the command was not able to complete but that the server can continue to process commands in future transactions.

  • FATAL— A FATAL error means the server cannot continue to process commands. A FATAL error aborts the current transaction and terminates your server process. FATAL errors are rarely seen except for one particular type: authentication failures. If you try to connect to a server with an invalid username or password, the authentication failure is reported as a FATAL error.

  • PANIC— A PANIC is similar to a FATAL error in that the server process cannot continue to execute commands. A PANIC is more severe than a FATAL error because it forces all server processes to terminate (a FATAL error terminates only your server process). PANICs are extremely rare. In fact, there is nothing that a client application can do to cause a PANIC. A PANIC represents a programming error made by a PostgreSQL designer or a failure in the PostgreSQL environment, such as corrupted memory or a failed disk drive.

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-terminated character string (the info/notice/warning/ message) as its only argument. Every connection object holds a (possibly NULL) pointer to a single noticer object. When libpqxx wants to report an INFO, NOTICE, or WARNING message, it calls the operator() member function of the connection's current noticer. If a connection doesn't own a noticer (that is, if the connection's noticer pointer is NULL), the text of the message is written to the applications stderr stream.

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 characters to the message; libpqxx ensures that every message is terminated with a newline character. Attaching a noticer-derived object to a connection is a little tricky because the connection wants to take ownership of the noticer. You can infer this change in ownership from set_noticer()'s complex function prototype:

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 it—the connection object automatically deletes its noticer when the connection is destroyed. Second, because a connection uses delete to destroy its noticer, you must use new to allocate noticer objects. So, the process of allocating and attaching a noticer looks like this:

...
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 deleted 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.200.76