use strict;
-use FileHandle;
-use Fcntl qw/:flock/;
+use Fcntl qw( :DEFAULT :flock :seek );
use Digest::MD5 ();
use Scalar::Util ();
-use vars qw/$VERSION/;
-$VERSION = "0.96";
+use vars qw( $VERSION );
+$VERSION = q(0.97);
##
# Set to 4 and 'N' for 32-bit offset tags (default). Theoretical limit of 4 GB per file.
##
#my $DATA_LENGTH_SIZE = 4;
#my $DATA_LENGTH_PACK = 'N';
-my ($LONG_SIZE, $LONG_PACK, $DATA_LENGTH_SIZE, $DATA_LENGTH_PACK);
+our ($LONG_SIZE, $LONG_PACK, $DATA_LENGTH_SIZE, $DATA_LENGTH_PACK);
##
# Maximum number of buckets per list before another level of indexing is done.
##
# Setup digest function for keys
##
-my ($DIGEST_FUNC, $HASH_SIZE);
+our ($DIGEST_FUNC, $HASH_SIZE);
#my $DIGEST_FUNC = \&Digest::MD5::md5;
##
##
# Setup file and tag signatures. These should never change.
##
-sub SIG_FILE () { 'DPDB' }
-sub SIG_HASH () { 'H' }
-sub SIG_ARRAY () { 'A' }
-sub SIG_NULL () { 'N' }
-sub SIG_DATA () { 'D' }
-sub SIG_INDEX () { 'I' }
-sub SIG_BLIST () { 'B' }
-sub SIG_SIZE () { 1 }
+sub SIG_FILE () { 'DPDB' }
+sub SIG_HASH () { 'H' }
+sub SIG_ARRAY () { 'A' }
+sub SIG_SCALAR () { 'S' }
+sub SIG_NULL () { 'N' }
+sub SIG_DATA () { 'D' }
+sub SIG_INDEX () { 'I' }
+sub SIG_BLIST () { 'B' }
+sub SIG_SIZE () { 1 }
##
# Setup constants for users to pass to new()
##
-sub TYPE_HASH () { return SIG_HASH; }
-sub TYPE_ARRAY () { return SIG_ARRAY; }
+sub TYPE_HASH () { return SIG_HASH; }
+sub TYPE_ARRAY () { return SIG_ARRAY; }
+sub TYPE_SCALAR () { return SIG_SCALAR; }
+
+sub _get_args {
+ my $proto = shift;
+
+ my $args;
+ if (scalar(@_) > 1) {
+ if ( @_ % 2 ) {
+ $proto->_throw_error( "Odd number of parameters to " . (caller(1))[2] );
+ }
+ $args = {@_};
+ }
+ elsif ( my $type = Scalar::Util::reftype($_[0]) ) {
+ if ( $type ne 'HASH' ) {
+ $proto->_throw_error( "Not a hashref in args to " . (caller(1))[2] );
+ }
+ $args = $_[0];
+ }
+ else {
+ $args = { file => shift };
+ }
+
+ return $args;
+}
sub new {
##
# providing a hybrid OO/tie interface.
##
my $class = shift;
- my $args;
- if (scalar(@_) > 1) { $args = {@_}; }
- else { $args = { file => shift }; }
+ my $args = $class->_get_args( @_ );
##
# Check if we want a tied hash or array.
##
my $self;
if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) {
+ $class = 'DBM::Deep::Array';
+ require DBM::Deep::Array;
tie @$self, $class, %$args;
}
else {
+ $class = 'DBM::Deep::Hash';
+ require DBM::Deep::Hash;
tie %$self, $class, %$args;
}
return bless $self, $class;
}
-{
- my @outer_params = qw( type base_offset );
- sub _init {
- ##
- # Setup $self and bless into this class.
- ##
- my $class = shift;
- my $args = shift;
-
- my $self = {
- type => TYPE_HASH,
- base_offset => length(SIG_FILE),
- root => {
- file => undef,
- fh => undef,
- end => 0,
- links => 0,
- autoflush => undef,
- locking => undef,
- volatile => undef,
- debug => undef,
- mode => 'r+',
- filter_store_key => undef,
- filter_store_value => undef,
- filter_fetch_key => undef,
- filter_fetch_value => undef,
- autobless => undef,
- locked => 0,
- %$args,
- },
- };
-
- bless $self, $class;
-
- foreach my $outer_parm ( @outer_params ) {
- next unless exists $args->{$outer_parm};
- $self->{$outer_parm} = $args->{$outer_parm}
- }
-
- if ( exists $args->{root} ) {
- $self->{root} = $args->{root};
- }
- else {
- # This is cleanup based on the fact that the $args
- # coming in is for both the root and non-root items
- delete $self->root->{$_} for @outer_params;
- }
- $self->root->{links}++;
+sub _init {
+ ##
+ # Setup $self and bless into this class.
+ ##
+ my $class = shift;
+ my $args = shift;
- if (!defined($self->fh)) { $self->_open(); }
+ # These are the defaults to be optionally overridden below
+ my $self = bless {
+ type => TYPE_HASH,
+ base_offset => length(SIG_FILE),
+ }, $class;
- return $self;
+ foreach my $param ( keys %$self ) {
+ next unless exists $args->{$param};
+ $self->{$param} = delete $args->{$param}
}
-}
+
+ $self->{root} = exists $args->{root}
+ ? $args->{root}
+ : DBM::Deep::_::Root->new( $args );
-sub _get_self { tied( %{$_[0]} ) || $_[0] }
+ if (!defined($self->fh)) { $self->_open(); }
-sub TIEHASH {
- ##
- # Tied hash constructor method, called by Perl's tie() function.
- ##
- my $class = shift;
- my $args;
- if (scalar(@_) > 1) { $args = {@_}; }
- #XXX This use of ref() is bad and is a bug
- elsif (ref($_[0])) { $args = $_[0]; }
- else { $args = { file => shift }; }
-
- $args->{type} = TYPE_HASH;
+ return $self;
+}
- return $class->_init($args);
+sub TIEHASH {
+ shift;
+ require DBM::Deep::Hash;
+ return DBM::Deep::Hash->TIEHASH( @_ );
}
sub TIEARRAY {
-##
-# Tied array constructor method, called by Perl's tie() function.
-##
- my $class = shift;
- my $args;
- if (scalar(@_) > 1) { $args = {@_}; }
- #XXX This use of ref() is bad and is a bug
- elsif (ref($_[0])) { $args = $_[0]; }
- else { $args = { file => shift }; }
-
- $args->{type} = TYPE_ARRAY;
-
- return $class->_init($args);
+ shift;
+ require DBM::Deep::Array;
+ return DBM::Deep::Array->TIEARRAY( @_ );
}
-sub DESTROY {
- ##
- # Class deconstructor. Close file handle if there are no more refs.
- ##
- my $self = _get_self($_[0]);
- return unless $self;
-
- $self->root->{links}--;
-
- if (!$self->root->{links}) {
- $self->_close();
- }
-}
+#XXX Unneeded now ...
+#sub DESTROY {
+#}
sub _open {
##
- # Open a FileHandle to the database, create if nonexistent.
- # Make sure file signature matches DeepDB spec.
+ # Open a fh to the database, create if nonexistent.
+ # Make sure file signature matches DBM::Deep spec.
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
if (defined($self->fh)) { $self->_close(); }
-# eval {
- if (!(-e $self->root->{file}) && $self->root->{mode} eq 'r+') {
- my $temp = FileHandle->new( $self->root->{file}, 'w' );
- }
-
- #XXX Convert to set_fh()
- $self->root->{fh} = FileHandle->new( $self->root->{file}, $self->root->{mode} );
-# }; if ($@ ) { $self->_throw_error( "Received error: $@\n" ); }
+ eval {
+ # Theoretically, adding O_BINARY should remove the need for the binmode
+ # Of course, testing it is going to be ... interesting.
+ my $flags = O_RDWR | O_CREAT | O_BINARY;
+
+ my $fh;
+ sysopen( $fh, $self->root->{file}, $flags )
+ or $fh = undef;
+ $self->root->{fh} = $fh;
+ }; if ($@ ) { $self->_throw_error( "Received error: $@\n" ); }
if (! defined($self->fh)) {
- return $self->_throw_error("Cannot open file: " . $self->root->{file} . ": $!");
+ return $self->_throw_error("Cannot sysopen file: " . $self->root->{file} . ": $!");
}
my $fh = $self->fh;
#XXX Can we remove this by using the right sysopen() flags?
+ # Maybe ... q.v. above
binmode $fh; # for win32
if ($self->root->{autoflush}) {
-# $self->fh->autoflush();
my $old = select $fh;
$|=1;
select $old;
}
+ # Set the
+ seek($fh, 0, SEEK_SET);
+
my $signature;
- seek($fh, 0, 0);
my $bytes_read = read( $fh, $signature, length(SIG_FILE));
##
# File is empty -- write signature and master index
##
if (!$bytes_read) {
- seek($fh, 0, 0);
- $fh->print(SIG_FILE);
- $self->root->{end} = length(SIG_FILE);
+ seek($fh, 0, SEEK_SET);
+ print($fh SIG_FILE);
$self->_create_tag($self->base_offset, $self->type, chr(0) x $INDEX_SIZE);
my $plain_key = "[base]";
- $fh->print( pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key );
- $self->root->{end} += $DATA_LENGTH_SIZE + length($plain_key);
+ print($fh pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key );
-# $fh->flush();
+ # Flush the filehandle
my $old_fh = select $fh;
my $old_af = $|;
$| = 1;
$| = $old_af;
select $old_fh;
+ my @stats = stat($fh);
+ $self->root->{inode} = $stats[1];
+ $self->root->{end} = $stats[7];
+
return 1;
}
return $self->_throw_error("Signature not found -- file is not a Deep DB");
}
- $self->root->{end} = (stat($fh))[7];
+ my @stats = stat($fh);
+ $self->root->{inode} = $stats[1];
+ $self->root->{end} = $stats[7];
##
# Get our type from master index signature
##
my $tag = $self->_load_tag($self->base_offset);
+
#XXX We probably also want to store the hash algorithm name and not assume anything
+#XXX The cool thing would be to allow a different hashing algorithm at every level
+
if (!$tag) {
return $self->_throw_error("Corrupted file, no master index record");
}
sub _close {
##
- # Close database FileHandle
+ # Close database fh
##
- my $self = _get_self($_[0]);
- undef $self->root->{fh};
+ my $self = $_[0]->_get_self;
+ close $self->root->{fh} if $self->root->{fh};
+ $self->root->{fh} = undef;
}
sub _create_tag {
my $fh = $self->fh;
- seek($fh, $offset, 0);
- $fh->print( $sig . pack($DATA_LENGTH_PACK, $size) . $content );
+ seek($fh, $offset, SEEK_SET);
+ print($fh $sig . pack($DATA_LENGTH_PACK, $size) . $content );
if ($offset == $self->root->{end}) {
$self->root->{end} += SIG_SIZE + $DATA_LENGTH_SIZE + $size;
my $fh = $self->fh;
- seek($fh, $offset, 0);
+ seek($fh, $offset, SEEK_SET);
if (eof $fh) { return undef; }
my $sig;
my $location = 0;
my $result = 2;
- my $is_dbm_deep = eval { $value->isa( 'DBM::Deep' ) };
+ # added ref() check first to avoid eval and runtime exception for every
+ # scalar value being stored. performance tweak.
+ my $is_dbm_deep = ref($value) && eval { $value->isa( 'DBM::Deep' ) };
+
my $internal_ref = $is_dbm_deep && ($value->root eq $self->root);
my $fh = $self->fh;
? $value->base_offset
: $self->root->{end};
- seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE), 0);
- $fh->print( $md5 . pack($LONG_PACK, $location) );
+ seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE), SEEK_SET);
+ print($fh $md5 . pack($LONG_PACK, $location) );
last;
}
elsif ($md5 eq $key) {
if ($internal_ref) {
$location = $value->base_offset;
- seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE), 0);
- $fh->print( $md5 . pack($LONG_PACK, $location) );
+ seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE), SEEK_SET);
+ print($fh $md5 . pack($LONG_PACK, $location) );
}
else {
- seek($fh, $subloc + SIG_SIZE, 0);
+ seek($fh, $subloc + SIG_SIZE, SEEK_SET);
my $size;
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
##
my $actual_length;
my $r = Scalar::Util::reftype( $value ) || '';
- if ( $r eq 'HASH' || $r eq 'ARRAY' ) { $actual_length = $INDEX_SIZE; }
+ if ( $r eq 'HASH' || $r eq 'ARRAY' ) {
+ $actual_length = $INDEX_SIZE;
+
+ # if autobless is enabled, must also take into consideration
+ # the class name, as it is stored along with key/value.
+ if ( $self->root->{autobless} ) {
+ my $value_class = Scalar::Util::blessed($value);
+ if ( defined $value_class && $value_class ne 'DBM::Deep' ) {
+ $actual_length += length($value_class);
+ }
+ } # autobless
+ }
else { $actual_length = length($value); }
if ($actual_length <= $size) {
}
else {
$location = $self->root->{end};
- seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $HASH_SIZE, 0);
- $fh->print( pack($LONG_PACK, $location) );
+ seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $HASH_SIZE, SEEK_SET);
+ print($fh pack($LONG_PACK, $location) );
}
}
last;
# If bucket didn't fit into list, split into a new index level
##
if (!$location) {
- seek($fh, $tag->{ref_loc}, 0);
- $fh->print( pack($LONG_PACK, $self->root->{end}) );
+ seek($fh, $tag->{ref_loc}, SEEK_SET);
+ print($fh pack($LONG_PACK, $self->root->{end}) );
my $index_tag = $self->_create_tag($self->root->{end}, SIG_INDEX, chr(0) x $INDEX_SIZE);
my @offsets = ();
if ($offsets[$num]) {
my $offset = $offsets[$num] + SIG_SIZE + $DATA_LENGTH_SIZE;
- seek($fh, $offset, 0);
+ seek($fh, $offset, SEEK_SET);
my $subkeys;
read( $fh, $subkeys, $BUCKET_LIST_SIZE);
for (my $k=0; $k<$MAX_BUCKETS; $k++) {
my $subloc = unpack($LONG_PACK, substr($subkeys, ($k * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE));
if (!$subloc) {
- seek($fh, $offset + ($k * $BUCKET_SIZE), 0);
- $fh->print( $key . pack($LONG_PACK, $old_subloc || $self->root->{end}) );
+ seek($fh, $offset + ($k * $BUCKET_SIZE), SEEK_SET);
+ print($fh $key . pack($LONG_PACK, $old_subloc || $self->root->{end}) );
last;
}
} # k loop
}
else {
$offsets[$num] = $self->root->{end};
- seek($fh, $index_tag->{offset} + ($num * $LONG_SIZE), 0);
- $fh->print( pack($LONG_PACK, $self->root->{end}) );
+ seek($fh, $index_tag->{offset} + ($num * $LONG_SIZE), SEEK_SET);
+ print($fh pack($LONG_PACK, $self->root->{end}) );
my $blist_tag = $self->_create_tag($self->root->{end}, SIG_BLIST, chr(0) x $BUCKET_LIST_SIZE);
- seek($fh, $blist_tag->{offset}, 0);
- $fh->print( $key . pack($LONG_PACK, $old_subloc || $self->root->{end}) );
+ seek($fh, $blist_tag->{offset}, SEEK_SET);
+ print($fh $key . pack($LONG_PACK, $old_subloc || $self->root->{end}) );
}
} # key is real
} # i loop
##
if ($location) {
my $content_length;
- seek($fh, $location, 0);
+ seek($fh, $location, SEEK_SET);
##
# Write signature based on content type, set content length and write actual value.
##
my $r = Scalar::Util::reftype($value) || '';
if ($r eq 'HASH') {
- $fh->print( TYPE_HASH );
- $fh->print( pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE );
+ print($fh TYPE_HASH );
+ print($fh pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE );
$content_length = $INDEX_SIZE;
}
elsif ($r eq 'ARRAY') {
- $fh->print( TYPE_ARRAY );
- $fh->print( pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE );
+ print($fh TYPE_ARRAY );
+ print($fh pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE );
$content_length = $INDEX_SIZE;
}
elsif (!defined($value)) {
- $fh->print( SIG_NULL );
- $fh->print( pack($DATA_LENGTH_PACK, 0) );
+ print($fh SIG_NULL );
+ print($fh pack($DATA_LENGTH_PACK, 0) );
$content_length = 0;
}
else {
- $fh->print( SIG_DATA );
- $fh->print( pack($DATA_LENGTH_PACK, length($value)) . $value );
+ print($fh SIG_DATA );
+ print($fh pack($DATA_LENGTH_PACK, length($value)) . $value );
$content_length = length($value);
}
##
# Plain key is stored AFTER value, as keys are typically fetched less often.
##
- $fh->print( pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key );
+ print($fh pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key );
##
# If value is blessed, preserve class name
##
# Blessed ref -- will restore later
##
- $fh->print( chr(1) );
- $fh->print( pack($DATA_LENGTH_PACK, length($value_class)) . $value_class );
+ print($fh chr(1) );
+ print($fh pack($DATA_LENGTH_PACK, length($value_class)) . $value_class );
$content_length += 1;
$content_length += $DATA_LENGTH_SIZE + length($value_class);
}
else {
- $fh->print( chr(0) );
+ print($fh chr(0) );
$content_length += 1;
}
}
}
##
- # If content is a hash or array, create new child DeepDB object and
+ # If content is a hash or array, create new child DBM::Deep object and
# pass each key or element to it.
##
if ($r eq 'HASH') {
root => $self->root,
);
foreach my $key (keys %{$value}) {
- $branch->{$key} = $value->{$key};
+ $branch->STORE( $key, $value->{$key} );
}
}
elsif ($r eq 'ARRAY') {
);
my $index = 0;
foreach my $element (@{$value}) {
- $branch->[$index] = $element;
+ $branch->STORE( $index, $element );
$index++;
}
}
my $keys = $tag->{content};
my $fh = $self->fh;
-
+
##
# Iterate through buckets, looking for a key match
##
# Found match -- seek to offset and read signature
##
my $signature;
- seek($fh, $subloc, 0);
+ seek($fh, $subloc, SEEK_SET);
read( $fh, $signature, SIG_SIZE);
##
- # If value is a hash or array, return new DeepDB object with correct offset
+ # If value is a hash or array, return new DBM::Deep object with correct offset
##
if (($signature eq TYPE_HASH) || ($signature eq TYPE_ARRAY)) {
my $obj = DBM::Deep->new(
# Skip over value and plain key to see if object needs
# to be re-blessed
##
- seek($fh, $DATA_LENGTH_SIZE + $INDEX_SIZE, 1);
+ seek($fh, $DATA_LENGTH_SIZE + $INDEX_SIZE, SEEK_CUR);
my $size;
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
- if ($size) { seek($fh, $size, 1); }
+ if ($size) { seek($fh, $size, SEEK_CUR); }
my $bless_bit;
read( $fh, $bless_bit, 1);
##
# Matched key -- delete bucket and return
##
- seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE), 0);
- $fh->print( substr($keys, ($i+1) * $BUCKET_SIZE ) );
- $fh->print( chr(0) x $BUCKET_SIZE );
+ seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE), SEEK_SET);
+ print($fh substr($keys, ($i+1) * $BUCKET_SIZE ) );
+ print($fh chr(0) x $BUCKET_SIZE );
return 1;
} # i loop
##
# Seek to bucket location and skip over signature
##
- seek($fh, $subloc + SIG_SIZE, 0);
+ seek($fh, $subloc + SIG_SIZE, SEEK_SET);
##
# Skip over value to get to plain key
##
my $size;
read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
- if ($size) { seek($fh, $size, 1); }
+ if ($size) { seek($fh, $size, SEEK_CUR); }
##
# Read in plain key and return as scalar
##
# Locate next key, given digested previous one
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
$self->{prev_md5} = $_[1] ? $_[1] : undef;
$self->{return_next} = 0;
# times before unlock(), then the same number of unlocks() must
# be called before the lock is released.
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
my $type = $_[1];
$type = LOCK_EX unless defined $type;
+ if (!defined($self->fh)) { return; }
+
if ($self->root->{locking}) {
- if (!$self->root->{locked}) { flock($self->fh, $type); }
+ if (!$self->root->{locked}) {
+ flock($self->fh, $type);
+
+ # double-check file inode, in case another process
+ # has optimize()d our file while we were waiting.
+ if ((stat($self->root->{file}))[1] != $self->root->{inode}) {
+ $self->_open(); # re-open
+ flock($self->fh, $type); # re-lock
+ }
+ }
$self->root->{locked}++;
+
+ return 1;
}
+
+ return;
}
sub unlock {
# If db locking is set, unlock the db file. See note in lock()
# regarding calling lock() multiple times.
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
+
+ if (!defined($self->fh)) { return; }
if ($self->root->{locking} && $self->root->{locked} > 0) {
$self->root->{locked}--;
if (!$self->root->{locked}) { flock($self->fh, LOCK_UN); }
+
+ return 1;
}
+
+ return;
}
#XXX These uses of ref() need verified
# Copy single level of keys or elements to new DB handle.
# Recurse for nested structures
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
my $db_temp = $_[1];
if ($self->type eq TYPE_HASH) {
##
# Recursively export into standard Perl hashes and arrays.
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
my $temp;
if ($self->type eq TYPE_HASH) { $temp = {}; }
#XXX This use of ref() seems to be ok
if (!ref($_[0])) { return; } # Perl calls import() on use -- ignore
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
my $struct = $_[1];
#XXX This use of ref() seems to be ok
# Rebuild entire database into new file, then move
# it back on top of original.
##
- my $self = _get_self($_[0]);
- if ($self->root->{links} > 1) {
- return $self->_throw_error("Cannot optimize: reference count is greater than 1");
- }
+ my $self = $_[0]->_get_self;
+
+#XXX Need to create a new test for this
+# if ($self->root->{links} > 1) {
+# return $self->_throw_error("Cannot optimize: reference count is greater than 1");
+# }
my $db_temp = DBM::Deep->new(
file => $self->root->{file} . '.tmp',
chmod( $perms, $self->root->{file} . '.tmp' );
# q.v. perlport for more information on this variable
- if ( $^O eq 'MSWin32' ) {
+ if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
##
# Potential race condition when optmizing on Win32 with locking.
# The Windows filesystem requires that the filehandle be closed
##
# Make copy of object and return
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
return DBM::Deep->new(
type => $self->type,
##
# Setup filter function for storing or fetching the key or value
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
my $type = lc $_[1];
my $func = $_[2] ? $_[2] : undef;
##
# Get access to the root structure
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
return $self->{root};
}
sub fh {
##
- # Get access to the raw FileHandle
+ # Get access to the raw fh
##
#XXX It will be useful, though, when we split out HASH and ARRAY
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
return $self->root->{fh};
}
##
# Get type of current node (TYPE_HASH or TYPE_ARRAY)
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
return $self->{type};
}
##
# Get base_offset of current node (TYPE_HASH or TYPE_ARRAY)
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
return $self->{base_offset};
}
# Get last error string, or undef if no error
##
return $_[0]
- ? ( _get_self($_[0])->{root}->{error} or undef )
+ #? ( _get_self($_[0])->{root}->{error} or undef )
+ ? ( $_[0]->_get_self->{root}->{error} or undef )
: $@;
}
##
# Store error string in self
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
my $error_text = $_[1];
- $self->root->{error} = $error_text;
+ if ( Scalar::Util::blessed $self ) {
+ $self->root->{error} = $error_text;
- unless ($self->root->{debug}) {
+ unless ($self->root->{debug}) {
+ die "DBM::Deep: $error_text\n";
+ }
+
+ warn "DBM::Deep: $error_text\n";
+ return;
+ }
+ else {
die "DBM::Deep: $error_text\n";
}
-
- warn "DBM::Deep: $error_text\n";
- return;
}
sub clear_error {
##
# Clear error state
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
undef $self->root->{error};
}
##
# Store single hash key/value or array element in database.
##
- my $self = _get_self($_[0]);
- my $key = ($self->root->{filter_store_key} && $self->type eq TYPE_HASH) ? $self->root->{filter_store_key}->($_[1]) : $_[1];
- #XXX What is ref() checking here?
- #YYY User may be storing a hash, in which case we do not want it run
- #YYY through the filtering system
- my $value = ($self->root->{filter_store_value} && !ref($_[2])) ? $self->root->{filter_store_value}->($_[2]) : $_[2];
+ my $self = $_[0]->_get_self;
+ my $key = $_[1];
+
+ # User may be storing a hash, in which case we do not want it run
+ # through the filtering system
+ my $value = ($self->root->{filter_store_value} && !ref($_[2]))
+ ? $self->root->{filter_store_value}->($_[2])
+ : $_[2];
- my $unpacked_key = $key;
- if (($self->type eq TYPE_ARRAY) && ($key =~ /^\d+$/)) { $key = pack($LONG_PACK, $key); }
my $md5 = $DIGEST_FUNC->($key);
##
if (!defined($self->fh) && !$self->_open()) {
return;
}
-
- my $fh = $self->fh;
+ ##
##
# Request exclusive lock for writing
##
$self->lock( LOCK_EX );
+
+ my $fh = $self->fh;
##
# If locking is enabled, set 'end' parameter again, in case another
my $new_tag = $self->_index_lookup($tag, $num);
if (!$new_tag) {
my $ref_loc = $tag->{offset} + ($num * $LONG_SIZE);
- seek($fh, $ref_loc, 0);
- $fh->print( pack($LONG_PACK, $self->root->{end}) );
+ seek($fh, $ref_loc, SEEK_SET);
+ print($fh pack($LONG_PACK, $self->root->{end}) );
$tag = $self->_create_tag($self->root->{end}, SIG_BLIST, chr(0) x $BUCKET_LIST_SIZE);
$tag->{ref_loc} = $ref_loc;
##
my $result = $self->_add_bucket( $tag, $md5, $key, $value );
- ##
- # If this object is an array, and bucket was not a replace, and key is numerical,
- # and index is equal or greater than current length, advance length variable.
- ##
- if (($result == 2) && ($self->type eq TYPE_ARRAY) && ($unpacked_key =~ /^\d+$/) && ($unpacked_key >= $self->FETCHSIZE())) {
- $self->STORESIZE( $unpacked_key + 1 );
- }
-
$self->unlock();
return $result;
##
# Fetch single value or element given plain key or array index
##
- my $self = _get_self($_[0]);
-
- my $key = $_[1];
- if ( $self->type eq TYPE_HASH ) {
- if ( my $filter = $self->root->{filter_store_key} ) {
- $key = $filter->( $key );
- }
- }
- elsif ( $self->type eq TYPE_ARRAY ) {
- if ( $key =~ /^\d+$/ ) {
- $key = pack($LONG_PACK, $key);
- }
- }
-
- my $md5 = $DIGEST_FUNC->($key);
+ my $self = shift->_get_self;
+ my $key = shift;
##
# Make sure file is open
##
if (!defined($self->fh)) { $self->_open(); }
+ my $md5 = $DIGEST_FUNC->($key);
+
##
# Request shared lock for reading
##
$self->unlock();
#XXX What is ref() checking here?
- return ($result && !ref($result) && $self->root->{filter_fetch_value}) ? $self->root->{filter_fetch_value}->($result) : $result;
+ #YYY Filters only apply on scalar values, so the ref check is making
+ #YYY sure the fetched bucket is a scalar, not a child hash or array.
+ return ($result && !ref($result) && $self->root->{filter_fetch_value})
+ ? $self->root->{filter_fetch_value}->($result)
+ : $result;
}
sub DELETE {
##
# Delete single key/value pair or element given plain key or array index
##
- my $self = _get_self($_[0]);
- my $key = ($self->root->{filter_store_key} && $self->type eq TYPE_HASH) ? $self->root->{filter_store_key}->($_[1]) : $_[1];
+ my $self = $_[0]->_get_self;
+ my $key = $_[1];
- my $unpacked_key = $key;
- if (($self->type eq TYPE_ARRAY) && ($key =~ /^\d+$/)) { $key = pack($LONG_PACK, $key); }
my $md5 = $DIGEST_FUNC->($key);
##
##
# Delete bucket
##
+ my $value = $self->_get_bucket_value( $tag, $md5 );
+ if ($value && !ref($value) && $self->root->{filter_fetch_value}) {
+ $value = $self->root->{filter_fetch_value}->($value);
+ }
+
my $result = $self->_delete_bucket( $tag, $md5 );
##
# If this object is an array and the key deleted was on the end of the stack,
# decrement the length variable.
##
- if ($result && ($self->type eq TYPE_ARRAY) && ($unpacked_key == $self->FETCHSIZE() - 1)) {
- $self->STORESIZE( $unpacked_key );
- }
$self->unlock();
- return $result;
+ return $value;
}
sub EXISTS {
##
# Check if a single key or element exists given plain key or array index
##
- my $self = _get_self($_[0]);
- my $key = ($self->root->{filter_store_key} && $self->type eq TYPE_HASH) ? $self->root->{filter_store_key}->($_[1]) : $_[1];
+ my $self = $_[0]->_get_self;
+ my $key = $_[1];
- if (($self->type eq TYPE_ARRAY) && ($key =~ /^\d+$/)) { $key = pack($LONG_PACK, $key); }
my $md5 = $DIGEST_FUNC->($key);
##
##
# Clear all keys from hash, or all elements from array.
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
##
# Make sure file is open
my $fh = $self->fh;
- seek($fh, $self->base_offset, 0);
+ seek($fh, $self->base_offset, SEEK_SET);
if (eof $fh) {
$self->unlock();
return;
return 1;
}
-sub FIRSTKEY {
- ##
- # Locate and return first key (in no particular order)
- ##
- my $self = _get_self($_[0]);
- if ($self->type ne TYPE_HASH) {
- return $self->_throw_error("FIRSTKEY method only supported for hashes");
- }
-
- ##
- # Make sure file is open
- ##
- if (!defined($self->fh)) { $self->_open(); }
-
- ##
- # Request shared lock for reading
- ##
- $self->lock( LOCK_SH );
-
- my $result = $self->_get_next_key();
-
- $self->unlock();
-
- return ($result && $self->root->{filter_fetch_key}) ? $self->root->{filter_fetch_key}->($result) : $result;
-}
-
-sub NEXTKEY {
- ##
- # Return next key (in no particular order), given previous one
- ##
- my $self = _get_self($_[0]);
- if ($self->type ne TYPE_HASH) {
- return $self->_throw_error("NEXTKEY method only supported for hashes");
- }
- my $prev_key = ($self->root->{filter_store_key} && $self->type eq TYPE_HASH) ? $self->root->{filter_store_key}->($_[1]) : $_[1];
- my $prev_md5 = $DIGEST_FUNC->($prev_key);
-
- ##
- # Make sure file is open
- ##
- if (!defined($self->fh)) { $self->_open(); }
-
- ##
- # Request shared lock for reading
- ##
- $self->lock( LOCK_SH );
-
- my $result = $self->_get_next_key( $prev_md5 );
-
- $self->unlock();
-
- return ($result && $self->root->{filter_fetch_key}) ? $self->root->{filter_fetch_key}->($result) : $result;
-}
-
##
-# The following methods are for arrays only
+# Public method aliases
##
+sub put { (shift)->STORE( @_ ) }
+sub store { (shift)->STORE( @_ ) }
+sub get { (shift)->FETCH( @_ ) }
+sub fetch { (shift)->FETCH( @_ ) }
+sub delete { (shift)->DELETE( @_ ) }
+sub exists { (shift)->EXISTS( @_ ) }
+sub clear { (shift)->CLEAR( @_ ) }
-sub FETCHSIZE {
- ##
- # Return the length of the array
- ##
- my $self = _get_self($_[0]);
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("FETCHSIZE method only supported for arrays");
- }
-
- my $SAVE_FILTER = $self->root->{filter_fetch_value};
- $self->root->{filter_fetch_value} = undef;
-
- my $packed_size = $self->FETCH('length');
-
- $self->root->{filter_fetch_value} = $SAVE_FILTER;
-
- if ($packed_size) { return int(unpack($LONG_PACK, $packed_size)); }
- else { return 0; }
-}
+package DBM::Deep::_::Root;
-sub STORESIZE {
- ##
- # Set the length of the array
- ##
- my $self = _get_self($_[0]);
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("STORESIZE method only supported for arrays");
- }
- my $new_length = $_[1];
-
- my $SAVE_FILTER = $self->root->{filter_store_value};
- $self->root->{filter_store_value} = undef;
-
- my $result = $self->STORE('length', pack($LONG_PACK, $new_length));
-
- $self->root->{filter_store_value} = $SAVE_FILTER;
-
- return $result;
-}
-
-sub POP {
- ##
- # Remove and return the last element on the array
- ##
- my $self = _get_self($_[0]);
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("POP method only supported for arrays");
- }
- my $length = $self->FETCHSIZE();
-
- if ($length) {
- my $content = $self->FETCH( $length - 1 );
- $self->DELETE( $length - 1 );
- return $content;
- }
- else {
- return;
- }
-}
-
-sub PUSH {
- ##
- # Add new element(s) to the end of the array
- ##
- my $self = _get_self(shift);
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("PUSH method only supported for arrays");
- }
- my $length = $self->FETCHSIZE();
-
- while (my $content = shift @_) {
- $self->STORE( $length, $content );
- $length++;
- }
+sub new {
+ my $class = shift;
+ my ($args) = @_;
+
+ my $self = bless {
+ file => undef,
+ fh => undef,
+ end => 0,
+ autoflush => undef,
+ locking => undef,
+ volatile => undef,
+ debug => undef,
+ filter_store_key => undef,
+ filter_store_value => undef,
+ filter_fetch_key => undef,
+ filter_fetch_value => undef,
+ autobless => undef,
+ locked => 0,
+ %$args,
+ }, $class;
+
+ return $self;
}
-sub SHIFT {
- ##
- # Remove and return first element on the array.
- # Shift over remaining elements to take up space.
- ##
- my $self = _get_self($_[0]);
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("SHIFT method only supported for arrays");
- }
- my $length = $self->FETCHSIZE();
-
- if ($length) {
- my $content = $self->FETCH( 0 );
-
- ##
- # Shift elements over and remove last one.
- ##
- for (my $i = 0; $i < $length - 1; $i++) {
- $self->STORE( $i, $self->FETCH($i + 1) );
- }
- $self->DELETE( $length - 1 );
-
- return $content;
- }
- else {
- return;
- }
-}
+sub DESTROY {
+ my $self = shift;
+ return unless $self;
-sub UNSHIFT {
- ##
- # Insert new element(s) at beginning of array.
- # Shift over other elements to make space.
- ##
- my $self = _get_self($_[0]);shift @_;
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("UNSHIFT method only supported for arrays");
- }
- my @new_elements = @_;
- my $length = $self->FETCHSIZE();
- my $new_size = scalar @new_elements;
-
- if ($length) {
- for (my $i = $length - 1; $i >= 0; $i--) {
- $self->STORE( $i + $new_size, $self->FETCH($i) );
- }
- }
-
- for (my $i = 0; $i < $new_size; $i++) {
- $self->STORE( $i, $new_elements[$i] );
- }
-}
+ close $self->{fh} if $self->{fh};
-sub SPLICE {
- ##
- # Splices section of array with optional new section.
- # Returns deleted section, or last element deleted in scalar context.
- ##
- my $self = _get_self($_[0]);shift @_;
- if ($self->type ne TYPE_ARRAY) {
- return $self->_throw_error("SPLICE method only supported for arrays");
- }
- my $length = $self->FETCHSIZE();
-
- ##
- # Calculate offset and length of splice
- ##
- my $offset = shift || 0;
- if ($offset < 0) { $offset += $length; }
-
- my $splice_length;
- if (scalar @_) { $splice_length = shift; }
- else { $splice_length = $length - $offset; }
- if ($splice_length < 0) { $splice_length += ($length - $offset); }
-
- ##
- # Setup array with new elements, and copy out old elements for return
- ##
- my @new_elements = @_;
- my $new_size = scalar @new_elements;
-
- my @old_elements = ();
- for (my $i = $offset; $i < $offset + $splice_length; $i++) {
- push @old_elements, $self->FETCH( $i );
- }
-
- ##
- # Adjust array length, and shift elements to accomodate new section.
- ##
- if ( $new_size != $splice_length ) {
- if ($new_size > $splice_length) {
- for (my $i = $length - 1; $i >= $offset + $splice_length; $i--) {
- $self->STORE( $i + ($new_size - $splice_length), $self->FETCH($i) );
- }
- }
- else {
- for (my $i = $offset + $splice_length; $i < $length; $i++) {
- $self->STORE( $i + ($new_size - $splice_length), $self->FETCH($i) );
- }
- for (my $i = 0; $i < $splice_length - $new_size; $i++) {
- $self->DELETE( $length - 1 );
- $length--;
- }
- }
- }
-
- ##
- # Insert new elements into array
- ##
- for (my $i = $offset; $i < $offset + $new_size; $i++) {
- $self->STORE( $i, shift @new_elements );
- }
-
- ##
- # Return deleted section, or last element in scalar context.
- ##
- return wantarray ? @old_elements : $old_elements[-1];
+ return;
}
-#XXX We don't need to define it.
-#XXX It will be useful, though, when we split out HASH and ARRAY
-#sub EXTEND {
- ##
- # Perl will call EXTEND() when the array is likely to grow.
- # We don't care, but include it for compatibility.
- ##
-#}
-
-##
-# Public method aliases
-##
-*put = *store = *STORE;
-*get = *fetch = *FETCH;
-*delete = *DELETE;
-*exists = *EXISTS;
-*clear = *CLEAR;
-*first_key = *FIRSTKEY;
-*next_key = *NEXTKEY;
-*length = *FETCHSIZE;
-*pop = *POP;
-*push = *PUSH;
-*shift = *SHIFT;
-*unshift = *UNSHIFT;
-*splice = *SPLICE;
-
1;
__END__
$db->{key} = 'value'; # tie() style
print $db->{key};
- $db->put('key', 'value'); # OO style
+ $db->put('key' => 'value'); # OO style
print $db->get('key');
# true multi-level support
$db->{my_complex} = [
'hello', { perl => 'rules' },
- 42, 99 ];
+ 42, 99,
+ ];
=head1 DESCRIPTION
=head1 INSTALLATION
-Hopefully you are using CPAN's excellent Perl module, which will download
+Hopefully you are using Perl's excellent CPAN module, which will download
and install the module for you. If not, get the tarball, and run these
commands:
opened in "r+" (read/write) mode, and the type of object returned is a
hash, unless otherwise specified (see L<OPTIONS> below).
-
-
You can pass a number of options to the constructor to specify things like
locking, autoflush, etc. This is done by passing an inline hash:
B<Note:> Specifing the C<type> parameter only takes effect when beginning
a new DB file. If you create a DBM::Deep object with an existing file, the
-C<type> will be loaded from the file header, and ignored if it is passed
-to the constructor.
+C<type> will be loaded from the file header, and an error will be thrown if
+the wrong type is passed in.
=head2 TIE CONSTRUCTION
-Alternatively, you can create a DBM::Deep handle by using Perl's built-in
-tie() function. This is not ideal, because you get only a basic, tied hash
-(or array) which is not blessed, so you can't call any functions on it.
+Alternately, you can create a DBM::Deep handle by using Perl's built-in
+tie() function. The object returned from tie() can be used to call methods,
+such as lock() and unlock(), but cannot be used to assign to the DBM::Deep
+file (as expected with most tie'd objects).
my %hash;
- tie %hash, "DBM::Deep", "foo.db";
+ my $db = tie %hash, "DBM::Deep", "foo.db";
my @array;
- tie @array, "DBM::Deep", "bar.db";
+ my $db = tie @array, "DBM::Deep", "bar.db";
As with the OO constructor, you can replace the DB filename parameter with
a hash containing one or more options (see L<OPTIONS> just below for the
filesystem path, partial path, or a plain filename if the file is in the
current working directory. This is a required parameter.
-=item * mode
-
-File open mode (read-only, read-write, etc.) string passed to Perl's FileHandle
-module. This is an optional parameter, and defaults to "r+" (read/write).
-B<Note:> If the default (r+) mode is selected, the file will also be auto-
-created if it doesn't exist.
-
=item * type
This parameter specifies what type of object to create, a hash or array. Use
=item * autoflush
-Specifies whether autoflush is to be enabled on the underlying FileHandle.
+Specifies whether autoflush is to be enabled on the underlying filehandle.
This obviously slows down write operations, but is required if you may have
multiple processes accessing the same DB file (also consider enable I<locking>
or at least I<volatile>). Pass any true value to enable. This is an optional
contains your entire Perl script, as well as the data following the __DATA__
marker. This will not work, because DBM::Deep uses absolute seek()s into the
file. Instead, consider reading *DATA into an IO::Scalar handle, then passing
-in that.
+in that. Also please note optimize() will NOT work when passing in only a
+handle. Pass in a real filename in order to use optimize().
=back
=head1 TIE INTERFACE
With DBM::Deep you can access your databases using Perl's standard hash/array
-syntax. Because all DBM::Deep objects are I<tied> to hashes or arrays, you can treat
-them as such. DBM::Deep will intercept all reads/writes and direct them to the right
-place -- the DB file. This has nothing to do with the L<TIE CONSTRUCTION>
-section above. This simply tells you how to use DBM::Deep using regular hashes
-and arrays, rather than calling functions like C<get()> and C<put()> (although those
-work too). It is entirely up to you how to want to access your databases.
+syntax. Because all DBM::Deep objects are I<tied> to hashes or arrays, you can
+treat them as such. DBM::Deep will intercept all reads/writes and direct them
+to the right place -- the DB file. This has nothing to do with the
+L<TIE CONSTRUCTION> section above. This simply tells you how to use DBM::Deep
+using regular hashes and arrays, rather than calling functions like C<get()>
+and C<put()> (although those work too). It is entirely up to you how to want
+to access your databases.
=head2 HASHES
=over
-=item * put()
+=item * put() / store()
Stores a new hash key/value pair, or sets an array element value. Takes two
arguments, the hash key or array index, and the new value. The value can be
$db->put("foo", "bar"); # for hashes
$db->put(1, "bar"); # for arrays
-=item * get()
+=item * get() / fetch()
Fetches the value of a hash key or array element. Takes one argument: the hash
key or array index. Returns a scalar, hash ref or array ref, depending on the
locking => 1
);
-This causes DBM::Deep to C<flock()> the underlying FileHandle object with exclusive
+This causes DBM::Deep to C<flock()> the underlying filehandle with exclusive
mode for writes, and shared mode for reads. This is required if you have
multiple processes accessing the same database file, to avoid file corruption.
Please note that C<flock()> does NOT work for files over NFS. See L<DB OVER
You can explicitly lock a database, so it remains locked for multiple
transactions. This is done by calling the C<lock()> method, and passing an
-optional lock mode argument (defaults to exclusive mode). This is particularly
+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.
The C<import()> method can be called on any database level (not just the base
level), and works with both hash and array DB types.
-
-
B<Note:> Make sure your existing structure has no circular references in it.
These will cause an infinite loop when importing.
large databases -- you can store a lot more data in a DBM::Deep object than an
in-memory Perl structure.
-
-
B<Note:> Make sure your database has no circular references in it.
These will cause an infinite loop when exporting.
my $db = DBM::Deep->new( "foo.db" ); # create hash
eval { $db->push("foo"); }; # ILLEGAL -- push is array-only call
+ print $@; # prints error message
print $db->error(); # prints error message
You can then call C<clear_error()> to clear the current error state.
$db->clear_error();
If you set the C<debug> option to true when creating your DBM::Deep object,
-all errors are considered NON-FATAL, and dumped to STDERR. This is only
-for debugging purposes.
+all errors are considered NON-FATAL, and dumped to STDERR. This should only
+be used for debugging purposes and not production work. DBM::Deep expects errors
+to be thrown, not propagated back up the stack.
=head1 LARGEFILE SUPPORT
instead of 32-bit longs. After setting these values your DB files have a
theoretical maximum size of 16 XB (exabytes).
-
-
B<Note:> Changing these values will B<NOT> work for existing database files.
Only change this for new files, and make sure it stays set consistently
throughout the file's life. If you do set these values, you can no longer
access 32-bit DB files. You can, however, call C<set_pack(4, 'N')> to change
back to 32-bit mode.
-
-
B<Note:> I have not personally tested files > 2 GB -- all my systems have
only a 32-bit Perl. However, I have received user reports that this does
indeed work!
=head1 LOW-LEVEL ACCESS
-If you require low-level access to the underlying FileHandle that DBM::Deep uses,
+If you require low-level access to the underlying filehandle that DBM::Deep uses,
you can call the C<fh()> method, which returns the handle:
my $fh = $db->fh();
This method can be called on the root level of the datbase, or any child
hashes or arrays. All levels share a I<root> structure, which contains things
-like the FileHandle, a reference counter, and all your options you specified
+like the filehandle, a reference counter, and all the options specified
when you created the object. You can get access to this root structure by
calling the C<root()> method.
operation completes. Oh, and if locking is enabled, the DB is automatically
locked for the entire duration of the copy.
-
-
B<WARNING:> Only call optimize() on the top-level node of the database, and
make sure there are no child references lying around. DBM::Deep keeps a reference
counter, and if it is greater than 1, optimize() will abort and return undef.
Beware of using DB files over NFS. DBM::Deep uses flock(), which works well on local
filesystems, but will NOT protect you from file corruption over NFS. I've heard
about setting up your NFS server with a locking daemon, then using lockf() to
-lock your files, but your milage may vary there as well. From what I
+lock your files, but your mileage may vary there as well. From what I
understand, there is no real way to do it. However, if you need access to the
-underlying FileHandle in DBM::Deep for using some other kind of locking scheme like
+underlying filehandle in DBM::Deep for using some other kind of locking scheme like
lockf(), see the L<LOW-LEVEL ACCESS> section above.
=head2 COPYING OBJECTS
my $copy = $db->clone();
+B<Note>: Since clone() here is cloning the object, not the database location, any
+modifications to either $db or $copy will be visible in both.
+
=head2 LARGE ARRAYS
Beware of using C<shift()>, C<unshift()> or C<splice()> with large arrays.
These functions cause every element in the array to move, which can be murder
on DBM::Deep, as every element has to be fetched from disk, then stored again in
-a different location. This may be addressed in a later version.
+a different location. This will be addressed in the forthcoming version 1.00.
=head1 PERFORMANCE
DBM::Deep files always start with a 32-bit signature to identify the file type.
This is at offset 0. The signature is "DPDB" in network byte order. This is
-checked when the file is opened.
+checked for when the file is opened and an error will be thrown if it's not found.
=head2 TAG
bytes (in 64-bit mode) of data. The type is I<H> for hash or I<A> for array,
depending on how the DBM::Deep object was constructed.
-
-
The index works by looking at a I<MD5 Hash> of the hash key (or array index
number). The first 8-bit char of the MD5 signature is the offset into the
index, multipled by 4 in 32-bit mode, or 8 in 64-bit mode. The value of the
index element is a file offset of the next tag for the key/element in question,
which is usually a I<Bucket List> tag (see below).
-
-
The next tag I<could> be another index, depending on how many keys/elements
exist. See L<RE-INDEXING> below for details.
key itself. Since the value is likely to be fetched more often than the plain
key, I figured it would be I<slightly> faster to store the value first.
-
-
If the type is I<H> (hash) or I<A> (array), the value is another I<Master Index>
record for the nested structure, where the process begins all over again.
process, as a new MD5 char from the key is being examined (it is unlikely that
the keys will all share the same next char of their MD5s).
-
-
Because of the way the I<MD5> algorithm works, it is impossible to tell exactly
when the Bucket Lists will turn into indexes, but the first round tends to
happen right around 4,000 keys. You will see a I<slight> decrease in
=head2 STORING
-When a new key/element is stored, the key (or index number) is first ran through
+When a new key/element is stored, the key (or index number) is first run through
I<Digest::MD5> to get a 128-bit signature (example, in hex:
b05783b0773d894396d475ced9d2f4f6). Then, the I<Master Index> record is checked
for the first char of the signature (in this case I<b>). If it does not exist,
question. If we found a match, the I<Bucket> tag is loaded, where the value and
plain key are stored.
-
-
Fetching the plain key occurs when calling the I<first_key()> and I<next_key()>
methods. In this process the indexes are walked systematically, and each key
fetched in increasing MD5 order (which is why it appears random). Once the
=head1 CODE COVERAGE
-I use B<Devel::Cover> to test the code coverage of my tests, below is the B<Devel::Cover> report on this
-module's test suite.
+We use B<Devel::Cover> to test the code coverage of my tests, below is the
+B<Devel::Cover> report on this module's test suite.
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
- File stmt bran cond sub pod time total
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
- blib/lib/DBM/Deep.pm 94.9 84.5 77.8 100.0 11.1 100.0 89.7
- Total 94.9 84.5 77.8 100.0 11.1 100.0 89.7
- ---------------------------- ------ ------ ------ ------ ------ ------ ------
+---------------------------- ------ ------ ------ ------ ------ ------ ------
+File stmt bran cond sub pod time total
+---------------------------- ------ ------ ------ ------ ------ ------ ------
+blib/lib/DBM/Deep.pm 93.7 82.5 71.9 96.5 25.9 82.8 87.9
+blib/lib/DBM/Deep/Array.pm 98.8 88.0 90.9 100.0 n/a 12.8 96.3
+blib/lib/DBM/Deep/Hash.pm 95.2 80.0 100.0 100.0 n/a 4.4 92.3
+Total 94.8 83.2 76.5 97.6 25.9 100.0 89.7
+---------------------------- ------ ------ ------ ------ ------ ------ ------
-=head1 AUTHOR
+=head1 AUTHORS
Joseph Huckaby, L<jhuckaby@cpan.org>
+Rob Kinyon, L<rkinyon@cpan.org>
Special thanks to Adam Sah and Rich Gaushell! You know why :-)
=head1 LICENSE
-Copyright (c) 2002-2005 Joseph Huckaby. All Rights Reserved.
+Copyright (c) 2002-2006 Joseph Huckaby. All Rights Reserved.
This is free software, you may use it and distribute it under the
same terms as Perl itself.