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)
=head1 NAME
-DBM::Deep::Engine
+DBM::Deep::Engine::File
=head1 PURPOSE
-This is an internal-use-only object for L<DBM::Deep/>. It mediates the low-level
-mapping between the L<DBM::Deep/> objects and the storage medium.
-
-The purpose of this documentation is to provide low-level documentation for
-developers. It is B<not> intended to be used by the general public. This
-documentation and what it documents can and will change without notice.
-
-=head1 OVERVIEW
-
-The engine exposes an API to the DBM::Deep objects (DBM::Deep, DBM::Deep::Array,
-and DBM::Deep::Hash) for their use to access the actual stored values. This API
-is the following:
-
-=over 4
-
-=item * new
-
-=item * read_value
-
-=item * get_classname
-
-=item * make_reference
-
-=item * key_exists
-
-=item * delete_key
-
-=item * write_value
-
-=item * get_next_key
-
-=item * setup_fh
-
-=item * begin_work
-
-=item * commit
-
-=item * rollback
-
-=item * lock_exclusive
-
-=item * lock_shared
-
-=item * unlock
-
-=back
-
-They are explained in their own sections below. These methods, in turn, may
-provide some bounds-checking, but primarily act to instantiate objects in the
-Engine::Sector::* hierarchy and dispatch to them.
-
-=head1 TRANSACTIONS
-
-Transactions in DBM::Deep are implemented using a variant of MVCC. This attempts
-to keep the amount of actual work done against the file low while stil providing
-Atomicity, Consistency, and Isolation. Durability, unfortunately, cannot be done
-with only one file.
-
-=head2 STALENESS
-
-If another process uses a transaction slot and writes stuff to it, then
-terminates, the data that process wrote it still within the file. In order to
-address this, there is also a transaction staleness counter associated within
-every write. Each time a transaction is started, that process increments that
-transaction's staleness counter. If, when it reads a value, the staleness
-counters aren't identical, DBM::Deep will consider the value on disk to be stale
-and discard it.
-
-=head2 DURABILITY
-
-The fourth leg of ACID is Durability, the guarantee that when a commit returns,
-the data will be there the next time you read from it. This should be regardless
-of any crashes or powerdowns in between the commit and subsequent read.
-DBM::Deep does provide that guarantee; once the commit returns, all of the data
-has been transferred from the transaction shadow to the HEAD. The issue arises
-with partial commits - a commit that is interrupted in some fashion. In keeping
-with DBM::Deep's "tradition" of very light error-checking and non-existent
-error-handling, there is no way to recover from a partial commit. (This is
-probably a failure in Consistency as well as Durability.)
-
-Other DBMSes use transaction logs (a separate file, generally) to achieve
-Durability. As DBM::Deep is a single-file, we would have to do something
-similar to what SQLite and BDB do in terms of committing using synchonized
-writes. To do this, we would have to use a much higher RAM footprint and some
-serious programming that make my head hurts just to think about it.
+This is the engine for use with L<DBM::Deep::Storage::File/>.
=head1 EXTERNAL METHODS
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 ) {
});
unless ( $value_sector ) {
- $value_sector = DBM::Deep::Engine::Sector::Null->new({
+ $value_sector = DBM::Deep::Sector::File::Null->new({
engine => $self,
data => undef,
});
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 ) {
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 ) {
});
unless ( $value_sector ) {
- $value_sector = DBM::Deep::Engine::Sector::Null->new({
+ $value_sector = DBM::Deep::Sector::File::Null->new({
engine => $self,
data => undef,
});
});
}
- 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 ),
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 ) {
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 ) {
});
}
-=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) = @_;
}
# 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 ) {
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;
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,
}
#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 ),
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,
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.
+ # 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', {
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) = @_;
$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,
});
# 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,
});
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) = @_;
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) = @_;
$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;
}
}
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) = @_;
);
if ( $head_loc > 1 ) {
- $self->_load_sector( $head_loc )->free;
+ $self->load_sector( $head_loc )->free;
}
}
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.
}
}
-=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
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
=cut
-sub storage { $_[0]{storage} }
sub byte_size { $_[0]{byte_size} }
sub hash_size { $_[0]{hash_size} }
sub hash_chars { $_[0]{hash_chars} }
sub chains_loc { $_[0]{chains_loc} }
sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
-sub cache { $_[0]{cache} ||= {} }
-sub clear_cache { %{$_[0]->cache} = () }
-
=head2 _dump_file()
This method takes no arguments. It's used to print out a textual representation
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 = "";
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 ) {