I'm trying to sort our some legacy code that serves two purposes. It uses DBI to create a database and later uses DBI to connect to that database. Unfortunately, it used the same code for each. That means that if you create a sales
database, later, when use reconnect, you must explicitly call $dbh->do('use sales')
. That leads to all sorts of problems, such as developers forgetting to do that or the database handle reconnecting and forgetting which database it was in.
What we're trying to do as a first pass fix is to have the DBI::connect()
method use HandleError
to reconnect to MySQL if the database doesn't exist, thus allowing us to create the database. For various legacy reasons (yeah, we've all been there), it's much harder to try to trap the "Unknown database" error outside of the of the connect()
method.
Thus, my first pass at solving this is as follows:
use strict;
use warnings;
use DBI;
use PadWalker 'peek_my';
my $dbh = DBI->connect(
$dsn,
$user,
$pass,
{ RaiseError => 1,
PrintError => 0,
HandleError => \&reconnect_if_unknown_database,
},
);
sub reconnect_if_unknown_database {
my ($msg, $drh, $dbh) = @_;
return unless $msg =~ /Unknown database/;
my ($dsn, $user, $pass, $attr) = @{peek_my(1)}{qw/$dsn $user $pass $attr/};
unless ($dsn && $user && $pass && $attr) {
return; # don't do this if we can't get everything
}
# they're all scalar refs.
$_ = $$_ foreach $dsn, $user, $pass, $attr;
unless ($dsn =~ s/^[^;]+;/DBI:mysql:mysql;/) {
return; # can't parse dsn, so return
}
delete $attr->{HandleError}; # infinite loops tickle
$_[2] = DBI->connect($dsn, $user, $pass, $attr);
}
That works and it's currently transparent to the end user, but it also feels like a steaming pile of ones and zeros. Is there a better way of reconnecting to a different database on connect failure?