Tim Bunce wrote:
> On Tue, Jan 19, 2010 at 09:04:43PM +0000, Martin J. Evans wrote:
>> Hi,
>>
>> Is there anything special a subclassed DBI module (DBIx::Log4perl in
>> this case) needs to do for the clone method?
>>
>> The DBI docs currently say "The clone method duplicates the $dbh
>> connection by connecting with the same parameters ($dsn, $user,
>> $password) as originally used."
>
> sub clone {
> my ($old_dbh, $attr) = @_;
> my $closure = $old_dbh->{dbi_connect_closure} or return;
>
> That's a closure created by connect() that performs the $drh->connect call.
>
> unless ($attr) {
> # copy attributes visible in the attribute cache
> keys %$old_dbh; # reset iterator
> while ( my ($k, $v) = each %$old_dbh ) {
> # ignore non-code refs, i.e., caches, handles, Err etc
> next if ref $v && ref $v ne 'CODE'; # HandleError etc
> $attr->{$k} = $v;
> }
> # explicitly set attributes which are unlikely to be in the
> # attribute cache, i.e., boolean's and some others
> $attr->{$_} = $old_dbh->FETCH($_) for (qw(
> AutoCommit ChopBlanks InactiveDestroy
> LongTruncOk PrintError PrintWarn Profile RaiseError
> ShowErrorStatement TaintIn TaintOut
> ));
> }
> # use Data::Dumper; warn Dumper([$old_dbh, $attr]);
> my $new_dbh = &$closure($old_dbh, $attr);
> unless ($new_dbh) {
> # need to copy err/errstr from driver back into $old_dbh
> my $drh = $old_dbh->{Driver};
> return $old_dbh->set_err($drh->err, $drh->errstr, $drh->state);
> }
> return $new_dbh;
> }
>
>> but I don't see any call to connect when clone is called.
>
> You don't see a call to DBI->connect, but there is a call to
> $drh->connect via the closure.
>
>> I presume there is something I need to do - any ideas?
>
> The closure calles the connected() method ad that's a good method to
> override to (re)setup any private stuff you need.
>
> Tim.
>
>
Tim,
Thank you for the pointers. I had all DBIx::Log4perl setup in the
connect method (strangely I don't recollect reading about the connected
method) and after moving it all to the connected method and deleting my
failing attempt at clone override this now seems to work except:
o when I pass DBIx::Log4perl attributes in the connection e.g.,
connect('dbi:Oracle:xxx','user', 'pass', {DBIx_l4p_logmask => 1})
I get warnings from the connect closure like this:
Can't set DBIx::Log4perl::db=HASH(0x87116c0)->{DBIx_l4p_logmask}:
unrecognised attribute name or invalid value
Previously I didn't get these as I parsed my attributes out in my
connect method then deleted them before DBI saw them but now I need them
to get down to the connected method but I don't want those warnings and
the code checks before calling connected:
if (%$apply) {
if ($apply->{DbTypeSubclass}) {
my $DbTypeSubclass = delete $apply->{DbTypeSubclass};
DBI::_rebless_dbtype_subclass($dbh,
$rebless_class||$class, $DbTypeSubclass);
}
my $a;
foreach $a (qw(Profile RaiseError PrintError AutoCommit)) { # do
these first
next unless exists $apply->{$a};
$dbh->{$a} = delete $apply->{$a};
}
while ( my ($a, $v) = each %$apply) {
# MJE warnings generated here
eval { $dbh->{$a} = $v } or $@ && warn $@;
}
}
# confirm to driver (ie if subclassed) that we've connected
sucessfully
# and finished the attribute setup. pass in the original arguments
$dbh->connected(@orig_args); #if ref $dbh ne 'DBI::db' or $proxy;
and I cannot capture them in connect as this does not work for clone (as
my connect never gets called if you clone).
o "Can't locate auto/DBIx/Log4perl/st/DELETE.al"
I feel this is something I've done wrong but I cannot find it yet. My
connect method is trying to call $dbh->func('dbms_output_enable') in
DBD::Oracle (as it always has done) but it is failing in my execute
attempting to undefine HandleError:
#
# If DBDSPECIFIC is enabled and this is DBD::Oracle we will attempt to
# to retrieve any dbms_output. However, 'dbms_output_get' actually
# creates a new statement, prepares it, executes it, binds parameters
# and then fetches the dbms_output. This will cause this execute method
# to be called again and we could recurse forever. To prevent that
# happening we set {dbd_specific} flag before calling dbms_output_get
# and clear it afterwards.
#
# Also in DBI (at least up to 1.54) and most DBDs, the same memory is
# used for a dbh errstr/err/state and each statement under it. As a
# result, if you sth1->execute (it fails) then $sth2->execute which
# succeeds, sth1->errstr/err are undeffed :-(
# see http://www.nntp.perl.org/group/perl.dbi.users/2007/02/msg30971.html
# To sort this out, we save the errstr/err/state on the first sth
# and put them back after using the second sth (ensuring we temporarily
# turn off any error handler to avoid set_err calling them again).
#
if (($h->{logger}->is_debug()) &&
($h->{logmask} & DBIX_L4P_LOG_DBDSPECIFIC) &&
($h->{driver} eq 'Oracle') && (!$h->{dbd_specific})) {
my ($errstr, $err, $state) = (
$sth->errstr, $sth->err, $sth->state);
$h->{dbd_specific} = 1;
my $dbh = $sth->FETCH('Database');
my @lines = $dbh->func('dbms_output_get');
$sth->_dbix_l4p_debug($h, 2, 'dbms', @lines) if (scalar(@lines) > 0);
$h->{dbd_specific} = 0;
{
local $sth->{HandleError} = undef; # MJE FAILS HERE
local $sth->{HandleSetErr} = undef;
$sth->set_err($err, $errstr, $state);
}
}
Martin
--
Martin J. Evans
Easysoft Limited
http://www.easysoft.com