On Wed, Feb 06, 2002 at 09:14:19AM +0100, Henrik Tougaard wrote:
> > Don't be surprised if you haven't heard of the Handlers attribute,
> > it's not documented. And don't bother finding out about it because
> > it's probably going away.
> >
> > But I think I have mentioned it a couple of times and wanted to
> > check if anyone was using it for anything? And if so, what (so
> > I can try to make sure there's a better way to do the same thing.)
>
> Jup. We use it.
>
> WE have an environment where we have DBI (and DBD::Ingres of course)
> and CORBA (using COPE - the perl CORBA ORB).
> They have different ways of handling exceptions.
> The DBI uses RaiseError, that calls a 'die', which is normally good.
> COPE uses Experimental::Exception (for historic reasons).
> CORBA has its own exception system and COPE models certain
> Exp::Exc exceptions to CORBA-exceptions.
> A 'normal' die, ie. with a string, is not a CORBA exception.
>
> So we use a $dbh->{handler} to catch the DBI exception and turn it
> into a Experimental::Exception exception, that is a CORBA exception
> of a type that is valid for the method called.
Ah yes, that rings a bell.
> This could be done with a lot of eval- and try-blocks, but that
> is counter to the idea of RaiseError - ie. code as if there were
> no errors and if errors occur the 'do the rigth thing'.
> And $dbh->{handler} does this nicely. I do agree that it is not
> fit for public consumption, you do need to know a bit (a lot?)
> about the internals of DBI and DBD to be able to use it.
>
> To sum it up: we do need a way of catching DBI errors and turning
> them into something else.
And now you have it...
> Something with a better interface would be nice. It would probably
> need to differ between $dbh errors and $sth erros (and $drh errors
> of course) as you often need to do things another way if you are
> in the middle of a select than if you are trying to update.
How does this sound:
=item C<HandleError> (code ref, inherited) I<NEW>
This attribute can be used to provide your own alternative behaviour
in case of errors. If set to a reference to a subroutine then that
subroutine is called when an error is detected (at the same point that
C<RaiseError> and C<PrintError> are handled).
The subroutine is called with three parameters: the error message
string that C<RaiseError> and C<PrintError> would use,
the DBI handle being used, and the first value being returned by
the method that failed (typically undef).
If the subroutine returns a false value then the C<RaiseError>
and/or C<PrintError> attributes are checked and acted upon as normal.
For example, to get a full stack trace for any error:
use Carp;
$h->{HandleError} = sub { confess(shift) };
Or to turn errors into exceptions:
use Exception; # or your own favourite exception module
$h->{HandleError} = sub { Exception->new('DBI')->raise($_[0]) };
It is possible to 'stack' multiple HandleError handlers by using
closures:
sub your_subroutine {
my $previous_handler = $h->{HandleError};
$h->{HandleError} = sub {
return 1 if $previous_handler and &$previous_handler(@_);
... your code here ...
}
}
Using a C<my> inside a subroutine to store the previous C<HandleError>
value is important. See L<perlsub> and L<perlref> for more information
about I<closures>.
It is possible for C<HandleError> to hide an error, to a limited
degree, by using L</set_err> to reset $DBI::err and $DBI::errstr,
and altering the return value of the failed method. For example:
$h->{HandleError} = sub {
return 0 unless $_[0] =~ /^\S+ fetchrow_arrayref failed:/;
return 0 unless $_[1]->err == 1234; # the error to 'hide'
$h->set_err(0,""); # turn off the error
$_[2] = [ ... ]; # supply alternative return value
return 1;
};
This only works for methods which return a single value and is hard
to make reliable (avoiding infinite loops, for example) and so isn't
recommended for general use! If you find a I<good> use for it then
please let me know.
Hopefully that'll enable what you want (and more :)
Tim.