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";
#XXX Does this need to be updated with different hashing algorithms?
$self->{index_size} = (2**8) * $self->{long_size};
-#ACID This needs modified - DONE
$self->{bucket_size} = $self->{hash_size} + $self->{long_size} * 3;
$self->{bucket_list_size} = $self->{max_buckets} * $self->{bucket_size};
sub write_file_header {
my $self = shift;
+ local($/,$\);
+
my $fh = $self->_fh;
my $loc = $self->_request_space( length( SIG_FILE ) + 21 );
SIG_HEADER,
pack('N', 1), # header version
pack('N', 12), # header size
- pack('N', 0), # file version
- pack('S', $self->{long_size}),
+ pack('N', 0), # currently running transaction IDs
+ 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;
}
sub read_file_header {
my $self = shift;
+ local($/,$\);
+
my $fh = $self->_fh;
seek($fh, 0 + $self->_fileobj->{file_offset}, SEEK_SET);
);
unless ( $file_signature eq SIG_FILE ) {
- $self->{fileobj}->close;
+ $self->_fileobj->close;
$self->_throw_error( "Signature not found -- file is not a Deep DB" );
}
unless ( $sig_header eq SIG_HEADER ) {
- $self->{fileobj}->close;
+ $self->_fileobj->close;
$self->_throw_error( "Old file version found." );
}
my $buffer2;
$bytes_read += read( $fh, $buffer2, $size );
- my ($file_version, @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 );
+
if ( @values < 5 || grep { !defined } @values ) {
- $self->{fileobj}->close;
+ $self->_fileobj->close;
$self->_throw_error("Corrupted file - bad header");
}
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");
}
}
my ($offset, $sig, $content) = @_;
my $size = length( $content );
+ local($/,$\);
+
my $fh = $self->_fh;
if ( defined $offset ) {
my $self = shift;
my ($offset) = @_;
+ local($/,$\);
+
# print join(':',map{$_||''}caller(1)), $/;
my $fh = $self->_fh;
# 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($/,$\);
# This verifies that only supported values will be stored.
{
my $actual_length = $self->_length_needed( $value, $plain_key );
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
+ #ACID - This is a mutation. Must only find the exact transaction
+ 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;
+ }
- print "$subloc - $offset - $size\n";
# $self->_release_space( $size, $subloc );
# Updating a known md5
#XXX This needs updating to use _release_space
);
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($/,$\);
my $fh = $self->_fh;
my $root = $self->_fileobj;
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 $self = shift;
my ($md5, $tag) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
my $root = $self->_fileobj;
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($/,$\);
my $fh = $self->_fh;
##
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) = @_;
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
- if ( $subloc ) {
- return $self->read_from_loc( $subloc );
+ #ACID - This is a read. Can find exact or HEAD
+ 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) = @_;
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
+ local($/,$\);
+
+ #ACID - This is a mutation. Must only find the exact transaction
+ my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5, 1 );
#XXX This needs _release_space()
if ( $subloc ) {
my $fh = $self->_fh;
my $self = shift;
my ($tag, $md5) = @_;
- my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
- return $subloc && 1;
+ #ACID - This is a read. Can find exact or HEAD
+ my ($subloc, $offset, $size, $is_deleted) = $self->_find_in_buckets( $tag, $md5 );
+ return ($subloc && !$is_deleted) && 1;
}
sub find_bucket_list {
my ($offset, $md5, $args) = @_;
$args = {} unless $args;
+ local($/,$\);
+
##
# Locate offset for bucket list using digest index system
##
my $self = shift;
my ($obj, $offset, $ch, $force_return_next) = @_;
+ local($/,$\);
+
my $tag = $self->load_tag( $offset );
my $fh = $self->_fh;
# Utilities
-#ACID This needs modified - DONE
sub _get_key_subloc {
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 {
my $self = shift;
- my ($tag, $md5) = @_;
+ my ($tag, $md5, $exact) = @_;
my $trans_id = $self->_fileobj->transaction_id;
+ my @zero;
+
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,
);
- return ($subloc, $i * $self->{bucket_size}, $size) unless $subloc;
+ my @rv = ($subloc, $i * $self->{bucket_size}, $size, $is_deleted);
+
+ unless ( $subloc ) {
+ 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;
- next BUCKET if $key ne $md5 || $transaction_id != $trans_id;
+ # Save off the HEAD in case we need it.
+ @zero = ($i,$key,$subloc,$size,$transaction_id,$is_deleted) if $transaction_id == 0;
- return ($subloc, $i * $self->{bucket_size}, $size);
+ next BUCKET if $transaction_id != $trans_id;
+
+ return @rv;
}
return;
my $self = shift;
my ($size, $loc) = @_;
+ local($/,$\);
+
my $next_loc = 0;
my $fh = $self->_fh;
my $self = shift;
my ($spot, $amount, $unpack) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
seek( $fh, $spot + $self->_fileobj->{file_offset}, SEEK_SET );
my $self = shift;
my ($spot, $data) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
seek( $fh, $spot, SEEK_SET );
print( $fh $data );
sub get_file_version {
my $self = shift;
+ local($/,$\);
+
my $fh = $self->_fh;
seek( $fh, 13 + $self->_fileobj->{file_offset}, SEEK_SET );
my $self = shift;
my ($new_version) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
seek( $fh, 13 + $self->_fileobj->{file_offset}, SEEK_SET );