On Thu, 23 Jun 2005, Sam Tregar wrote:
> That looks do-able. I'll try to work up a patch in the next couple
> days. I'll include the DBI->installed_drivers() method as well, if
> that's ok.
Here it is. I've implemented a ChildHandles attribute and the
installed_drivers() method. There is one bug left, marked "BROKEN" in
the test script. It concerns what happens when you try to retrieve
ChildHandles for a handle that doesn't have any children. Right now
you get an error like this:
Can't get DBI::db=HASH(0x9bd53bc)->{ChildHandles}: unrecognised
attribute at /home/sam/module-dev/DBI-1.48/blib/lib/DBD/ExampleP.pm
line 221.
I'm sure I could fix this by adding ChildHandles to the list of
attributes in DBI.xs, but you said you didn't think that would be
necessary. Ideas?
-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-25 12:14:25.352097808 -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,26 @@
# 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 (the easy way to do
+ # this with grep undoes the weak-refs, for some reason)
+ if (@$handles % 120 == 0 and @$handles) {
+ my $last = $#$handles;
+ for my $i (0 .. $#$handles) {
+ last if $i > $last;
+ next if defined $handles->[$i];
+ splice @$handles, $i, 1;
+ $last--;
+ }
+ }
+
+ push @$handles, $h;
+ Scalar::Util::weaken($handles->[-1]);
+ }
+
return $h unless wantarray;
($h, $i);
}
@@ -2700,6 +2730,11 @@
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 hash mapping driver names to driver handles.
=item C<installed_versions>
@@ -3275,6 +3310,31 @@
driver handle, returns a reference to the cache (hash) of
database handles created by the L</connect_cached> method.
+=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:
+
+ my %drivers = DBI->installed_drivers();
+ foreach my $drh (values %drivers) {
+ print "DRH: $drh\n";
+ foreach my $dbh (@{$drh->{ChildHandles}}) {
+ next unless defined $dbh;
+ print "DBH: $dbh\n";
+ foreach my $sth (@{$dbh->{ChildHandles}}) {
+ next unless defined $sth;
+ print "STH: $sth\n";
+ }
+ }
+ }
+
=item C<CompatMode> (boolean, inherited)
The C<CompatMode> attribute is used by emulation layers (such as
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-24 20:11:21.170372464 -0400
@@ -0,0 +1,80 @@
+#!perl -w
+
+use strict;
+
+#
+# test script for the ChildHandles attribute
+#
+
+use DBI;
+
+use Test;
+BEGIN { plan tests => 17; }
+{
+ # 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 });
+
+
+##### BROKEN ##### BROKEN ##### BROKEN ##### BROKEN ##### BROKEN
+# ChildHandles should start empty
+# my $empty = $dbh->{ChildHandles};
+# ok(scalar @$empty, 0);
+##### BROKEN ##### BROKEN ##### BROKEN ##### BROKEN ##### BROKEN
+
+# 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);
+}
+
+# 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);
+}