use warnings;
use Fcntl qw( :DEFAULT :flock :seek );
+use Scalar::Util ();
# File-wide notes:
# * All the local($/,$\); are to protect read() and print() from -l.
+# * To add to bucket_size, make sure you modify the following:
+# - calculate_sizes()
+# - _get_key_subloc()
+# - add_bucket() - where the buckets are printed
##
# Setup file and tag signatures. These should never change.
max_buckets => 16,
fileobj => undef,
+ obj => undef,
}, $class;
if ( defined $args->{pack_size} ) {
if ( lc $args->{pack_size} eq 'small' ) {
$args->{long_size} = 2;
- $args->{long_pack} = 'S';
+ $args->{long_pack} = 'n';
}
elsif ( lc $args->{pack_size} eq 'medium' ) {
$args->{long_size} = 4;
next unless exists $args->{$param};
$self->{$param} = $args->{$param};
}
+ Scalar::Util::weaken( $self->{obj} ) if $self->{obj};
if ( $self->{max_buckets} < 16 ) {
warn "Floor of max_buckets is 16. Setting it to 16 from '$self->{max_buckets}'\n";
pack('N', 1), # header version
pack('N', 12), # header size
pack('N', 0), # currently running transaction IDs
- pack('S', $self->{long_size}),
+ pack('n', $self->{long_size}),
pack('A', $self->{long_pack}),
- pack('S', $self->{data_size}),
+ pack('n', $self->{data_size}),
pack('A', $self->{data_pack}),
- pack('S', $self->{max_buckets}),
+ pack('n', $self->{max_buckets}),
);
+ $self->_fileobj->set_transaction_offset( 13 );
+
return;
}
my $buffer2;
$bytes_read += read( $fh, $buffer2, $size );
- my ($running_transactions, @values) = unpack( 'N S A S A S', $buffer2 );
+ my ($running_transactions, @values) = unpack( 'N n A n A n', $buffer2 );
$self->_fileobj->set_transaction_offset( 13 );
my $self = shift;
my ($obj) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
flock $fh, LOCK_EX;
# File is empty -- write header and master index
##
if (!$bytes_read) {
+ $self->_fileobj->audit( "# Database created on" );
+
$self->write_file_header;
$obj->{base_offset} = $self->_request_space( $self->tag_size( $self->{index_size} ) );
##
# Get our type from master index header
##
- my $tag = $self->load_tag($obj->_base_offset)
- or $self->_throw_error("Corrupted file, no master index record");
+ my $tag = $self->load_tag($obj->_base_offset);
+ unless ( $tag ) {
+ flock $fh, LOCK_UN;
+ $self->_throw_error("Corrupted file, no master index record");
+ }
unless ($obj->_type eq $tag->{signature}) {
+ flock $fh, LOCK_UN;
$self->_throw_error("File type mismatch");
}
}
# plain (undigested) key and value.
##
my $self = shift;
- my ($tag, $md5, $plain_key, $value) = @_;
+ my ($tag, $md5, $plain_key, $value, $deleted, $orig_key) = @_;
+ $deleted ||= 0;
local($/,$\);
my $actual_length = $self->_length_needed( $value, $plain_key );
#ACID - This is a mutation. Must only find the exact transaction
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5, 1 );
+ my ($subloc, $offset, $size,$is_deleted) = $self->_find_in_buckets( $tag, $md5, 1 );
+
+ my @transactions;
+ if ( $self->_fileobj->transaction_id == 0 ) {
+ @transactions = $self->_fileobj->current_transactions;
+ }
# $self->_release_space( $size, $subloc );
# Updating a known md5
);
print( $fh pack($self->{long_pack}, $location ) );
print( $fh pack($self->{long_pack}, $actual_length ) );
- print( $fh pack($self->{long_pack}, $root->transaction_id ) );
+ print( $fh pack('n n', $root->transaction_id, $deleted ) );
}
}
# Adding a new md5
seek( $fh, $tag->{offset} + $offset + $root->{file_offset}, SEEK_SET );
print( $fh $md5 . pack($self->{long_pack}, $location ) );
print( $fh pack($self->{long_pack}, $actual_length ) );
- print( $fh pack($self->{long_pack}, $root->transaction_id ) );
+ print( $fh pack('n n', $root->transaction_id, $deleted ) );
+
+ for ( @transactions ) {
+ my $tag2 = $self->load_tag( $tag->{offset} - SIG_SIZE - $self->{data_size} );
+ $self->_fileobj->{transaction_id} = $_;
+ $self->add_bucket( $tag2, $md5, '', '', 1, $orig_key );
+ $self->_fileobj->{transaction_id} = 0;
+ }
}
# If bucket didn't fit into list, split into a new index level
# split_index() will do the _request_space() call
$location = $self->split_index( $md5, $tag );
}
- $self->write_value( $location, $plain_key, $value );
+ $self->write_value( $location, $plain_key, $value, $orig_key );
return $result;
}
sub write_value {
my $self = shift;
- my ($location, $key, $value) = @_;
+ my ($location, $key, $value, $orig_key) = @_;
local($/,$\);
tie %$value, 'DBM::Deep', {
base_offset => $location,
fileobj => $root,
+ parent => $self->{obj},
+ parent_key => $orig_key,
};
%$value = %x;
}
tie @$value, 'DBM::Deep', {
base_offset => $location,
fileobj => $root,
+ parent => $self->{obj},
+ parent_key => $orig_key,
};
@$value = @x;
}
my $keys = $tag->{content}
. $md5 . pack($self->{long_pack}, $newtag_loc)
. pack($self->{long_pack}, 0) # size
- . pack($self->{long_pack}, 0); # transaction #
+ . pack($self->{long_pack}, 0); # transaction ID
my @newloc = ();
BUCKET:
sub read_from_loc {
my $self = shift;
- my ($subloc) = @_;
+ my ($subloc, $orig_key) = @_;
local($/,$\);
##
if (($signature eq SIG_HASH) || ($signature eq SIG_ARRAY)) {
my $new_obj = DBM::Deep->new({
- type => $signature,
+ type => $signature,
base_offset => $subloc,
fileobj => $self->_fileobj,
+ parent => $self->{obj},
+ parent_key => $orig_key,
});
if ($new_obj->_fileobj->{autobless}) {
read( $fh, $new_loc, $size );
$new_loc = unpack( $self->{long_pack}, $new_loc );
- return $self->read_from_loc( $new_loc );
+ return $self->read_from_loc( $new_loc, $orig_key );
}
else {
return;
# Fetch single value given tag and MD5 digested key.
##
my $self = shift;
- my ($tag, $md5) = @_;
+ my ($tag, $md5, $orig_key) = @_;
#ACID - This is a read. Can find exact or HEAD
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
- if ( $subloc ) {
- return $self->read_from_loc( $subloc );
+ my ($subloc, $offset, $size,$is_deleted) = $self->_find_in_buckets( $tag, $md5 );
+ if ( $subloc && !$is_deleted ) {
+ return $self->read_from_loc( $subloc, $orig_key );
}
return;
}
# Delete single key/value pair given tag and MD5 digested key.
##
my $self = shift;
- my ($tag, $md5) = @_;
+ my ($tag, $md5, $orig_key) = @_;
local($/,$\);
my ($tag, $md5) = @_;
#ACID - This is a read. Can find exact or HEAD
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
- return $subloc && 1;
+ my ($subloc, $offset, $size, $is_deleted) = $self->_find_in_buckets( $tag, $md5 );
+ return ($subloc && !$is_deleted) && 1;
}
sub find_bucket_list {
my $self = shift;
my ($keys, $idx) = @_;
- my ($key, $subloc, $size, $transaction) = unpack(
+ my ($key, $subloc, $size, $transaction_id, $is_deleted) = unpack(
# This is 'a', not 'A'. Please read the pack() documentation for the
# difference between the two and why it's important.
- "a$self->{hash_size} $self->{long_pack} $self->{long_pack} $self->{long_pack}",
+ "a$self->{hash_size} $self->{long_pack}2 n2",
substr(
$keys,
($idx * $self->{bucket_size}),
),
);
- return ($key, $subloc, $size, $transaction);
+ return ($key, $subloc, $size, $transaction_id, $is_deleted);
}
sub _find_in_buckets {
BUCKET:
for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
- my ($key, $subloc, $size, $transaction_id) = $self->_get_key_subloc(
+ my ($key, $subloc, $size, $transaction_id, $is_deleted) = $self->_get_key_subloc(
$tag->{content}, $i,
);
- my @rv = ($subloc, $i * $self->{bucket_size}, $size);
+ my @rv = ($subloc, $i * $self->{bucket_size}, $size, $is_deleted);
unless ( $subloc ) {
- return @zero if !$exact && @zero and $trans_id;
+ if ( !$exact && @zero and $trans_id ) {
+ @rv = ($zero[2], $zero[0] * $self->{bucket_size},$zero[3],$is_deleted);
+ }
return @rv;
}
next BUCKET if $key ne $md5;
# Save off the HEAD in case we need it.
- @zero = @rv if $transaction_id == 0;
+ @zero = ($i,$key,$subloc,$size,$transaction_id,$is_deleted) if $transaction_id == 0;
next BUCKET if $transaction_id != $trans_id;