First checkin of the reversion back from the failed optimization effort. I will be...
rkinyon@cpan.org [Sat, 18 Oct 2008 18:58:26 +0000 (18:58 +0000)]
git-svn-id: http://svn.ali.as/cpan/trunk/DBM-Deep@4447 88f4d9cd-8a04-0410-9d60-8f63309c3137

19 files changed:
Changes
lib/DBM/Deep.pm
lib/DBM/Deep.pod
lib/DBM/Deep/Array.pm
lib/DBM/Deep/Engine.pm
lib/DBM/Deep/Engine/Sector/BucketList.pm
lib/DBM/Deep/Engine/Sector/Reference.pm
lib/DBM/Deep/Hash.pm
lib/DBM/Deep/Iterator.pm
t/01_basic.t
t/02_hash.t
t/03_bighash.t
t/04_array.t
t/27_filehandle.t
t/41_transaction_multilevel.t
t/43_transaction_maximum.t
t/44_upgrade_db.t
t/97_dump_file.t
utils/upgrade_db.pl

diff --git a/Changes b/Changes
index e627f61..794c0e6 100644 (file)
--- a/Changes
+++ b/Changes
@@ -7,7 +7,6 @@ Revision history for DBM::Deep.
       a lock_shared() method. The :flock constants are no longer
       imported into the DBM::Deep namespace.
       **** THIS IS AN API CHANGE ****
-    - Start the process of optimization.
 
 1.0013 Jun 13 23:15:00 2008 EST
     - (This version is compatible with 1.0012)
