DBI:如果第一个数据库不存在,则连接到另一个数据库

6
我正在尝试整理一些旧代码,它有两个用途。它使用DBI来创建一个数据库,然后再使用DBI来连接该数据库。不幸的是,它对于每个操作都使用了相同的代码。这意味着,如果您创建了一个名为sales的数据库,当您重新连接时,必须显式调用$dbh->do('use sales')。这会导致各种问题,例如开发人员忘记这样做或数据库句柄重新连接并忘记所在的数据库。
我们首先尝试解决的问题是让DBI::connect()方法使用HandleError来重新连接MySQL,如果数据库不存在,则允许我们创建数据库。由于各种历史原因(是啊,我们都经历过),要在connect()方法之外捕获“未知数据库”错误要困难得多。
因此,我第一次尝试解决这个问题的方法如下:
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);
}

这种方法虽然可行,且对最终用户来说是透明的,但它看起来也像一堆杂乱的二进制代码。在连接失败时,有更好的重新连接到 不同 数据库的方法吗?


一个完整的测试用例会非常有帮助。没有“每个都使用了相同的代码”的清晰说明,很难确定如何提供帮助。 - Tim Bunce
Tim: 我猜一个更好的描述是“我能否让DBI在失败时重新连接,仅更改DSN?”我不知道是否有更好的方法来做到这一点。 - Ovid
1个回答

7

我不确定这种方法是否有效,但它可能更好地替代了使用PadWalker

use strict;use warnings;
use DBI;

my %attr = (RaiseError => 1, PrintError => 0);

my $dbh = DBI->connect(
    $dsn,
    $user,
    $pass,
    {
        %attr,
        HandleError => sub {
            reconnect_if_unknown_database(
                $dsn, $user, $pass, \%attr, @_
            )
        },
    },
);

sub reconnect_if_unknown_database {
    my ($dsn, $user, $pass, $attr, $msg, $drh, $dbh) = @_;

    return unless $msg =~ /Unknown database/;

    return unless $dsn =~ s/^[^;]+;/DBI:mysql:mysql;/;

    $_[-1] = DBI->connect($dsn, $user, $pass, $attr);
}

1
你知道他们说的“间接层数更多”,哈哈。 - Sinan Ünür
1
可能应该在列表末尾加上@_,因为你无法真正控制其大小。 - ikegami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接