Richard Clarke wrote:
> Using Perrin's article on E-Toys is perhaps a good place to start. In the
> Model object which performs various DB procedures, what actions were taken
> if for some reason the connection to the database failed or if an SQL error
> happened? Was the whole "execute()" block put in an eval procedure and then
> a generic error page produced; Or were either per procedure evals or
> Exception objects used to propogate specific errors up to the control object
> so that it could display errors in the current page/view?

Well, naturally the answer is "it depends."  Most database errors can't 
be gracefully recovered from, so we would let them propagate up.  If it 
was possible for a database error to be caused by user input (say, a 
duplicate login name) that would need to be caught and handled.  It 
would also be caught if any special cleanup of non-database resources 
was needed.

Errors that propagate up are caught by an eval wrapping the whole 
handler method which issues a rollback to the database, logs the error, 
and prints either a pretty error page or a stack trace depending on the 
current debug settings.

Here's an excerpt from the documentation I wrote for our exception base 
class.  This uses the try/catch syntax from Error.pm.

=head1 HANDLING DBI ERRORS

Since DBI errors are the most common source of exceptions in our
application, I'm giving them special treatment here.  Because we are
planning to use the RaiseError option of DBI, you can expect DBI to
die whenever it hits a problem, as opposed to returning an undef value
that you have to check for.  This means that most of the time you
don't need to do anything special for handling DBI errors.  They will
propagate up and be caught at the top level.

DBI exceptions will be propagated as instances of the Error::Simple
class.

You can catch your own exceptions without catching the DBI
exceptions by catching specific classes other than Error::Simple.

   try {
       # lookup password in Oracle
       my $sth = $dbh->prepare_cached($sql_query);
       $sth->execute($bind_value);
       if (!$sth->rows) {
           # should have matched something
           throw ESF::Error::User::BogusPassword -text $password;
       }
       $ary_ref  = $sth->fetchrow_arrayref;
       # ... etc. ...
   }
   catch ESF::Error::User::BogusPassword with {
       # handle this error
   }; # <-- don't forget!

It's okay to use transactions, and to put in your C<$dbh->commit>
statement at the end assuming everything will work.  If DBI throws an
exception that propagates to the top without being caught, we will
automatically issue a C<$dbh->rollback> command.

Sometimes, you may want to catch a specific DBI error.  Be careful
when doing this, because you need to know the error number or text of
the error message in order to trap the right exception.

DBI's raise error setting will cause it to fill the text attribute of
the Error::Simple objects it throws with the message given by Oracle,
so if you want to trap errors of type ORA-172, you can do this:

   try {
       my $sth = $dbh->prepare_cached($sql_query);
       $sth->execute($bind_value);
   }
   catch Error::Simple with {
       my $err = shift;
       if ($err->text() =~ m/ORA-172/) {
           # handle the error here
       } else {
           # let this error propagate
           $err->throw();
       }
   }; # <-- don't forget!


- Perrin

Reply via email to