Here's a revised version of the ChildHandles patch.  I think I've
implemented all the requested changes.  I also added an implementation
of Type for DBI::PurePerl which was missing.

-sam
diff -Naur DBI-1.48.orig/DBI.pm DBI-1.48/DBI.pm
--- DBI-1.48.orig/DBI.pm        2005-03-14 11:45:38.000000000 -0500
+++ DBI-1.48/DBI.pm     2005-06-27 14:17:17.166716296 -0400
@@ -21,6 +21,7 @@
   use DBI;
 
   @driver_names = DBI->available_drivers;
+  %drivers      = DBI->installed_drivers;
   @data_sources = DBI->data_sources($driver_name, \%attr);
 
   $dbh = DBI->connect($data_source, $username, $auth, \%attr);
@@ -271,7 +272,16 @@
     DBI->trace_msg("DBI connect via $DBI::connect_via in 
$INC{'Apache/DBI.pm'}\n");
 }
 
+# check for weaken support, used by ChildHandles
+my $HAS_WEAKEN = eval { 
+    require Scalar::Util;
+    # this will croak() if this Scalar::Util doesn't have a working weaken().
+    Scalar::Util::weaken(my $test = \"foo"); 
+    1;
+};
+
 %DBI::installed_drh = ();  # maps driver names to installed driver handles
+sub installed_drivers { %DBI::installed_drh }
 
 # Setup special DBI dynamic variables. See DBI::var::FETCH for details.
 # These are dynamically associated with the last handle used.
@@ -1166,6 +1176,20 @@
     # Now add magic so DBI method dispatch works
     DBI::_setup_handle($h, $imp_class, $parent, $imp_data);
 
+    # add to the parent's ChildHandles
+    if ($HAS_WEAKEN && $parent) {
+        my $handles = $parent->{ChildHandles} ||= [];
+
+        # purge destroyed handles occasionally
+        if (@$handles % 120 == 0 and @$handles) {
+            @$handles = grep { defined } @$handles;
+            Scalar::Util::weaken($_) for @$handles; # re-weaken after grep
+        }
+
+        push @$handles, $h;
+        Scalar::Util::weaken($handles->[-1]);
+    }
+
     return $h unless wantarray;
     ($h, $i);
 }
@@ -2700,6 +2724,13 @@
 some drivers are hidden by others of the same name in earlier
 directories. Passing a true value for C<$quiet> will inhibit the warning.
 
+=item C<installed_drivers>
+
+  %drivers = DBI->installed_drivers();
+
+Returns a list of driver name and driver handle pairs for all
+installed drivers. The driver name does not include the 'DBD::'
+prefix.
 
 =item C<installed_versions>
 
@@ -3275,6 +3306,35 @@
 driver handle, returns a reference to the cache (hash) of
 database handles created by the L</connect_cached> method.
 
+=item C<Type> (scalar)
+
+The C<Type> attribute identifies the type of a DBI handle.  Returns
+"dr" for driver handles, "db" for database handles and "st" for
+statement handles.
+
+=item C<ChildHandles> (array ref)
+
+The ChildHandles attribute contains a reference to an array of all the
+handles created by this handle which are still accessible.  The
+contents of the array are weak-refs and will become undef when the
+handle goes out of scope.  C<ChildHandles> is only available if you
+have the L<Scalar::Util|Scalar::Util> module installed and
+C<Scalar::Util::weaken()> is working.
+
+For example, to enumerate all driver handles, database handles and
+statement handles:
+
+    sub show_child_handles {
+        my ($h, $level) = @_;
+        $level ||= 0;
+        printf "%sh %s %s\n", $h->{Type}, "\t" x $level, $h;
+        show_child_handles($_, $level + 1) 
+            for (grep { defined } @{$h->{ChildHandles}});
+    }
+
+    my %drivers = DBI->installed_drivers();
+    show_child_handles($_) for (values %drivers);
+
 =item C<CompatMode> (boolean, inherited)
 
 The C<CompatMode> attribute is used by emulation layers (such as
diff -Naur DBI-1.48.orig/DBI.xs DBI-1.48/DBI.xs
--- DBI-1.48.orig/DBI.xs        2005-01-20 06:06:28.000000000 -0500
+++ DBI-1.48/DBI.xs     2005-06-27 14:25:43.345765384 -0400
@@ -1442,6 +1442,9 @@
        DBIc_set(imp_xxh,DBIcf_HandleSetErr, on);
        cacheit = 1; /* child copy setup by dbih_setup_handle() */
     }
