DBI: raiseerror in eval

2020-07-26 15:25发布

问题:

This question refers to this comment from Ikegami:

[...] But if you're going to put an eval around every statement, just use RaiseError => 0. [...]

in this thread.

What do I gain, if I set RaiseError to 0 in such situations?

#!/usr/bin/env perl
use warnings;
use 5.10.1;
use DBI;

my $db = 'my_test_sqlite_db.sqlite';
open my $fh, '>', $db or die $!;
close $fh or die $!;

my ( $dbh, $sth );
eval {
    $dbh = DBI->connect( "DBI:SQLite:dbname=$db", "", "", {} );
};
if ( $@ ) { print $@ };

my $table = 'my_sqlite_table';

say   "RaiseError = 1";
say   "PrintError = 0";
$dbh->{RaiseError} = 1;
$dbh->{PrintError} = 0;
eval {
    $sth = $dbh->prepare( "SELECT * FROM $table" );
    $sth->execute();
};
if ( $@ ) { print "ERROR: $@" };

say "\nRaiseError = 0";
say   "PrintError = 1";
$dbh->{RaiseError} = 0;
$dbh->{PrintError} = 1;
eval {
    $sth = $dbh->prepare( "SELECT * FROM $table" );
    $sth->execute();
};
if ( $@ ) { print "ERROR: $@" };

say "\nRaiseError = 0";
say   "PrintError = 0";
$dbh->{RaiseError} = 0;
$dbh->{PrintError} = 0;
eval {
    $sth = $dbh->prepare( "SELECT * FROM $table" );
    $sth->execute();
};
if ( $@ ) { print "ERROR: $@" };

Output:

RaiseError = 1
PrintError = 0
ERROR: DBD::SQLite::db prepare failed: no such table: my_sqlite_table at ./perl2.pl line 23.

RaiseError = 0
PrintError = 1
DBD::SQLite::db prepare failed: no such table: my_sqlite_table at ./perl2.pl line 33.
ERROR: Can't call method "execute" on an undefined value at ./perl2.pl line 34.

RaiseError = 0
PrintError = 0
ERROR: Can't call method "execute" on an undefined value at ./perl2.pl line 44.

回答1:

If failed for some reasons, most $dbh methods will either:

  • (if RaiseError option is set to 0) return undef
  • (if RaiseError option is set to 1) immediately exit the script ('die') with error reason given as exit message.

The key point here is that it's up to you how to process the errors. If you wish, you can just ignore them, for example (the following obviously will work with RaiseError set to 0 only):

for my $db ( ... ) {
    my $dbh = get_database_handle( $db )
       or next;
    ...
}

In this snippet (copied from @ikegami's answer you've mentioned in your question) you loop through some list of settings for DB connection; if some connection gives you an undef, you just go for another one, and do nothing with error.

Usually, though, you have to do more than just 'nexting' when error happens - but then again, you have two choices: either check each $dbh-related statement with something like this:

$sth = $dbh->prepare('some_params') 
  or process_db_error('In prepare');
...
$res = $sth->execute('another_set_of_params') 
  or process_db_error('In execute');
...
$res->doAnythingElse('something completely different') 
  or process_db_error('In something completely different');

(as or parts will be executed only if their corresponding 'left parts' evaluate to false in Boolean context).

...or just wrap all this into Perlish 'try-catch' block:

if (!eval {    
   $sth = $dbh->prepare('some_params');
   ...
   $res = $sth->execute('another_set_of_params');
   ...
   $res->doSomethingElse('something completely different') 
   ...
   1  # No exception
}) {
   process_db_error($@);
}

What's to choose, it's up to you: it's a common decision between 'errors in return statements' (except that to fetch an actual error you have to ask $dbh object) and, well, exceptions.

But the bottom line is you cannot write just this:

$sth = $dbh->do_something('that_can_result_in_error');
$sth->do_something('else');

... if you did set RaiseError to 0. In this case script won't die, $sth will be assigned an undef, and you get yourself a 'derivative' error (as you cannot call a method on undef).

And that exactly what happened in the last part of code in your original question.