What exceptions does DBI throw? - perl

If I set RaiseError = 1, what exception is raised when connecting or executing?
If I were to surround my execute() method in try catch, what exception should I be catching?

The Perl Error.pm module, which provides try and catch, is deprecated. Exceptions, inasmuch as they exist in Perl, are untyped, and here's how you catch one:
eval {
do_something_which_may_throw_exception();
};
if ($#) {
print "Exception: $#\n";
};
In short, the eval { ... } block acts as a "try", and the if ($#) { ... } acts as a "catch", wherein the exception text is contained in the special variable $#.

The DBI documentation lists and explains a lot of options, many of which relate to error handling.
Perl has two major error handling idioms:
Returning a false value. The reason for the error is in some global variable.
die with some error message (fatal).
By default, DBI uses the first idiom. The error reason is in $DBI::errstr. For this to work, you have to check the return values of each and every call to the DBI API.
When you feel lazy, you can use exceptions. Setting RaiseError in the handle constructor will make DBI methods throw an exception. From the docs:
RaiseError
Type: boolean, inherited
The RaiseError attribute can be used to force errors to raise exceptions rather than simply return error codes in the normal way. It is "off" by default. When set "on", any method which results in an error will cause the DBI to effectively do a die("$class $method failed: $DBI::errstr"), where $class is the driver class and $method is the name of the method that failed. E.g.,
DBD::Oracle::db prepare failed: ... error text here ...
[…]
Typically RaiseError is used in conjunction with eval { ... } to catch the exception that's been thrown and followed by an if ($#) { ... } block to handle the caught exception. For example:
eval {
...
$sth->execute();
...
};
if ($#) {
# $sth->err and $DBI::err will be true if error was from DBI
warn $#; # print the error
... # do whatever you need to deal with the error
}
In that eval block the $DBI::lasth variable can be useful for diagnosis and reporting if you can't be sure which handle triggered the error.
As you can see, exceptions in Perl aren't handled with try/catch, but with eval { ... }. After an eval that dies, the $# error variable will be set to that error, and you are free to handle it. Note that DBI does not use exception objects.

If you'd like to get back formal exception objects from DBI, you can use the HandleError attribute and Exception::Class::DBI. I use it myself. From the Synopsis:
use DBI;
use Exception::Class::DBI;
my $dbh = DBI->connect($dsn, $user, $pass, {
PrintError => 0,
RaiseError => 0,
HandleError => Exception::Class::DBI->handler,
});
eval { $dbh->do($sql) };
if (my $ex = $#) {
print STDERR "DBI Exception:\n";
print STDERR " Exception Type: ", ref $ex, "\n";
print STDERR " Error: ", $ex->error, "\n";
print STDERR " Err: ", $ex->err, "\n";
print STDERR " Errstr: ", $ex->errstr, "\n";
print STDERR " State: ", $ex->state, "\n";
print STDERR " Return Value: ", ($ex->retval || 'undef'), "\n";
}

Related

what does $^S mean in perl?

What does $^S mean in perl?
looking at https://perldoc.perl.org/perlvar all I can find is
Having to even think about the $^S variable in your exception handlers
is simply wrong.
perl -e 'print qq|What does "\$^S":$^S mean in perl?\n Thank you\n|'
perlvar actually says the following for $^S:
Current state of the interpreter.
$^S State
--------- -------------------------------------
undef Parsing module, eval, or main program
true (1) Executing an eval or try block
false (0) Otherwise
The first state may happen in $SIG{__DIE__} and $SIG{__WARN__} handlers.
Say you want to decorate exceptions with a timestamp, but only those that aren't caught to avoid breaking anything or having multiple timestamps added.
$ perl -e'
use POSIX qw( strftime );
# Decorate error messages, part 1.
local $SIG{ __DIE__ } = sub {
# Avoid exiting prematurely.
return if $^S;
my $ts = strftime( "%FT%TZ", gmtime() );
print( STDERR "[$ts] $_[0]" );
exit( $! || $? >> 8 || 255 );
};
# Decorate error messages, part 2.
local $SIG{ __WARN__ } = sub {
# Avoid mangling exceptions that are being handled.
return if $^S;
my $ts = strftime( "%FT%TZ", gmtime() );
print( STDERR "[$ts] $_[0]" );
};
eval {
die( "Some caught exception\n" );
};
if ( $# ) {
warn( "Caught: $#" );
}
die( "Some uncaught exception\n" );
'
[2022-12-15T05:57:44Z] Caught: Some caught exception
[2022-12-15T05:57:44Z] Some uncaught exception
Without checking $^S, we would have exited prematurely.
It's a bit further down on perlvar.
Current state of the interpreter.
$^S State
--------- -------------------------------------
undef Parsing module, eval, or main program
true (1) Executing an eval or try block
false (0) Otherwise
Confusingly, the variable's full use English name is $EXCEPTIONS_BEING_CAUGHT, despite, as the documentation goes on to admit, the fact that the variable really doesn't inform you as to whether or not an exception is being caught.

Ignoring errors in a Perl END block

How do I ignore a die() that occurs in a Perl END block?
As it is now I get
END failed--call queue aborted
And the error bubbles up the calling script.
Put your end block inside an eval { .... } - this should prevent the behaviour you describe.
#!/usr/bin/perl
print("hi there!\n");
END {
eval{
print("goodbye\n");
die("this can't hurt....");
};
#detect if an error occurred in the eval
if ( $# ){
print ("an error occurred: $#\n");
}
}
Place your code inside eval block and if you want to retrieve the error message that die provides you can capture using if condition outside the block.
#!/usr/bin/perl
my $val=1;
eval
{
print "Inside Eval\n";
if($val == 1)
{
die "hello world\n";
}
print "End of Eval\n";
};
if ( $# )
{
print "Error message - $#" . "\n";
}
Try::Tiny is an excellent wrapper for eval, allowing you to explicitly handle run-time exceptions.

Why am I seeing DBI errors on the console even though I have wrapped the DBI calls in an eval?

I have a database query that I am running inside an eval, to trap the error. Problem is that the error message is outputting to console, even though it is being trapped. How do I stop the error message from doing this, as I want to parse it myself and spit back my own messages?
my $dbh = DBI->connect('dbi:Pg:dbname=database;host=localhost',
'user', 'pass',
{RaiseError => 1}
);
eval{
$sth = $dbh->prepare($sql);
$sth->execute;
};
if($#){
#Do my parse/print stuff here I know
}
It's not a good idea to trap and ignore errors, whether they are fatal or not. Also, it is not advisable to check $# in the way you are doing it (see the questions on this site about perl exceptions for better ways to trap exceptions; I use Try::Tiny below, which is arguably the lightest-weight route of all).
Instead of proceeding with a DBI operation when an earlier one might have failed, you should check error conditions at every step:
use strict; use warnings;
use Try::Tiny;
try {
my $sth = $dbh->prepare($sql) or die $dbh->errstr;
$sth->execute or die $sth->errstr;
} catch {
print "got error $_\n";
# return from function, or do something else to handle error
};
And remember, always use strict; use warnings; in every module and script. Your code excerpt suggests that you are not yet doing this.
You can specify 'PrintError => 0' in your connect call (or use HandleError):
my $dbh = DBI->connect('dbi:Pg:dbname=database;host=localhost', $user, $passwd, {
PrintError => 0,
RaiseError => 1,
});
Or to set per statement handle:
my $sth = $dbh->prepare("SELECT * from my_table");
$sth->{PrintError} = 0;
$sth->execute();
...etc.
Also, don't depend on $# for indicating an error. A better way to use eval is:
my $result = eval {
...
$sth->...etc.
1;
}
unless ($result) {
# Do error handling..log/print $#
}
eval { } will trap a fatal error (from a die or Carp::croak call), but not a non-fatal error message (from warn or carp). To handle warning messages, see how to install a warning handler in documentation for %SIG or warn.
A trivial workaround is to use a trivial warning handler inside your eval block.
eval {
local $SIG{__WARN__} = sub { };
...
};
See also: perlfaq7: How do I temporarily block warnings?

How do I test for an exception type in perl?

How can I check what kind of exception caused the script or eval block to terminate?
I need to know the type of error, and where the exception occurred.
The Perl way
Idiomatic Perl is that we are either ignoring all errors or capturing them for logging or forwarding elsewhere:
eval { func() }; # ignore error
or:
eval { func() };
if ($#) {
carp "Inner function failed: $#";
do_something_with($#);
}
or (using Try::Tiny - see that page for reasons why you might want to use it over Perl's built-in exception handling):
try { func() }
catch {
carp "Inner function failed: $_";
do_something_with($_);
};
If you want to check the type of exception, use regexes:
if ( $# =~ /open file "(.*?)" for reading:/ ) {
# ...
}
The line and file is also in that string too.
This is pretty nasty though, because you have to know the exact string. If you really want good error handling, use an exception module from CPAN.
Exception::Class
$# doesn't have to be a string, it can be an object. Exception::Class lets you declare and throw exception objects Java-style. You can pass arbitrary information (filename, etc.) with the error when you throw it and get that information out using object methods rather than regex parsing - including the file and line number of the exception.
If you're using a third party module that does not use Error::Exception, consider
$SIG{__DIE__} = sub { Exception::Class::Base->throw( error => join '', #_ ); };
This will transform all errors into Exception::Class objects.
Error::Exception sets up proper stringification for Exception::Class objects.

How do I handle both caught and uncaught errors in a Perl subroutine?

This is a followup to "How can I get around a ‘die’ call in a Perl library I can’t modify?".
I have a subroutine that calls a Library-Which-Crashes-Sometimes many times. Rather than couch each call within this subroutine with an eval{}, I just allow it to die, and use an eval{} on the level that calls my subroutine:
my $status=eval{function($param);};
unless($status){print $#; next;}; # print error and go to
# next file if function() fails
However, there are error conditions that I can and do catch in function(). What is the most proper/elegant way to design the error-catching in the subroutine and the calling routine so that I get the correct behavior for both caught and uncaught errors?
Block eval can be nested:
sub function {
eval {
die "error that can be handled\n";
1;
} or do {
#propagate the error if it isn't the one we expect
die $# unless $# eq "error that can be handled\n";
#handle the error
};
die "uncaught error";
}
eval { function(); 1 } or do {
warn "caught error $#";
};
I'm not completely sure what you want to do, but I think you can do it with a handler.
$SIG{__DIE__} = sub { print $# } ;
eval{ function($param); 1 } or next;