Final fixes before releasing last developer release
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine / File.pm
index ccd9541..0af33b8 100644 (file)
@@ -4,24 +4,32 @@ use 5.006_000;
 
 use strict;
 use warnings FATAL => 'all';
+no warnings 'recursion';
 
 use base qw( DBM::Deep::Engine );
 
-# Never import symbols into our namespace. We are a class, not a library.
 use Scalar::Util ();
 
+use DBM::Deep::Null ();
+use DBM::Deep::Sector::File ();
 use DBM::Deep::Storage::File ();
 
-use DBM::Deep::Engine::Sector::Data ();
-use DBM::Deep::Engine::Sector::BucketList ();
-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::Null ();
+sub sector_type { 'DBM::Deep::Sector::File' }
+sub iterator_class { 'DBM::Deep::Iterator::File' }
 
 my $STALE_SIZE = 2;
 
+# Setup file and tag signatures.  These should never change.
+sub SIG_FILE     () { 'DPDB' }
+sub SIG_HEADER   () { 'h'    }
+sub SIG_NULL     () { 'N'    }
+sub SIG_DATA     () { 'D'    }
+sub SIG_INDEX    () { 'I'    }
+sub SIG_BLIST    () { 'B'    }
+sub SIG_FREE     () { 'F'    }
+sub SIG_SIZE     () {  1     }
+# SIG_HASH and SIG_ARRAY are defined in DBM::Deep::Engine
+
 # Please refer to the pack() documentation for further information
 my %StP = (
     1 => 'C', # Unsigned char value (no order needed as it's just one byte)
@@ -36,7 +44,7 @@ DBM::Deep::Engine::File
 
 =head1 PURPOSE
 
-This is the engine for use with L<DBM::Deep::Storage::File/>.
+This is the engine for use with L<DBM::Deep::Storage::File>.
 
 =head1 EXTERNAL METHODS
 
@@ -123,19 +131,12 @@ sub new {
     return $self;
 }
 
-=head2 read_value( $obj, $key )
-
-This takes an object that provides _base_offset() and a string. It returns the
-value stored in the corresponding Sector::Value's data section.
-
-=cut
-
 sub read_value {
     my $self = shift;
     my ($obj, $key) = @_;
 
     # This will be a Reference sector
-    my $sector = $self->_load_sector( $obj->_base_offset )
+    my $sector = $self->load_sector( $obj->_base_offset )
         or return;
 
     if ( $sector->staleness != $obj->_staleness ) {
@@ -150,7 +151,7 @@ sub read_value {
     });
 
     unless ( $value_sector ) {
-        $value_sector = DBM::Deep::Engine::Sector::Null->new({
+        $value_sector = DBM::Deep::Sector::File::Null->new({
             engine => $self,
             data   => undef,
         });
@@ -165,23 +166,12 @@ sub read_value {
     return $value_sector->data;
 }
 
-=head2 get_classname( $obj )
-
-This takes an object that provides _base_offset() and returns the classname (if
-any) associated with it.
-
-It delegates to Sector::Reference::get_classname() for the heavy lifting.
-
-It performs a staleness check.
-
-=cut
-
 sub get_classname {
     my $self = shift;
     my ($obj) = @_;
 
     # This will be a Reference sector
-    my $sector = $self->_load_sector( $obj->_base_offset )
+    my $sector = $self->load_sector( $obj->_base_offset )
         or DBM::Deep->_throw_error( "How did get_classname fail (no sector for '$obj')?!" );
 
     if ( $sector->staleness != $obj->_staleness ) {
@@ -191,22 +181,12 @@ sub get_classname {
     return $sector->get_classname;
 }
 
-=head2 make_reference( $obj, $old_key, $new_key )
-
-This takes an object that provides _base_offset() and two strings. The
-strings correspond to the old key and new key, respectively. This operation
-is equivalent to (given C<< $db->{foo} = []; >>) C<< $db->{bar} = $db->{foo} >>.
-
-This returns nothing.
-
-=cut
-
 sub make_reference {
     my $self = shift;
     my ($obj, $old_key, $new_key) = @_;
 
     # This will be a Reference sector
-    my $sector = $self->_load_sector( $obj->_base_offset )
+    my $sector = $self->load_sector( $obj->_base_offset )
         or DBM::Deep->_throw_error( "How did make_reference fail (no sector for '$obj')?!" );
 
     if ( $sector->staleness != $obj->_staleness ) {
@@ -221,7 +201,7 @@ sub make_reference {
     });
 
     unless ( $value_sector ) {
-        $value_sector = DBM::Deep::Engine::Sector::Null->new({
+        $value_sector = DBM::Deep::Sector::File::Null->new({
             engine => $self,
             data   => undef,
         });
@@ -233,7 +213,7 @@ sub make_reference {
         });
     }
 
-    if ( $value_sector->isa( 'DBM::Deep::Engine::Sector::Reference' ) ) {
+    if ( $value_sector->isa( 'DBM::Deep::Sector::File::Reference' ) ) {
         $sector->write_data({
             key     => $new_key,
             key_md5 => $self->_apply_digest( $new_key ),
@@ -252,19 +232,13 @@ sub make_reference {
     return;
 }
 
-=head2 key_exists( $obj, $key )
-
-This takes an object that provides _base_offset() and a string for
-the key to be checked. This returns 1 for true and "" for false.
-
-=cut
-
+# exists returns '', not undefined.
 sub key_exists {
     my $self = shift;
     my ($obj, $key) = @_;
 
     # This will be a Reference sector
-    my $sector = $self->_load_sector( $obj->_base_offset )
+    my $sector = $self->load_sector( $obj->_base_offset )
         or return '';
 
     if ( $sector->staleness != $obj->_staleness ) {
@@ -280,19 +254,11 @@ sub key_exists {
     return $data ? 1 : '';
 }
 
-=head2 delete_key( $obj, $key )
-
-This takes an object that provides _base_offset() and a string for
-the key to be deleted. This returns the result of the Sector::Reference
-delete_key() method.
-
-=cut
-
 sub delete_key {
     my $self = shift;
     my ($obj, $key) = @_;
 
-    my $sector = $self->_load_sector( $obj->_base_offset )
+    my $sector = $self->load_sector( $obj->_base_offset )
         or return;
 
     if ( $sector->staleness != $obj->_staleness ) {
@@ -305,15 +271,6 @@ sub delete_key {
     });
 }
 
-=head2 write_value( $obj, $key, $value )
-
-This takes an object that provides _base_offset(), a string for the
-key, and a value. This value can be anything storable within L<DBM::Deep/>.
-
-This returns 1 upon success.
-
-=cut
-
 sub write_value {
     my $self = shift;
     my ($obj, $key, $value) = @_;
@@ -330,7 +287,7 @@ sub write_value {
     }
 
     # This will be a Reference sector
-    my $sector = $self->_load_sector( $obj->_base_offset )
+    my $sector = $self->load_sector( $obj->_base_offset )
         or DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
 
     if ( $sector->staleness != $obj->_staleness ) {
@@ -339,7 +296,7 @@ sub write_value {
 
     my ($class, $type);
     if ( !defined $value ) {
-        $class = 'DBM::Deep::Engine::Sector::Null';
+        $class = 'DBM::Deep::Sector::File::Null';
     }
     elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
         my $tmpvar;
@@ -360,8 +317,8 @@ sub write_value {
                 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
             }
 
-            # First, verify if we're storing the same thing to this spot. If we are, then
-            # this should be a no-op. -EJS, 2008-05-19
+            # First, verify if we're storing the same thing to this spot. If we
+            # are, then this should be a no-op. -EJS, 2008-05-19
             my $loc = $sector->get_data_location_for({
                 key_md5 => $self->_apply_digest( $key ),
                 allow_head => 1,
@@ -372,7 +329,7 @@ sub write_value {
             }
 
             #XXX Can this use $loc?
-            my $value_sector = $self->_load_sector( $tmpvar->_base_offset );
+            my $value_sector = $self->load_sector( $tmpvar->_base_offset );
             $sector->write_data({
                 key     => $key,
                 key_md5 => $self->_apply_digest( $key ),
@@ -383,18 +340,18 @@ sub write_value {
             return 1;
         }
 
-        $class = 'DBM::Deep::Engine::Sector::Reference';
+        $class = 'DBM::Deep::Sector::File::Reference';
         $type = substr( $r, 0, 1 );
     }
     else {
         if ( tied($value) ) {
             DBM::Deep->_throw_error( "Cannot store something that is tied." );
         }
-        $class = 'DBM::Deep::Engine::Sector::Scalar';
+        $class = 'DBM::Deep::Sector::File::Scalar';
     }
 
-    # Create this after loading the reference sector in case something bad happens.
-    # This way, we won't allocate value sector(s) needlessly.
+    # Create this after loading the reference sector in case something bad
+    # happens. This way, we won't allocate value sector(s) needlessly.
     my $value_sector = $class->new({
         engine => $self,
         data   => $value,
@@ -407,49 +364,12 @@ sub write_value {
         value   => $value_sector,
     });
 
-    # This code is to make sure we write all the values in the $value to the disk
-    # and to make sure all changes to $value after the assignment are reflected
-    # on disk. This may be counter-intuitive at first, but it is correct dwimmery.
-    #   NOTE - simply tying $value won't perform a STORE on each value. Hence, the
-    # copy to a temp value.
-    if ( $r eq 'ARRAY' ) {
-        my @temp = @$value;
-        tie @$value, 'DBM::Deep', {
-            base_offset => $value_sector->offset,
-            staleness   => $value_sector->staleness,
-            storage     => $self->storage,
-            engine      => $self,
-        };
-        @$value = @temp;
-        bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
-    }
-    elsif ( $r eq 'HASH' ) {
-        my %temp = %$value;
-        tie %$value, 'DBM::Deep', {
-            base_offset => $value_sector->offset,
-            staleness   => $value_sector->staleness,
-            storage     => $self->storage,
-            engine      => $self,
-        };
-
-        %$value = %temp;
-        bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
-    }
+    $self->_descend( $value, $value_sector );
 
     return 1;
 }
 
-=head2 setup_fh( $obj )
-
-This takes an object that provides _base_offset(). It will do everything needed
-in order to properly initialize all values for necessary functioning. If this is
-called upon an already initialized object, this will also reset the inode.
-
-This returns 1.
-
-=cut
-
-sub setup_fh {
+sub setup {
     my $self = shift;
     my ($obj) = @_;
 
@@ -462,7 +382,7 @@ sub setup_fh {
             $self->_write_file_header;
 
             # 1) Create Array/Hash entry
-            my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
+            my $initial_reference = DBM::Deep::Sector::File::Reference->new({
                 engine => $self,
                 type   => $obj->_type,
             });
@@ -474,7 +394,7 @@ sub setup_fh {
         # Reading from an existing file
         else {
             $obj->{base_offset} = $bytes_read;
-            my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
+            my $initial_reference = DBM::Deep::Sector::File::Reference->new({
                 engine => $self,
                 offset => $obj->_base_offset,
             });
@@ -495,18 +415,6 @@ sub setup_fh {
     return 1;
 }
 
-=head2 begin_work( $obj )
-
-This takes an object that provides _base_offset(). It will set up all necessary
-bookkeeping in order to run all work within a transaction.
-
-If $obj is already within a transaction, an error wiill be thrown. If there are
-no more available transactions, an error will be thrown.
-
-This returns undef.
-
-=cut
-
 sub begin_work {
     my $self = shift;
     my ($obj) = @_;
@@ -537,17 +445,6 @@ sub begin_work {
     return;
 }
 
-=head2 rollback( $obj )
-
-This takes an object that provides _base_offset(). It will revert all
-actions taken within the running transaction.
-
-If $obj is not within a transaction, an error will be thrown.
-
-This returns 1.
-
-=cut
-
 sub rollback {
     my $self = shift;
     my ($obj) = @_;
@@ -571,7 +468,7 @@ sub rollback {
         $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
 
         if ( $data_loc > 1 ) {
-            $self->_load_sector( $data_loc )->free;
+            $self->load_sector( $data_loc )->free;
         }
     }
 
@@ -586,17 +483,6 @@ sub rollback {
     return 1;
 }
 
-=head2 commit( $obj )
-
-This takes an object that provides _base_offset(). It will apply all
-actions taken within the transaction to the HEAD.
-
-If $obj is not within a transaction, an error will be thrown.
-
-This returns 1.
-
-=cut
-
 sub commit {
     my $self = shift;
     my ($obj) = @_;
@@ -626,7 +512,7 @@ sub commit {
         );
 
         if ( $head_loc > 1 ) {
-            $self->_load_sector( $head_loc )->free;
+            $self->load_sector( $head_loc )->free;
         }
     }
 
@@ -641,56 +527,6 @@ sub commit {
     return 1;
 }
 
-=head2 lock_exclusive()
-
-This takes an object that provides _base_offset(). It will guarantee that
-the storage has taken precautions to be safe for a write.
-
-This returns nothing.
-
-=cut
-
-sub lock_exclusive {
-    my $self = shift;
-    my ($obj) = @_;
-    return $self->storage->lock_exclusive( $obj );
-}
-
-=head2 lock_shared()
-
-This takes an object that provides _base_offset(). It will guarantee that
-the storage has taken precautions to be safe for a read.
-
-This returns nothing.
-
-=cut
-
-sub lock_shared {
-    my $self = shift;
-    my ($obj) = @_;
-    return $self->storage->lock_shared( $obj );
-}
-
-=head2 unlock()
-
-This takes an object that provides _base_offset(). It will guarantee that
-the storage has released all locks taken.
-
-This returns nothing.
-
-=cut
-
-sub unlock {
-    my $self = shift;
-    my ($obj) = @_;
-
-    my $rv = $self->storage->unlock( $obj );
-
-    $self->flush if $rv;
-
-    return $rv;
-}
-
 =head1 INTERNAL METHODS
 
 The following methods are internal-use-only to DBM::Deep::Engine::File.
@@ -975,67 +811,6 @@ settings that set how the file is interpreted.
     }
 }
 
-=head2 _load_sector( $offset )
-
-This will instantiate and return the sector object that represents the data found
-at $offset.
-
-=cut
-
-sub _load_sector {
-    my $self = shift;
-    my ($offset) = @_;
-
-    # 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'" );
-}
-
 =head2 _apply_digest( @stuff )
 
 This will apply the digest methd (default to Digest::MD5::md5) to the arguments
@@ -1155,31 +930,12 @@ sub _request_sector {
     return $loc;
 }
 
-=head2 flush()
-
-This takes no arguments. It will do everything necessary to flush all things to
-disk. This is usually called during unlock() and setup_fh().
-
-This returns nothing.
-
-=cut
-
-sub flush {
-    my $self = shift;
-
-    # Why do we need to have the storage flush? Shouldn't autoflush take care of things?
-    # -RobK, 2008-06-26
-    $self->storage->flush;
-}
-
 =head2 ACCESSORS
 
 The following are readonly attributes.
 
 =over 4
 
-=item * storage
-
 =item * byte_size
 
 =item * hash_size
@@ -1200,7 +956,6 @@ The following are readonly attributes.
 
 =cut
 
-sub storage     { $_[0]{storage} }
 sub byte_size   { $_[0]{byte_size} }
 sub hash_size   { $_[0]{hash_size} }
 sub hash_chars  { $_[0]{hash_chars} }
@@ -1247,8 +1002,28 @@ sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
 sub chains_loc     { $_[0]{chains_loc} }
 sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
 
-sub cache       { $_[0]{cache} ||= {} }
-sub clear_cache { %{$_[0]->cache} = () }
+sub supports {
+    shift;
+    my ($feature) = @_;
+
+    return 1 if $feature eq 'transactions';
+    return if $feature eq 'singletones';
+    return;
+}
+
+sub clear {
+    my $self = shift;
+    my $obj = shift;
+
+    my $sector = $self->load_sector( $obj->_base_offset )
+        or return;
+
+    return unless $sector->staleness == $obj->_staleness;
+
+    $sector->clear;
+
+    return;
+}
 
 =head2 _dump_file()
 
@@ -1271,8 +1046,8 @@ sub _dump_file {
 
     my %sizes = (
         'D' => $self->data_sector_size,
-        'B' => DBM::Deep::Engine::Sector::BucketList->new({engine=>$self,offset=>1})->size,
-        'I' => DBM::Deep::Engine::Sector::Index->new({engine=>$self,offset=>1})->size,
+        'B' => DBM::Deep::Sector::File::BucketList->new({engine=>$self,offset=>1})->size,
+        'I' => DBM::Deep::Sector::File::Index->new({engine=>$self,offset=>1})->size,
     );
 
     my $return = "";
@@ -1306,7 +1081,7 @@ sub _dump_file {
     SECTOR:
     while ( $spot < $self->storage->{end} ) {
         # Read each sector in order.
-        my $sector = $self->_load_sector( $spot );
+        my $sector = $self->load_sector( $spot );
         if ( !$sector ) {
             # Find it in the free-sectors that were found already
             foreach my $type ( keys %sectors ) {