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

Reply via email to