Updated MANIFEST, Changes, and other similar distro maintenance
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine.pm
CommitLineData
662db255 1package DBM::Deep::Engine;
696cadb7 2
3use 5.6.0;
4
5use strict;
6
7our $VERSION = q(0.99_03);
8
696cadb7 9use Scalar::Util ();
10
11# File-wide notes:
8fbac729 12# * Every method in here assumes that the storage has been appropriately
696cadb7 13# safeguarded. This can be anything from flock() to some sort of manual
14# mutex. But, it's the caller's responsability to make sure that this has
15# been done.
16
17# Setup file and tag signatures. These should never change.
18sub SIG_FILE () { 'DPDB' }
19sub SIG_HEADER () { 'h' }
20sub SIG_INTERNAL () { 'i' }
21sub SIG_HASH () { 'H' }
22sub SIG_ARRAY () { 'A' }
23sub SIG_NULL () { 'N' }
24sub SIG_DATA () { 'D' }
25sub SIG_INDEX () { 'I' }
26sub SIG_BLIST () { 'B' }
27sub SIG_FREE () { 'F' }
28sub SIG_KEYS () { 'K' }
29sub SIG_SIZE () { 1 }
d58fd793 30sub STALE_SIZE () { 1 }
696cadb7 31
8fbac729 32# Please refer to the pack() documentation for further information
33my %StP = (
b4e17919 34 1 => 'C', # Unsigned char value (no order specified, presumably ASCII)
8fbac729 35 2 => 'n', # Unsigned short in "network" (big-endian) order
36 4 => 'N', # Unsigned long in "network" (big-endian) order
37 8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
c83524c6 38);
39
7645a22a 40################################################################################
41
696cadb7 42sub new {
43 my $class = shift;
44 my ($args) = @_;
45
46 my $self = bless {
c83524c6 47 byte_size => 4,
696cadb7 48
c83524c6 49 digest => undef,
6a4f323c 50 hash_size => 16, # In bytes
51 hash_chars => 256, # Number of chars the algorithm uses per byte
696cadb7 52 max_buckets => 16,
3976d8c9 53 num_txns => 16, # HEAD plus 15 running txns
8cb9205a 54 trans_id => 0, # Default to the HEAD
696cadb7 55
6f999f6e 56 entries => {}, # This is the list of entries for transactions
696cadb7 57 storage => undef,
696cadb7 58 }, $class;
59
60 if ( defined $args->{pack_size} ) {
61 if ( lc $args->{pack_size} eq 'small' ) {
c83524c6 62 $args->{byte_size} = 2;
696cadb7 63 }
64 elsif ( lc $args->{pack_size} eq 'medium' ) {
c83524c6 65 $args->{byte_size} = 4;
696cadb7 66 }
67 elsif ( lc $args->{pack_size} eq 'large' ) {
c83524c6 68 $args->{byte_size} = 8;
696cadb7 69 }
70 else {
71 die "Unknown pack_size value: '$args->{pack_size}'\n";
72 }
73 }
74
75 # Grab the parameters we want to use
76 foreach my $param ( keys %$self ) {
77 next unless exists $args->{$param};
78 $self->{$param} = $args->{$param};
79 }
696cadb7 80
8fbac729 81 $self->{byte_pack} = $StP{ $self->byte_size };
c83524c6 82
696cadb7 83 ##
84 # Number of buckets per blist before another level of indexing is
85 # done. Increase this value for slightly greater speed, but larger database
86 # files. DO NOT decrease this value below 16, due to risk of recursive
87 # reindex overrun.
88 ##
89 if ( $self->{max_buckets} < 16 ) {
90 warn "Floor of max_buckets is 16. Setting it to 16 from '$self->{max_buckets}'\n";
91 $self->{max_buckets} = 16;
92 }
93
c83524c6 94 if ( !$self->{digest} ) {
95 require Digest::MD5;
96 $self->{digest} = \&Digest::MD5::md5;
97 }
98
696cadb7 99 return $self;
100}
101
102################################################################################
103
104sub read_value {
105 my $self = shift;
c9f02899 106 my ($obj, $key) = @_;
3976d8c9 107
108 # This will be a Reference sector
c9f02899 109 my $sector = $self->_load_sector( $obj->_base_offset )
76c68c87 110 or return;
3976d8c9 111
8af340bf 112 if ( $sector->staleness != $obj->_staleness ) {
113 return;
114 }
115
3976d8c9 116 my $key_md5 = $self->_apply_digest( $key );
117
2432d6cc 118 my $value_sector = $sector->get_data_for({
119 key_md5 => $key_md5,
120 allow_head => 1,
121 });
3976d8c9 122
2432d6cc 123 unless ( $value_sector ) {
4056dff7 124 $value_sector = DBM::Deep::Engine::Sector::Null->new({
125 engine => $self,
126 data => undef,
127 });
128
2432d6cc 129 $sector->write_data({
130 key_md5 => $key_md5,
131 key => $key,
132 value => $value_sector,
133 });
4056dff7 134 }
3976d8c9 135
136 return $value_sector->data;
696cadb7 137}
138
84467b9f 139sub get_classname {
140 my $self = shift;
c9f02899 141 my ($obj) = @_;
84467b9f 142
143 # This will be a Reference sector
c9f02899 144 my $sector = $self->_load_sector( $obj->_base_offset )
76c68c87 145 or die "How did get_classname fail (no sector for '$obj')?!\n";
84467b9f 146
8af340bf 147 if ( $sector->staleness != $obj->_staleness ) {
148 return;
149 }
150
84467b9f 151 return $sector->get_classname;
152}
153
696cadb7 154sub key_exists {
155 my $self = shift;
c9f02899 156 my ($obj, $key) = @_;
c000ae6e 157
158 # This will be a Reference sector
c9f02899 159 my $sector = $self->_load_sector( $obj->_base_offset )
76c68c87 160 or return '';
c000ae6e 161
8af340bf 162 if ( $sector->staleness != $obj->_staleness ) {
163 return '';
164 }
165
2432d6cc 166 my $data = $sector->get_data_for({
167 key_md5 => $self->_apply_digest( $key ),
168 allow_head => 1,
169 });
c000ae6e 170
e86cef36 171 # exists() returns 1 or '' for true/false.
2432d6cc 172 return $data ? 1 : '';
696cadb7 173}
174
175sub delete_key {
176 my $self = shift;
c9f02899 177 my ($obj, $key) = @_;
e86cef36 178
c9f02899 179 my $sector = $self->_load_sector( $obj->_base_offset )
76c68c87 180 or return;
e86cef36 181
8af340bf 182 if ( $sector->staleness != $obj->_staleness ) {
183 return;
184 }
185
2432d6cc 186 return $sector->delete_key({
187 key_md5 => $self->_apply_digest( $key ),
188 allow_head => 0,
189 });
696cadb7 190}
191
192sub write_value {
193 my $self = shift;
c9f02899 194 my ($obj, $key, $value) = @_;
3976d8c9 195
764e6cb9 196 my $r = Scalar::Util::reftype( $value ) || '';
d49782fe 197 {
198 last if $r eq '';
199 last if $r eq 'HASH';
200 last if $r eq 'ARRAY';
201
202 DBM::Deep->_throw_error(
203 "Storage of references of type '$r' is not supported."
204 );
205 }
206
764e6cb9 207 my ($class, $type);
68369f26 208 if ( !defined $value ) {
4eee718c 209 $class = 'DBM::Deep::Engine::Sector::Null';
68369f26 210 }
764e6cb9 211 elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
d49782fe 212 if ( $r eq 'ARRAY' && tied(@$value) ) {
25eb38b8 213 DBM::Deep->_throw_error( "Cannot store something that is tied." );
d49782fe 214 }
215 if ( $r eq 'HASH' && tied(%$value) ) {
25eb38b8 216 DBM::Deep->_throw_error( "Cannot store something that is tied." );
d49782fe 217 }
764e6cb9 218 $class = 'DBM::Deep::Engine::Sector::Reference';
ed38e772 219 $type = substr( $r, 0, 1 );
764e6cb9 220 }
68369f26 221 else {
4eee718c 222 $class = 'DBM::Deep::Engine::Sector::Scalar';
68369f26 223 }
3976d8c9 224
2432d6cc 225 # This will be a Reference sector
226 my $sector = $self->_load_sector( $obj->_base_offset )
76c68c87 227 or die "Cannot write to a deleted spot in DBM::Deep.\n";
ed38e772 228
8af340bf 229 if ( $sector->staleness != $obj->_staleness ) {
230 die "Cannot write to a deleted spot in DBM::Deep.\n";
231 }
232
2432d6cc 233 # Create this after loading the reference sector in case something bad happens.
234 # This way, we won't allocate value sector(s) needlessly.
4eee718c 235 my $value_sector = $class->new({
236 engine => $self,
237 data => $value,
764e6cb9 238 type => $type,
4eee718c 239 });
240
2432d6cc 241 $sector->write_data({
242 key => $key,
243 key_md5 => $self->_apply_digest( $key ),
244 value => $value_sector,
245 });
764e6cb9 246
247 # This code is to make sure we write all the values in the $value to the disk
ed38e772 248 # and to make sure all changes to $value after the assignment are reflected
249 # on disk. This may be counter-intuitive at first, but it is correct dwimmery.
250 # NOTE - simply tying $value won't perform a STORE on each value. Hence, the
251 # copy to a temp value.
764e6cb9 252 if ( $r eq 'ARRAY' ) {
ed38e772 253 my @temp = @$value;
764e6cb9 254 tie @$value, 'DBM::Deep', {
255 base_offset => $value_sector->offset,
8af340bf 256 staleness => $value_sector->staleness,
764e6cb9 257 storage => $self->storage,
c9f02899 258 engine => $self,
764e6cb9 259 };
ed38e772 260 @$value = @temp;
764e6cb9 261 bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
262 }
263 elsif ( $r eq 'HASH' ) {
ed38e772 264 my %temp = %$value;
764e6cb9 265 tie %$value, 'DBM::Deep', {
266 base_offset => $value_sector->offset,
8af340bf 267 staleness => $value_sector->staleness,
764e6cb9 268 storage => $self->storage,
c9f02899 269 engine => $self,
764e6cb9 270 };
ed38e772 271
272 %$value = %temp;
764e6cb9 273 bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
274 }
275
276 return 1;
696cadb7 277}
278
8af340bf 279# XXX Add staleness here
696cadb7 280sub get_next_key {
281 my $self = shift;
c9f02899 282 my ($obj, $prev_key) = @_;
ed38e772 283
284 # XXX Need to add logic about resetting the iterator if any key in the reference has changed
285 unless ( $prev_key ) {
0f4ed906 286 $obj->{iterator} = DBM::Deep::Iterator->new({
c9f02899 287 base_offset => $obj->_base_offset,
ed38e772 288 engine => $self,
289 });
4eee718c 290 }
291
8af340bf 292 return $obj->{iterator}->get_next_key( $obj );
696cadb7 293}
294
295################################################################################
296
297sub setup_fh {
298 my $self = shift;
299 my ($obj) = @_;
300
301 # We're opening the file.
302 unless ( $obj->_base_offset ) {
696cadb7 303 my $bytes_read = $self->_read_file_header;
696cadb7 304
305 # Creating a new file
306 unless ( $bytes_read ) {
307 $self->_write_file_header;
c83524c6 308
309 # 1) Create Array/Hash entry
8fbac729 310 my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
311 engine => $self,
312 type => $obj->_type,
313 });
314 $obj->{base_offset} = $initial_reference->offset;
8af340bf 315 $obj->{staleness} = $initial_reference->staleness;
c83524c6 316
8fbac729 317 $self->storage->flush;
696cadb7 318 }
319 # Reading from an existing file
320 else {
321 $obj->{base_offset} = $bytes_read;
764e6cb9 322 my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
323 engine => $self,
324 offset => $obj->_base_offset,
325 });
326 unless ( $initial_reference ) {
696cadb7 327 DBM::Deep->_throw_error("Corrupted file, no master index record");
328 }
329
764e6cb9 330 unless ($obj->_type eq $initial_reference->type) {
696cadb7 331 DBM::Deep->_throw_error("File type mismatch");
332 }
8af340bf 333
334 $obj->{staleness} = $initial_reference->staleness;
696cadb7 335 }
336 }
696cadb7 337
696cadb7 338 return 1;
339}
340
8cb9205a 341sub begin_work {
c9f02899 342 my $self = shift;
8cb9205a 343 my ($obj) = @_;
344
345 if ( $self->trans_id ) {
6f999f6e 346 DBM::Deep->_throw_error( "Cannot begin_work within a transaction" );
8cb9205a 347 }
348
cf03415a 349 my @slots = $self->read_txn_slots;
8cb9205a 350 for my $i ( 1 .. @slots ) {
351 next if $slots[$i];
352 $slots[$i] = 1;
353 $self->set_trans_id( $i );
354 last;
355 }
cf03415a 356 $self->write_txn_slots( @slots );
8cb9205a 357
358 if ( !$self->trans_id ) {
6f999f6e 359 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
8cb9205a 360 }
361
362 return;
c9f02899 363}
696cadb7 364
8cb9205a 365sub rollback {
696cadb7 366 my $self = shift;
8cb9205a 367 my ($obj) = @_;
368
369 if ( !$self->trans_id ) {
6f999f6e 370 DBM::Deep->_throw_error( "Cannot rollback without a transaction" );
371 }
372
373 # Each entry is the file location for a bucket that has a modification for
374 # this transaction. The entries need to be expunged.
375 foreach my $entry (@{ $self->get_entries } ) {
376 # Remove the entry here
377 my $read_loc = $entry
378 + $self->hash_size
379 + $self->byte_size
17164f8a 380 + $self->trans_id * ( $self->byte_size + 4 );
6f999f6e 381
382 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
383 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
384 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
385
386 if ( $data_loc > 1 ) {
387 $self->_load_sector( $data_loc )->free;
388 }
8cb9205a 389 }
6f999f6e 390
391 $self->clear_entries;
392
cf03415a 393 my @slots = $self->read_txn_slots;
6f999f6e 394 $slots[$self->trans_id] = 0;
cf03415a 395 $self->write_txn_slots( @slots );
6de4e4e9 396 $self->inc_txn_staleness_counter( $self->trans_id );
6f999f6e 397 $self->set_trans_id( 0 );
398
399 return 1;
c9f02899 400}
696cadb7 401
8cb9205a 402sub commit {
c9f02899 403 my $self = shift;
8cb9205a 404 my ($obj) = @_;
405
406 if ( !$self->trans_id ) {
6f999f6e 407 DBM::Deep->_throw_error( "Cannot commit without a transaction" );
408 }
409
6f999f6e 410 foreach my $entry (@{ $self->get_entries } ) {
6f999f6e 411 # Overwrite the entry in head with the entry in trans_id
412 my $base = $entry
413 + $self->hash_size
414 + $self->byte_size;
415
416 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
417 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
418 my $trans_loc = $self->storage->read_at(
17164f8a 419 $base + $self->trans_id * ( $self->byte_size + 4 ), $self->byte_size,
6f999f6e 420 );
421
422 $self->storage->print_at( $base, $trans_loc );
423 $self->storage->print_at(
17164f8a 424 $base + $self->trans_id * ( $self->byte_size + 4 ),
6de4e4e9 425 pack( $StP{$self->byte_size} . ' N', (0) x 2 ),
6f999f6e 426 );
427
428 if ( $head_loc > 1 ) {
429 $self->_load_sector( $head_loc )->free;
430 }
8cb9205a 431 }
6f999f6e 432
433 $self->clear_entries;
434
cf03415a 435 my @slots = $self->read_txn_slots;
6f999f6e 436 $slots[$self->trans_id] = 0;
cf03415a 437 $self->write_txn_slots( @slots );
6de4e4e9 438 $self->inc_txn_staleness_counter( $self->trans_id );
6f999f6e 439 $self->set_trans_id( 0 );
440
441 return 1;
8cb9205a 442}
443
cf03415a 444sub read_txn_slots {
8cb9205a 445 my $self = shift;
cf03415a 446 return split '', unpack( 'b32',
447 $self->storage->read_at(
448 $self->trans_loc, 4,
449 )
450 );
8cb9205a 451}
452
cf03415a 453sub write_txn_slots {
8cb9205a 454 my $self = shift;
455 $self->storage->print_at( $self->trans_loc,
cf03415a 456 pack( 'b32', join('', @_) ),
8cb9205a 457 );
c9f02899 458}
696cadb7 459
cf03415a 460sub get_running_txn_ids {
461 my $self = shift;
462 my @transactions = $self->read_txn_slots;
463 my @trans_ids = grep { $transactions[$_] } 0 .. $#transactions;
464}
465
6de4e4e9 466sub get_txn_staleness_counter {
467 my $self = shift;
468 my ($trans_id) = @_;
b4e17919 469
470 # Hardcode staleness of 0 for the HEAD
471 return 0 unless $trans_id;
472
41e27db3 473 my $x = unpack( 'N',
6de4e4e9 474 $self->storage->read_at(
b4e17919 475 $self->trans_loc + 4 * $trans_id,
41e27db3 476 4,
6de4e4e9 477 )
478 );
41e27db3 479 return $x;
6de4e4e9 480}
481
482sub inc_txn_staleness_counter {
483 my $self = shift;
484 my ($trans_id) = @_;
b4e17919 485
486 # Hardcode staleness of 0 for the HEAD
487 return unless $trans_id;
488
6de4e4e9 489 $self->storage->print_at(
b4e17919 490 $self->trans_loc + 4 * $trans_id,
6de4e4e9 491 pack( 'N', $self->get_txn_staleness_counter( $trans_id ) + 1 ),
492 );
493}
494
6f999f6e 495sub get_entries {
496 my $self = shift;
497 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
498}
499
500sub add_entry {
501 my $self = shift;
502 my ($trans_id, $loc) = @_;
503
6f999f6e 504 $self->{entries}{$trans_id} ||= {};
505 $self->{entries}{$trans_id}{$loc} = undef;
6f999f6e 506}
507
65bd261b 508# If the buckets are being relocated because of a reindexing, the entries
509# mechanism needs to be made aware of it.
510sub reindex_entry {
511 my $self = shift;
512 my ($old_loc, $new_loc) = @_;
513
514 TRANS:
515 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
516 foreach my $orig_loc ( keys %{ $locs } ) {
517 if ( $orig_loc == $old_loc ) {
518 delete $locs->{orig_loc};
519 $locs->{$new_loc} = undef;
520 next TRANS;
521 }
522 }
523 }
524}
525
6f999f6e 526sub clear_entries {
527 my $self = shift;
6f999f6e 528 delete $self->{entries}{$self->trans_id};
529}
530
c9f02899 531################################################################################
b9ec359f 532
c9f02899 533{
534 my $header_fixed = length( SIG_FILE ) + 1 + 4 + 4;
696cadb7 535
c9f02899 536 sub _write_file_header {
537 my $self = shift;
696cadb7 538
d58fd793 539 my $header_var = 1 + 1 + 4 + 4 * $self->num_txns + 3 * $self->byte_size;
696cadb7 540
c9f02899 541 my $loc = $self->storage->request_space( $header_fixed + $header_var );
c83524c6 542
c9f02899 543 $self->storage->print_at( $loc,
544 SIG_FILE,
545 SIG_HEADER,
546 pack('N', 1), # header version - at this point, we're at 9 bytes
547 pack('N', $header_var), # header size
548 # --- Above is $header_fixed. Below is $header_var
549 pack('C', $self->byte_size),
550 pack('C', $self->max_buckets),
b4e17919 551 pack('N', 0 ), # Transaction activeness bitfield
552 pack('N' . $self->num_txns, 0 x $self->num_txns ), # Transaction staleness counters
c9f02899 553 pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
554 pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
d58fd793 555 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
c9f02899 556 );
696cadb7 557
c9f02899 558 $self->set_trans_loc( $header_fixed + 2 );
cf03415a 559 $self->set_chains_loc( $header_fixed + 2 + 4 + 4 * $self->num_txns );
696cadb7 560
c9f02899 561 return;
696cadb7 562 }
563
c9f02899 564 sub _read_file_header {
565 my $self = shift;
696cadb7 566
c9f02899 567 my $buffer = $self->storage->read_at( 0, $header_fixed );
568 return unless length($buffer);
696cadb7 569
c9f02899 570 my ($file_signature, $sig_header, $header_version, $size) = unpack(
571 'A4 A N N', $buffer
572 );
b9ec359f 573
c9f02899 574 unless ( $file_signature eq SIG_FILE ) {
575 $self->storage->close;
576 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
577 }
696cadb7 578
c9f02899 579 unless ( $sig_header eq SIG_HEADER ) {
580 $self->storage->close;
581 DBM::Deep->_throw_error( "Old file version found." );
582 }
696cadb7 583
c9f02899 584 my $buffer2 = $self->storage->read_at( undef, $size );
585 my @values = unpack( 'C C', $buffer2 );
696cadb7 586
c9f02899 587 $self->set_trans_loc( $header_fixed + 2 );
cf03415a 588 $self->set_chains_loc( $header_fixed + 2 + 4 + 4 * $self->num_txns );
c9f02899 589
590 if ( @values < 2 || grep { !defined } @values ) {
591 $self->storage->close;
592 DBM::Deep->_throw_error("Corrupted file - bad header");
593 }
594
595 #XXX Add warnings if values weren't set right
596 @{$self}{qw(byte_size max_buckets)} = @values;
b9ec359f 597
d58fd793 598 my $header_var = 1 + 1 + 4 + 4 * $self->num_txns + 3 * $self->byte_size;
c9f02899 599 unless ( $size eq $header_var ) {
600 $self->storage->close;
601 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
602 }
603
604 return length($buffer) + length($buffer2);
605 }
696cadb7 606}
607
3976d8c9 608sub _load_sector {
609 my $self = shift;
610 my ($offset) = @_;
611
0f4ed906 612 # Add a catch for offset of 0 or 1
613 return if $offset <= 1;
614
3976d8c9 615 my $type = $self->storage->read_at( $offset, 1 );
b9ec359f 616 return if $type eq chr(0);
617
3976d8c9 618 if ( $type eq $self->SIG_ARRAY || $type eq $self->SIG_HASH ) {
619 return DBM::Deep::Engine::Sector::Reference->new({
620 engine => $self,
621 type => $type,
622 offset => $offset,
623 });
624 }
2432d6cc 625 # XXX Don't we need key_md5 here?
3976d8c9 626 elsif ( $type eq $self->SIG_BLIST ) {
627 return DBM::Deep::Engine::Sector::BucketList->new({
628 engine => $self,
629 type => $type,
630 offset => $offset,
631 });
632 }
d58fd793 633 elsif ( $type eq $self->SIG_INDEX ) {
634 return DBM::Deep::Engine::Sector::Index->new({
635 engine => $self,
636 type => $type,
637 offset => $offset,
638 });
639 }
68369f26 640 elsif ( $type eq $self->SIG_NULL ) {
641 return DBM::Deep::Engine::Sector::Null->new({
642 engine => $self,
643 type => $type,
644 offset => $offset,
645 });
646 }
647 elsif ( $type eq $self->SIG_DATA ) {
648 return DBM::Deep::Engine::Sector::Scalar->new({
649 engine => $self,
650 type => $type,
651 offset => $offset,
652 });
653 }
b9ec359f 654 # This was deleted from under us, so just return and let the caller figure it out.
655 elsif ( $type eq $self->SIG_FREE ) {
656 return;
657 }
3976d8c9 658
ed38e772 659 die "'$offset': Don't know what to do with type '$type'\n";
3976d8c9 660}
661
662sub _apply_digest {
663 my $self = shift;
664 return $self->{digest}->(@_);
665}
666
c0507636 667sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
668sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
669sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
670
ed38e772 671sub _add_free_sector {
672 my $self = shift;
c0507636 673 my ($multiple, $offset, $size) = @_;
b9ec359f 674
c0507636 675 my $chains_offset = $multiple * $self->byte_size;
b9ec359f 676
8af340bf 677 my $storage = $self->storage;
678
679 # Increment staleness.
c0507636 680 # XXX Can this increment+modulo be done by "&= 0x1" ?
d58fd793 681 my $staleness = unpack( $StP{STALE_SIZE()}, $storage->read_at( $offset + SIG_SIZE, STALE_SIZE ) );
682 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * STALE_SIZE ) );
683 $storage->print_at( $offset + SIG_SIZE, pack( $StP{STALE_SIZE()}, $staleness ) );
b9ec359f 684
8af340bf 685 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
686
687 $storage->print_at( $self->chains_loc + $chains_offset,
b9ec359f 688 pack( $StP{$self->byte_size}, $offset ),
689 );
690
8af340bf 691 # Record the old head in the new sector after the signature and staleness counter
d58fd793 692 $storage->print_at( $offset + SIG_SIZE + STALE_SIZE, $old_head );
b9ec359f 693}
694
c0507636 695sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
696sub _request_data_sector { shift->_request_sector( 1, @_ ) }
697sub _request_index_sector { shift->_request_sector( 2, @_ ) }
698
b9ec359f 699sub _request_sector {
700 my $self = shift;
c0507636 701 my ($multiple, $size) = @_;
b9ec359f 702
c0507636 703 my $chains_offset = $multiple * $self->byte_size;
b9ec359f 704
705 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
706 my $loc = unpack( $StP{$self->byte_size}, $old_head );
707
708 # We don't have any free sectors of the right size, so allocate a new one.
709 unless ( $loc ) {
8af340bf 710 my $offset = $self->storage->request_space( $size );
711
712 # Zero out the new sector. This also guarantees correct increases
713 # in the filesize.
714 $self->storage->print_at( $offset, chr(0) x $size );
715
716 return $offset;
b9ec359f 717 }
718
8af340bf 719 # Read the new head after the signature and the staleness counter
d58fd793 720 my $new_head = $self->storage->read_at( $loc + SIG_SIZE + STALE_SIZE, $self->byte_size );
b9ec359f 721 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
722
723 return $loc;
ed38e772 724}
725
696cadb7 726################################################################################
727
3976d8c9 728sub storage { $_[0]{storage} }
729sub byte_size { $_[0]{byte_size} }
730sub hash_size { $_[0]{hash_size} }
6a4f323c 731sub hash_chars { $_[0]{hash_chars} }
3976d8c9 732sub num_txns { $_[0]{num_txns} }
733sub max_buckets { $_[0]{max_buckets} }
c000ae6e 734sub blank_md5 { chr(0) x $_[0]->hash_size }
8fbac729 735
8cb9205a 736sub trans_id { $_[0]{trans_id} }
737sub set_trans_id { $_[0]{trans_id} = $_[1] }
738
c9f02899 739sub trans_loc { $_[0]{trans_loc} }
740sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
741
b9ec359f 742sub chains_loc { $_[0]{chains_loc} }
743sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
744
8fbac729 745################################################################################
746
0f4ed906 747package DBM::Deep::Iterator;
ed38e772 748
749sub new {
750 my $class = shift;
751 my ($args) = @_;
752
753 my $self = bless {
754 breadcrumbs => [],
755 engine => $args->{engine},
756 base_offset => $args->{base_offset},
ed38e772 757 }, $class;
758
759 Scalar::Util::weaken( $self->{engine} );
760
761 return $self;
762}
763
0f4ed906 764sub reset { $_[0]{breadcrumbs} = [] }
765
766sub get_sector_iterator {
ed38e772 767 my $self = shift;
0f4ed906 768 my ($loc) = @_;
769
770 my $sector = $self->{engine}->_load_sector( $loc )
771 or return;
772
773 if ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
774 return DBM::Deep::Iterator::Index->new({
775 iterator => $self,
776 sector => $sector,
777 });
778 }
779 elsif ( $sector->isa( 'DBM::Deep::Engine::Sector::BucketList' ) ) {
780 return DBM::Deep::Iterator::BucketList->new({
781 iterator => $self,
782 sector => $sector,
783 });
784 }
785 else {
786 die "Why did $loc make a $sector?";
787 }
ed38e772 788}
789
790sub get_next_key {
791 my $self = shift;
8af340bf 792 my ($obj) = @_;
ed38e772 793
794 my $crumbs = $self->{breadcrumbs};
0f4ed906 795 my $e = $self->{engine};
ed38e772 796
797 unless ( @$crumbs ) {
798 # This will be a Reference sector
0f4ed906 799 my $sector = $e->_load_sector( $self->{base_offset} )
b9ec359f 800 # If no sector is found, thist must have been deleted from under us.
801 or return;
8af340bf 802
803 if ( $sector->staleness != $obj->_staleness ) {
804 return;
805 }
806
0f4ed906 807 my $loc = $sector->get_blist_loc
808 or return;
809
810 push @$crumbs, $self->get_sector_iterator( $loc );
ed38e772 811 }
812
0f4ed906 813 FIND_NEXT_KEY: {
814 # We're at the end.
815 unless ( @$crumbs ) {
ed38e772 816 $self->reset;
0f4ed906 817 return;
ed38e772 818 }
819
0f4ed906 820 my $iterator = $crumbs->[-1];
821
822 # This level is done.
823 if ( $iterator->at_end ) {
824 pop @$crumbs;
825 redo FIND_NEXT_KEY;
2432d6cc 826 }
827
0f4ed906 828 if ( $iterator->isa( 'DBM::Deep::Iterator::Index' ) ) {
829 # If we don't have any more, it will be caught at the
830 # prior check.
831 if ( my $next = $iterator->get_next_iterator ) {
832 push @$crumbs, $next;
833 }
834 redo FIND_NEXT_KEY;
835 }
ed38e772 836
0f4ed906 837 unless ( $iterator->isa( 'DBM::Deep::Iterator::BucketList' ) ) {
838 DBM::Deep->_throw_error(
839 "Should have a bucketlist iterator here - instead have $iterator"
840 );
2432d6cc 841 }
842
0f4ed906 843 # At this point, we have a BucketList iterator
844 my $key = $iterator->get_next_key;
845 if ( defined $key ) {
846 return $key;
ed38e772 847 }
848
0f4ed906 849 # We hit the end of the bucketlist iterator, so redo
850 redo FIND_NEXT_KEY;
851 }
852
853 DBM::Deep->_throw_error( "get_next_key(): How did we get here?" );
854}
855
856package DBM::Deep::Iterator::Index;
857
858sub new {
859 my $self = bless $_[1] => $_[0];
860 $self->{curr_index} = 0;
861 return $self;
862}
863
864sub at_end {
865 my $self = shift;
866 return $self->{curr_index} >= $self->{iterator}{engine}->hash_chars;
867}
868
869sub get_next_iterator {
870 my $self = shift;
871
872 my $loc;
873 while ( !$loc ) {
874 return if $self->at_end;
875 $loc = $self->{sector}->get_entry( $self->{curr_index}++ );
ed38e772 876 }
877
0f4ed906 878 return $self->{iterator}->get_sector_iterator( $loc );
879}
880
881package DBM::Deep::Iterator::BucketList;
882
883sub new {
884 my $self = bless $_[1] => $_[0];
885 $self->{curr_index} = 0;
886 return $self;
887}
888
889sub at_end {
890 my $self = shift;
891 return $self->{curr_index} >= $self->{iterator}{engine}->max_buckets;
892}
893
894sub get_next_key {
895 my $self = shift;
896
897 return if $self->at_end;
898
9ce79856 899 my $data_loc = $self->{sector}->get_data_location_for({
900 allow_head => 1,
901 idx => $self->{curr_index}++,
902 }) or return;
903
904 my $key_sector = $self->{sector}->get_key_for( $self->{curr_index} - 1 );
905
906 #XXX Is this check necessary now?
0f4ed906 907 return unless $key_sector;
908
909 return $key_sector->data;
ed38e772 910}
911
3976d8c9 912package DBM::Deep::Engine::Sector;
913
914sub new {
915 my $self = bless $_[1], $_[0];
916 Scalar::Util::weaken( $self->{engine} );
917 $self->_init;
918 return $self;
919}
920sub _init {}
2432d6cc 921sub clone { die "Must be implemented in the child class" }
3976d8c9 922
923sub engine { $_[0]{engine} }
924sub offset { $_[0]{offset} }
925sub type { $_[0]{type} }
926
d58fd793 927sub base_size {
928 my $self = shift;
929 return $self->engine->SIG_SIZE + $self->engine->STALE_SIZE;
930}
b6fc126b 931
ed38e772 932sub free {
933 my $self = shift;
934
b6fc126b 935 my $e = $self->engine;
936
937 $e->storage->print_at( $self->offset, $e->SIG_FREE );
8af340bf 938 # Skip staleness counter
b6fc126b 939 $e->storage->print_at( $self->offset + $self->base_size,
940 chr(0) x ($self->size - $self->base_size),
b9ec359f 941 );
942
c0507636 943 my $free_meth = $self->free_meth;
944 $e->$free_meth( $self->offset, $self->size );
ed38e772 945
b9ec359f 946 return;
ed38e772 947}
3976d8c9 948
949package DBM::Deep::Engine::Sector::Data;
8fbac729 950
951our @ISA = qw( DBM::Deep::Engine::Sector );
952
3976d8c9 953# This is in bytes
954sub size { return 256 }
c0507636 955sub free_meth { return '_add_free_data_sector' }
3976d8c9 956
2432d6cc 957sub clone {
958 my $self = shift;
959 return ref($self)->new({
960 engine => $self->engine,
961 data => $self->data,
962 type => $self->type,
963 });
964}
965
3976d8c9 966package DBM::Deep::Engine::Sector::Scalar;
967
968our @ISA = qw( DBM::Deep::Engine::Sector::Data );
969
ad4ae302 970sub free {
971 my $self = shift;
972
973 my $chain_loc = $self->chain_loc;
974
975 $self->SUPER::free();
976
977 if ( $chain_loc ) {
978 $self->engine->_load_sector( $chain_loc )->free;
979 }
980
981 return;
982}
983
3976d8c9 984sub type { $_[0]{engine}->SIG_DATA }
8fbac729 985sub _init {
986 my $self = shift;
987
988 my $engine = $self->engine;
989
3976d8c9 990 unless ( $self->offset ) {
b6fc126b 991 my $data_section = $self->size - $self->base_size - 1 * $engine->byte_size - 1;
3976d8c9 992
c0507636 993 $self->{offset} = $engine->_request_data_sector( $self->size );
ad4ae302 994
2432d6cc 995 my $data = delete $self->{data};
ad4ae302 996 my $dlen = length $data;
997 my $continue = 1;
998 my $curr_offset = $self->offset;
999 while ( $continue ) {
1000
1001 my $next_offset = 0;
1002
1003 my ($leftover, $this_len, $chunk);
1004 if ( $dlen > $data_section ) {
1005 $leftover = 0;
1006 $this_len = $data_section;
1007 $chunk = substr( $data, 0, $this_len );
1008
1009 $dlen -= $data_section;
c0507636 1010 $next_offset = $engine->_request_data_sector( $self->size );
ad4ae302 1011 $data = substr( $data, $this_len );
1012 }
1013 else {
1014 $leftover = $data_section - $dlen;
1015 $this_len = $dlen;
1016 $chunk = $data;
1017
1018 $continue = 0;
1019 }
1020
8af340bf 1021 $engine->storage->print_at( $curr_offset, $self->type ); # Sector type
1022 # Skip staleness
d58fd793 1023 $engine->storage->print_at( $curr_offset + $self->base_size,
ad4ae302 1024 pack( $StP{$engine->byte_size}, $next_offset ), # Chain loc
1025 pack( $StP{1}, $this_len ), # Data length
1026 $chunk, # Data to be stored in this sector
1027 chr(0) x $leftover, # Zero-fill the rest
1028 );
1029
1030 $curr_offset = $next_offset;
1031 }
3976d8c9 1032
1033 return;
1034 }
1035}
1036
1037sub data_length {
1038 my $self = shift;
1039
ad4ae302 1040 my $buffer = $self->engine->storage->read_at(
b6fc126b 1041 $self->offset + $self->base_size + $self->engine->byte_size, 1
8fbac729 1042 );
ad4ae302 1043
1044 return unpack( $StP{1}, $buffer );
1045}
1046
1047sub chain_loc {
1048 my $self = shift;
1049 my $chain_loc = $self->engine->storage->read_at(
b6fc126b 1050 $self->offset + $self->base_size, $self->engine->byte_size,
ad4ae302 1051 );
1052 return unpack( $StP{$self->engine->byte_size}, $chain_loc );
3976d8c9 1053}
1054
1055sub data {
1056 my $self = shift;
8fbac729 1057
378b4748 1058 my $data;
1059 while ( 1 ) {
1060 my $chain_loc = $self->chain_loc;
ad4ae302 1061
378b4748 1062 $data .= $self->engine->storage->read_at(
b6fc126b 1063 $self->offset + $self->base_size + $self->engine->byte_size + 1, $self->data_length,
378b4748 1064 );
ad4ae302 1065
378b4748 1066 last unless $chain_loc;
1067
1068 $self = $self->engine->_load_sector( $chain_loc );
ad4ae302 1069 }
1070
1071 return $data;
8fbac729 1072}
1073
68369f26 1074package DBM::Deep::Engine::Sector::Null;
1075
1076our @ISA = qw( DBM::Deep::Engine::Sector::Data );
1077
1078sub type { $_[0]{engine}->SIG_NULL }
1079sub data_length { 0 }
1080sub data { return }
1081
1082sub _init {
1083 my $self = shift;
1084
1085 my $engine = $self->engine;
1086
1087 unless ( $self->offset ) {
b6fc126b 1088 my $leftover = $self->size - $self->base_size - 1 * $engine->byte_size - 1;
68369f26 1089
c0507636 1090 $self->{offset} = $engine->_request_data_sector( $self->size );
8af340bf 1091 $engine->storage->print_at( $self->offset, $self->type ); # Sector type
1092 # Skip staleness counter
b6fc126b 1093 $engine->storage->print_at( $self->offset + $self->base_size,
68369f26 1094 pack( $StP{$engine->byte_size}, 0 ), # Chain loc
1095 pack( $StP{1}, $self->data_length ), # Data length
1096 chr(0) x $leftover, # Zero-fill the rest
1097 );
1098
1099 return;
1100 }
1101}
1102
3976d8c9 1103package DBM::Deep::Engine::Sector::Reference;
8fbac729 1104
3976d8c9 1105our @ISA = qw( DBM::Deep::Engine::Sector::Data );
1106
1107sub _init {
1108 my $self = shift;
1109
d58fd793 1110 my $e = $self->engine;
3976d8c9 1111
1112 unless ( $self->offset ) {
ba075714 1113 my $classname = Scalar::Util::blessed( delete $self->{data} );
d58fd793 1114 my $leftover = $self->size - $self->base_size - 2 * $e->byte_size;
d4f34951 1115
1116 my $class_offset = 0;
1117 if ( defined $classname ) {
1118 my $class_sector = DBM::Deep::Engine::Sector::Scalar->new({
d58fd793 1119 engine => $e,
d4f34951 1120 data => $classname,
1121 });
1122 $class_offset = $class_sector->offset;
1123 }
3976d8c9 1124
c0507636 1125 $self->{offset} = $e->_request_data_sector( $self->size );
d58fd793 1126 $e->storage->print_at( $self->offset, $self->type ); # Sector type
8af340bf 1127 # Skip staleness counter
d58fd793 1128 $e->storage->print_at( $self->offset + $self->base_size,
1129 pack( $StP{$e->byte_size}, 0 ), # Index/BList loc
1130 pack( $StP{$e->byte_size}, $class_offset ), # Classname loc
c0507636 1131 chr(0) x $leftover, # Zero-fill the rest
3976d8c9 1132 );
8af340bf 1133 }
1134 else {
d58fd793 1135 $self->{type} = $e->storage->read_at( $self->offset, 1 );
3976d8c9 1136 }
764e6cb9 1137
8af340bf 1138 $self->{staleness} = unpack(
d58fd793 1139 $StP{$e->STALE_SIZE},
1140 $e->storage->read_at( $self->offset + $e->SIG_SIZE, $e->STALE_SIZE ),
8af340bf 1141 );
764e6cb9 1142
1143 return;
3976d8c9 1144}
1145
d938be6a 1146sub free {
1147 my $self = shift;
1148
1149 my $blist_loc = $self->get_blist_loc;
1150 $self->engine->_load_sector( $blist_loc )->free if $blist_loc;
1151
1152 my $class_loc = $self->get_class_offset;
1153 $self->engine->_load_sector( $class_loc )->free if $class_loc;
1154
1155 $self->SUPER::free();
1156}
1157
8af340bf 1158sub staleness { $_[0]{staleness} }
1159
2432d6cc 1160sub get_data_for {
1161 my $self = shift;
1162 my ($args) = @_;
1163
1164 # Assume that the head is not allowed unless otherwise specified.
1165 $args->{allow_head} = 0 unless exists $args->{allow_head};
1166
1167 # Assume we don't create a new blist location unless otherwise specified.
1168 $args->{create} = 0 unless exists $args->{create};
1169
1170 my $blist = $self->get_bucket_list({
1171 key_md5 => $args->{key_md5},
0f4ed906 1172 key => $args->{key},
2432d6cc 1173 create => $args->{create},
1174 });
1175 return unless $blist && $blist->{found};
1176
1177 # At this point, $blist knows where the md5 is. What it -doesn't- know yet
1178 # is whether or not this transaction has this key. That's part of the next
1179 # function call.
1180 my $location = $blist->get_data_location_for({
1181 allow_head => $args->{allow_head},
1182 }) or return;
1183
1184 return $self->engine->_load_sector( $location );
1185}
1186
1187sub write_data {
1188 my $self = shift;
1189 my ($args) = @_;
1190
1191 my $blist = $self->get_bucket_list({
1192 key_md5 => $args->{key_md5},
0f4ed906 1193 key => $args->{key},
2432d6cc 1194 create => 1,
6f999f6e 1195 }) or die "How did write_data fail (no blist)?!\n";
2432d6cc 1196
1197 # Handle any transactional bookkeeping.
1198 if ( $self->engine->trans_id ) {
d938be6a 1199 if ( ! $blist->has_md5 ) {
2432d6cc 1200 $blist->mark_deleted({
1201 trans_id => 0,
1202 });
1203 }
1204 }
1205 else {
cf03415a 1206 my @trans_ids = $self->engine->get_running_txn_ids;
d938be6a 1207 if ( $blist->has_md5 ) {
2432d6cc 1208 if ( @trans_ids ) {
1209 my $old_value = $blist->get_data_for;
1210 foreach my $other_trans_id ( @trans_ids ) {
b4e17919 1211 next if $blist->get_data_location_for({
1212 trans_id => $other_trans_id,
1213 allow_head => 0,
1214 });
2432d6cc 1215 $blist->write_md5({
1216 trans_id => $other_trans_id,
1217 key => $args->{key},
1218 key_md5 => $args->{key_md5},
1219 value => $old_value->clone,
1220 });
1221 }
1222 }
1223 }
1224 else {
1225 if ( @trans_ids ) {
1226 foreach my $other_trans_id ( @trans_ids ) {
1227 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
1228 $blist->mark_deleted({
1229 trans_id => $other_trans_id,
1230 });
1231 }
1232 }
1233 }
1234 }
1235
6f999f6e 1236 #XXX Is this safe to do transactionally?
2432d6cc 1237 # Free the place we're about to write to.
1238 if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
1239 $blist->get_data_for({ allow_head => 0 })->free;
1240 }
1241
1242 $blist->write_md5({
1243 key => $args->{key},
1244 key_md5 => $args->{key_md5},
1245 value => $args->{value},
1246 });
1247}
1248
1249sub delete_key {
1250 my $self = shift;
1251 my ($args) = @_;
1252
1253 # XXX What should happen if this fails?
1254 my $blist = $self->get_bucket_list({
1255 key_md5 => $args->{key_md5},
1256 }) or die "How did delete_key fail (no blist)?!\n";
1257
6f999f6e 1258 # Save the location so that we can free the data
1259 my $location = $blist->get_data_location_for({
1260 allow_head => 0,
1261 });
26897a1c 1262 my $old_value = $location && $self->engine->_load_sector( $location );
6f999f6e 1263
1264 if ( $self->engine->trans_id == 0 ) {
cf03415a 1265 my @trans_ids = $self->engine->get_running_txn_ids;
6f999f6e 1266 if ( @trans_ids ) {
1267 foreach my $other_trans_id ( @trans_ids ) {
1268 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
1269 $blist->write_md5({
1270 trans_id => $other_trans_id,
1271 key => $args->{key},
1272 key_md5 => $args->{key_md5},
1273 value => $old_value->clone,
1274 });
1275 }
1276 }
1277 }
1278
1279 $blist->mark_deleted( $args );
1280
26897a1c 1281 my $data;
1282 if ( $old_value ) {
1283 $data = $old_value->data;
1284 $old_value->free;
1285 }
6f999f6e 1286
1287 return $data;
2432d6cc 1288}
1289
3976d8c9 1290sub get_blist_loc {
1291 my $self = shift;
1292
2432d6cc 1293 my $e = $self->engine;
b6fc126b 1294 my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
2432d6cc 1295 return unpack( $StP{$e->byte_size}, $blist_loc );
3976d8c9 1296}
1297
1298sub get_bucket_list {
1299 my $self = shift;
1300 my ($args) = @_;
4eee718c 1301 $args ||= {};
3976d8c9 1302
1303 # XXX Add in check here for recycling?
1304
1305 my $engine = $self->engine;
1306
1307 my $blist_loc = $self->get_blist_loc;
1308
1309 # There's no index or blist yet
1310 unless ( $blist_loc ) {
1311 return unless $args->{create};
1312
1313 my $blist = DBM::Deep::Engine::Sector::BucketList->new({
2432d6cc 1314 engine => $engine,
1315 key_md5 => $args->{key_md5},
3976d8c9 1316 });
2432d6cc 1317
b6fc126b 1318 $engine->storage->print_at( $self->offset + $self->base_size,
3976d8c9 1319 pack( $StP{$engine->byte_size}, $blist->offset ),
1320 );
2432d6cc 1321
3976d8c9 1322 return $blist;
1323 }
1324
d938be6a 1325 # Add searching here through the index layers, if any
1326 my $sector = $engine->_load_sector( $blist_loc )
1327 or die "Cannot read sector at $blist_loc in get_bucket_list()";
1328 my $i = 0;
1329 my $last_sector = undef;
1330 while ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
6a4f323c 1331 $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
d938be6a 1332 $last_sector = $sector;
625a24b4 1333 if ( $blist_loc ) {
1334 $sector = $engine->_load_sector( $blist_loc )
1335 or die "Cannot read sector at $blist_loc in get_bucket_list()";
1336 }
1337 else {
1338 $sector = undef;
1339 last;
1340 }
1341 }
1342
1343 # This means we went through the Index sector(s) and found an empty slot
1344 unless ( $sector ) {
1345 return unless $args->{create};
1346
1347 die "No last_sector when attempting to build a new entry"
1348 unless $last_sector;
1349
1350 my $blist = DBM::Deep::Engine::Sector::BucketList->new({
1351 engine => $engine,
1352 key_md5 => $args->{key_md5},
1353 });
1354
1355 $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
1356
1357 return $blist;
d938be6a 1358 }
1359
1360 $sector->find_md5( $args->{key_md5} );
1361
1362 # See whether or not we need to reindex the bucketlist
6a4f323c 1363 if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {
6a4f323c 1364 my $new_index = DBM::Deep::Engine::Sector::Index->new({
1365 engine => $engine,
1366 });
1367
1368 my %blist_cache;
65bd261b 1369 #XXX q.v. the comments for this function.
1370 foreach my $entry ( $sector->chopped_up ) {
1371 my ($spot, $md5) = @{$entry};
6a4f323c 1372 my $idx = ord( substr( $md5, $i, 1 ) );
1373
0f4ed906 1374 # XXX This is inefficient
6a4f323c 1375 my $blist = $blist_cache{$idx}
1376 ||= DBM::Deep::Engine::Sector::BucketList->new({
1377 engine => $engine,
1378 });
1379
1380 $new_index->set_entry( $idx => $blist->offset );
1381
65bd261b 1382 my $new_spot = $blist->write_at_next_open( $md5 );
1383 $engine->reindex_entry( $spot => $new_spot );
6a4f323c 1384 }
1385
0f4ed906 1386 # Handle the new item separately.
1387 {
1388 my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
1389 my $blist = $blist_cache{$idx}
1390 ||= DBM::Deep::Engine::Sector::BucketList->new({
1391 engine => $engine,
1392 });
1393
1394 $new_index->set_entry( $idx => $blist->offset );
1395
1396 #XXX THIS IS HACKY!
1397 $blist->find_md5( $args->{key_md5} );
1398 $blist->write_md5({
1399 key => $args->{key},
1400 key_md5 => $args->{key_md5},
1401 value => DBM::Deep::Engine::Sector::Null->new({
1402 engine => $engine,
1403 data => undef,
1404 }),
1405 });
1406 }
1407
6a4f323c 1408 if ( $last_sector ) {
1409 $last_sector->set_entry(
1410 ord( substr( $args->{key_md5}, $i - 1, 1 ) ),
1411 $new_index->offset,
1412 );
1413 } else {
1414 $engine->storage->print_at( $self->offset + $self->base_size,
1415 pack( $StP{$engine->byte_size}, $new_index->offset ),
1416 );
1417 }
1418
1419 $sector->free;
1420
1421 $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
1422 $sector->find_md5( $args->{key_md5} );
d938be6a 1423 }
1424
1425 return $sector;
3976d8c9 1426}
1427
d938be6a 1428sub get_class_offset {
ba075714 1429 my $self = shift;
1430
d938be6a 1431 my $e = $self->engine;
1432 return unpack(
1433 $StP{$e->byte_size},
1434 $e->storage->read_at(
1435 $self->offset + $self->base_size + 1 * $e->byte_size, $e->byte_size,
1436 ),
ba075714 1437 );
d938be6a 1438}
1439
1440sub get_classname {
1441 my $self = shift;
1442
1443 my $class_offset = $self->get_class_offset;
ba075714 1444
d4f34951 1445 return unless $class_offset;
ba075714 1446
d4f34951 1447 return $self->engine->_load_sector( $class_offset )->data;
ba075714 1448}
1449
764e6cb9 1450sub data {
1451 my $self = shift;
1452
1453 my $new_obj = DBM::Deep->new({
1454 type => $self->type,
1455 base_offset => $self->offset,
8af340bf 1456 staleness => $self->staleness,
764e6cb9 1457 storage => $self->engine->storage,
c9f02899 1458 engine => $self->engine,
764e6cb9 1459 });
1460
ba075714 1461 if ( $self->engine->storage->{autobless} ) {
1462 my $classname = $self->get_classname;
1463 if ( defined $classname ) {
1464 bless $new_obj, $classname;
1465 }
1466 }
1467
764e6cb9 1468 return $new_obj;
1469}
1470
3976d8c9 1471package DBM::Deep::Engine::Sector::BucketList;
1472
1473our @ISA = qw( DBM::Deep::Engine::Sector );
1474
1475sub _init {
1476 my $self = shift;
1477
1478 my $engine = $self->engine;
1479
1480 unless ( $self->offset ) {
c000ae6e 1481 my $leftover = $self->size - $self->base_size;
3976d8c9 1482
c0507636 1483 $self->{offset} = $engine->_request_blist_sector( $self->size );
8af340bf 1484 $engine->storage->print_at( $self->offset, $engine->SIG_BLIST ); # Sector type
1485 # Skip staleness counter
b6fc126b 1486 $engine->storage->print_at( $self->offset + $self->base_size,
3976d8c9 1487 chr(0) x $leftover, # Zero-fill the data
1488 );
1489 }
8fbac729 1490
2432d6cc 1491 if ( $self->{key_md5} ) {
1492 $self->find_md5;
1493 }
1494
8fbac729 1495 return $self;
1496}
8fbac729 1497
3976d8c9 1498sub size {
1499 my $self = shift;
2432d6cc 1500 unless ( $self->{size} ) {
1501 my $e = $self->engine;
d938be6a 1502 # Base + numbuckets * bucketsize
1503 $self->{size} = $self->base_size + $e->max_buckets * $self->bucket_size;
2432d6cc 1504 }
1505 return $self->{size};
c000ae6e 1506}
1507
c0507636 1508sub free_meth { return '_add_free_blist_sector' }
1509
c000ae6e 1510sub bucket_size {
1511 my $self = shift;
2432d6cc 1512 unless ( $self->{bucket_size} ) {
1513 my $e = $self->engine;
6de4e4e9 1514 # Key + head (location) + transactions (location + staleness-counter)
17164f8a 1515 my $location_size = $e->byte_size + $e->num_txns * ( $e->byte_size + 4 );
6de4e4e9 1516 $self->{bucket_size} = $e->hash_size + $location_size;
2432d6cc 1517 }
1518 return $self->{bucket_size};
3976d8c9 1519}
8fbac729 1520
65bd261b 1521# XXX This is such a poor hack. I need to rethink this code.
6a4f323c 1522sub chopped_up {
1523 my $self = shift;
1524
1525 my $e = $self->engine;
1526
625a24b4 1527 my @buckets;
6a4f323c 1528 foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
65bd261b 1529 my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
1530 my $md5 = $e->storage->read_at( $spot, $e->hash_size );
6a4f323c 1531
1532 last if $md5 eq $e->blank_md5;
1533
1534 my $rest = $e->storage->read_at( undef, $self->bucket_size - $e->hash_size );
65bd261b 1535 push @buckets, [ $spot, $md5 . $rest ];
6a4f323c 1536 }
1537
625a24b4 1538 return @buckets;
6a4f323c 1539}
1540
1541sub write_at_next_open {
1542 my $self = shift;
65bd261b 1543 my ($entry) = @_;
6a4f323c 1544
1545 #XXX This is such a hack!
625a24b4 1546 $self->{_next_open} = 0 unless exists $self->{_next_open};
6a4f323c 1547
65bd261b 1548 my $spot = $self->offset + $self->base_size + $self->{_next_open}++ * $self->bucket_size;
1549 $self->engine->storage->print_at( $spot, $entry );
1550
1551 return $spot;
6a4f323c 1552}
1553
3976d8c9 1554sub has_md5 {
c000ae6e 1555 my $self = shift;
2432d6cc 1556 unless ( exists $self->{found} ) {
1557 $self->find_md5;
1558 }
1559 return $self->{found};
c000ae6e 1560}
1561
1562sub find_md5 {
1563 my $self = shift;
c000ae6e 1564
2432d6cc 1565 $self->{found} = undef;
1566 $self->{idx} = -1;
c000ae6e 1567
d938be6a 1568 if ( @_ ) {
1569 $self->{key_md5} = shift;
1570 }
1571
2432d6cc 1572 # If we don't have an MD5, then what are we supposed to do?
1573 unless ( exists $self->{key_md5} ) {
6f999f6e 1574 DBM::Deep->_throw_error( "Cannot find_md5 without a key_md5 set" );
2432d6cc 1575 }
8cb9205a 1576
2432d6cc 1577 my $e = $self->engine;
1578 foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
1579 my $potential = $e->storage->read_at(
1580 $self->offset + $self->base_size + $idx * $self->bucket_size, $e->hash_size,
1581 );
1582
1583 if ( $potential eq $e->blank_md5 ) {
1584 $self->{idx} = $idx;
1585 return;
1586 }
8cb9205a 1587
2432d6cc 1588 if ( $potential eq $self->{key_md5} ) {
1589 $self->{found} = 1;
1590 $self->{idx} = $idx;
1591 return;
8cb9205a 1592 }
c000ae6e 1593 }
1594
1595 return;
3976d8c9 1596}
1597
1598sub write_md5 {
1599 my $self = shift;
2432d6cc 1600 my ($args) = @_;
2432d6cc 1601
6f999f6e 1602 DBM::Deep->_throw_error( "write_md5: no key" ) unless exists $args->{key};
1603 DBM::Deep->_throw_error( "write_md5: no key_md5" ) unless exists $args->{key_md5};
1604 DBM::Deep->_throw_error( "write_md5: no value" ) unless exists $args->{value};
3976d8c9 1605
1606 my $engine = $self->engine;
6f999f6e 1607
1608 $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
1609
2432d6cc 1610 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
6f999f6e 1611 $engine->add_entry( $args->{trans_id}, $spot );
4eee718c 1612
2432d6cc 1613 unless ($self->{found}) {
4eee718c 1614 my $key_sector = DBM::Deep::Engine::Sector::Scalar->new({
6f999f6e 1615 engine => $engine,
2432d6cc 1616 data => $args->{key},
4eee718c 1617 });
1618
1619 $engine->storage->print_at( $spot,
2432d6cc 1620 $args->{key_md5},
6f999f6e 1621 pack( $StP{$engine->byte_size}, $key_sector->offset ),
4eee718c 1622 );
1623 }
1624
6f999f6e 1625 my $loc = $spot
2432d6cc 1626 + $engine->hash_size
1627 + $engine->byte_size
17164f8a 1628 + $args->{trans_id} * ( $engine->byte_size + 4 );
6f999f6e 1629
1630 $engine->storage->print_at( $loc,
2432d6cc 1631 pack( $StP{$engine->byte_size}, $args->{value}->offset ),
6de4e4e9 1632 pack( 'N', $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
2432d6cc 1633 );
1634}
1635
1636sub mark_deleted {
1637 my $self = shift;
1638 my ($args) = @_;
6f999f6e 1639 $args ||= {};
1640
1641 my $engine = $self->engine;
1642
1643 $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
2432d6cc 1644
1645 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
6f999f6e 1646 $engine->add_entry( $args->{trans_id}, $spot );
1647
1648 my $loc = $spot
1649 + $engine->hash_size
1650 + $engine->byte_size
17164f8a 1651 + $args->{trans_id} * ( $engine->byte_size + 4 );
6f999f6e 1652
1653 $engine->storage->print_at( $loc,
1654 pack( $StP{$engine->byte_size}, 1 ), # 1 is the marker for deleted
6de4e4e9 1655 pack( 'N', $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
3976d8c9 1656 );
1657}
1658
e86cef36 1659sub delete_md5 {
3976d8c9 1660 my $self = shift;
2432d6cc 1661 my ($args) = @_;
3976d8c9 1662
e86cef36 1663 my $engine = $self->engine;
2432d6cc 1664 return undef unless $self->{found};
4eee718c 1665
1666 # Save the location so that we can free the data
2432d6cc 1667 my $location = $self->get_data_location_for({
1668 allow_head => 0,
1669 });
1670 my $key_sector = $self->get_key_for;
4eee718c 1671
6f999f6e 1672 #XXX This isn't going to work right and you know it! This eradicates data
1673 # that we're not ready to eradicate just yet.
2432d6cc 1674 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
4eee718c 1675 $engine->storage->print_at( $spot,
1676 $engine->storage->read_at(
1677 $spot + $self->bucket_size,
2432d6cc 1678 $self->bucket_size * ( $engine->num_txns - $self->{idx} - 1 ),
4eee718c 1679 ),
1680 chr(0) x $self->bucket_size,
e86cef36 1681 );
1682
3ed26433 1683 $key_sector->free;
1684
ed38e772 1685 my $data_sector = $self->engine->_load_sector( $location );
1686 my $data = $data_sector->data;
ed38e772 1687 $data_sector->free;
5c0f86e1 1688
1689 return $data;
e86cef36 1690}
1691
ed38e772 1692sub get_data_location_for {
e86cef36 1693 my $self = shift;
2432d6cc 1694 my ($args) = @_;
1695 $args ||= {};
1696
1697 $args->{allow_head} = 0 unless exists $args->{allow_head};
1698 $args->{trans_id} = $self->engine->trans_id unless exists $args->{trans_id};
1699 $args->{idx} = $self->{idx} unless exists $args->{idx};
e86cef36 1700
6de4e4e9 1701 my $e = $self->engine;
1702
1703 my $spot = $self->offset + $self->base_size
2432d6cc 1704 + $args->{idx} * $self->bucket_size
6de4e4e9 1705 + $e->hash_size
1706 + $e->byte_size
17164f8a 1707 + $args->{trans_id} * ( $e->byte_size + 4 );
6de4e4e9 1708
1709 my $buffer = $e->storage->read_at(
1710 $spot,
17164f8a 1711 $e->byte_size + 4,
3976d8c9 1712 );
6de4e4e9 1713 my ($loc, $staleness) = unpack( $StP{$e->byte_size} . ' N', $buffer );
1714
1715 # We have found an entry that is old, so get rid of it
41e27db3 1716 if ( $staleness != (my $s = $e->get_txn_staleness_counter( $args->{trans_id} ) ) ) {
6de4e4e9 1717 $e->storage->print_at(
1718 $spot,
1719 pack( $StP{$e->byte_size} . ' N', (0) x 2 ),
1720 );
1721 $loc = 0;
1722 }
8cb9205a 1723
1724 # If we're in a transaction and we never wrote to this location, try the
1725 # HEAD instead.
2432d6cc 1726 if ( $args->{trans_id} && !$loc && $args->{allow_head} ) {
1727 return $self->get_data_location_for({
1728 trans_id => 0,
1729 allow_head => 1,
17164f8a 1730 idx => $args->{idx},
2432d6cc 1731 });
8cb9205a 1732 }
2432d6cc 1733 return $loc <= 1 ? 0 : $loc;
e86cef36 1734}
1735
1736sub get_data_for {
1737 my $self = shift;
2432d6cc 1738 my ($args) = @_;
1739 $args ||= {};
e86cef36 1740
2432d6cc 1741 return unless $self->{found};
1742 my $location = $self->get_data_location_for({
1743 allow_head => $args->{allow_head},
1744 });
ed38e772 1745 return $self->engine->_load_sector( $location );
1746}
1747
1748sub get_key_for {
1749 my $self = shift;
1750 my ($idx) = @_;
2432d6cc 1751 $idx = $self->{idx} unless defined $idx;
ed38e772 1752
0f4ed906 1753 if ( $idx >= $self->engine->max_buckets ) {
1754 DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx" );
1755 }
1756
ed38e772 1757 my $location = $self->engine->storage->read_at(
1758 $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
1759 $self->engine->byte_size,
1760 );
1761 $location = unpack( $StP{$self->engine->byte_size}, $location );
1762 return unless $location;
68369f26 1763 return $self->engine->_load_sector( $location );
3976d8c9 1764}
696cadb7 1765
8af340bf 1766package DBM::Deep::Engine::Sector::Index;
76c68c87 1767
1768our @ISA = qw( DBM::Deep::Engine::Sector );
1769
1770sub _init {
1771 my $self = shift;
1772
1773 my $engine = $self->engine;
1774
1775 unless ( $self->offset ) {
1776 my $leftover = $self->size - $self->base_size;
1777
c0507636 1778 $self->{offset} = $engine->_request_index_sector( $self->size );
6a4f323c 1779 $engine->storage->print_at( $self->offset, $engine->SIG_INDEX ); # Sector type
8af340bf 1780 # Skip staleness counter
b6fc126b 1781 $engine->storage->print_at( $self->offset + $self->base_size,
d58fd793 1782 chr(0) x $leftover, # Zero-fill the rest
76c68c87 1783 );
1784 }
1785
76c68c87 1786 return $self;
1787}
1788
76c68c87 1789sub size {
1790 my $self = shift;
1791 unless ( $self->{size} ) {
1792 my $e = $self->engine;
d58fd793 1793 $self->{size} = $self->base_size + $e->byte_size * $e->hash_chars;
76c68c87 1794 }
1795 return $self->{size};
1796}
1797
c0507636 1798sub free_meth { return '_add_free_index_sector' }
1799
d938be6a 1800sub free {
1801 my $self = shift;
1802 my $e = $self->engine;
1803
1804 for my $i ( 0 .. $e->hash_chars - 1 ) {
1805 my $l = $self->location_for( $i ) or next;
1806 $e->_load_sector( $l )->free;
1807 }
1808
1809 $self->SUPER::free();
1810}
1811
6a4f323c 1812sub _loc_for {
1813 my $self = shift;
1814 my ($idx) = @_;
1815 return $self->offset + $self->base_size + $idx * $self->engine->byte_size;
1816}
1817
1818sub get_entry {
d938be6a 1819 my $self = shift;
1820 my ($idx) = @_;
1821
1822 my $e = $self->engine;
1823
0f4ed906 1824 die "get_entry: Out of range ($idx)"
1825 if $idx < 0 || $idx >= $e->hash_chars;
1826
d938be6a 1827 return unpack(
1828 $StP{$e->byte_size},
6a4f323c 1829 $e->storage->read_at( $self->_loc_for( $idx ), $e->byte_size ),
1830 );
1831}
1832
1833sub set_entry {
1834 my $self = shift;
1835 my ($idx, $loc) = @_;
1836
0f4ed906 1837 my $e = $self->engine;
1838
1839 die "set_entry: Out of range ($idx)"
1840 if $idx < 0 || $idx >= $e->hash_chars;
1841
6a4f323c 1842 $self->engine->storage->print_at(
1843 $self->_loc_for( $idx ),
0f4ed906 1844 pack( $StP{$e->byte_size}, $loc ),
d938be6a 1845 );
1846}
1847
76c68c87 18481;
1849__END__