index 1defc2b..4d6a980 100644 (file)
@@ -142,17 +142,17 @@ sub TIEARRAY {
 
 sub lock_exclusive {
     my $self = shift->_get_self;
-    return $self->_engine->lock_exclusive( $self );
+    return $self->_engine->lock_exclusive( $self, @_ );
 }
 *lock = \&lock_exclusive;
 sub lock_shared {
     my $self = shift->_get_self;
-    return $self->_engine->lock_shared( $self );
+    return $self->_engine->lock_shared( $self, @_ );
 }
 
 sub unlock {
     my $self = shift->_get_self;
-    return $self->_engine->unlock( $self );
+    return $self->_engine->unlock( $self, @_ );
 }
 
 sub _copy_value {
index 7252f7c..33b7eb9 100644 (file)
@@ -385,7 +385,7 @@ value.
 
   $db->clear(); # hashes or arrays
 
-=item * lock_exclusive() / lock_shared() / lock() / unlock()
+=item * lock() / unlock() / lock_exclusive() / lock_shared()
 
 q.v. L</LOCKING> for more info.
 
@@ -555,12 +555,12 @@ NFS> below for more.
 =head2 Explicit Locking
 
 You can explicitly lock a database, so it remains locked for multiple
-actions. This is done by calling the C<lock()> method, and passing an
-optional lock mode argument (defaults to exclusive mode). This is particularly
-useful for things like counters, where the current value needs to be fetched,
-then incremented, then stored again.
+actions. This is done by calling the C<lock_exclusive()> method (for when you
+want to write) or the C<lock_shared()> method (for when you want to read).
+This is particularly useful for things like counters, where the current value
+needs to be fetched, then incremented, then stored again.
 
-  $db->lock();
+  $db->lock_exclusive();
   my $counter = $db->get("counter");
   $counter++;
   $db->put("counter", $counter);
@@ -568,13 +568,10 @@ then incremented, then stored again.
 
   # or...
 
-  $db->lock();
+  $db->lock_exclusive();
   $db->{counter}++;
   $db->unlock();
 
-If you want a shared lock, you will need to call C<lock_shared()>. C<lock()> is
-an alias to C<lock_exclusive()>.
-
 =head2 Win32/Cygwin
 
 Due to Win32 actually enforcing the read-only status of a shared lock, all
index 5f8494e..5521477 100644 (file)
@@ -3,7 +3,9 @@ package DBM::Deep::Array;
 use 5.006_000;
 
 use strict;
-use warnings FATAL => 'all';
+use warnings;
+
+our $VERSION = q(1.0013);
 
 # This is to allow DBM::Deep::Array to handle negative indices on
 # its own. Otherwise, Perl would intercept the call to negative
@@ -26,17 +28,12 @@ sub TIEARRAY {
 
     $args->{type} = $class->TYPE_ARRAY;
 
-    my $self = $class->_init($args);
-
-#    $self->STORESIZE;
-
-    return $self;
+    return $class->_init($args);
 }
 
 sub FETCH {
     my $self = shift->_get_self;
     my ($key) = @_;
-    warn "ARRAY:FETCH( $key )\n" if DBM::Deep::DEBUG;
 
     $self->lock_shared;
 
@@ -68,7 +65,6 @@ sub FETCH {
 sub STORE {
     my $self = shift->_get_self;
     my ($key, $value) = @_;
-    warn "ARRAY::STORE($self, $key)\n" if DBM::Deep::DEBUG;
 
     $self->lock_exclusive;
 
@@ -110,7 +106,6 @@ sub STORE {
 sub EXISTS {
     my $self = shift->_get_self;
     my ($key) = @_;
-    warn "ARRAY::EXISTS($self, $key)\n" if DBM::Deep::DEBUG;
 
     $self->lock_shared;
 
@@ -181,14 +176,12 @@ sub DELETE {
 # going to work.
 sub FETCHSIZE {
     my $self = shift->_get_self;
-    warn "ARRAY::FETCHSIZE($self)\n" if DBM::Deep::DEBUG;
 
     $self->lock_shared;
 
     my $SAVE_FILTER = $self->_engine->storage->{filter_fetch_value};
     $self->_engine->storage->{filter_fetch_value} = undef;
 
-    # If there is no flushing, then things get out of sync.
     my $size = $self->FETCH('length') || 0;
 
     $self->_engine->storage->{filter_fetch_value} = $SAVE_FILTER;
@@ -201,7 +194,6 @@ sub FETCHSIZE {
 sub STORESIZE {
     my $self = shift->_get_self;
     my ($new_length) = @_;
-    warn "ARRAY::STORESIZE($self, $new_length)\n" if DBM::Deep::DEBUG;
 
     $self->lock_exclusive;
 
@@ -219,7 +211,6 @@ sub STORESIZE {
 
 sub POP {
     my $self = shift->_get_self;
-    warn "ARRAY::POP($self)\n" if DBM::Deep::DEBUG;
 
     $self->lock_exclusive;
 
@@ -241,7 +232,6 @@ sub POP {
 
 sub PUSH {
     my $self = shift->_get_self;
-    warn "ARRAY::PUSH($self)\n" if DBM::Deep::DEBUG;
 
     $self->lock_exclusive;
 
@@ -268,7 +258,7 @@ sub _move_value {
 
 sub SHIFT {
     my $self = shift->_get_self;
-    warn "ARRAY::SHIFT($self)\n" if DBM::Deep::DEBUG;
+    warn "SHIFT($self)\n" if DBM::Deep::DEBUG;
 
     $self->lock_exclusive;
 
@@ -297,7 +287,6 @@ sub SHIFT {
 
 sub UNSHIFT {
     my $self = shift->_get_self;
-    warn "ARRAY::UNSHIFT($self)\n" if DBM::Deep::DEBUG;
     my @new_elements = @_;
 
     $self->lock_exclusive;
@@ -324,7 +313,6 @@ sub UNSHIFT {
 
 sub SPLICE {
     my $self = shift->_get_self;
-    warn "ARRAY::SPLICE($self)\n" if DBM::Deep::DEBUG;
 
     $self->lock_exclusive;
 
@@ -391,7 +379,6 @@ sub SPLICE {
 # We don't need to populate it, yet.
 # It will be useful, though, when we split out HASH and ARRAY
 sub EXTEND {
-    warn "ARRAY::EXTEND()\n" if DBM::Deep::DEBUG;
     ##
     # Perl will call EXTEND() when the array is likely to grow.
     # We don't care, but include it because it gets called at times.
index 0faa0d3..6fb2039 100644 (file)
@@ -29,7 +29,7 @@ sub SIG_BLIST    () { 'B'    }
 sub SIG_FREE     () { 'F'    }
 sub SIG_SIZE     () {  1     }
 
-our $STALE_SIZE = 2;
+my $STALE_SIZE = 2;
 
 # Please refer to the pack() documentation for further information
 my %StP = (
@@ -38,17 +38,6 @@ my %StP = (
     4 => 'N', # Unsigned long in "network" (big-endian) order
     8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
 );
-sub StP { $StP{$_[1]} }
-
-# Import these after the SIG_* definitions because those definitions are used
-# in the headers of these classes. -RobK, 2008-06-20
-use DBM::Deep::Engine::Sector::BucketList;
-use DBM::Deep::Engine::Sector::FileHeader;
-use DBM::Deep::Engine::Sector::Index;
-use DBM::Deep::Engine::Sector::Null;
-use DBM::Deep::Engine::Sector::Reference;
-use DBM::Deep::Engine::Sector::Scalar;
-use DBM::Deep::Iterator;
 
 ################################################################################
 
@@ -186,7 +175,7 @@ sub make_reference {
 
     # This will be a Reference sector
     my $sector = $self->_load_sector( $obj->_base_offset )
-        or DBM::Deep->_throw_error( "How did make_reference fail (no sector for '$obj')?!" );
+        or DBM::Deep->_throw_error( "How did get_classname fail (no sector for '$obj')?!" );
 
     if ( $sector->staleness != $obj->_staleness ) {
         return;
@@ -284,10 +273,10 @@ sub write_value {
 
     # This will be a Reference sector
     my $sector = $self->_load_sector( $obj->_base_offset )
-        or DBM::Deep->_throw_error( "1: Cannot write to a deleted spot in DBM::Deep." );
+        or DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
 
     if ( $sector->staleness != $obj->_staleness ) {
-        DBM::Deep->_throw_error( "2: Cannot write to a deleted spot in DBM::Deep." );
+        DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
     }
 
     my ($class, $type);
@@ -414,42 +403,43 @@ sub setup_fh {
     my $self = shift;
     my ($obj) = @_;
 
-    return 1 if $obj->_base_offset;
+    # We're opening the file.
+    unless ( $obj->_base_offset ) {
+        my $bytes_read = $self->_read_file_header;
 
-    my $header = $self->_load_header;
+        # Creating a new file
+        unless ( $bytes_read ) {
+            $self->_write_file_header;
 
-    # Creating a new file
-    if ( $header->is_new ) {
-        # 1) Create Array/Hash entry
-        my $sector = DBM::Deep::Engine::Sector::Reference->new({
-            engine => $self,
-            type   => $obj->_type,
-        });
-        $obj->{base_offset} = $sector->offset;
-        $obj->{staleness} = $sector->staleness;
+            # 1) Create Array/Hash entry
+            my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
+                engine => $self,
+                type   => $obj->_type,
+            });
+            $obj->{base_offset} = $initial_reference->offset;
+            $obj->{staleness} = $initial_reference->staleness;
 
-        $self->flush;
-    }
-    # Reading from an existing file
-    else {
-        $obj->{base_offset} = $header->size;
-        my $sector = DBM::Deep::Engine::Sector::Reference->new({
-            engine => $self,
-            offset => $obj->_base_offset,
-        });
-        unless ( $sector ) {
-            DBM::Deep->_throw_error("Corrupted file, no master index record");
+            $self->storage->flush;
         }
+        # Reading from an existing file
+        else {
+            $obj->{base_offset} = $bytes_read;
+            my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
+                engine => $self,
+                offset => $obj->_base_offset,
+            });
+            unless ( $initial_reference ) {
+                DBM::Deep->_throw_error("Corrupted file, no master index record");
+            }
 
-        unless ($obj->_type eq $sector->type) {
-            DBM::Deep->_throw_error("File type mismatch");
-        }
+            unless ($obj->_type eq $initial_reference->type) {
+                DBM::Deep->_throw_error("File type mismatch");
+            }
 
-        $obj->{staleness} = $sector->staleness;
+            $obj->{staleness} = $initial_reference->staleness;
+        }
     }
 
-    $self->storage->set_inode;
-
     return 1;
 }
 
@@ -491,9 +481,23 @@ sub rollback {
         DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
     }
 
-    foreach my $entry ( @{ $self->get_entries } ) {
-        my ($sector, $idx) = split ':', $entry;
-        $self->_load_sector( $sector )->rollback( $idx );
+    # Each entry is the file location for a bucket that has a modification for
+    # this transaction. The entries need to be expunged.
+    foreach my $entry (@{ $self->get_entries } ) {
+        # Remove the entry here
+        my $read_loc = $entry
+          + $self->hash_size
+          + $self->byte_size
+          + $self->byte_size
+          + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
+
+        my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
+        $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
+        $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
+
+        if ( $data_loc > 1 ) {
+            $self->_load_sector( $data_loc )->free;
+        }
     }
 
     $self->clear_entries;
@@ -515,9 +519,29 @@ sub commit {
         DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
     }
 
-    foreach my $entry ( @{ $self->get_entries } ) {
-        my ($sector, $idx) = split ':', $entry;
-        $self->_load_sector( $sector )->commit( $idx );
+    foreach my $entry (@{ $self->get_entries } ) {
+        # Overwrite the entry in head with the entry in trans_id
+        my $base = $entry
+          + $self->hash_size
+          + $self->byte_size;
+
+        my $head_loc = $self->storage->read_at( $base, $self->byte_size );
+        $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
+
+        my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
+        my $trans_loc = $self->storage->read_at(
+            $spot, $self->byte_size,
+        );
+
+        $self->storage->print_at( $base, $trans_loc );
+        $self->storage->print_at(
+            $spot,
+            pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
+        );
+
+        if ( $head_loc > 1 ) {
+            $self->_load_sector( $head_loc )->free;
+        }
     }
 
     $self->clear_entries;
@@ -533,12 +557,21 @@ sub commit {
 
 sub read_txn_slots {
     my $self = shift;
-    return $self->_load_header->read_txn_slots(@_);
+    my $bl = $self->txn_bitfield_len;
+    my $num_bits = $bl * 8;
+    return split '', unpack( 'b'.$num_bits,
+        $self->storage->read_at(
+            $self->trans_loc, $bl,
+        )
+    );
 }
 
 sub write_txn_slots {
     my $self = shift;
-    return $self->_load_header->write_txn_slots(@_);
+    my $num_bits = $self->txn_bitfield_len * 8;
+    $self->storage->print_at( $self->trans_loc,
+        pack( 'b'.$num_bits, join('', @_) ),
+    );
 }
 
 sub get_running_txn_ids {
@@ -549,12 +582,30 @@ sub get_running_txn_ids {
 
 sub get_txn_staleness_counter {
     my $self = shift;
-    return $self->_load_header->get_txn_staleness_counter(@_);
+    my ($trans_id) = @_;
+
+    # Hardcode staleness of 0 for the HEAD
+    return 0 unless $trans_id;
+
+    return unpack( $StP{$STALE_SIZE},
+        $self->storage->read_at(
+            $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
+            $STALE_SIZE,
+        )
+    );
 }
 
 sub inc_txn_staleness_counter {
     my $self = shift;
-    return $self->_load_header->inc_txn_staleness_counter(@_);
+    my ($trans_id) = @_;
+
+    # Hardcode staleness of 0 for the HEAD
+    return 0 unless $trans_id;
+
+    $self->storage->print_at(
+        $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
+        pack( $StP{$STALE_SIZE}, $self->get_txn_staleness_counter( $trans_id ) + 1 ),
+    );
 }
 
 sub get_entries {
@@ -564,25 +615,23 @@ sub get_entries {
 
 sub add_entry {
     my $self = shift;
-    my ($trans_id, $loc, $idx) = @_;
-
-    return unless $trans_id;
+    my ($trans_id, $loc) = @_;
 
     $self->{entries}{$trans_id} ||= {};
-    $self->{entries}{$trans_id}{"$loc:$idx"} = undef;
+    $self->{entries}{$trans_id}{$loc} = undef;
 }
 
 # If the buckets are being relocated because of a reindexing, the entries
 # mechanism needs to be made aware of it.
 sub reindex_entry {
     my $self = shift;
-    my ($old_loc, $old_idx, $new_loc, $new_idx) = @_;
+    my ($old_loc, $new_loc) = @_;
 
     TRANS:
     while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
-        if ( exists $locs->{"$old_loc:$old_idx"} ) {
-            delete $locs->{"$old_loc:$old_idx"};
-            $locs->{"$new_loc:$new_idx"} = undef;
+        if ( exists $locs->{$old_loc} ) {
+            delete $locs->{$old_loc};
+            $locs->{$new_loc} = undef;
             next TRANS;
         }
     }
@@ -595,144 +644,246 @@ sub clear_entries {
 
 ################################################################################
 
-sub _apply_digest {
-    my $self = shift;
-    return $self->{digest}->(@_);
-}
+{
+    my $header_fixed = length( SIG_FILE ) + 1 + 4 + 4;
+    my $this_file_version = 3;
 
-sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
-sub _add_free_data_sector  { shift->_add_free_sector( 1, @_ ) }
-sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
-sub _add_free_sector       { shift->_load_header->add_free_sector( @_ ) }
+    sub _write_file_header {
+        my $self = shift;
 
-sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
-sub _request_data_sector  { shift->_request_sector( 1, @_ ) }
-sub _request_index_sector { shift->_request_sector( 2, @_ ) }
-sub _request_sector       { shift->_load_header->request_sector( @_ ) }
+        my $nt = $self->num_txns;
+        my $bl = $self->txn_bitfield_len;
 
-################################################################################
+        my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
 
-{
-    my %t = (
-        SIG_ARRAY => 'Reference',
-        SIG_HASH  => 'Reference',
-        SIG_BLIST => 'BucketList',
-        SIG_INDEX => 'Index',
-        SIG_NULL  => 'Null',
-        SIG_DATA  => 'Scalar',
-    );
+        my $loc = $self->storage->request_space( $header_fixed + $header_var );
 
-    my %class_for;
-    while ( my ($k,$v) = each %t ) {
-        $class_for{ DBM::Deep::Engine->$k } = "DBM::Deep::Engine::Sector::$v";
-    }
+        $self->storage->print_at( $loc,
+            SIG_FILE,
+            SIG_HEADER,
+            pack('N', $this_file_version), # At this point, we're at 9 bytes
+            pack('N', $header_var),        # header size
+            # --- Above is $header_fixed. Below is $header_var
+            pack('C', $self->byte_size),
 
-    sub load_sector {
-        my $self = shift;
-        my ($offset) = @_;
+            # These shenanigans are to allow a 256 within a C
+            pack('C', $self->max_buckets - 1),
+            pack('C', $self->data_sector_size - 1),
 
-        my $data = $self->get_data( $offset )
-            or return;#die "Cannot read from '$offset'\n";
-        my $type = substr( $$data, 0, 1 );
-        my $class = $class_for{ $type };
-        return $class->new({
-            engine => $self,
-            type   => $type,
-            offset => $offset,
-        });
-    }
-    *_load_sector = \&load_sector;
+            pack('C', $nt),
+            pack('C' . $bl, 0 ),                           # Transaction activeness bitfield
+            pack($StP{$STALE_SIZE}.($nt-1), 0 x ($nt-1) ), # Transaction staleness counters
+            pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
+            pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
+            pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
+        );
 
-    sub load_header {
-        my $self = shift;
+        #XXX Set these less fragilely
+        $self->set_trans_loc( $header_fixed + 4 );
+        $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
 
-        #XXX Does this mean we make too many objects? -RobK, 2008-06-23
-        return DBM::Deep::Engine::Sector::FileHeader->new({
-            engine => $self,
-            offset => 0,
-        });
+        return;
     }
-    *_load_header = \&load_header;
 
-    sub get_data {
+    sub _read_file_header {
         my $self = shift;
-        my ($offset, $size) = @_;
-        return unless defined $offset;
 
-        unless ( exists $self->sector_cache->{$offset} ) {
-            # Don't worry about the header sector. It will manage itself.
-            return unless $offset;
+        my $buffer = $self->storage->read_at( 0, $header_fixed );
+        return unless length($buffer);
 
-            if ( !defined $size ) {
-                my $type = $self->storage->read_at( $offset, 1 )
-                    or die "($offset): Cannot read from '$offset' to find the type\n";
+        my ($file_signature, $sig_header, $file_version, $size) = unpack(
+            'A4 A N N', $buffer
+        );
 
-                if ( $type eq $self->SIG_FREE ) {
-                    return;
-                }
+        unless ( $file_signature eq SIG_FILE ) {
+            $self->storage->close;
+            DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
+        }
 
-                my $class = $class_for{$type}
-                    or die "($offset): Cannot find class for '$type'\n";
-                $size = $class->size( $self )
-                    or die "($offset): '$class' doesn't return a size\n";
-                $self->sector_cache->{$offset} = $type . $self->storage->read_at( undef, $size - 1 );
-            }
-            else {
-                $self->sector_cache->{$offset} = $self->storage->read_at( $offset, $size )
-                    or return;
-            }
+        unless ( $sig_header eq SIG_HEADER ) {
+            $self->storage->close;
+            DBM::Deep->_throw_error( "Pre-1.00 file version found" );
+        }
+
+        unless ( $file_version == $this_file_version ) {
+            $self->storage->close;
+            DBM::Deep->_throw_error(
+                "Wrong file version found - " .  $file_version .
+                " - expected " . $this_file_version
+            );
+        }
+
+        my $buffer2 = $self->storage->read_at( undef, $size );
+        my @values = unpack( 'C C C C', $buffer2 );
+
+        if ( @values != 4 || grep { !defined } @values ) {
+            $self->storage->close;
+            DBM::Deep->_throw_error("Corrupted file - bad header");
+        }
+
+        #XXX Add warnings if values weren't set right
+        @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
+
+        # These shenangians are to allow a 256 within a C
+        $self->{max_buckets} += 1;
+        $self->{data_sector_size} += 1;
+
+        my $bl = $self->txn_bitfield_len;
+
+        my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
+        unless ( $size == $header_var ) {
+            $self->storage->close;
+            DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
         }
 
-        return \$self->sector_cache->{$offset};
+        $self->set_trans_loc( $header_fixed + scalar(@values) );
+        $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
+
+        return length($buffer) + length($buffer2);
     }
 }
 
-sub sector_cache {
+sub _load_sector {
     my $self = shift;
-    return $self->{sector_cache} ||= {};
-}
+    my ($offset) = @_;
 
-sub clear_sector_cache {
-    my $self = shift;
-    $self->{sector_cache} = {};
+    # Add a catch for offset of 0 or 1
+    return if !$offset || $offset <= 1;
+
+    my $type = $self->storage->read_at( $offset, 1 );
+    return if $type eq chr(0);
+
+    if ( $type eq $self->SIG_ARRAY || $type eq $self->SIG_HASH ) {
+        return DBM::Deep::Engine::Sector::Reference->new({
+            engine => $self,
+            type   => $type,
+            offset => $offset,
+        });
+    }
+    # XXX Don't we need key_md5 here?
+    elsif ( $type eq $self->SIG_BLIST ) {
+        return DBM::Deep::Engine::Sector::BucketList->new({
+            engine => $self,
+            type   => $type,
+            offset => $offset,
+        });
+    }
+    elsif ( $type eq $self->SIG_INDEX ) {
+        return DBM::Deep::Engine::Sector::Index->new({
+            engine => $self,
+            type   => $type,
+            offset => $offset,
+        });
+    }
+    elsif ( $type eq $self->SIG_NULL ) {
+        return DBM::Deep::Engine::Sector::Null->new({
+            engine => $self,
+            type   => $type,
+            offset => $offset,
+        });
+    }
+    elsif ( $type eq $self->SIG_DATA ) {
+        return DBM::Deep::Engine::Sector::Scalar->new({
+            engine => $self,
+            type   => $type,
+            offset => $offset,
+        });
+    }
+    # This was deleted from under us, so just return and let the caller figure it out.
+    elsif ( $type eq $self->SIG_FREE ) {
+        return;
+    }
+
+    DBM::Deep->_throw_error( "'$offset': Don't know what to do with type '$type'" );
 }
 
-sub dirty_sectors {
+sub _apply_digest {
     my $self = shift;
-    return $self->{dirty_sectors} ||= {};
+    return $self->{digest}->(@_);
 }
 
-sub clear_dirty_sectors {
+sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
+sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
+sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
+
+sub _add_free_sector {
     my $self = shift;
-    $self->{dirty_sectors} = {};
+    my ($multiple, $offset, $size) = @_;
+
+    my $chains_offset = $multiple * $self->byte_size;
+
+    my $storage = $self->storage;
+
+    # Increment staleness.
+    # XXX Can this increment+modulo be done by "&= 0x1" ?
+    my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + SIG_SIZE, $STALE_SIZE ) );
+    $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
+    $storage->print_at( $offset + SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
+
+    my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
+
+    $storage->print_at( $self->chains_loc + $chains_offset,
+        pack( $StP{$self->byte_size}, $offset ),
+    );
+
+    # Record the old head in the new sector after the signature and staleness counter
+    $storage->print_at( $offset + SIG_SIZE + $STALE_SIZE, $old_head );
 }
 
-sub add_dirty_sector {
+sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
+sub _request_data_sector { shift->_request_sector( 1, @_ ) }
+sub _request_index_sector { shift->_request_sector( 2, @_ ) }
+
+sub _request_sector {
     my $self = shift;
-    my ($offset) = @_;
+    my ($multiple, $size) = @_;
+
+    my $chains_offset = $multiple * $self->byte_size;
+
+    my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
+    my $loc = unpack( $StP{$self->byte_size}, $old_head );
+
+    # We don't have any free sectors of the right size, so allocate a new one.
+    unless ( $loc ) {
+        my $offset = $self->storage->request_space( $size );
+
+        # Zero out the new sector. This also guarantees correct increases
+        # in the filesize.
+        $self->storage->print_at( $offset, chr(0) x $size );
 
-    $self->dirty_sectors->{ $offset } = undef;
+        return $offset;
+    }
+
+    # Read the new head after the signature and the staleness counter
+    my $new_head = $self->storage->read_at( $loc + SIG_SIZE + $STALE_SIZE, $self->byte_size );
+    $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
+    $self->storage->print_at(
+        $loc + SIG_SIZE + $STALE_SIZE,
+        pack( $StP{$self->byte_size}, 0 ),
+    );
+
+    return $loc;
 }
 
+################################################################################
+
 sub flush {
     my $self = shift;
 
-    my $sectors = $self->dirty_sectors;
-    for my $offset (sort { $a <=> $b } keys %{ $sectors }) {
-        $self->storage->print_at( $offset, $self->sector_cache->{$offset} );
-    }
+#    my $sectors = $self->dirty_sectors;
+#    for my $offset (sort { $a <=> $b } keys %{ $sectors }) {
+#        $self->storage->print_at( $offset, $self->sector_cache->{$offset} );
+#    }
 
     # Why do we need to have the storage flush? Shouldn't autoflush take care of things?
     # -RobK, 2008-06-26
     $self->storage->flush;
 
-    $self->clear_dirty_sectors;
+#    $self->clear_dirty_sectors;
 
-    $self->clear_sector_cache;
+#    $self->clear_sector_cache;
 }
 
-################################################################################
-
 sub lock_exclusive {
     my $self = shift;
     my ($obj) = @_;
@@ -794,12 +945,9 @@ sub clear_cache { %{$_[0]->cache} = () }
 
 sub _dump_file {
     my $self = shift;
-    $self->flush;
 
     # Read the header
-    my $header_sector = DBM::Deep::Engine::Sector::FileHeader->new({
-        engine => $self,
-    });
+    my $spot = $self->_read_file_header();
 
     my %types = (
         0 => 'B',
@@ -815,9 +963,6 @@ sub _dump_file {
 
     my $return = "";
 
-    # Filesize
-    $return .= "Size: " . (-s $self->storage->{fh}) . $/;
-
     # Header values
     $return .= "NumTxns: " . $self->num_txns . $/;
 
@@ -844,7 +989,6 @@ sub _dump_file {
         $return .= $/;
     }
 
-    my $spot = $header_sector->size;
     SECTOR:
     while ( $spot < $self->storage->{end} ) {
         # Read each sector in order.
@@ -903,5 +1047,1322 @@ sub _dump_file {
     return $return;
 }
 
+################################################################################
+
+package DBM::Deep::Iterator;
+
+sub new {
+    my $class = shift;
+    my ($args) = @_;
+
+    my $self = bless {
+        breadcrumbs => [],
+        engine      => $args->{engine},
+        base_offset => $args->{base_offset},
+    }, $class;
+
+    Scalar::Util::weaken( $self->{engine} );
+
+    return $self;
+}
+
+sub reset { $_[0]{breadcrumbs} = [] }
+
+sub get_sector_iterator {
+    my $self = shift;
+    my ($loc) = @_;
+
+    my $sector = $self->{engine}->_load_sector( $loc )
+        or return;
+
+    if ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
+        return DBM::Deep::Iterator::Index->new({
+            iterator => $self,
+            sector   => $sector,
+        });
+    }
+    elsif ( $sector->isa( 'DBM::Deep::Engine::Sector::BucketList' ) ) {
+        return DBM::Deep::Iterator::BucketList->new({
+            iterator => $self,
+            sector   => $sector,
+        });
+    }
+
+    DBM::Deep->_throw_error( "get_sector_iterator(): Why did $loc make a $sector?" );
+}
+
+sub get_next_key {
+    my $self = shift;
+    my ($obj) = @_;
+
+    my $crumbs = $self->{breadcrumbs};
+    my $e = $self->{engine};
+
+    unless ( @$crumbs ) {
+        # This will be a Reference sector
+        my $sector = $e->_load_sector( $self->{base_offset} )
+            # If no sector is found, thist must have been deleted from under us.
+            or return;
+
+        if ( $sector->staleness != $obj->_staleness ) {
+            return;
+        }
+
+        my $loc = $sector->get_blist_loc
+            or return;
+
+        push @$crumbs, $self->get_sector_iterator( $loc );
+    }
+
+    FIND_NEXT_KEY: {
+        # We're at the end.
+        unless ( @$crumbs ) {
+            $self->reset;
+            return;
+        }
+
+        my $iterator = $crumbs->[-1];
+
+        # This level is done.
+        if ( $iterator->at_end ) {
+            pop @$crumbs;
+            redo FIND_NEXT_KEY;
+        }
+
+        if ( $iterator->isa( 'DBM::Deep::Iterator::Index' ) ) {
+            # If we don't have any more, it will be caught at the
+            # prior check.
+            if ( my $next = $iterator->get_next_iterator ) {
+                push @$crumbs, $next;
+            }
+            redo FIND_NEXT_KEY;
+        }
+
+        unless ( $iterator->isa( 'DBM::Deep::Iterator::BucketList' ) ) {
+            DBM::Deep->_throw_error(
+                "Should have a bucketlist iterator here - instead have $iterator"
+            );
+        }
+
+        # At this point, we have a BucketList iterator
+        my $key = $iterator->get_next_key;
+        if ( defined $key ) {
+            return $key;
+        }
+        #XXX else { $iterator->set_to_end() } ?
+
+        # We hit the end of the bucketlist iterator, so redo
+        redo FIND_NEXT_KEY;
+    }
+
+    DBM::Deep->_throw_error( "get_next_key(): How did we get here?" );
+}
+
+package DBM::Deep::Iterator::Index;
+
+sub new {
+    my $self = bless $_[1] => $_[0];
+    $self->{curr_index} = 0;
+    return $self;
+}
+
+sub at_end {
+    my $self = shift;
+    return $self->{curr_index} >= $self->{iterator}{engine}->hash_chars;
+}
+
+sub get_next_iterator {
+    my $self = shift;
+
+    my $loc;
+    while ( !$loc ) {
+        return if $self->at_end;
+        $loc = $self->{sector}->get_entry( $self->{curr_index}++ );
+    }
+
+    return $self->{iterator}->get_sector_iterator( $loc );
+}
+
+package DBM::Deep::Iterator::BucketList;
+
+sub new {
+    my $self = bless $_[1] => $_[0];
+    $self->{curr_index} = 0;
+    return $self;
+}
+
+sub at_end {
+    my $self = shift;
+    return $self->{curr_index} >= $self->{iterator}{engine}->max_buckets;
+}
+
+sub get_next_key {
+    my $self = shift;
+
+    return if $self->at_end;
+
+    my $idx = $self->{curr_index}++;
+
+    my $data_loc = $self->{sector}->get_data_location_for({
+        allow_head => 1,
+        idx        => $idx,
+    }) or return;
+
+    #XXX Do we want to add corruption checks here?
+    return $self->{sector}->get_key_for( $idx )->data;
+}
+
+package DBM::Deep::Engine::Sector;
+
+sub new {
+    my $self = bless $_[1], $_[0];
+    Scalar::Util::weaken( $self->{engine} );
+    $self->_init;
+    return $self;
+}
+
+#sub _init {}
+#sub clone { DBM::Deep->_throw_error( "Must be implemented in the child class" ); }
+
+sub engine { $_[0]{engine} }
+sub offset { $_[0]{offset} }
+sub type   { $_[0]{type} }
+
+sub base_size {
+   my $self = shift;
+   return $self->engine->SIG_SIZE + $STALE_SIZE;
+}
+
+sub free {
+    my $self = shift;
+
+    my $e = $self->engine;
+
+    $e->storage->print_at( $self->offset, $e->SIG_FREE );
+    # Skip staleness counter
+    $e->storage->print_at( $self->offset + $self->base_size,
+        chr(0) x ($self->size - $self->base_size),
+    );
+
+    my $free_meth = $self->free_meth;
+    $e->$free_meth( $self->offset, $self->size );
+
+    return;
+}
+
+package DBM::Deep::Engine::Sector::Data;
+
+our @ISA = qw( DBM::Deep::Engine::Sector );
+
+# This is in bytes
+sub size { $_[0]{engine}->data_sector_size }
+sub free_meth { return '_add_free_data_sector' }
+
+sub clone {
+    my $self = shift;
+    return ref($self)->new({
+        engine => $self->engine,
+        type   => $self->type,
+        data   => $self->data,
+    });
+}
+
+package DBM::Deep::Engine::Sector::Scalar;
+
+our @ISA = qw( DBM::Deep::Engine::Sector::Data );
+
+sub free {
+    my $self = shift;
+
+    my $chain_loc = $self->chain_loc;
+
+    $self->SUPER::free();
+
+    if ( $chain_loc ) {
+        $self->engine->_load_sector( $chain_loc )->free;
+    }
+
+    return;
+}
+
+sub type { $_[0]{engine}->SIG_DATA }
+sub _init {
+    my $self = shift;
+
+    my $engine = $self->engine;
+
+    unless ( $self->offset ) {
+        my $data_section = $self->size - $self->base_size - $engine->byte_size - 1;
+
+        $self->{offset} = $engine->_request_data_sector( $self->size );
+
+        my $data = delete $self->{data};
+        my $dlen = length $data;
+        my $continue = 1;
+        my $curr_offset = $self->offset;
+        while ( $continue ) {
+
+            my $next_offset = 0;
+
+            my ($leftover, $this_len, $chunk);
+            if ( $dlen > $data_section ) {
+                $leftover = 0;
+                $this_len = $data_section;
+                $chunk = substr( $data, 0, $this_len );
+
+                $dlen -= $data_section;
+                $next_offset = $engine->_request_data_sector( $self->size );
+                $data = substr( $data, $this_len );
+            }
+            else {
+                $leftover = $data_section - $dlen;
+                $this_len = $dlen;
+                $chunk = $data;
+
+                $continue = 0;
+            }
+
+            $engine->storage->print_at( $curr_offset, $self->type ); # Sector type
+            # Skip staleness
+            $engine->storage->print_at( $curr_offset + $self->base_size,
+                pack( $StP{$engine->byte_size}, $next_offset ),  # Chain loc
+                pack( $StP{1}, $this_len ),                      # Data length
+                $chunk,                                          # Data to be stored in this sector
+                chr(0) x $leftover,                              # Zero-fill the rest
+            );
+
+            $curr_offset = $next_offset;
+        }
+
+        return;
+    }
+}
+
+sub data_length {
+    my $self = shift;
+
+    my $buffer = $self->engine->storage->read_at(
+        $self->offset + $self->base_size + $self->engine->byte_size, 1
+    );
+
+    return unpack( $StP{1}, $buffer );
+}
+
+sub chain_loc {
+    my $self = shift;
+    return unpack(
+        $StP{$self->engine->byte_size},
+        $self->engine->storage->read_at(
+            $self->offset + $self->base_size,
+            $self->engine->byte_size,
+        ),
+    );
+}
+
+sub data {
+    my $self = shift;
+#    my ($args) = @_;
+#    $args ||= {};
+
+    my $data;
+    while ( 1 ) {
+        my $chain_loc = $self->chain_loc;
+
+        $data .= $self->engine->storage->read_at(
+            $self->offset + $self->base_size + $self->engine->byte_size + 1, $self->data_length,
+        );
+
+        last unless $chain_loc;
+
+        $self = $self->engine->_load_sector( $chain_loc );
+    }
+
+    return $data;
+}
+
+package DBM::Deep::Engine::Sector::Null;
+
+our @ISA = qw( DBM::Deep::Engine::Sector::Data );
+
+sub type { $_[0]{engine}->SIG_NULL }
+sub data_length { 0 }
+sub data { return }
+
+sub _init {
+    my $self = shift;
+
+    my $engine = $self->engine;
+
+    unless ( $self->offset ) {
+        my $leftover = $self->size - $self->base_size - 1 * $engine->byte_size - 1;
+
+        $self->{offset} = $engine->_request_data_sector( $self->size );
+        $engine->storage->print_at( $self->offset, $self->type ); # Sector type
+        # Skip staleness counter
+        $engine->storage->print_at( $self->offset + $self->base_size,
+            pack( $StP{$engine->byte_size}, 0 ),  # Chain loc
+            pack( $StP{1}, $self->data_length ),  # Data length
+            chr(0) x $leftover,                   # Zero-fill the rest
+        );
+
+        return;
+    }
+}
+
+package DBM::Deep::Engine::Sector::Reference;
+
+our @ISA = qw( DBM::Deep::Engine::Sector::Data );
+
+sub _init {
+    my $self = shift;
+
+    my $e = $self->engine;
+
+    unless ( $self->offset ) {
+        my $classname = Scalar::Util::blessed( delete $self->{data} );
+        my $leftover = $self->size - $self->base_size - 3 * $e->byte_size;
+
+        my $class_offset = 0;
+        if ( defined $classname ) {
+            my $class_sector = DBM::Deep::Engine::Sector::Scalar->new({
+                engine => $e,
+                data   => $classname,
+            });
+            $class_offset = $class_sector->offset;
+        }
+
+        $self->{offset} = $e->_request_data_sector( $self->size );
+        $e->storage->print_at( $self->offset, $self->type ); # Sector type
+        # Skip staleness counter
+        $e->storage->print_at( $self->offset + $self->base_size,
+            pack( $StP{$e->byte_size}, 0 ),             # Index/BList loc
+            pack( $StP{$e->byte_size}, $class_offset ), # Classname loc
+            pack( $StP{$e->byte_size}, 1 ),             # Initial refcount
+            chr(0) x $leftover,                         # Zero-fill the rest
+        );
+    }
+    else {
+        $self->{type} = $e->storage->read_at( $self->offset, 1 );
+    }
+
+    $self->{staleness} = unpack(
+        $StP{$STALE_SIZE},
+        $e->storage->read_at( $self->offset + $e->SIG_SIZE, $STALE_SIZE ),
+    );
+
+    return;
+}
+
+sub staleness { $_[0]{staleness} }
+
+sub get_data_location_for {
+    my $self = shift;
+    my ($args) = @_;
+
+    # Assume that the head is not allowed unless otherwise specified.
+    $args->{allow_head} = 0 unless exists $args->{allow_head};
+
+    # Assume we don't create a new blist location unless otherwise specified.
+    $args->{create} = 0 unless exists $args->{create};
+
+    my $blist = $self->get_bucket_list({
+        key_md5 => $args->{key_md5},
+        key => $args->{key},
+        create  => $args->{create},
+    });
+    return unless $blist && $blist->{found};
+
+    # At this point, $blist knows where the md5 is. What it -doesn't- know yet
+    # is whether or not this transaction has this key. That's part of the next
+    # function call.
+    my $location = $blist->get_data_location_for({
+        allow_head => $args->{allow_head},
+    }) or return;
+
+    return $location;
+}
+
+sub get_data_for {
+    my $self = shift;
+    my ($args) = @_;
+
+    my $location = $self->get_data_location_for( $args )
+        or return;
+
+    return $self->engine->_load_sector( $location );
+}
+
+sub write_data {
+    my $self = shift;
+    my ($args) = @_;
+
+    my $blist = $self->get_bucket_list({
+        key_md5 => $args->{key_md5},
+        key => $args->{key},
+        create  => 1,
+    }) or DBM::Deep->_throw_error( "How did write_data fail (no blist)?!" );
+
+    # Handle any transactional bookkeeping.
+    if ( $self->engine->trans_id ) {
+        if ( ! $blist->has_md5 ) {
+            $blist->mark_deleted({
+                trans_id => 0,
+            });
+        }
+    }
+    else {
+        my @trans_ids = $self->engine->get_running_txn_ids;
+        if ( $blist->has_md5 ) {
+            if ( @trans_ids ) {
+                my $old_value = $blist->get_data_for;
+                foreach my $other_trans_id ( @trans_ids ) {
+                    next if $blist->get_data_location_for({
+                        trans_id   => $other_trans_id,
+                        allow_head => 0,
+                    });
+                    $blist->write_md5({
+                        trans_id => $other_trans_id,
+                        key      => $args->{key},
+                        key_md5  => $args->{key_md5},
+                        value    => $old_value->clone,
+                    });
+                }
+            }
+        }
+        else {
+            if ( @trans_ids ) {
+                foreach my $other_trans_id ( @trans_ids ) {
+                    #XXX This doesn't seem to possible to ever happen . . .
+                    next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
+                    $blist->mark_deleted({
+                        trans_id => $other_trans_id,
+                    });
+                }
+            }
+        }
+    }
+
+    #XXX Is this safe to do transactionally?
+    # Free the place we're about to write to.
+    if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
+        $blist->get_data_for({ allow_head => 0 })->free;
+    }
+
+    $blist->write_md5({
+        key      => $args->{key},
+        key_md5  => $args->{key_md5},
+        value    => $args->{value},
+    });
+}
+
+sub delete_key {
+    my $self = shift;
+    my ($args) = @_;
+
+    # XXX What should happen if this fails?
+    my $blist = $self->get_bucket_list({
+        key_md5 => $args->{key_md5},
+    }) or DBM::Deep->_throw_error( "How did delete_key fail (no blist)?!" );
+
+    # Save the location so that we can free the data
+    my $location = $blist->get_data_location_for({
+        allow_head => 0,
+    });
+    my $old_value = $location && $self->engine->_load_sector( $location );
+
+    my @trans_ids = $self->engine->get_running_txn_ids;
+
+    # If we're the HEAD and there are running txns, then we need to clone this value to the other
+    # transactions to preserve Isolation.
+    if ( $self->engine->trans_id == 0 ) {
+        if ( @trans_ids ) {
+            foreach my $other_trans_id ( @trans_ids ) {
+                next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
+                $blist->write_md5({
+                    trans_id => $other_trans_id,
+                    key      => $args->{key},
+                    key_md5  => $args->{key_md5},
+                    value    => $old_value->clone,
+                });
+            }
+        }
+    }
+
+    my $data;
+    if ( @trans_ids ) {
+        $blist->mark_deleted( $args );
+
+        if ( $old_value ) {
+            $data = $old_value->data({ export => 1 });
+            $old_value->free;
+        }
+    }
+    else {
+        $data = $blist->delete_md5( $args );
+    }
+
+    return $data;
+}
+
+sub get_blist_loc {
+    my $self = shift;
+
+    my $e = $self->engine;
+    my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
+    return unpack( $StP{$e->byte_size}, $blist_loc );
+}
+
+sub get_bucket_list {
+    my $self = shift;
+    my ($args) = @_;
+    $args ||= {};
+
+    # XXX Add in check here for recycling?
+
+    my $engine = $self->engine;
+
+    my $blist_loc = $self->get_blist_loc;
+
+    # There's no index or blist yet
+    unless ( $blist_loc ) {
+        return unless $args->{create};
+
+        my $blist = DBM::Deep::Engine::Sector::BucketList->new({
+            engine  => $engine,
+            key_md5 => $args->{key_md5},
+        });
+
+        $engine->storage->print_at( $self->offset + $self->base_size,
+            pack( $StP{$engine->byte_size}, $blist->offset ),
+        );
+
+        return $blist;
+    }
+
+    my $sector = $engine->_load_sector( $blist_loc )
+        or DBM::Deep->_throw_error( "Cannot read sector at $blist_loc in get_bucket_list()" );
+    my $i = 0;
+    my $last_sector = undef;
+    while ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
+        $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
+        $last_sector = $sector;
+        if ( $blist_loc ) {
+            $sector = $engine->_load_sector( $blist_loc )
+                or DBM::Deep->_throw_error( "Cannot read sector at $blist_loc in get_bucket_list()" );
+        }
+        else {
+            $sector = undef;
+            last;
+        }
+    }
+
+    # This means we went through the Index sector(s) and found an empty slot
+    unless ( $sector ) {
+        return unless $args->{create};
+
+        DBM::Deep->_throw_error( "No last_sector when attempting to build a new entry" )
+            unless $last_sector;
+
+        my $blist = DBM::Deep::Engine::Sector::BucketList->new({
+            engine  => $engine,
+            key_md5 => $args->{key_md5},
+        });
+
+        $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
+
+        return $blist;
+    }
+
+    $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
+    if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
+        my $redo;
+
+        my $new_index = DBM::Deep::Engine::Sector::Index->new({
+            engine => $engine,
+        });
+
+        my %blist_cache;
+        #XXX q.v. the comments for this function.
+        foreach my $entry ( $sector->chopped_up ) {
+            my ($spot, $md5) = @{$entry};
+            my $idx = ord( substr( $md5, $i, 1 ) );
+
+            # XXX This is inefficient
+            my $blist = $blist_cache{$idx}
+                ||= DBM::Deep::Engine::Sector::BucketList->new({
+                    engine => $engine,
+                });
+
+            $new_index->set_entry( $idx => $blist->offset );
+
+            my $new_spot = $blist->write_at_next_open( $md5 );
+            $engine->reindex_entry( $spot => $new_spot );
+        }
+
+        # Handle the new item separately.
+        {
+            my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
+
+            # If all the previous blist's items have been thrown into one
+            # blist and the new item belongs in there too, we need
+            # another index.
+            if ( keys %blist_cache == 1 and each %blist_cache == $idx ) {
+                ++$i, ++$redo;
+            } else {
+                my $blist = $blist_cache{$idx}
+                    ||= DBM::Deep::Engine::Sector::BucketList->new({
+                        engine => $engine,
+                    });
+    
+                $new_index->set_entry( $idx => $blist->offset );
+    
+                #XXX THIS IS HACKY!
+                $blist->find_md5( $args->{key_md5} );
+                $blist->write_md5({
+                    key     => $args->{key},
+                    key_md5 => $args->{key_md5},
+                    value   => DBM::Deep::Engine::Sector::Null->new({
+                        engine => $engine,
+                        data   => undef,
+                    }),
+                });
+            }
+#            my $blist = $blist_cache{$idx}
+#                ||= DBM::Deep::Engine::Sector::BucketList->new({
+#                    engine => $engine,
+#                });
+#
+#            $new_index->set_entry( $idx => $blist->offset );
+#
+#            #XXX THIS IS HACKY!
+#            $blist->find_md5( $args->{key_md5} );
+#            $blist->write_md5({
+#                key     => $args->{key},
+#                key_md5 => $args->{key_md5},
+#                value   => DBM::Deep::Engine::Sector::Null->new({
+#                    engine => $engine,
+#                    data   => undef,
+#                }),
+#            });
+        }
+
+        if ( $last_sector ) {
+            $last_sector->set_entry(
+                ord( substr( $args->{key_md5}, $i - 1, 1 ) ),
+                $new_index->offset,
+            );
+        } else {
+            $engine->storage->print_at( $self->offset + $self->base_size,
+                pack( $StP{$engine->byte_size}, $new_index->offset ),
+            );
+        }
+
+        $sector->clear;
+        $sector->free;
+
+        if ( $redo ) {
+            (undef, $sector) = %blist_cache;
+            $last_sector = $new_index;
+            redo;
+        }
+
+        $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
+        $sector->find_md5( $args->{key_md5} );
+    }}
+
+    return $sector;
+}
+
+sub get_class_offset {
+    my $self = shift;
+
+    my $e = $self->engine;
+    return unpack(
+        $StP{$e->byte_size},
+        $e->storage->read_at(
+            $self->offset + $self->base_size + 1 * $e->byte_size, $e->byte_size,
+        ),
+    );
+}
+
+sub get_classname {
+    my $self = shift;
+
+    my $class_offset = $self->get_class_offset;
+
+    return unless $class_offset;
+
+    return $self->engine->_load_sector( $class_offset )->data;
+}
+
+sub data {
+    my $self = shift;
+    my ($args) = @_;
+    $args ||= {};
+
+    my $obj;
+    unless ( $obj = $self->engine->cache->{ $self->offset } ) {
+        $obj = DBM::Deep->new({
+            type        => $self->type,
+            base_offset => $self->offset,
+            staleness   => $self->staleness,
+            storage     => $self->engine->storage,
+            engine      => $self->engine,
+        });
+
+        if ( $self->engine->storage->{autobless} ) {
+            my $classname = $self->get_classname;
+            if ( defined $classname ) {
+                bless $obj, $classname;
+            }
+        }
+
+        $self->engine->cache->{$self->offset} = $obj;
+    }
+
+    # We're not exporting, so just return.
+    unless ( $args->{export} ) {
+        return $obj;
+    }
+
+    # We shouldn't export if this is still referred to.
+    if ( $self->get_refcount > 1 ) {
+        return $obj;
+    }
+
+    return $obj->export;
+}
+
+sub free {
+    my $self = shift;
+
+    # We're not ready to be removed yet.
+    if ( $self->decrement_refcount > 0 ) {
+        return;
+    }
+
+    # Rebless the object into DBM::Deep::Null.
+    eval { %{ $self->engine->cache->{ $self->offset } } = (); };
+    eval { @{ $self->engine->cache->{ $self->offset } } = (); };
+    bless $self->engine->cache->{ $self->offset }, 'DBM::Deep::Null';
+    delete $self->engine->cache->{ $self->offset };
+
+    my $blist_loc = $self->get_blist_loc;
+    $self->engine->_load_sector( $blist_loc )->free if $blist_loc;
+
+    my $class_loc = $self->get_class_offset;
+    $self->engine->_load_sector( $class_loc )->free if $class_loc;
+
+    $self->SUPER::free();
+}
+
+sub increment_refcount {
+    my $self = shift;
+
+    my $refcount = $self->get_refcount;
+
+    $refcount++;
+
+    $self->write_refcount( $refcount );
+
+    return $refcount;
+}
+
+sub decrement_refcount {
+    my $self = shift;
+
+    my $refcount = $self->get_refcount;
+
+    $refcount--;
+
+    $self->write_refcount( $refcount );
+
+    return $refcount;
+}
+
+sub get_refcount {
+    my $self = shift;
+
+    my $e = $self->engine;
+    return unpack(
+        $StP{$e->byte_size},
+        $e->storage->read_at(
+            $self->offset + $self->base_size + 2 * $e->byte_size, $e->byte_size,
+        ),
+    );
+}
+
+sub write_refcount {
+    my $self = shift;
+    my ($num) = @_;
+
+    my $e = $self->engine;
+    $e->storage->print_at(
+        $self->offset + $self->base_size + 2 * $e->byte_size,
+        pack( $StP{$e->byte_size}, $num ),
+    );
+}
+
+package DBM::Deep::Engine::Sector::BucketList;
+
+our @ISA = qw( DBM::Deep::Engine::Sector );
+
+sub _init {
+    my $self = shift;
+
+    my $engine = $self->engine;
+
+    unless ( $self->offset ) {
+        my $leftover = $self->size - $self->base_size;
+
+        $self->{offset} = $engine->_request_blist_sector( $self->size );
+        $engine->storage->print_at( $self->offset, $engine->SIG_BLIST ); # Sector type
+        # Skip staleness counter
+        $engine->storage->print_at( $self->offset + $self->base_size,
+            chr(0) x $leftover, # Zero-fill the data
+        );
+    }
+
+    if ( $self->{key_md5} ) {
+        $self->find_md5;
+    }
+
+    return $self;
+}
+
+sub clear {
+    my $self = shift;
+    $self->engine->storage->print_at( $self->offset + $self->base_size,
+        chr(0) x ($self->size - $self->base_size), # Zero-fill the data
+    );
+}
+
+sub size {
+    my $self = shift;
+    unless ( $self->{size} ) {
+        my $e = $self->engine;
+        # Base + numbuckets * bucketsize
+        $self->{size} = $self->base_size + $e->max_buckets * $self->bucket_size;
+    }
+    return $self->{size};
+}
+
+sub free_meth { return '_add_free_blist_sector' }
+
+sub free {
+    my $self = shift;
+
+    my $e = $self->engine;
+    foreach my $bucket ( $self->chopped_up ) {
+        my $rest = $bucket->[-1];
+
+        # Delete the keysector
+        my $l = unpack( $StP{$e->byte_size}, substr( $rest, $e->hash_size, $e->byte_size ) );
+        my $s = $e->_load_sector( $l ); $s->free if $s;
+
+        # Delete the HEAD sector
+        $l = unpack( $StP{$e->byte_size},
+            substr( $rest,
+                $e->hash_size + $e->byte_size,
+                $e->byte_size,
+            ),
+        );
+        $s = $e->_load_sector( $l ); $s->free if $s;
+
+        foreach my $txn ( 0 .. $e->num_txns - 2 ) {
+            my $l = unpack( $StP{$e->byte_size},
+                substr( $rest,
+                    $e->hash_size + 2 * $e->byte_size + $txn * ($e->byte_size + $STALE_SIZE),
+                    $e->byte_size,
+                ),
+            );
+            my $s = $e->_load_sector( $l ); $s->free if $s;
+        }
+    }
+
+    $self->SUPER::free();
+}
+
+sub bucket_size {
+    my $self = shift;
+    unless ( $self->{bucket_size} ) {
+        my $e = $self->engine;
+        # Key + head (location) + transactions (location + staleness-counter)
+        my $location_size = $e->byte_size + $e->byte_size + ($e->num_txns - 1) * ($e->byte_size + $STALE_SIZE);
+        $self->{bucket_size} = $e->hash_size + $location_size;
+    }
+    return $self->{bucket_size};
+}
+
+# XXX This is such a poor hack. I need to rethink this code.
+sub chopped_up {
+    my $self = shift;
+
+    my $e = $self->engine;
+
+    my @buckets;
+    foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
+        my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
+        my $md5 = $e->storage->read_at( $spot, $e->hash_size );
+
+        #XXX If we're chopping, why would we ever have the blank_md5?
+        last if $md5 eq $e->blank_md5;
+
+        my $rest = $e->storage->read_at( undef, $self->bucket_size - $e->hash_size );
+        push @buckets, [ $spot, $md5 . $rest ];
+    }
+
+    return @buckets;
+}
+
+sub write_at_next_open {
+    my $self = shift;
+    my ($entry) = @_;
+
+    #XXX This is such a hack!
+    $self->{_next_open} = 0 unless exists $self->{_next_open};
+
+    my $spot = $self->offset + $self->base_size + $self->{_next_open}++ * $self->bucket_size;
+    $self->engine->storage->print_at( $spot, $entry );
+
+    return $spot;
+}
+
+sub has_md5 {
+    my $self = shift;
+    unless ( exists $self->{found} ) {
+        $self->find_md5;
+    }
+    return $self->{found};
+}
+
+sub find_md5 {
+    my $self = shift;
+
+    $self->{found} = undef;
+    $self->{idx}   = -1;
+
+    if ( @_ ) {
+        $self->{key_md5} = shift;
+    }
+
+    # If we don't have an MD5, then what are we supposed to do?
+    unless ( exists $self->{key_md5} ) {
+        DBM::Deep->_throw_error( "Cannot find_md5 without a key_md5 set" );
+    }
+
+    my $e = $self->engine;
+    foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
+        my $potential = $e->storage->read_at(
+            $self->offset + $self->base_size + $idx * $self->bucket_size, $e->hash_size,
+        );
+
+        if ( $potential eq $e->blank_md5 ) {
+            $self->{idx} = $idx;
+            return;
+        }
+
+        if ( $potential eq $self->{key_md5} ) {
+            $self->{found} = 1;
+            $self->{idx} = $idx;
+            return;
+        }
+    }
+
+    return;
+}
+
+sub write_md5 {
+    my $self = shift;
+    my ($args) = @_;
+
+    DBM::Deep->_throw_error( "write_md5: no key" ) unless exists $args->{key};
+    DBM::Deep->_throw_error( "write_md5: no key_md5" ) unless exists $args->{key_md5};
+    DBM::Deep->_throw_error( "write_md5: no value" ) unless exists $args->{value};
+
+    my $engine = $self->engine;
+
+    $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
+
+    my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
+    $engine->add_entry( $args->{trans_id}, $spot );
+
+    unless ($self->{found}) {
+        my $key_sector = DBM::Deep::Engine::Sector::Scalar->new({
+            engine => $engine,
+            data   => $args->{key},
+        });
+
+        $engine->storage->print_at( $spot,
+            $args->{key_md5},
+            pack( $StP{$engine->byte_size}, $key_sector->offset ),
+        );
+    }
+
+    my $loc = $spot
+      + $engine->hash_size
+      + $engine->byte_size;
+
+    if ( $args->{trans_id} ) {
+        $loc += $engine->byte_size + ($args->{trans_id} - 1) * ( $engine->byte_size + $STALE_SIZE );
+
+        $engine->storage->print_at( $loc,
+            pack( $StP{$engine->byte_size}, $args->{value}->offset ),
+            pack( $StP{$STALE_SIZE}, $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
+        );
+    }
+    else {
+        $engine->storage->print_at( $loc,
+            pack( $StP{$engine->byte_size}, $args->{value}->offset ),
+        );
+    }
+}
+
+sub mark_deleted {
+    my $self = shift;
+    my ($args) = @_;
+    $args ||= {};
+
+    my $engine = $self->engine;
+
+    $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
+
+    my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
+    $engine->add_entry( $args->{trans_id}, $spot );
+
+    my $loc = $spot
+      + $engine->hash_size
+      + $engine->byte_size;
+
+    if ( $args->{trans_id} ) {
+        $loc += $engine->byte_size + ($args->{trans_id} - 1) * ( $engine->byte_size + $STALE_SIZE );
+
+        $engine->storage->print_at( $loc,
+            pack( $StP{$engine->byte_size}, 1 ), # 1 is the marker for deleted
+            pack( $StP{$STALE_SIZE}, $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
+        );
+    }
+    else {
+        $engine->storage->print_at( $loc,
+            pack( $StP{$engine->byte_size}, 1 ), # 1 is the marker for deleted
+        );
+    }
+
+}
+
+sub delete_md5 {
+    my $self = shift;
+    my ($args) = @_;
+
+    my $engine = $self->engine;
+    return undef unless $self->{found};
+
+    # Save the location so that we can free the data
+    my $location = $self->get_data_location_for({
+        allow_head => 0,
+    });
+    my $key_sector = $self->get_key_for;
+
+    my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
+    $engine->storage->print_at( $spot,
+        $engine->storage->read_at(
+            $spot + $self->bucket_size,
+            $self->bucket_size * ( $engine->max_buckets - $self->{idx} - 1 ),
+        ),
+        chr(0) x $self->bucket_size,
+    );
+
+    $key_sector->free;
+
+    my $data_sector = $self->engine->_load_sector( $location );
+    my $data = $data_sector->data({ export => 1 });
+    $data_sector->free;
+
+    return $data;
+}
+
+sub get_data_location_for {
+    my $self = shift;
+    my ($args) = @_;
+    $args ||= {};
+
+    $args->{allow_head} = 0 unless exists $args->{allow_head};
+    $args->{trans_id}   = $self->engine->trans_id unless exists $args->{trans_id};
+    $args->{idx}        = $self->{idx} unless exists $args->{idx};
+
+    my $e = $self->engine;
+
+    my $spot = $self->offset + $self->base_size
+      + $args->{idx} * $self->bucket_size
+      + $e->hash_size
+      + $e->byte_size;
+
+    if ( $args->{trans_id} ) {
+        $spot += $e->byte_size + ($args->{trans_id} - 1) * ( $e->byte_size + $STALE_SIZE );
+    }
+
+    my $buffer = $e->storage->read_at(
+        $spot,
+        $e->byte_size + $STALE_SIZE,
+    );
+    my ($loc, $staleness) = unpack( $StP{$e->byte_size} . ' ' . $StP{$STALE_SIZE}, $buffer );
+
+    # XXX Merge the two if-clauses below
+    if ( $args->{trans_id} ) {
+        # We have found an entry that is old, so get rid of it
+        if ( $staleness != (my $s = $e->get_txn_staleness_counter( $args->{trans_id} ) ) ) {
+            $e->storage->print_at(
+                $spot,
+                pack( $StP{$e->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ), 
+            );
+            $loc = 0;
+        }
+    }
+
+    # If we're in a transaction and we never wrote to this location, try the
+    # HEAD instead.
+    if ( $args->{trans_id} && !$loc && $args->{allow_head} ) {
+        return $self->get_data_location_for({
+            trans_id   => 0,
+            allow_head => 1,
+            idx        => $args->{idx},
+        });
+    }
+
+    return $loc <= 1 ? 0 : $loc;
+}
+
+sub get_data_for {
+    my $self = shift;
+    my ($args) = @_;
+    $args ||= {};
+
+    return unless $self->{found};
+    my $location = $self->get_data_location_for({
+        allow_head => $args->{allow_head},
+    });
+    return $self->engine->_load_sector( $location );
+}
+
+sub get_key_for {
+    my $self = shift;
+    my ($idx) = @_;
+    $idx = $self->{idx} unless defined $idx;
+
+    if ( $idx >= $self->engine->max_buckets ) {
+        DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx" );
+    }
+
+    my $location = $self->engine->storage->read_at(
+        $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
+        $self->engine->byte_size,
+    );
+    $location = unpack( $StP{$self->engine->byte_size}, $location );
+    DBM::Deep->_throw_error( "get_key_for: No location?" ) unless $location;
+
+    return $self->engine->_load_sector( $location );
+}
+
+package DBM::Deep::Engine::Sector::Index;
+
+our @ISA = qw( DBM::Deep::Engine::Sector );
+
+sub _init {
+    my $self = shift;
+
+    my $engine = $self->engine;
+
+    unless ( $self->offset ) {
+        my $leftover = $self->size - $self->base_size;
+
+        $self->{offset} = $engine->_request_index_sector( $self->size );
+        $engine->storage->print_at( $self->offset, $engine->SIG_INDEX ); # Sector type
+        # Skip staleness counter
+        $engine->storage->print_at( $self->offset + $self->base_size,
+            chr(0) x $leftover, # Zero-fill the rest
+        );
+    }
+
+    return $self;
+}
+
+#XXX Change here
+sub size {
+    my $self = shift;
+    unless ( $self->{size} ) {
+        my $e = $self->engine;
+        $self->{size} = $self->base_size + $e->byte_size * $e->hash_chars;
+    }
+    return $self->{size};
+}
+
+sub free_meth { return '_add_free_index_sector' }
+
+sub free {
+    my $self = shift;
+    my $e = $self->engine;
+
+    for my $i ( 0 .. $e->hash_chars - 1 ) {
+        my $l = $self->get_entry( $i ) or next;
+        $e->_load_sector( $l )->free;
+    }
+
+    $self->SUPER::free();
+}
+
+sub _loc_for {
+    my $self = shift;
+    my ($idx) = @_;
+    return $self->offset + $self->base_size + $idx * $self->engine->byte_size;
+}
+
+sub get_entry {
+    my $self = shift;
+    my ($idx) = @_;
+
+    my $e = $self->engine;
+
+    DBM::Deep->_throw_error( "get_entry: Out of range ($idx)" )
+        if $idx < 0 || $idx >= $e->hash_chars;
+
+    return unpack(
+        $StP{$e->byte_size},
+        $e->storage->read_at( $self->_loc_for( $idx ), $e->byte_size ),
+    );
+}
+
+sub set_entry {
+    my $self = shift;
+    my ($idx, $loc) = @_;
+
+    my $e = $self->engine;
+
+    DBM::Deep->_throw_error( "set_entry: Out of range ($idx)" )
+        if $idx < 0 || $idx >= $e->hash_chars;
+
+    $self->engine->storage->print_at(
+        $self->_loc_for( $idx ),
+        pack( $StP{$e->byte_size}, $loc ),
+    );
+}
+
+# This was copied from MARCEL's Class::Null. However, I couldn't use it because
+# I need an undef value, not an implementation of the Null Class pattern.
+package DBM::Deep::Null;
+
+use overload
+    'bool'   => sub { undef },
+    '""'     => sub { undef },
+    '0+'     => sub { undef },
+    fallback => 1,
+    nomethod => 'AUTOLOAD';
+
+sub AUTOLOAD { return; }
+
 1;
 __END__
index 95215b0..73d1e0b 100644 (file)
@@ -68,7 +68,7 @@ sub free {
                 $e->byte_size,
             ),
         );
-        $s = $e->_load_sector( $l ); $s->free if $s;
+        $s = $e->_load_sector( $l ); $s->free if $s; 
 
         foreach my $txn ( 0 .. $e->num_txns - 2 ) {
             my $l = unpack( $e->StP($e->byte_size),
@@ -352,7 +352,7 @@ sub get_key_for {
     $idx = $self->{idx} unless defined $idx;
 
     if ( $idx >= $self->engine->max_buckets ) {
-        DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx" );
+        DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx beyond max_buckets" );
     }
 
     my $location = $self->read(
@@ -371,11 +371,14 @@ sub rollback {
     my $e = $self->engine;
     my $trans_id = $e->trans_id;
 
+#    warn "Rolling back $idx ($trans_id)\n";
+
     my $base = $self->base_size + ($idx * $self->bucket_size) + $e->hash_size + $e->byte_size;
     my $spot = $base + $e->byte_size + ($trans_id - 1) * ( $e->byte_size + $DBM::Deep::Engine::STALE_SIZE );
 
     my $trans_loc = $self->read( $spot, $e->byte_size );
     $trans_loc = unpack( $e->StP($e->byte_size), $trans_loc );
+#    warn "$trans_loc\n";
 
     $self->write( $spot, pack( $e->StP($e->byte_size), 0 ) );
 
index 0c5e215..de102c5 100644 (file)
@@ -454,11 +454,11 @@ sub free {
     bless $self->engine->cache->{ $self->offset }, 'DBM::Deep::Null';
     delete $self->engine->cache->{ $self->offset };
 
-    my $blist_loc = $self->get_blist_loc;
-    $self->engine->_load_sector( $blist_loc )->free if $blist_loc;
-
-    my $class_loc = $self->get_class_offset;
-    $self->engine->_load_sector( $class_loc )->free if $class_loc;
+    foreach my $meth ( qw( get_blist_loc get_class_offset ) ) {
+        my $l = $self->$meth;
+        my $s = $self->engine->_load_sector( $l );
+        $s->free if $s;
+    }
 
     $self->SUPER::free();
 }
index e1d09d3..50dd19d 100644 (file)
@@ -72,13 +72,11 @@ sub FIRSTKEY {
     ##
     my $self = shift->_get_self;
 
-    warn "HASH:FIRSTKEY($self)\n" if DBM::Deep::DEBUG;
-
     $self->lock_shared;
     
     my $result = $self->_engine->get_next_key( $self );
     
-    $self->unlock();
+    $self->unlock;
     
     return ($result && $self->_engine->storage->{filter_fetch_key})
         ? $self->_engine->storage->{filter_fetch_key}->($result)
@@ -95,14 +93,12 @@ sub NEXTKEY {
         ? $self->_engine->storage->{filter_store_key}->($_[0])
         : $_[0];
 
-    warn "HASH:NEXTKEY($self,$prev_key)\n" if DBM::Deep::DEBUG;
-
     $self->lock_shared;
     
     my $result = $self->_engine->get_next_key( $self, $prev_key );
     
-    $self->unlock();
-
+    $self->unlock;
+    
     return ($result && $self->_engine->storage->{filter_fetch_key})
         ? $self->_engine->storage->{filter_fetch_key}->($result)
         : $result;
index 6de0e05..bd5905f 100644 (file)
@@ -57,6 +57,7 @@ sub get_next_key {
     my $crumbs = $self->{breadcrumbs};
     my $e = $self->{engine};
 
+    warn "1\n";
     unless ( @$crumbs ) {
         # This will be a Reference sector
         my $sector = $e->_load_sector( $self->{base_offset} )
@@ -73,6 +74,7 @@ sub get_next_key {
         push @$crumbs, $self->get_sector_iterator( $loc );
     }
 
+    warn "2: " . $obj->_dump_file;
     FIND_NEXT_KEY: {
         # We're at the end.
         unless ( @$crumbs ) {
index 4b9208e..5798da4 100644 (file)
@@ -2,7 +2,7 @@
 # DBM::Deep Test
 ##
 use strict;
-use Test::More tests => 4;
+use Test::More tests => 3;
 
 use t::common qw( new_fh );
 
@@ -27,4 +27,3 @@ isa_ok( $db, 'DBM::Deep' );
 ok(1, "We can successfully open a file!" );
 
 $db->{foo} = 'bar';
-is( $db->{foo}, 'bar' );
index ab428e5..6e9972a 100644 (file)
@@ -45,7 +45,6 @@ is( $db->{key4}, undef, "Autovivified key4" );
 ok( exists $db->{key4}, "Autovivified key4 now exists" );
 
 delete $db->{key4};
-
 ok( !exists $db->{key4}, "And key4 doesn't exists anymore" );
 
 # Keys will be done via an iterator that keeps a breadcrumb trail of the last
@@ -181,3 +180,4 @@ throws_ok {
 throws_ok {
     $db->exists(undef);
 } qr/Cannot use an undefined hash key/, "EXISTS fails on an undefined key";
+
index 6e6ccee..b362c0f 100644 (file)
@@ -12,8 +12,6 @@ use t::common qw( new_fh );
 
 plan tests => 9;
 
-my $locked = 0;
-
 use_ok( 'DBM::Deep' );
 
 diag "This test can take up to a minute to run. Please be patient.";
@@ -24,8 +22,6 @@ my $db = DBM::Deep->new(
        type => DBM::Deep->TYPE_HASH,
 );
 
-$db->lock_exclusive if $locked;
-
 $db->{foo} = {};
 my $foo = $db->{foo};
 
@@ -34,11 +30,9 @@ my $foo = $db->{foo};
 ##
 my $max_keys = 4000;
 
-warn localtime(time) . ": before put\n";
 for ( 0 .. $max_keys ) {
     $foo->put( "hello $_" => "there " . $_ * 2 );
 }
-warn localtime(time) . ": after put\n";
 
 my $count = -1;
 for ( 0 .. $max_keys ) {
@@ -48,23 +42,16 @@ for ( 0 .. $max_keys ) {
     };
 }
 is( $count, $max_keys, "We read $count keys" );
-warn localtime(time) . ": after read\n";
 
 my @keys = sort keys %$foo;
-warn localtime(time) . ": after keys\n";
 cmp_ok( scalar(@keys), '==', $max_keys + 1, "Number of keys is correct" );
 my @control =  sort map { "hello $_" } 0 .. $max_keys;
 cmp_deeply( \@keys, \@control, "Correct keys are there" );
 
-warn localtime(time) . ": before exists\n";
 ok( !exists $foo->{does_not_exist}, "EXISTS works on large hashes for non-existent keys" );
 is( $foo->{does_not_exist}, undef, "autovivification works on large hashes" );
 ok( exists $foo->{does_not_exist}, "EXISTS works on large hashes for newly-existent keys" );
 cmp_ok( scalar(keys %$foo), '==', $max_keys + 2, "Number of keys after autovivify is correct" );
 
-warn localtime(time) . ": before clear\n";
 $db->clear;
-warn localtime(time) . ": after clear\n";
 cmp_ok( scalar(keys %$db), '==', 0, "Number of keys after clear() is correct" );
-
-$db->unlock if $locked;
index 3eea452..24b52ec 100644 (file)
@@ -63,8 +63,7 @@ is( $db->fetch(4), 'elem4.1' );
 
 throws_ok {
     $db->[-6] = 'whoops!';
-} qr/Modification of non-creatable array value attempted, subscript -6/,
-  "Correct error thrown when attempting to modify a non-creatable array value";
+} qr/Modification of non-creatable array value attempted, subscript -6/, "Correct error thrown";
 
 my $popped = $db->pop;
 is( $db->length, 4, "... and we have four after popping" );
index 006f26a..aff3007 100644 (file)
@@ -98,7 +98,7 @@ locking => 0,
         } qr/Cannot write to a readonly filehandle/, "Can't write to a read-only filehandle";
         ok( !$db->exists( 'foo' ), "foo still doesn't exist" );
 
-        is( $db->{x}, 'b', "x is still 'b'" );
+        is( $db->{x}, 'b' );
     }
 
     exec( "$^X -Iblib/lib $filename" );
index d125582..2c3c44a 100644 (file)
@@ -27,7 +27,6 @@ $db1->{x} = { xy => { foo => 'y' } };
 is( $db1->{x}{xy}{foo}, 'y', "Before transaction, DB1's X is Y" );
 is( $db2->{x}{xy}{foo}, 'y', "Before transaction, DB2's X is Y" );
 
-#warn $db1->_dump_file;
 $db1->begin_work;
 
     cmp_bag( [ keys %$db1 ], [qw( x )], "DB1 keys correct" );
@@ -49,9 +48,8 @@ $db1->begin_work;
     cmp_bag( [ keys %{$db1->{x}} ], [qw( yz )], "DB1->X keys correct" );
     cmp_bag( [ keys %{$db2->{x}} ], [qw( xy )], "DB2->X keys correct" );
 
-#warn $db1->_dump_file;
 $db1->rollback;
-__END__
+
 cmp_bag( [ keys %$db1 ], [qw( x )], "DB1 keys correct" );
 cmp_bag( [ keys %$db2 ], [qw( x )], "DB2 keys correct" );
 
index d861010..e8462b3 100644 (file)
@@ -6,20 +6,17 @@ use t::common qw( new_fh );
 
 use DBM::Deep;
 
-my $max_txns = 250;
+my $max_txns = 255;
 
 my ($fh, $filename) = new_fh();
 
 my @dbs = grep { $_ } map {
-    my $x = 
     eval {
         DBM::Deep->new(
-            file     => $filename,
-            num_txns => $max_txns,
+            file => $filename,
+            num_txns  => $max_txns,
         );
     };
-    die $@ if $@;
-    $x;
 } 1 .. $max_txns;
 
 my $num = $#dbs;
index 335a62e..4d943d5 100644 (file)
@@ -11,9 +11,9 @@ BEGIN {
         if ( $^O =~ /bsd/i );
 
     my @failures;
-    eval " use Pod::Usage 1.3;"; push @failures, 'Pod::Usage' if $@;
-    eval " use IO::Scalar;"; push @failures, 'IO::Scalar' if $@;
-    eval " use FileHandle::Fmode;"; push @failures, 'FileHandle::Fmode' if $@;
+    eval "use Pod::Usage 1.3;"; push @failures, 'Pod::Usage' if $@;
+    eval "use IO::Scalar;"; push @failures, 'IO::Scalar' if $@;
+    eval "use FileHandle::Fmode;"; push @failures, 'FileHandle::Fmode' if $@;
     if ( @failures ) {
         my $missing = join ',', @failures;
         plan skip_all => "'$missing' must be installed to run these tests";
index fec9980..1445517 100644 (file)
@@ -11,7 +11,6 @@ my $db = DBM::Deep->new(
 );
 
 is( $db->_dump_file, <<"__END_DUMP__", "Dump of initial file correct" );
-Size: 94
 NumTxns: 1
 Chains(B):
 Chains(D):
@@ -22,7 +21,6 @@ __END_DUMP__
 $db->{foo} = 'bar';
 
 is( $db->_dump_file, <<"__END_DUMP__", "Dump of initial file correct" );
-Size: 609
 NumTxns: 1
 Chains(B):
 Chains(D):
index b1162cc..91003c3 100755 (executable)
@@ -28,7 +28,7 @@ my %is_dev = (
 my %opts = (
   man => 0,
   help => 0,
-  version => '1.0014',
+  version => '1.0013',
   autobless => 1,
 );
 GetOptions( \%opts,
@@ -71,7 +71,10 @@ my %db;
 
 {
   my $ver = $opts{version};
-  if ( $ver =~ /^1\.001[0-4]/ || $ver =~ /^1\.000[3-9]/) {
+  if ( $ver =~ /^1\.001[0-3]/) {
+    $ver = 3;
+  }
+  elsif ( $ver =~ /^1\.000[3-9]/) {
     $ver = 3;
   }
   elsif ( $ver =~ /^1\.000?[0-2]?/) {