RT# 50541: Fix for clear bug. Introduces a speed regression
Rob Kinyon [Wed, 6 Jan 2010 03:12:11 +0000 (22:12 -0500)]
Changes
lib/DBM/Deep.pm
lib/DBM/Deep/Array.pm
lib/DBM/Deep/Hash.pm
lib/DBM/Deep/Sector/File/Reference.pm
t/02_hash.t
t/common.pm

diff --git a/Changes b/Changes
index 7f1a918..bc138c5 100644 (file)
--- a/Changes
+++ b/Changes
@@ -11,6 +11,11 @@ Revision history for DBM::Deep.
     - (RT #51888) Applied POD patch (Thanks, FWIE!)
     - (RT #44981) Added VERSION to ::Array, ::Engine, and ::Hash
     - Removed extraneous slashes from POD links (Thanks ilmari!)
+    - (RT #50541) Fixed bug in clear() for hashes in the File backend.
+        - This has caused a regression in speed for clear() when clearing
+          large hashes using running with the File backend. ->clear() (on my
+          machine) now takes ( N / 40 ) ** (1.66) seconds. So, clearing 4000
+          keys (as is the test in t/03_bighash.t) would take ~2070 seconds.
 
 1.0019_001 Dec 31 22:00:00 2009 EST
     (This is the first developer release for 1.0020.)
index ff2585d..294f5ab 100644 (file)
@@ -572,25 +572,8 @@ sub CLEAR {
 
     $self->lock_exclusive;
 
-    #XXX Rewrite this dreck to do it in the engine as a tight loop vs.
-    # iterating over keys - such a WASTE - is this required for transactional
-    # clearning?! Surely that can be detected in the engine ...
-    if ( $self->_type eq TYPE_HASH ) {
-        my $key = $self->first_key;
-        while ( $key ) {
-            # Retrieve the key before deleting because we depend on next_key
-            my $next_key = $self->next_key( $key );
-            $self->_engine->delete_key( $self, $key, $key );
-            $key = $next_key;
-        }
-    }
-    else {
-        my $size = $self->FETCHSIZE;
-        for my $key ( 0 .. $size - 1 ) {
-            $self->_engine->delete_key( $self, $key, $key );
-        }
-        $self->STORESIZE( 0 );
-    }
+    # Dispatch to the specific clearing functionality.
+    $self->_clear;
 
     $self->unlock;
 
index 4b8a2cc..247d730 100644 (file)
@@ -394,6 +394,18 @@ sub _copy_node {
     return 1;
 }
 
+sub _clear {
+    my $self = shift;
+
+    my $size = $self->FETCHSIZE;
+    for my $key ( 0 .. $size - 1 ) {
+        $self->_engine->delete_key( $self, $key, $key );
+    }
+    $self->STORESIZE( 0 );
+
+    return;
+}
+
 sub length  { (shift)->FETCHSIZE(@_) }
 sub pop     { (shift)->POP(@_)       }
 sub push    { (shift)->PUSH(@_)      }
index c4ee232..102f367 100644 (file)
@@ -102,6 +102,16 @@ sub NEXTKEY {
 sub first_key { (shift)->FIRSTKEY(@_) }
 sub next_key  { (shift)->NEXTKEY(@_)  }
 
+sub _clear {
+    my $self = shift;
+
+    while ( my $key = $self->first_key ) {
+        $self->_engine->delete_key( $self, $key, $key );
+    }
+
+    return;
+}
+
 sub _copy_node {
     my $self = shift;
     my ($db_temp) = @_;
index 5b4ee12..7430b0a 100644 (file)
@@ -284,9 +284,10 @@ sub get_bucket_list {
     $sector->find_md5( $args->{key_md5} );
 
     # See whether or not we need to reindex the bucketlist
-    # Yes, the double-braces are there for a reason. if() doesn't create a redo-able block,
-    # so we have to create a bare block within the if() for redo-purposes. Patch and idea
-    # submitted by sprout@cpan.org. -RobK, 2008-01-09
+    # Yes, the double-braces are there for a reason. if() doesn't create a
+    # redo-able block, so we have to create a bare block within the if() for
+    # redo-purposes.
+    # Patch and idea submitted by sprout@cpan.org. -RobK, 2008-01-09
     if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
         my $redo;
 
index c8a0cab..774ab49 100644 (file)
@@ -177,4 +177,23 @@ while ( my $dbm_maker = $dbm_factory->() ) {
     } qr/Cannot use an undefined hash key/, "EXISTS fails on an undefined key";
 }
 
+{
+    # RT# 50541 (reported by Peter Scott)
+    # clear() leaves one key unless there's only one
+    my $dbm_factory = new_dbm();
+    while ( my $dbm_maker = $dbm_factory->() ) {
+        my $db = $dbm_maker->();
+
+        $db->{block} = { };
+        $db->{critical} = { };
+        $db->{minor} = { };
+
+        cmp_ok( scalar(keys( %$db )), '==', 3, "Have 3 keys" );
+
+        $db->clear;
+
+        cmp_ok( scalar(keys( %$db )), '==', 0, "clear clears everything" );
+    }
+}
+
 done_testing;
index 53d9e29..146c88b 100644 (file)
@@ -70,6 +70,7 @@ sub new_dbm {
 
     if ( $ENV{TEST_MYSQL_DSN} ) {
         push @reset_funcs, sub {
+            require 'DBI.pm';
             my $dbh = DBI->connect(
                 $ENV{TEST_MYSQL_DSN},
                 $ENV{TEST_MYSQL_USER},