Capturing Database-Dependent Information
Once your application receives a database error, you must first interpret it before you can handle it. The main problem here is that each database vendor has its own ways of representing and categorizing errors. The DB Interface Module solves this problem by providing class RWDBStatus with four variables reserved for database-dependent information. These are accessed using the member functions vendorError1(), vendorError2(), vendorMessage1(),and vendorMessage2(). The first two return long int, and the second two return RWCString.
Each DB Access Module defines the contents of RWDBStatus for its particular database. Each Access Module user’s guide provides details. As comparative examples, Table 185, Table 186, and Table 187 list the mappings used for three popular databases, Oracle, Sybase, and Microsoft SQL Server.
|
Field |
Contents |
|
|
|
|
|
“ |
|
|
Unused |
|
|
Unused |
|
|
Error code from the |
|
|
Unused |
You can see in these tables that the layouts are similar but not identical. This is not surprising, since error reporting is not standardized well among database APIs. Let us now return and modify our error handler to cope with this problem:
void errorHandler(const RWDBStatus& s) {
switch(s.errorCode()) {
case RWDBStatus::serverError:
cout << s.message() << endl
<< "Error number: " << s.vendorError1() << endl
<< "Server: " << s.vendorMessage1() << endl
<< "Severity: " << s.vendorError2() << endl;
break;
case RWDBStatus::serverMessage:
case RWDBStatus::vendorLib:
// handle other database errors
default:
// raise an exception
if (!status.isValid()) {
s.raise();
}
}
}
Now let's create an error condition. The following fragment attempts to drop a database table that doesn't exist:
RWDBTable junk = myDbase.table("junk");//table doesn't exist
RWDBStatus s = junk.drop();
On our system, this is the output when the example is run against an Oracle database and a Sybase database, respectively:
(Oracle output)
[SERVERERROR] ORA-00942: table or view
does not exist
Error number: 942
Server:
(Sybase output)
[SERVERERROR] Cannot drop the table 'junk',
because it doesn't exist in the system catalogs.
Error number: 3701
Server: SYBASE100
Severity: 11
Now suppose that your application needs to take some special action if a table doesn't exist. Evidently your application must be able to interpret database errors, not just report them, and to interpret them it must recognize the database. Perhaps the most straightforward way for your application to handle different databases, then, is to have separate error handlers for each database type:
const long OraNoTableError = 942;
const long SybNoTableError = 3701;
void oraErrorHandler(const RWDBStatus& s) {
RWDBStatus::ErrorCode code = s.errorCode();
if (code == RWDBStatus::serverError &&
s.vendorError1() == OraNoTableError) {
// take special action
}
else {
s.raise();
}
}
void sybErrorHandler(const RWDBStatus& s) {
RWDBStatus::ErrorCode code = s.errorCode();
if (code == RWDBStatus::serverError &&
s.vendorError1() == SybNoTableError) {
// take special action
}
else {
s.raise();
}
}
.
.
.
RWDBDatabase oracle = RWDBManager::database
( /*args for Oracle*/ );
RWDBDatabase sybase = RWDBManager::database
( /*args for Sybase*/ );
oracle.setErrorHandler(oraErrorHandler);
sybase.setErrorHandler(sybErrorHandler);
When the database-dependent table does not exist error comes up, it is trapped explicitly and handled by the take special action case in the appropriate error handler. The trade-off here is between portability and specificity. The more portable your application, the less database-dependent intelligence it can use, and vice versa.
To summarize, once you make the decision to interpret information in a database-dependent way, you can do it several different ways. In addition to the examples shown here, you may consider deriving from class RWDBStatus in order to obtain a database-dependent interface to it. Or you might design a database exception class with derived database-dependent variants, and explicitly throw() instances of your class instead of using the raise() method of RWDBStatus.