}) or die "How did write_value fail (no blist)?!\n";
my $r = Scalar::Util::reftype( $value ) || '';
+ #XXX Throw an error here on illegal values
my ($class, $type);
if ( !defined $value ) {
$class = 'DBM::Deep::Engine::Sector::Null';
}
elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
$class = 'DBM::Deep::Engine::Sector::Reference';
- $type = $r eq 'ARRAY' ? 'A' : 'H';
+ $type = substr( $r, 0, 1 );
}
else {
$class = 'DBM::Deep::Engine::Sector::Scalar';
}
+# if ( $blist->has_md5( $key_md5 ) ) {
+# $blist->load_data_for( $key_md5 )->free;
+# }
+
my $value_sector = $class->new({
engine => $self,
data => $value,
$blist->write_md5( $key_md5, $key, $value_sector->offset );
# This code is to make sure we write all the values in the $value to the disk
- # and to make sure all changes to $value are reflected on disk.
+ # and to make sure all changes to $value after the assignment are reflected
+ # on disk. This may be counter-intuitive at first, but it is correct dwimmery.
+ # NOTE - simply tying $value won't perform a STORE on each value. Hence, the
+ # copy to a temp value.
if ( $r eq 'ARRAY' ) {
- my @x = @$value;
+ my @temp = @$value;
tie @$value, 'DBM::Deep', {
base_offset => $value_sector->offset,
storage => $self->storage,
};
- @$value = @x;
+ @$value = @temp;
bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
}
elsif ( $r eq 'HASH' ) {
- my %x = %$value;
+ my %temp = %$value;
tie %$value, 'DBM::Deep', {
base_offset => $value_sector->offset,
storage => $self->storage,
};
- %$value = %x;
+
+ %$value = %temp;
bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
}
sub get_next_key {
my $self = shift;
- my ($trans_id, $base_offset) = @_;
-
- # This will be a Reference sector
- my $sector = $self->_load_sector( $base_offset )
- or die "How did this fail (no sector for '$base_offset')?!\n";
-
- return;
-
- # This is FIRSTKEY
- if ( @_ == 2 ) {
-# my $blist = $sector->get_bucket_list({
-# key_md5 => $key_md5,
-# }) or die "How did this fail (no blist)?!\n";
-#
-# return $blist->get_key_for_idx( 0 );
- }
-
- # This is NEXTKEY
-
- my $temp;
- if ( @_ > 2 ) {
- $temp = {
- prev_md5 => $self->_apply_digest($_[2]),
- return_next => 0,
- };
- }
- else {
- $temp = {
- prev_md5 => $self->blank_md5,
- return_next => 1,
- };
+ my ($trans_id, $base_offset, $prev_key) = @_;
+ print "get_next_key( $trans_id, $base_offset )\n" if $DEBUG;
+
+ # XXX Need to add logic about resetting the iterator if any key in the reference has changed
+ unless ( $prev_key ) {
+ $self->{iterator} = DBM::Deep::Engine::Iterator->new({
+ base_offset => $base_offset,
+ trans_id => $trans_id,
+ engine => $self,
+ });
}
- #return $self->traverse_index( $temp, $_val_offset, 0 );
- return;
+ return $self->iterator->get_next_key;
}
################################################################################
});
}
- die "Don't know what to do with type '$type' at offset '$offset'\n";
+ die "'$offset': Don't know what to do with type '$type'\n";
}
sub _apply_digest {
return $self->{digest}->(@_);
}
+sub _add_free_sector {
+ my $self = shift;
+ my ($offset, $size) = @_;
+}
+
################################################################################
sub storage { $_[0]{storage} }
sub hash_size { $_[0]{hash_size} }
sub num_txns { $_[0]{num_txns} }
sub max_buckets { $_[0]{max_buckets} }
+sub iterator { $_[0]{iterator} }
sub blank_md5 { chr(0) x $_[0]->hash_size }
################################################################################
+package DBM::Deep::Engine::Iterator;
+
+sub new {
+ my $class = shift;
+ my ($args) = @_;
+
+ my $self = bless {
+ breadcrumbs => [],
+ engine => $args->{engine},
+ base_offset => $args->{base_offset},
+ trans_id => $args->{trans_id},
+ }, $class;
+
+ Scalar::Util::weaken( $self->{engine} );
+
+ return $self;
+}
+
+sub reset {
+ my $self = shift;
+ $self->{breadcrumbs} = [];
+}
+
+sub get_next_key {
+ my $self = shift;
+
+ my $crumbs = $self->{breadcrumbs};
+
+ unless ( @$crumbs ) {
+ # This will be a Reference sector
+ my $sector = $self->{engine}->_load_sector( $self->{base_offset} )
+ or die "Iterator: How did this fail (no sector for '$self->{base_offset}')?!\n";
+ push @$crumbs, [ $sector->get_blist_loc, 0 ];
+ }
+
+ my $key;
+ while ( 1 ) {
+ my ($offset, $idx) = @{ $crumbs->[-1] };
+ unless ( $offset ) {
+ $self->reset;
+ last;
+ }
+
+ my $sector = $self->{engine}->_load_sector( $offset )
+ or die "Iterator: How did this fail (no sector for '$offset')?!\n";
+
+ my $key_sector = $sector->get_key_for( $idx );
+ unless ( $key_sector ) {
+ $self->reset;
+ last;
+ }
+
+ $crumbs->[-1][1]++;
+ $key = $key_sector->data;
+ last;
+ }
+
+ return $key;
+}
+
package DBM::Deep::Engine::Sector;
sub new {
sub offset { $_[0]{offset} }
sub type { $_[0]{type} }
+sub free {
+ my $self = shift;
+
+ return;
+ $self->engine->_add_free_sector(
+ $self->offset, $self->size,
+ );
+
+ $self->engine->storage->print_at( $self->offset,
+ chr(0) x $self->size,
+ );
+}
package DBM::Deep::Engine::Sector::Data;
my $data = delete $self->{data};
# XXX Need to build in chaining
+ #XXX This assumes that length($data) > $leftover
$leftover -= length( $data );
$self->{offset} = $engine->storage->request_space( $self->size );
});
}
-sub get_first_key {
- my $self = shift;
-
- my $blist = $self->get_bucket_list();
-}
-
-sub get_key_after {
-}
-
sub data {
my $self = shift;
$engine->storage->print_at( $spot,
$md5,
- $key_sector->offset,
+ pack( $StP{$self->engine->byte_size}, $key_sector->offset ),
);
}
return undef unless $found;
# Save the location so that we can free the data
- my $location = $self->get_location_for( $idx );
+ my $location = $self->get_data_location_for( $idx );
my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
$engine->storage->print_at( $spot,
chr(0) x $self->bucket_size,
);
- my $data = $self->engine->_load_sector( $location )->data;
+ my $data_sector = $self->engine->_load_sector( $location );
+ my $data = $data_sector->data;
# Free the data (somehow)
+ $data_sector->free;
return $data;
}
-sub get_location_for {
+sub get_data_location_for {
my $self = shift;
my ($idx) = @_;
my ($found, $idx) = $self->find_md5( $md5 );
return unless $found;
- my $location = $self->get_location_for( $idx );
+ my $location = $self->get_data_location_for( $idx );
+ return $self->engine->_load_sector( $location );
+}
+
+sub get_key_for {
+ my $self = shift;
+ my ($idx) = @_;
+
+ my $location = $self->engine->storage->read_at(
+ $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
+ $self->engine->byte_size,
+ );
+ $location = unpack( $StP{$self->engine->byte_size}, $location );
+ return unless $location;
return $self->engine->_load_sector( $location );
}
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
+# key it provided. There will also be an "edit revision number" on the
+# reference so that resetting the iterator can be done.
+#
+# Q: How do we make sure that the iterator is unique? Is it supposed to be?
+
##
# count keys
##
is( scalar keys %$db, 3, "keys() works against tied hash" );
-__END__
-=pod
##
# step through keys
is( $temphash->{key1}, 'value1', "First key copied successfully using OO interface" );
is( $temphash->{key2}, undef, "Second key copied successfully" );
is( $temphash->{key3}, 'value3', "Third key copied successfully" );
-=cut
+
##
# delete keys
##
is( $db->{key3}, 'value3', "The other key is still there" );
ok( !exists $db->{key1}, "key1 doesn't exist" );
ok( !exists $db->{key2}, "key2 doesn't exist" );
-=pod
+
is( scalar keys %$db, 1, "After deleting two keys, 1 remains" );
##
ok( $db->clear(), "clear() returns true" );
is( scalar keys %$db, 0, "After clear(), everything is removed" );
-=cut
+
##
# replace key
##
undef $db;
$db = DBM::Deep->new( $filename );
is( $db->get("key1"), "value222222222222222222222222", "The value we set is still there after closure" );
-=pod
+
##
# Make sure keys are still fetchable after replacing values
# with smaller ones (bug found by John Cardenas, DBM::Deep 0.93)
($first_key ne $next_key)
,"keys() still works if you replace long values with shorter ones"
);
-=cut
-# Test autovivification
+# Test autovivification
$db->{unknown}{bar} = 1;
ok( $db->{unknown}, 'Autovivified hash exists' );
cmp_ok( $db->{unknown}{bar}, '==', 1, 'And the value stored is there' );