hash_size => 16, # In bytes
max_buckets => 16,
num_txns => 16, # HEAD plus 15 running txns
+ trans_id => 0, # Default to the HEAD
storage => undef,
}, $class;
create => 1,
}) or die "How did read_value fail (no blist)?!\n";
- my $value_sector = $blist->get_data_for( $key_md5 );
+ my $value_sector = $blist->get_data_for( $key_md5, { allow_head => 1 } );
if ( !$value_sector ) {
# Autovivification
$value_sector = DBM::Deep::Engine::Sector::Null->new({
}) or die "How did key_exists fail (no blist)?!\n";
# exists() returns 1 or '' for true/false.
- return $blist->has_md5( $key_md5 ) ? 1 : '';
+ return $blist->has_md5( $key_md5, { allow_head => 1 } ) ? 1 : '';
}
sub delete_key {
}
if ( $blist->has_md5( $key_md5 ) ) {
- $blist->get_data_for( $key_md5 )->free;
+ $blist->get_data_for( $key_md5, { allow_head => 0 } )->free;
}
my $value_sector = $class->new({
return 1;
}
-# begin_work
-sub begin_transaction {
+sub begin_work {
my $self = shift;
+ my ($obj) = @_;
+
+ if ( $self->trans_id ) {
+ DBM::Deep->throw_error( "Cannot begin_work within a transaction" );
+ }
+
+ my @slots = $self->read_transaction_slots;
+ for my $i ( 1 .. @slots ) {
+ next if $slots[$i];
+ $slots[$i] = 1;
+ $self->set_trans_id( $i );
+ last;
+ }
+ $self->write_transaction_slots( @slots );
+
+ if ( !$self->trans_id ) {
+ DBM::Deep->throw_error( "Cannot begin_work - no available transactions" );
+ }
+
+ return;
}
-# rollback
-sub end_transaction {
+sub rollback {
my $self = shift;
+ my ($obj) = @_;
+
+ if ( !$self->trans_id ) {
+ DBM::Deep->throw_error( "Cannot rollback without a transaction" );
+ }
}
-# commit
-sub commit_transaction {
+sub commit {
my $self = shift;
+ my ($obj) = @_;
+
+ if ( !$self->trans_id ) {
+ DBM::Deep->throw_error( "Cannot commit without a transaction" );
+ }
+}
+
+sub read_transaction_slots {
+ my $self = shift;
+ return split '', unpack( "b32", $self->storage->read_at( $self->trans_loc, 4 ) );
+}
+
+sub write_transaction_slots {
+ my $self = shift;
+ $self->storage->print_at( $self->trans_loc,
+ pack( "b32", join('', @_) ),
+ );
}
################################################################################
sub max_buckets { $_[0]{max_buckets} }
sub blank_md5 { chr(0) x $_[0]->hash_size }
+sub trans_id { $_[0]{trans_id} }
+sub set_trans_id { $_[0]{trans_id} = $_[1] }
+
sub trans_loc { $_[0]{trans_loc} }
sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
sub find_md5 {
my $self = shift;
- my ($md5) = @_;
+ my ($md5, $opts) = @_;
+ $opts ||= {};
foreach my $idx ( 0 .. $self->engine->max_buckets - 1 ) {
my $potential = $self->engine->storage->read_at(
);
return (undef, $idx) if $potential eq $self->engine->blank_md5;
- return (1, $idx) if $md5 eq $potential;
+ if ( $md5 eq $potential ) {
+ my $location = $self->get_data_location_for(
+ $self->engine->trans_id, $idx, $opts,
+ );
+
+ if ( $location > 1 ) {
+ return (1, $idx);
+ }
+
+ return (undef, $idx);
+ }
}
return;
my ($md5, $key, $value_loc) = @_;
my $engine = $self->engine;
- my ($found, $idx) = $self->find_md5( $md5 );
+ my ($found, $idx) = $self->find_md5( $md5, { allow_head => 0 } );
my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
unless ($found) {
);
}
- $engine->storage->print_at( $spot + $self->engine->hash_size + $self->engine->byte_size,
+ $engine->storage->print_at(
+ $spot
+ + $self->engine->hash_size
+ + $self->engine->byte_size
+ + $self->engine->trans_id * $self->engine->byte_size,
pack( $StP{$engine->byte_size}, $value_loc ), # The pointer to the data in the HEAD
);
}
my ($md5) = @_;
my $engine = $self->engine;
- my ($found, $idx) = $self->find_md5( $md5 );
+ my ($found, $idx) = $self->find_md5( $md5, { allow_head => 0 } );
return undef unless $found;
# Save the location so that we can free the data
- my $location = $self->get_data_location_for( $idx );
+ my $location = $self->get_data_location_for( $self->engine->trans_id, $idx, { allow_head => 0 } );
my $key_sector = $self->get_key_for( $idx );
my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
sub get_data_location_for {
my $self = shift;
- my ($idx) = @_;
+ my ($trans_id, $idx, $opts) = @_;
+ $opts ||= {};
my $location = $self->engine->storage->read_at(
- $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size + $self->engine->byte_size,
+ $self->offset + $self->base_size
+ + $idx * $self->bucket_size
+ + $self->engine->hash_size
+ + $self->engine->byte_size
+ + $trans_id * $self->engine->byte_size,
$self->engine->byte_size,
);
- return unpack( $StP{$self->engine->byte_size}, $location );
+ my $loc = unpack( $StP{$self->engine->byte_size}, $location );
+
+ # If we're in a transaction and we never wrote to this location, try the
+ # HEAD instead.
+ if ( $trans_id && !$loc && $opts->{allow_head} ) {
+ return $self->get_data_location_for( 0, $idx );
+ }
+ return $loc;
}
sub get_data_for {
my $self = shift;
- my ($md5) = @_;
+ my ($md5, $opts) = @_;
+ $opts ||= {};
- my ($found, $idx) = $self->find_md5( $md5 );
+ my ($found, $idx) = $self->find_md5( $md5, $opts );
return unless $found;
- my $location = $self->get_data_location_for( $idx );
+ my $location = $self->get_data_location_for( $self->engine->trans_id, $idx, $opts );
return $self->engine->_load_sector( $location );
}