use strict;
-use Fcntl qw(:DEFAULT :flock :seek);
+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.96);
##
# Set to 4 and 'N' for 32-bit offset tags (default). Theoretical limit of 4 GB per file.
##
# 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.
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),
- };
-
- bless $self, $class;
-
- foreach my $outer_parm ( @outer_params ) {
- next unless exists $args->{$outer_parm};
- $self->{$outer_parm} = delete $args->{$outer_parm}
- }
-
- $self->{root} = exists $args->{root}
- ? $args->{root}
- : DBM::Deep::_::Root->new( $args );
+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 );
+
+ if (!defined($self->fh)) { $self->_open(); }
-sub _get_self {
- tied( %{$_[0]} ) || $_[0]
+ return $self;
}
sub TIEHASH {
#sub DESTROY {
#}
-my %translate_mode = (
- 'r' => '<',
- 'r+' => '+<',
- 'w' => '>',
- 'w+' => '+>',
- 'a' => '>>',
- 'a+' => '+>>',
-);
sub _open {
##
# Open a FileHandle to the database, create if nonexistent.
# Make sure file signature matches DeepDB spec.
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
if (defined($self->fh)) { $self->_close(); }
eval {
- my $filename = $self->root->{file};
+ # 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;
+
#XXX Can the mode be anything but r+, w+, or a+??
#XXX ie, it has to be in read-write mode
- my $mode = $translate_mode{ $self->root->{mode} };
+ #XXX So, should we verify that the mode is legitimate?
- if (!(-e $filename) && $mode eq '+<') {
- sysopen( FH, $filename, O_CREAT | O_WRONLY, 0666 );
- close FH;
+ #XXX Maybe the mode thingy should just go away. There's no good
+ #XXX reason for it ...
+ if ( $self->root->{mode} eq 'w+' ) {
+ $flags |= O_TRUNC;
}
my $fh;
- sysopen( $fh, $filename, O_RDWR )
+ sysopen( $fh, $self->root->{file}, $flags )
or $fh = undef;
$self->root->{fh} = $fh;
}; if ($@ ) { $self->_throw_error( "Received error: $@\n" ); }
my $fh = $self->fh;
#XXX Can we remove this by using the right sysopen() flags?
- #XXX I don't think so - there's an item in fopen(3) about rb+, but I'm not sure
- #XXX That will work.
+ # Maybe ... q.v. above
binmode $fh; # for win32
if ($self->root->{autoflush}) {
select $old;
}
- my $signature;
+ # Set the
seek($fh, 0, SEEK_SET);
+
+ my $signature;
my $bytes_read = read( $fh, $signature, length(SIG_FILE));
##
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");
##
# Close database FileHandle
##
- my $self = _get_self($_[0]);
+ my $self = $_[0]->_get_self;
close $self->root->{fh};
+ $self->root->{fh} = undef;
}
sub _create_tag {
##
# 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); }
$self->root->{locked}++;
# 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}--;
# 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]);
+ my $self = $_[0]->_get_self;
#XXX Need to create a new test for this
# if ($self->root->{links} > 1) {
##
# 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};
}
# Get access to the raw FileHandle
##
#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];
+ my $self = $_[0]->_get_self;
+ my $key = $_[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 $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);
##
##
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;
+ 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); }
##
# Delete bucket
##
+ my $value = $self->FETCH( $unpacked_key );
my $result = $self->_delete_bucket( $tag, $md5 );
##
$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
##
# Public method aliases
##
-*put = *store = *STORE;
-*get = *fetch = *FETCH;
-*delete = *DELETE;
-*exists = *EXISTS;
-*clear = *CLEAR;
+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( @_ ) }
package DBM::Deep::_::Root;
---------------------------- ------ ------ ------ ------ ------ ------ ------
File stmt bran cond sub pod time total
---------------------------- ------ ------ ------ ------ ------ ------ ------
- blib/lib/DBM/Deep.pm 94.1 82.9 74.5 98.0 10.5 98.1 88.2
- blib/lib/DBM/Deep/Array.pm 97.8 83.3 50.0 100.0 n/a 1.6 94.4
- blib/lib/DBM/Deep/Hash.pm 93.3 85.7 100.0 100.0 n/a 0.3 92.7
- Total 94.5 83.1 75.5 98.4 10.5 100.0 89.0
+ blib/lib/DBM/Deep.pm 93.9 82.4 74.7 97.9 10.5 85.7 88.0
+ blib/lib/DBM/Deep/Array.pm 97.8 84.6 50.0 100.0 n/a 9.0 94.6
+ blib/lib/DBM/Deep/Hash.pm 93.9 87.5 100.0 100.0 n/a 5.3 93.4
+ Total 94.4 82.9 75.8 98.5 10.5 100.0 89.0
---------------------------- ------ ------ ------ ------ ------ ------ ------
=head1 AUTHOR