1 package DBM::Deep::Engine2;
3 use base 'DBM::Deep::Engine';
10 our $VERSION = q(0.99_03);
12 use Fcntl qw( :DEFAULT :flock );
16 # * Every method in here assumes that the _storage has been appropriately
17 # safeguarded. This can be anything from flock() to some sort of manual
18 # mutex. But, it's the caller's responsability to make sure that this has
21 # Setup file and tag signatures. These should never change.
22 sub SIG_FILE () { 'DPDB' }
23 sub SIG_HEADER () { 'h' }
24 sub SIG_INTERNAL () { 'i' }
25 sub SIG_HASH () { 'H' }
26 sub SIG_ARRAY () { 'A' }
27 sub SIG_NULL () { 'N' }
28 sub SIG_DATA () { 'D' }
29 sub SIG_INDEX () { 'I' }
30 sub SIG_BLIST () { 'B' }
31 sub SIG_FREE () { 'F' }
32 sub SIG_KEYS () { 'K' }
35 # This is the transaction ID for the HEAD
40 my ($trans_id, $base_offset, $key) = @_;
42 # print "Trying to read $key from $base_offset ($trans_id)\n" if $key > 400;
43 my ($_val_offset, $_is_del) = $self->_find_value_offset({
44 offset => $base_offset,
45 trans_id => $trans_id,
48 die "Attempt to use a deleted value" if $_is_del;
49 die "Internal error!" if !$_val_offset;
51 my ($key_tag) = $self->_find_key_offset({
52 offset => $_val_offset,
53 key_md5 => $self->_apply_digest( $key ),
57 my ($val_offset, $is_del) = $self->_find_value_offset({
58 offset => $key_tag->{start},
59 trans_id => $trans_id,
63 die "Internal error!" if !$val_offset;
65 return $self->_read_value({
66 keyloc => $key_tag->{start},
67 offset => $val_offset,
73 my ($trans_id, $base_offset, $key) = @_;
75 my ($_val_offset, $_is_del) = $self->_find_value_offset({
76 offset => $base_offset,
77 trans_id => $trans_id,
80 die "Attempt to use a deleted value" if $_is_del;
81 die "Internal error!" if !$_val_offset;
83 my ($key_tag) = $self->_find_key_offset({
84 offset => $_val_offset,
85 key_md5 => $self->_apply_digest( $key ),
87 return '' if !$key_tag->{start};
89 my ($val_offset, $is_del) = $self->_find_value_offset({
90 offset => $key_tag->{start},
91 trans_id => $trans_id,
94 die "Internal error!" if !$_val_offset;
103 my ($trans_id, $base_offset) = @_;
105 my ($_val_offset, $_is_del) = $self->_find_value_offset({
106 offset => $base_offset,
107 trans_id => $trans_id,
110 die "Attempt to use a deleted value" if $_is_del;
111 die "Internal error!" if !$_val_offset;
113 # If the previous key was not specifed, start at the top and
114 # return the first one found.
118 prev_md5 => $self->_apply_digest($_[2]),
124 prev_md5 => chr(0) x $self->{hash_size},
129 return $self->traverse_index( $temp, $_val_offset, 0 );
134 my ($trans_id, $base_offset, $key) = @_;
136 my ($_val_offset, $_is_del) = $self->_find_value_offset({
137 offset => $base_offset,
138 trans_id => $trans_id,
141 die "Attempt to use a deleted value" if $_is_del;
142 die "Internal error!" if !$_val_offset;
144 my ($key_tag, $bucket_tag) = $self->_find_key_offset({
145 offset => $_val_offset,
146 key_md5 => $self->_apply_digest( $key ),
148 return if !$key_tag->{start};
150 my $value = $self->read_value( $trans_id, $base_offset, $key );
152 $self->_mark_as_deleted({
154 trans_id => $trans_id,
158 if ( my @transactions = $self->_storage->current_transactions ) {
159 foreach my $other_trans_id ( @transactions ) {
160 next if $self->_has_keyloc_entry({
162 trans_id => $other_trans_id,
164 $self->write_value( $other_trans_id, $base_offset, $key, $value );
168 $self->_mark_as_deleted({
170 trans_id => $trans_id,
172 # $self->_remove_key_offset({
173 # offset => $_val_offset,
174 # key_md5 => $self->_apply_digest( $key ),
183 my ($trans_id, $base_offset, $key, $value) = @_;
185 # This verifies that only supported values will be stored.
187 my $r = Scalar::Util::reftype( $value );
190 last if $r eq 'HASH';
191 last if $r eq 'ARRAY';
194 "Storage of references of type '$r' is not supported."
198 my ($_val_offset, $_is_del) = $self->_find_value_offset({
199 offset => $base_offset,
200 trans_id => $trans_id,
203 die "Attempt to use a deleted value" if $_is_del;
204 die "Internal error!" if !$_val_offset;
206 my ($key_tag, $bucket_tag) = $self->_find_key_offset({
207 offset => $_val_offset,
208 key_md5 => $self->_apply_digest( $key ),
211 die "Cannot find/create new key offset!" if !$key_tag->{start};
214 if ( $key_tag->{is_new} ) {
215 # Must mark the HEAD as deleted because it doesn't exist
216 $self->_mark_as_deleted({
223 # If the HEAD isn't new, then we must take other transactions
224 # into account. If it is, then there can be no other transactions.
225 if ( !$key_tag->{is_new} ) {
226 my $old_value = $self->read_value( $trans_id, $base_offset, $key );
227 if ( my @transactions = $self->_storage->current_transactions ) {
228 foreach my $other_trans_id ( @transactions ) {
229 next if $self->_has_keyloc_entry({
231 trans_id => $other_trans_id,
233 $self->write_value( $other_trans_id, $base_offset, $key, $old_value );
239 my $value_loc = $self->_storage->request_space(
240 $self->_length_needed( $value, $key ),
243 $self->_add_key_offset({
245 trans_id => $trans_id,
249 $self->_write_value( $key_tag->{start}, $value_loc, $key, $value, $key );
254 sub _find_value_offset {
258 use Data::Dumper;warn Dumper $args;
260 my $key_tag = $self->load_tag( $args->{offset} );
263 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
264 my ($loc, $trans_id, $is_deleted) = unpack(
265 "$self->{long_pack} C C",
266 substr( $key_tag->{content}, $i * $self->{key_size}, $self->{key_size} ),
269 if ( $trans_id == HEAD ) {
270 @head = ($loc, $is_deleted);
273 next if $loc && $args->{trans_id} != $trans_id;
274 return( $loc, $is_deleted );
277 return @head if $args->{allow_head};
281 sub _find_key_offset {
285 my $bucket_tag = $self->load_tag( $args->{offset} )
286 or $self->_throw_error( "INTERNAL ERROR - Cannot find tag" );
288 #XXX What happens when $ch >= $self->{hash_size} ??
289 for (my $ch = 0; $bucket_tag->{signature} ne SIG_BLIST; $ch++) {
290 my $num = ord substr($args->{key_md5}, $ch, 1);
292 my $ref_loc = $bucket_tag->{offset} + ($num * $self->{long_size});
293 $bucket_tag = $self->index_lookup( $bucket_tag, $num );
296 return if !$args->{create};
298 my $loc = $self->_storage->request_space(
299 $self->tag_size( $self->{bucket_list_size} ),
302 $self->_storage->print_at( $ref_loc, pack($self->{long_pack}, $loc) );
304 $bucket_tag = $self->write_tag(
306 chr(0)x$self->{bucket_list_size},
309 $bucket_tag->{ref_loc} = $ref_loc;
310 $bucket_tag->{ch} = $ch;
311 $bucket_tag->{is_new} = 1;
316 $bucket_tag->{ch} = $ch;
317 $bucket_tag->{ref_loc} = $ref_loc;
320 # Need to create a new keytag, too
321 if ( $bucket_tag->{is_new} ) {
322 # print "Creating new keytag\n";
323 my $keytag_loc = $self->_storage->request_space(
324 $self->tag_size( $self->{keyloc_size} ),
327 substr( $bucket_tag->{content}, 0, $self->{key_size} ) =
328 $args->{key_md5} . pack( "$self->{long_pack}", $keytag_loc );
330 $self->_storage->print_at( $bucket_tag->{offset}, $bucket_tag->{content} );
332 my $key_tag = $self->write_tag(
333 $keytag_loc, SIG_KEYS,
334 chr(0)x$self->{keyloc_size},
337 return( $key_tag, $bucket_tag );
340 my ($key, $subloc, $index);
342 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
343 ($key, $subloc) = $self->_get_key_subloc(
344 $bucket_tag->{content}, $i,
347 next BUCKET if $subloc && $key ne $args->{key_md5};
349 # Keep track of where we are, in case we need to create a new
355 # If we have a subloc to return or we don't want to create a new
356 # entry, we need to return now.
357 $args->{create} ||= 0;
358 # print "Found ($subloc) at $index ($args->{create})\n";
359 return ($self->load_tag( $subloc ), $bucket_tag) if $subloc || !$args->{create};
361 my $keytag_loc = $self->_storage->request_space(
362 $self->tag_size( $self->{keyloc_size} ),
365 # There's space left in this bucket
366 if ( defined $index ) {
367 # print "There's space left in the bucket for $keytag_loc\n";
368 substr( $bucket_tag->{content}, $index * $self->{key_size}, $self->{key_size} ) =
369 $args->{key_md5} . pack( "$self->{long_pack}", $keytag_loc );
371 $self->_storage->print_at( $bucket_tag->{offset}, $bucket_tag->{content} );
373 # We need to split the index
375 # print "Splitting the index for $keytag_loc\n";
376 $self->split_index( $bucket_tag, $args->{key_md5}, $keytag_loc );
379 my $key_tag = $self->write_tag(
380 $keytag_loc, SIG_KEYS,
381 chr(0)x$self->{keyloc_size},
384 return( $key_tag, $bucket_tag );
394 return $self->read_from_loc( $args->{keyloc}, $args->{offset} );
397 sub _mark_as_deleted {
402 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
403 my ($loc, $trans_id, $is_deleted) = unpack(
404 "$self->{long_pack} C C",
405 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ),
408 last unless $loc || $is_deleted;
410 if ( $trans_id == $args->{trans_id} ) {
411 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ) = pack(
412 "$self->{long_pack} C C",
421 $self->_storage->print_at(
422 $args->{tag}{offset}, $args->{tag}{content},
429 sub _has_keyloc_entry {
433 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
434 my ($loc, $trans_id, $is_deleted) = unpack(
435 "$self->{long_pack} C C",
436 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ),
439 return 1 if $trans_id == $args->{trans_id};
445 sub _remove_key_offset {
450 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
451 my ($loc, $trans_id, $is_deleted) = unpack(
452 "$self->{long_pack} C C",
453 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ),
456 if ( $trans_id == $args->{trans_id} ) {
457 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ) = '';
458 $args->{tag}{content} .= chr(0) x $self->{key_size};
465 $self->_storage->print_at(
466 $args->{tag}{offset}, $args->{tag}{content},
473 sub _add_key_offset {
478 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
479 my ($loc, $trans_id, $is_deleted) = unpack(
480 "$self->{long_pack} C C",
481 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ),
484 if ( $trans_id == $args->{trans_id} || (!$loc && !$is_deleted) ) {
485 substr( $args->{tag}{content}, $i * $self->{key_size}, $self->{key_size} ) = pack(
486 "$self->{long_pack} C C",
487 $args->{loc}, $args->{trans_id}, 0,
495 $self->_storage->print_at(
496 $args->{tag}{offset}, $args->{tag}{content},
500 die "Why didn't _add_key_offset() change something?!\n";
510 # Need to remove use of $fh here
511 my $fh = $self->_storage->{fh};
514 #XXX The duplication of calculate_sizes needs to go away
515 unless ( $obj->{base_offset} ) {
516 my $bytes_read = $self->read_file_header;
518 $self->calculate_sizes;
521 # File is empty -- write header and master index
524 $self->_storage->audit( "# Database created on" );
526 $self->write_file_header;
528 $obj->{base_offset} = $self->_storage->request_space(
529 $self->tag_size( $self->{keyloc_size} ),
531 warn "INITIAL BASE OFFSET: $obj->{base_offset}\n";
533 my $value_spot = $self->_storage->request_space(
534 $self->tag_size( $self->{index_size} ),
538 $obj->{base_offset}, SIG_KEYS,
539 pack( "$self->{long_pack} C C", $value_spot, HEAD, 0 ),
540 chr(0) x ($self->{index_size} - $self->{key_size}),
544 $value_spot, $obj->_type,
545 chr(0)x$self->{index_size},
548 # Flush the filehandle
549 my $old_fh = select $fh;
550 my $old_af = $|; $| = 1; $| = $old_af;
554 $obj->{base_offset} = $bytes_read;
555 warn "REOPEN BASE OFFSET: $obj->{base_offset}\n";
557 my ($_val_offset, $_is_del) = $self->_find_value_offset({
558 offset => $obj->{base_offset},
562 die "Attempt to use a deleted value" if $_is_del;
563 die "Internal error!" if !$_val_offset;
566 # Get our type from master index header
568 my $tag = $self->load_tag($_val_offset);
571 $self->_throw_error("Corrupted file, no master index record");
574 unless ($obj->_type eq $tag->{signature}) {
576 $self->_throw_error("File type mismatch");
581 $self->calculate_sizes;
584 #XXX We have to make sure we don't mess up when autoflush isn't turned on
585 $self->_storage->set_inode;