use Fcntl qw( :DEFAULT :flock :seek );
+# File-wide notes:
+# * All the local($/,$\); are to protect read() and print() from -l.
+
##
# Setup file and tag signatures. These should never change.
##
#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 );
sub read_file_header {
my $self = shift;
+ local($/,$\);
+
my $fh = $self->_fh;
seek($fh, 0 + $self->_fileobj->{file_offset}, SEEK_SET);
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;
my $self = shift;
my ($tag, $md5, $plain_key, $value) = @_;
+ local($/,$\);
+
# This verifies that only supported values will be stored.
{
my $r = Scalar::Util::reftype( $value );
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) = $self->_find_in_buckets( $tag, $md5, 1 );
- print "$subloc - $offset - $size\n";
# $self->_release_space( $size, $subloc );
# Updating a known md5
#XXX This needs updating to use _release_space
my $self = shift;
my ($location, $key, $value) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
my $root = $self->_fileobj;
my $self = shift;
my ($md5, $tag) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
my $root = $self->_fileobj;
my $self = shift;
my ($subloc) = @_;
+ local($/,$\);
+
my $fh = $self->_fh;
##
my $self = shift;
my ($tag, $md5) = @_;
+ #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 $self = shift;
my ($tag, $md5) = @_;
- 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) = @_;
+ #ACID - This is a read. Can find exact or HEAD
my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5 );
return $subloc && 1;
}
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) = @_;
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(
$tag->{content}, $i,
);
- return ($subloc, $i * $self->{bucket_size}, $size) unless $subloc;
+ my @rv = ($subloc, $i * $self->{bucket_size}, $size);
+
+ unless ( $subloc ) {
+ return @zero if !$exact && @zero and $trans_id;
+ 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 = @rv 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 );
use strict;
-use Test::More tests => 4;
+use Test::More tests => 13;
use Test::Exception;
use t::common qw( new_fh );
use_ok( 'DBM::Deep' );
my ($fh, $filename) = new_fh();
-my $db = DBM::Deep->new(
+my $db1 = DBM::Deep->new(
file => $filename,
+ locking => 1,
);
-$db->{x} = 'y';
-is( $db->{x}, 'y' );
-$db->begin_work;
-$db->{x} = 'z';
-is( $db->{x}, 'z' );
-$db->rollback;
-TODO: {
- local $TODO = "Haven't written transaction code yet";
- is( $db->{x}, 'y' );
-}
+my $db2 = DBM::Deep->new(
+ file => $filename,
+ locking => 1,
+);
+
+$db1->{x} = 'y';
+is( $db1->{x}, 'y', "Before transaction, DB1's X is Y" );
+is( $db2->{x}, 'y', "Before transaction, DB2's X is Y" );
+
+$db1->begin_work;
+
+is( $db1->{x}, 'y', "DB1 transaction started, no actions - DB1's X is Y" );
+is( $db2->{x}, 'y', "DB1 transaction started, no actions - DB2's X is Y" );
+
+$db1->{x} = 'z';
+is( $db1->{x}, 'z', "Within DB1 transaction, DB1's X is Z" );
+is( $db2->{x}, 'y', "Within DB1 transaction, DB2's X is still Y" );
+
+$db2->{other_x} = 'foo';
+is( $db2->{other_x}, 'foo', "Set other_x within DB1's transaction, so DB2 can see it" );
+is( $db1->{other_x}, undef, "Since other_x was added after the transaction began, DB1 doesn't see it." );
+
+$db1->rollback;
+
+is( $db1->{x}, 'y', "After rollback, DB1's X is Y" );
+is( $db2->{x}, 'y', "After rollback, DB2's X is Y" );
+
+is( $db1->{other_x}, 'foo', "After DB1 transaction is over, DB1 can see other_x" );
+is( $db2->{other_x}, 'foo', "After DB1 transaction is over, DB2 can still see other_x" );
# Add a commit test (using fork) - we don't have to use fork initially. Since
# the transaction is in the Engine object and each new() provides a new Engine