##
my $self = shift->_get_self;
my ($key, $value) = @_;
- warn "STORE($self, $key, $value)\n" if DEBUG;
+ warn "STORE($self, $key, @{[defined$value?$value:'undef']})\n" if DEBUG;
unless ( $self->_engine->storage->is_writable ) {
$self->_throw_error( 'Cannot write to a readonly filehandle' );
################################################################################
+sub dirty_sectors {
+ my $self = shift;
+ return $self->{dirty_sectors} ||= {};
+}
+
+sub add_dirty_sector {
+ my $self = shift;
+ my ($sector) = @_;
+
+# if ( exists $self->dirty_sectors->{ $sector->offset } ) {
+# DBM::Deep->_throw_error( "We have a duplicate sector!! " . $sector->offset );
+# }
+
+ $self->dirty_sectors->{ $sector->offset } = $sector;
+}
+
+sub clear_dirty_sectors {
+ my $self = shift;
+ $self->{dirty_sectors} = {};
+}
+
+sub flush {
+ my $self = shift;
+
+ for (values %{ $self->dirty_sectors }) {
+ $_->flush;
+ }
+
+ $self->clear_dirty_sectors;
+}
+
+################################################################################
+
sub lock_exclusive {
my $self = shift;
my ($obj) = @_;
sub unlock {
my $self = shift;
my ($obj) = @_;
- return $self->storage->unlock( $obj );
+
+ my $rv = $self->storage->unlock( $obj );
+
+ $self->flush if $rv;
+
+ return $rv;
}
################################################################################
my $return = "";
+ # Filesize
+ $return .= "Size: " . (-s $self->storage->{fh}) . $/;
+
# Header values
$return .= "NumTxns: " . $self->num_txns . $/;
sub new {
my $self = bless $_[1], $_[0];
Scalar::Util::weaken( $self->{engine} );
+
+ if ( $self->offset ) {
+ $self->{string} = $self->engine->storage->read_at(
+ $self->offset, $self->size,
+ );
+ }
+ else {
+ $self->{string} = chr(0) x $self->size;
+ }
+
$self->_init;
+
return $self;
}
sub engine { $_[0]{engine} }
sub offset { $_[0]{offset} }
-sub type { $_[0]{type} }
+sub type { $_[0]{type} }
sub base_size {
my $self = shift;
my $e = $self->engine;
- $e->storage->print_at( $self->offset, $e->SIG_FREE );
- # Skip staleness counter
- $e->storage->print_at( $self->offset + $self->base_size,
- chr(0) x ($self->size - $self->base_size),
- );
+ $self->write( 0, $e->SIG_FREE );
+ $self->write( $self->base_size, chr(0) x ($self->size - $self->base_size) );
+
+ $e->flush;
+
+# $e->storage->print_at( $self->offset, $e->SIG_FREE );
+# # Skip staleness counter
+# $e->storage->print_at( $self->offset + $self->base_size,
+# chr(0) x ($self->size - $self->base_size),
+# );
+
+ #TODO When freeing two sectors, we cannot flush them right away! This means the following:
+ # 1) The header has to understand about unflushed items.
+ # 2) Loading a sector has to go through a cache to make sure we see what's already been loaded.
+ # 3) The header should be cached.
my $free_meth = $self->free_meth;
$e->$free_meth( $self->offset, $self->size );
return;
}
+sub read {
+ my $self = shift;
+ my ($start, $length) = @_;
+ if ( $length ) {
+ return substr( $self->{string}, $start, $length );
+ }
+ else {
+ return substr( $self->{string}, $start );
+ }
+}
+
+sub write {
+ my $self = shift;
+ my ($start, $text) = @_;
+
+ substr( $self->{string}, $start, length($text) ) = $text;
+
+ $self->mark_dirty;
+}
+
+sub mark_dirty {
+ my $self = shift;
+ $self->engine->add_dirty_sector( $self );
+}
+
+sub flush {
+ my $self = shift;
+ $self->engine->storage->print_at( $self->offset, $self->{string} );
+}
+
1;
__END__
-#TODO: Convert this to a string
package DBM::Deep::Engine::Sector::BucketList;
use 5.006_000;
unless ( $self->offset ) {
$self->{offset} = $engine->_request_blist_sector( $self->size );
- my $string = chr(0) x $self->size;
- substr( $string, 0, 1, $engine->SIG_BLIST );
- $engine->storage->print_at( $self->offset, $string );
+ $self->write( 0, $engine->SIG_BLIST );
}
if ( $self->{key_md5} ) {
sub clear {
my $self = shift;
- $self->engine->storage->print_at( $self->offset + $self->base_size,
- chr(0) x ($self->size - $self->base_size), # Zero-fill the data
- );
+
+ # Zero-fill the data
+ $self->write( $self->base_size, chr(0) x ($self->size - $self->base_size) );
}
sub size {
my @buckets;
foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
- my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
- my $md5 = $e->storage->read_at( $spot, $e->hash_size );
+ my $spot = $self->base_size + $idx * $self->bucket_size;
+ my $data = $self->read( $spot, $self->bucket_size );
- #XXX If we're chopping, why would we ever have the blank_md5?
- last if $md5 eq $e->blank_md5;
+ # _dump_file() will run into the blank_md5. Otherwise, we should never run into it.
+ # -RobK, 2008-06-18
+ last if substr( $data, 0, $e->hash_size ) eq $e->blank_md5;
- my $rest = $e->storage->read_at( undef, $self->bucket_size - $e->hash_size );
- push @buckets, [ $spot, $md5 . $rest ];
+ push @buckets, [ $spot, $data ];
}
return @buckets;
#XXX This is such a hack!
$self->{_next_open} = 0 unless exists $self->{_next_open};
- my $spot = $self->offset + $self->base_size + $self->{_next_open}++ * $self->bucket_size;
- $self->engine->storage->print_at( $spot, $entry );
+ my $spot = $self->base_size + $self->{_next_open}++ * $self->bucket_size;
+ $self->write( $spot, $entry );
return $spot;
}
my $e = $self->engine;
foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
- my $potential = $e->storage->read_at(
- $self->offset + $self->base_size + $idx * $self->bucket_size, $e->hash_size,
+ my $potential = $self->read(
+ $self->base_size + $idx * $self->bucket_size, $e->hash_size,
);
if ( $potential eq $e->blank_md5 ) {
DBM::Deep->_throw_error( "write_md5: no key_md5" ) unless exists $args->{key_md5};
DBM::Deep->_throw_error( "write_md5: no value" ) unless exists $args->{value};
- my $engine = $self->engine;
+ my $e = $self->engine;
- $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
+ $args->{trans_id} = $e->trans_id unless exists $args->{trans_id};
- my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
- $engine->add_entry( $args->{trans_id}, $spot );
+ my $spot = $self->base_size + $self->{idx} * $self->bucket_size;
+ $e->add_entry( $args->{trans_id}, $self->offset + $spot );
unless ($self->{found}) {
my $key_sector = DBM::Deep::Engine::Sector::Scalar->new({
- engine => $engine,
+ engine => $e,
data => $args->{key},
});
- $engine->storage->print_at( $spot,
- $args->{key_md5},
- pack( $engine->StP($engine->byte_size), $key_sector->offset ),
- );
+ $self->write( $spot, $args->{key_md5} . pack( $e->StP($e->byte_size), $key_sector->offset ) );
}
- my $loc = $spot
- + $engine->hash_size
- + $engine->byte_size;
+ my $loc = $spot + $e->hash_size + $e->byte_size;
if ( $args->{trans_id} ) {
- $loc += $engine->byte_size + ($args->{trans_id} - 1) * ( $engine->byte_size + $DBM::Deep::Engine::STALE_SIZE );
+ $loc += $e->byte_size + ($args->{trans_id} - 1) * ( $e->byte_size + $DBM::Deep::Engine::STALE_SIZE );
- $engine->storage->print_at( $loc,
- pack( $engine->StP($engine->byte_size), $args->{value}->offset ),
- pack( $engine->StP($DBM::Deep::Engine::STALE_SIZE), $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
+ $self->write( $loc,
+ pack( $e->StP($e->byte_size), $args->{value}->offset )
+ . pack( $e->StP($DBM::Deep::Engine::STALE_SIZE), $e->get_txn_staleness_counter( $args->{trans_id} ) ),
);
}
else {
- $engine->storage->print_at( $loc,
- pack( $engine->StP($engine->byte_size), $args->{value}->offset ),
- );
+ $self->write( $loc, pack( $e->StP($e->byte_size), $args->{value}->offset ) );
}
}
my ($args) = @_;
$args ||= {};
- my $engine = $self->engine;
+ my $e = $self->engine;
- $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
+ $args->{trans_id} = $e->trans_id unless exists $args->{trans_id};
- my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
- $engine->add_entry( $args->{trans_id}, $spot );
+ my $spot = $self->base_size + $self->{idx} * $self->bucket_size;
+ $e->add_entry( $args->{trans_id}, $self->offset + $spot );
my $loc = $spot
- + $engine->hash_size
- + $engine->byte_size;
+ + $e->hash_size
+ + $e->byte_size;
if ( $args->{trans_id} ) {
- $loc += $engine->byte_size + ($args->{trans_id} - 1) * ( $engine->byte_size + $DBM::Deep::Engine::STALE_SIZE );
+ $loc += $e->byte_size + ($args->{trans_id} - 1) * ( $e->byte_size + $DBM::Deep::Engine::STALE_SIZE );
- $engine->storage->print_at( $loc,
- pack( $engine->StP($engine->byte_size), 1 ), # 1 is the marker for deleted
- pack( $engine->StP($DBM::Deep::Engine::STALE_SIZE), $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
+ $self->write( $loc,
+ pack( $e->StP($e->byte_size), 1 ) # 1 is the marker for deleted
+ . pack( $e->StP($DBM::Deep::Engine::STALE_SIZE), $e->get_txn_staleness_counter( $args->{trans_id} ) ),
);
}
else {
- $engine->storage->print_at( $loc,
- pack( $engine->StP($engine->byte_size), 1 ), # 1 is the marker for deleted
- );
+ # 1 is the marker for deleted
+ $self->write( $loc, pack( $e->StP($e->byte_size), 1 ) );
}
-
}
sub delete_md5 {
});
my $key_sector = $self->get_key_for;
- my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
- $engine->storage->print_at( $spot,
- $engine->storage->read_at(
+ my $spot = $self->base_size + $self->{idx} * $self->bucket_size;
+
+ # Shuffle everything down to cover the deleted bucket's spot.
+ $self->write( $spot,
+ $self->read(
$spot + $self->bucket_size,
$self->bucket_size * ( $engine->max_buckets - $self->{idx} - 1 ),
- ),
- chr(0) x $self->bucket_size,
+ )
+ . chr(0) x $self->bucket_size,
);
$key_sector->free;
my $e = $self->engine;
- my $spot = $self->offset + $self->base_size
+ my $spot = $self->base_size
+ $args->{idx} * $self->bucket_size
+ $e->hash_size
+ $e->byte_size;
$spot += $e->byte_size + ($args->{trans_id} - 1) * ( $e->byte_size + $DBM::Deep::Engine::STALE_SIZE );
}
- my $buffer = $e->storage->read_at(
- $spot,
- $e->byte_size + $DBM::Deep::Engine::STALE_SIZE,
+ my $buffer = $self->read( $spot, $e->byte_size + $DBM::Deep::Engine::STALE_SIZE );
+ my ($loc, $staleness) = unpack(
+ $e->StP($e->byte_size) . ' ' . $e->StP($DBM::Deep::Engine::STALE_SIZE),
+ $buffer,
);
- my ($loc, $staleness) = unpack( $e->StP($e->byte_size) . ' ' . $e->StP($DBM::Deep::Engine::STALE_SIZE), $buffer );
# XXX Merge the two if-clauses below
if ( $args->{trans_id} ) {
DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx" );
}
- my $location = $self->engine->storage->read_at(
- $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
+ my $location = $self->read(
+ $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
$self->engine->byte_size,
);
$location = unpack( $self->engine->StP($self->engine->byte_size), $location );
-#TODO: Convert this to a string
package DBM::Deep::Engine::Sector::Index;
use 5.006_000;
unless ( $self->offset ) {
$self->{offset} = $engine->_request_index_sector( $self->size );
- my $string = chr(0) x $self->size;
- substr( $string, 0, 1, $engine->SIG_INDEX );
- $engine->storage->print_at( $self->offset, $string );
+ $self->write( 0, $engine->SIG_INDEX );
}
return $self;
}
#XXX Change here
+#XXX Why? -RobK, 2008-06-18
sub size {
my $self = shift;
unless ( $self->{size} ) {
sub _loc_for {
my $self = shift;
my ($idx) = @_;
- return $self->offset + $self->base_size + $idx * $self->engine->byte_size;
+ return $self->base_size + $idx * $self->engine->byte_size;
}
sub get_entry {
return unpack(
$e->StP($e->byte_size),
- $e->storage->read_at( $self->_loc_for( $idx ), $e->byte_size ),
+ $self->read( $self->_loc_for( $idx ), $e->byte_size ),
);
}
DBM::Deep->_throw_error( "set_entry: Out of range ($idx)" )
if $idx < 0 || $idx >= $e->hash_chars;
- $self->engine->storage->print_at(
- $self->_loc_for( $idx ),
- pack( $e->StP($e->byte_size), $loc ),
- );
+ $self->write( $self->_loc_for( $idx ), pack( $e->StP($e->byte_size), $loc ) );
}
1;
unless ( $self->offset ) {
$self->{offset} = $engine->_request_data_sector( $self->size );
- my $string = chr(0) x $self->size;
-
- substr( $string, 0, 1, $self->type );
- substr( $string, $self->base_size, $engine->byte_size + 1,
+ $self->write( 0, $self->type );
+ $self->write( $self->base_size,
pack( $engine->StP($engine->byte_size), 0 ) # Chain loc
. pack( $engine->StP(1), $self->data_length ), # Data length
);
- $engine->storage->print_at( $self->offset, $string );
-
return;
}
}
-#TODO: Convert this to a string
package DBM::Deep::Engine::Sector::Reference;
use 5.006_000;
$class_offset = $class_sector->offset;
}
- my $string = chr(0) x $self->size;
- substr( $string, 0, 1, $self->type );
- substr( $string, $self->base_size, 3 * $e->byte_size,
+ $self->write( 0, $self->type );
+ $self->write( $self->base_size,
pack( $e->StP($e->byte_size), 0 ) # Index/BList loc
. pack( $e->StP($e->byte_size), $class_offset ) # Classname loc
. pack( $e->StP($e->byte_size), 1 ) # Initial refcount
);
- $e->storage->print_at( $self->offset, $string );
}
else {
- $self->{type} = $e->storage->read_at( $self->offset, 1 );
+ $self->{type} = $self->read( 0, $e->SIG_SIZE );
}
$self->{staleness} = unpack(
$e->StP($DBM::Deep::Engine::STALE_SIZE),
- $e->storage->read_at( $self->offset + $e->SIG_SIZE, $DBM::Deep::Engine::STALE_SIZE ),
+ $self->read( $e->SIG_SIZE, $DBM::Deep::Engine::STALE_SIZE ),
);
return;
my $self = shift;
my $e = $self->engine;
- my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
- return unpack( $e->StP($e->byte_size), $blist_loc );
+ return unpack(
+ $e->StP($e->byte_size),
+ $self->read( $self->base_size, $e->byte_size ),
+ );
}
sub get_bucket_list {
key_md5 => $args->{key_md5},
});
- $engine->storage->print_at( $self->offset + $self->base_size,
- pack( $engine->StP($engine->byte_size), $blist->offset ),
- );
+ $self->write( $self->base_size, pack( $engine->StP($engine->byte_size), $blist->offset ) );
return $blist;
}
$new_index->offset,
);
} else {
- $engine->storage->print_at( $self->offset + $self->base_size,
- pack( $engine->StP($engine->byte_size), $new_index->offset ),
- );
+ $self->write( $self->base_size, pack( $engine->StP($engine->byte_size), $new_index->offset ) );
}
$sector->clear;
my $e = $self->engine;
return unpack(
$e->StP($e->byte_size),
- $e->storage->read_at(
- $self->offset + $self->base_size + 1 * $e->byte_size, $e->byte_size,
+ $self->read(
+ $self->base_size + 1 * $e->byte_size,
+ $e->byte_size,
),
);
}
my $e = $self->engine;
return unpack(
$e->StP($e->byte_size),
- $e->storage->read_at(
- $self->offset + $self->base_size + 2 * $e->byte_size, $e->byte_size,
+ $self->read(
+ $self->base_size + 2 * $e->byte_size,
+ $e->byte_size,
),
);
}
my ($num) = @_;
my $e = $self->engine;
- $e->storage->print_at(
- $self->offset + $self->base_size + 2 * $e->byte_size,
+ $self->write(
+ $self->base_size + 2 * $e->byte_size,
pack( $e->StP($e->byte_size), $num ),
);
}
my $data_section = $self->size - $self->base_size - $engine->byte_size - 1;
-
-
my $curr_offset = $self->offset;
my $continue = 1;
while ( $continue ) {
}
my $string = chr(0) x $self->size;
- substr( $string, 0, 1, $self->type );
+ substr( $string, 0, $engine->SIG_SIZE, $self->type );
substr( $string, $self->base_size, $engine->byte_size + 1,
pack( $engine->StP($engine->byte_size), $next_offset ) # Chain loc
. pack( $engine->StP(1), $this_len ), # Data length
if ($self->{locking} && $self->{locked} > 0) {
$self->{locked}--;
- if (!$self->{locked}) { flock($self->{fh}, LOCK_UN); }
- return 1;
+ if (!$self->{locked}) {
+ flock($self->{fh}, LOCK_UN);
+ return 1;
+ }
+
+ return;
}
return;
##
my $self = shift->_get_self;
+ warn "HASH:FIRSTKEY($self)\n" if DBM::Deep::DEBUG;
+
$self->lock_shared;
my $result = $self->_engine->get_next_key( $self );
? $self->_engine->storage->{filter_store_key}->($_[0])
: $_[0];
+ warn "HASH:NEXTKEY($self,$prev_key)\n" if DBM::Deep::DEBUG;
+
$self->lock_shared;
my $result = $self->_engine->get_next_key( $self, $prev_key );
$self->unlock();
-
+
return ($result && $self->_engine->storage->{filter_fetch_key})
? $self->_engine->storage->{filter_fetch_key}->($result)
: $result;
# DBM::Deep Test
##
use strict;
-use Test::More tests => 3;
+use Test::More tests => 4;
use t::common qw( new_fh );
ok(1, "We can successfully open a file!" );
$db->{foo} = 'bar';
+is( $db->{foo}, 'bar' );
ok( exists $db->{key4}, "Autovivified key4 now exists" );
delete $db->{key4};
+
ok( !exists $db->{key4}, "And key4 doesn't exists anymore" );
# Keys will be done via an iterator that keeps a breadcrumb trail of the last
);
is( $db->_dump_file, <<"__END_DUMP__", "Dump of initial file correct" );
+Size: 94
NumTxns: 1
Chains(B):
Chains(D):
$db->{foo} = 'bar';
is( $db->_dump_file, <<"__END_DUMP__", "Dump of initial file correct" );
+Size: 609
NumTxns: 1
Chains(B):
Chains(D):