+    else if (strEQ(key, "ChildHandles")) {
+        cacheit = 1; /* just save it in the hash */
+    }
     else if (strEQ(key, "Profile")) {
        char *dbi_class = "DBI::Profile";
        if (on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVHV)) ) {
@@ -1754,6 +1757,15 @@
             else if (strEQ(key, "CachedKids")) {
                 valuesv = &sv_undef;
             }
+            else if (strEQ(key, "ChildHandles")) {
+                // get the value from the hash, otherwise return a []
+                svp = hv_fetch((HV*)SvRV(h), key, keylen, FALSE);
+                if (svp) { 
+                    valuesv = newSVsv(*svp);
+                } else {
+                    valuesv = newRV_noinc((SV*)newAV());
+                }
+            } 
             else if (strEQ(key, "CompatMode")) {
                 valuesv = boolSV(DBIc_COMPAT(imp_xxh));
             }
diff -Naur DBI-1.48.orig/lib/DBI/PurePerl.pm DBI-1.48/lib/DBI/PurePerl.pm
--- DBI-1.48.orig/lib/DBI/PurePerl.pm   2004-12-16 11:41:06.000000000 -0500
+++ DBI-1.48/lib/DBI/PurePerl.pm        2005-06-27 14:23:08.429316264 -0400
@@ -143,6 +143,7 @@
        Attribution
        BegunWork
        CachedKids
+        ChildHandles
        CursorName
        Database
        DebugDispatch
@@ -169,6 +170,7 @@
        SCALE
        Statement
        TYPE
+        Type
        TraceLevel
        Username
        Version
@@ -645,6 +647,13 @@
        return ($h->FETCH('TaintIn') && $h->FETCH('TaintOut')) if $key 
eq'Taint';
        return (1==0) if $is_flag_attribute{$key}; # return perl-style sv_no, 
not undef
        return $DBI::dbi_debug if $key eq 'TraceLevel';
+        return [] if $key eq 'ChildHandles';
+        if ($key eq 'Type') {
+            return "dr" if $h->isa('DBI::dr');
+            return "db" if $h->isa('DBI::db');
+            return "st" if $h->isa('DBI::st');
+            Carp::carp( sprintf "Can't get determine Type for %s",$h );
+        }
        if (!$is_valid_attribute{$key} and $key =~ m/^[A-Z]/) {
            local $^W; # hide undef warnings
            Carp::carp( sprintf "Can't get %s->{%s}: unrecognised attribute 
(@{[ %$h ]})",$h,$key )
diff -Naur DBI-1.48.orig/t/71installed_drivers.t 
DBI-1.48/t/71installed_drivers.t
--- DBI-1.48.orig/t/71installed_drivers.t       1969-12-31 19:00:00.000000000 
-0500
+++ DBI-1.48/t/71installed_drivers.t    2005-06-24 18:08:33.000000000 -0400
@@ -0,0 +1,26 @@
+#!perl -w
+
+use strict;
+
+#
+# test script for the installed_drivers class method
+#
+
+use DBI;
+
+use Test;
+BEGIN { plan tests => 4; }
+
+# installed drivers should start empty
+my %drivers = DBI->installed_drivers();
+ok(scalar keys %drivers, 0);
+
+# make a connection
+my $dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+
+# now the driver should be registered
+%drivers = DBI->installed_drivers();
+ok(scalar keys %drivers, 1);
+ok(exists $drivers{ExampleP});
+ok($drivers{ExampleP}->isa('DBI::dr'));
+
diff -Naur DBI-1.48.orig/t/72childhandles.t DBI-1.48/t/72childhandles.t
--- DBI-1.48.orig/t/72childhandles.t    1969-12-31 19:00:00.000000000 -0500
+++ DBI-1.48/t/72childhandles.t 2005-06-27 14:13:26.858728432 -0400
@@ -0,0 +1,95 @@
+#!perl -w
+
+use strict;
+
+#
+# test script for the ChildHandles attribute
+#
+
+use DBI;
+
+use Test;
+BEGIN { plan tests => 22; }
+{
+    # make 10 connections
+    my @dbh;
+    for (1 .. 10) {
+        my $dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+        push(@dbh, $dbh);
+    }
+    
+    # get the driver handle
+    my %drivers = DBI->installed_drivers();
+    my $driver = $drivers{ExampleP};
+    ok($driver);
+
+    # get the kids, should be the 10 connections
+    my $db_handles = $driver->{ChildHandles};
+    ok(scalar @$db_handles, 10);
+
+    # make sure all the handles are there
+    foreach my $h (@dbh) {
+        ok(grep { $h == $_ } @$db_handles);
+    }
+}
+
+# now all the out-of-scope DB handles should be gone
+{
+    my %drivers = DBI->installed_drivers();
+    my $driver = $drivers{ExampleP};
+
+    my $handles = $driver->{ChildHandles};
+    my @db_handles = grep { defined } @$handles;
+    ok(scalar @db_handles, 0);
+}
+
+my $dbh = DBI->connect("dbi:ExampleP:", '', '', { RaiseError=>1 });
+
+
+# ChildHandles should start with an empty array-ref
+my $empty = $dbh->{ChildHandles};
+ok(scalar @$empty, 0);
+
+# test child handles for statement handles
+{
+    my @sth;
+    for (1 .. 200) {
+        my $sth = $dbh->prepare('SELECT name FROM t');
+        push(@sth, $sth);
+    }
+    my $handles = $dbh->{ChildHandles};
+    ok(scalar @$handles, 200);
+
+    # test a recursive walk like the one in the docs
+    my @lines;
+    sub show_child_handles {
+        my ($h, $level) = @_;
+        $level ||= 0;
+        push(@lines, 
+             sprintf "%sh %s %s\n", $h->{Type}, "\t" x $level, $h);
+        show_child_handles($_, $level + 1) 
+          for (grep { defined } @{$h->{ChildHandles}});
+    }   
+    show_child_handles($_) for (values %{{DBI->installed_drivers()}});
+
+    ok(scalar @lines, 202);
+    ok($lines[0] =~ /^drh/);
+    ok($lines[1] =~ /^dbh/);
+    ok($lines[2] =~ /^sth/);
+}
+
+# they should be gone now
+my $handles = $dbh->{ChildHandles};
+my @live = grep { defined $_ } @$handles;
+ok(scalar @live, 0);
+
+# test that the childhandle array won't grow uncontrollably.
+{
+    for (1 .. 1000) {
+        my $sth = $dbh->prepare('SELECT name FROM t');
+    }
+    my $handles = $dbh->{ChildHandles};
+    ok(scalar @$handles < 1000);
+    my @live = grep { defined } @$handles;
+    ok(scalar @live, 0);
+}

Reply via email to