Keys now works
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine3.pm
CommitLineData
696cadb7 1package DBM::Deep::Engine3;
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
696cadb7 32################################################################################
33
8fbac729 34# Please refer to the pack() documentation for further information
35my %StP = (
b4e17919 36 1 => 'C', # Unsigned char value (no order specified, presumably ASCII)
8fbac729 37 2 => 'n', # Unsigned short in "network" (big-endian) order
38 4 => 'N', # Unsigned long in "network" (big-endian) order
39 8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
c83524c6 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
508sub clear_entries {
509 my $self = shift;
6f999f6e 510 delete $self->{entries}{$self->trans_id};
511}
512
c9f02899 513################################################################################
b9ec359f 514
c9f02899 515{
516 my $header_fixed = length( SIG_FILE ) + 1 + 4 + 4;
696cadb7 517
c9f02899 518 sub _write_file_header {
519 my $self = shift;
696cadb7 520
d58fd793 521 my $header_var = 1 + 1 + 4 + 4 * $self->num_txns + 3 * $self->byte_size;
696cadb7 522
c9f02899 523 my $loc = $self->storage->request_space( $header_fixed + $header_var );
c83524c6 524
c9f02899 525 $self->storage->print_at( $loc,
526 SIG_FILE,
527 SIG_HEADER,
528 pack('N', 1), # header version - at this point, we're at 9 bytes
529 pack('N', $header_var), # header size
530 # --- Above is $header_fixed. Below is $header_var
531 pack('C', $self->byte_size),
532 pack('C', $self->max_buckets),
b4e17919 533 pack('N', 0 ), # Transaction activeness bitfield
534 pack('N' . $self->num_txns, 0 x $self->num_txns ), # Transaction staleness counters
c9f02899 535 pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
536 pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
d58fd793 537 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
c9f02899 538 );
696cadb7 539
c9f02899 540 $self->set_trans_loc( $header_fixed + 2 );
cf03415a 541 $self->set_chains_loc( $header_fixed + 2 + 4 + 4 * $self->num_txns );
696cadb7 542
c9f02899 543 return;
696cadb7 544 }
545
c9f02899 546 sub _read_file_header {
547 my $self = shift;
696cadb7 548
c9f02899 549 my $buffer = $self->storage->read_at( 0, $header_fixed );
550 return unless length($buffer);
696cadb7 551
c9f02899 552 my ($file_signature, $sig_header, $header_version, $size) = unpack(
553 'A4 A N N', $buffer
554 );
b9ec359f 555
c9f02899 556 unless ( $file_signature eq SIG_FILE ) {
557 $self->storage->close;
558 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
559 }
696cadb7 560
c9f02899 561 unless ( $sig_header eq SIG_HEADER ) {
562 $self->storage->close;
563 DBM::Deep->_throw_error( "Old file version found." );
564 }
696cadb7 565
c9f02899 566 my $buffer2 = $self->storage->read_at( undef, $size );
567 my @values = unpack( 'C C', $buffer2 );
696cadb7 568
c9f02899 569 $self->set_trans_loc( $header_fixed + 2 );
cf03415a 570 $self->set_chains_loc( $header_fixed + 2 + 4 + 4 * $self->num_txns );
c9f02899 571
572 if ( @values < 2 || grep { !defined } @values ) {
573 $self->storage->close;
574 DBM::Deep->_throw_error("Corrupted file - bad header");
575 }
576
577 #XXX Add warnings if values weren't set right
578 @{$self}{qw(byte_size max_buckets)} = @values;
b9ec359f 579
d58fd793 580 my $header_var = 1 + 1 + 4 + 4 * $self->num_txns + 3 * $self->byte_size;
c9f02899 581 unless ( $size eq $header_var ) {
582 $self->storage->close;
583 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
584 }
585
586 return length($buffer) + length($buffer2);
587 }
696cadb7 588}
589
3976d8c9 590sub _load_sector {
591 my $self = shift;
592 my ($offset) = @_;
593
0f4ed906 594 # Add a catch for offset of 0 or 1
595 return if $offset <= 1;
596
3976d8c9 597 my $type = $self->storage->read_at( $offset, 1 );
b9ec359f 598 return if $type eq chr(0);
599
3976d8c9 600 if ( $type eq $self->SIG_ARRAY || $type eq $self->SIG_HASH ) {
601 return DBM::Deep::Engine::Sector::Reference->new({
602 engine => $self,
603 type => $type,
604 offset => $offset,
605 });
606 }
2432d6cc 607 # XXX Don't we need key_md5 here?
3976d8c9 608 elsif ( $type eq $self->SIG_BLIST ) {
609 return DBM::Deep::Engine::Sector::BucketList->new({
610 engine => $self,
611 type => $type,
612 offset => $offset,
613 });
614 }
d58fd793 615 elsif ( $type eq $self->SIG_INDEX ) {
616 return DBM::Deep::Engine::Sector::Index->new({
617 engine => $self,
618 type => $type,
619 offset => $offset,
620 });
621 }
68369f26 622 elsif ( $type eq $self->SIG_NULL ) {
623 return DBM::Deep::Engine::Sector::Null->new({
624 engine => $self,
625 type => $type,
626 offset => $offset,
627 });
628 }
629 elsif ( $type eq $self->SIG_DATA ) {
630 return DBM::Deep::Engine::Sector::Scalar->new({
631 engine => $self,
632 type => $type,
633 offset => $offset,
634 });
635 }
b9ec359f 636 # This was deleted from under us, so just return and let the caller figure it out.
637 elsif ( $type eq $self->SIG_FREE ) {
638 return;
639 }
3976d8c9 640
ed38e772 641 die "'$offset': Don't know what to do with type '$type'\n";
3976d8c9 642}
643
644sub _apply_digest {
645 my $self = shift;
646 return $self->{digest}->(@_);
647}
648
c0507636 649sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
650sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
651sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
652
ed38e772 653sub _add_free_sector {
654 my $self = shift;
c0507636 655 my ($multiple, $offset, $size) = @_;
b9ec359f 656
c0507636 657 my $chains_offset = $multiple * $self->byte_size;
b9ec359f 658
8af340bf 659 my $storage = $self->storage;
660
661 # Increment staleness.
c0507636 662 # XXX Can this increment+modulo be done by "&= 0x1" ?
d58fd793 663 my $staleness = unpack( $StP{STALE_SIZE()}, $storage->read_at( $offset + SIG_SIZE, STALE_SIZE ) );
664 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * STALE_SIZE ) );
665 $storage->print_at( $offset + SIG_SIZE, pack( $StP{STALE_SIZE()}, $staleness ) );
b9ec359f 666
8af340bf 667 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
668
669 $storage->print_at( $self->chains_loc + $chains_offset,
b9ec359f 670 pack( $StP{$self->byte_size}, $offset ),
671 );
672
8af340bf 673 # Record the old head in the new sector after the signature and staleness counter
d58fd793 674 $storage->print_at( $offset + SIG_SIZE + STALE_SIZE, $old_head );
b9ec359f 675}
676
c0507636 677sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
678sub _request_data_sector { shift->_request_sector( 1, @_ ) }
679sub _request_index_sector { shift->_request_sector( 2, @_ ) }
680
b9ec359f 681sub _request_sector {
682 my $self = shift;
c0507636 683 my ($multiple, $size) = @_;
b9ec359f 684
c0507636 685 my $chains_offset = $multiple * $self->byte_size;
b9ec359f 686
687 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
688 my $loc = unpack( $StP{$self->byte_size}, $old_head );
689
690 # We don't have any free sectors of the right size, so allocate a new one.
691 unless ( $loc ) {
8af340bf 692 my $offset = $self->storage->request_space( $size );
693
694 # Zero out the new sector. This also guarantees correct increases
695 # in the filesize.
696 $self->storage->print_at( $offset, chr(0) x $size );
697
698 return $offset;
b9ec359f 699 }
700
8af340bf 701 # Read the new head after the signature and the staleness counter
d58fd793 702 my $new_head = $self->storage->read_at( $loc + SIG_SIZE + STALE_SIZE, $self->byte_size );
b9ec359f 703 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
704
705 return $loc;
ed38e772 706}
707
696cadb7 708################################################################################
709
3976d8c9 710sub storage { $_[0]{storage} }
711sub byte_size { $_[0]{byte_size} }
712sub hash_size { $_[0]{hash_size} }
6a4f323c 713sub hash_chars { $_[0]{hash_chars} }
3976d8c9 714sub num_txns { $_[0]{num_txns} }
715sub max_buckets { $_[0]{max_buckets} }
c000ae6e 716sub blank_md5 { chr(0) x $_[0]->hash_size }
8fbac729 717
8cb9205a 718sub trans_id { $_[0]{trans_id} }
719sub set_trans_id { $_[0]{trans_id} = $_[1] }
720
c9f02899 721sub trans_loc { $_[0]{trans_loc} }
722sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
723
b9ec359f 724sub chains_loc { $_[0]{chains_loc} }
725sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
726
8fbac729 727################################################################################
728
0f4ed906 729package DBM::Deep::Iterator;
ed38e772 730
731sub new {
732 my $class = shift;
733 my ($args) = @_;
734
735 my $self = bless {
736 breadcrumbs => [],
737 engine => $args->{engine},
738 base_offset => $args->{base_offset},
ed38e772 739 }, $class;
740
741 Scalar::Util::weaken( $self->{engine} );
742
743 return $self;
744}
745
0f4ed906 746sub reset { $_[0]{breadcrumbs} = [] }
747
748sub get_sector_iterator {
ed38e772 749 my $self = shift;
0f4ed906 750 my ($loc) = @_;
751
752 my $sector = $self->{engine}->_load_sector( $loc )
753 or return;
754
755 if ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
756 return DBM::Deep::Iterator::Index->new({
757 iterator => $self,
758 sector => $sector,
759 });
760 }
761 elsif ( $sector->isa( 'DBM::Deep::Engine::Sector::BucketList' ) ) {
762 return DBM::Deep::Iterator::BucketList->new({
763 iterator => $self,
764 sector => $sector,
765 });
766 }
767 else {
768 die "Why did $loc make a $sector?";
769 }
ed38e772 770}
771
772sub get_next_key {
773 my $self = shift;
8af340bf 774 my ($obj) = @_;
ed38e772 775
776 my $crumbs = $self->{breadcrumbs};
0f4ed906 777 my $e = $self->{engine};
ed38e772 778
779 unless ( @$crumbs ) {
780 # This will be a Reference sector
0f4ed906 781 my $sector = $e->_load_sector( $self->{base_offset} )
b9ec359f 782 # If no sector is found, thist must have been deleted from under us.
783 or return;
8af340bf 784
785 if ( $sector->staleness != $obj->_staleness ) {
786 return;
787 }
788
0f4ed906 789 my $loc = $sector->get_blist_loc
790 or return;
791
792 push @$crumbs, $self->get_sector_iterator( $loc );
ed38e772 793 }
794
0f4ed906 795 FIND_NEXT_KEY: {
796 # We're at the end.
797 unless ( @$crumbs ) {
ed38e772 798 $self->reset;
0f4ed906 799 return;
ed38e772 800 }
801
0f4ed906 802 my $iterator = $crumbs->[-1];
803
804 # This level is done.
805 if ( $iterator->at_end ) {
806 pop @$crumbs;
807 redo FIND_NEXT_KEY;
2432d6cc 808 }
809
0f4ed906 810 if ( $iterator->isa( 'DBM::Deep::Iterator::Index' ) ) {
811 # If we don't have any more, it will be caught at the
812 # prior check.
813 if ( my $next = $iterator->get_next_iterator ) {
814 push @$crumbs, $next;
815 }
816 redo FIND_NEXT_KEY;
817 }
ed38e772 818
0f4ed906 819 unless ( $iterator->isa( 'DBM::Deep::Iterator::BucketList' ) ) {
820 DBM::Deep->_throw_error(
821 "Should have a bucketlist iterator here - instead have $iterator"
822 );
2432d6cc 823 }
824
0f4ed906 825 # At this point, we have a BucketList iterator
826 my $key = $iterator->get_next_key;
827 if ( defined $key ) {
828 return $key;
ed38e772 829 }
830
0f4ed906 831 # We hit the end of the bucketlist iterator, so redo
832 redo FIND_NEXT_KEY;
833 }
834
835 DBM::Deep->_throw_error( "get_next_key(): How did we get here?" );
836}
837
838package DBM::Deep::Iterator::Index;
839
840sub new {
841 my $self = bless $_[1] => $_[0];
842 $self->{curr_index} = 0;
843 return $self;
844}
845
846sub at_end {
847 my $self = shift;
848 return $self->{curr_index} >= $self->{iterator}{engine}->hash_chars;
849}
850
851sub get_next_iterator {
852 my $self = shift;
853
854 my $loc;
855 while ( !$loc ) {
856 return if $self->at_end;
857 $loc = $self->{sector}->get_entry( $self->{curr_index}++ );
ed38e772 858 }
859
0f4ed906 860 return $self->{iterator}->get_sector_iterator( $loc );
861}
862
863package DBM::Deep::Iterator::BucketList;
864
865sub new {
866 my $self = bless $_[1] => $_[0];
867 $self->{curr_index} = 0;
868 return $self;
869}
870
871sub at_end {
872 my $self = shift;
873 return $self->{curr_index} >= $self->{iterator}{engine}->max_buckets;
874}
875
876sub get_next_key {
877 my $self = shift;
878
879 return if $self->at_end;
880
9ce79856 881 my $data_loc = $self->{sector}->get_data_location_for({
882 allow_head => 1,
883 idx => $self->{curr_index}++,
884 }) or return;
885
886 my $key_sector = $self->{sector}->get_key_for( $self->{curr_index} - 1 );
887
888 #XXX Is this check necessary now?
0f4ed906 889 return unless $key_sector;
890
891 return $key_sector->data;
ed38e772 892}
893
3976d8c9 894package DBM::Deep::Engine::Sector;
895
896sub new {
897 my $self = bless $_[1], $_[0];
898 Scalar::Util::weaken( $self->{engine} );
899 $self->_init;
900 return $self;
901}
902sub _init {}
2432d6cc 903sub clone { die "Must be implemented in the child class" }
3976d8c9 904
905sub engine { $_[0]{engine} }
906sub offset { $_[0]{offset} }
907sub type { $_[0]{type} }
908
d58fd793 909sub base_size {
910 my $self = shift;
911 return $self->engine->SIG_SIZE + $self->engine->STALE_SIZE;
912}
b6fc126b 913
ed38e772 914sub free {
915 my $self = shift;
916
b6fc126b 917 my $e = $self->engine;
918
919 $e->storage->print_at( $self->offset, $e->SIG_FREE );
8af340bf 920 # Skip staleness counter
b6fc126b 921 $e->storage->print_at( $self->offset + $self->base_size,
922 chr(0) x ($self->size - $self->base_size),
b9ec359f 923 );
924
c0507636 925 my $free_meth = $self->free_meth;
926 $e->$free_meth( $self->offset, $self->size );
ed38e772 927
b9ec359f 928 return;
ed38e772 929}
3976d8c9 930
931package DBM::Deep::Engine::Sector::Data;
8fbac729 932
933our @ISA = qw( DBM::Deep::Engine::Sector );
934
3976d8c9 935# This is in bytes
936sub size { return 256 }
c0507636 937sub free_meth { return '_add_free_data_sector' }
3976d8c9 938
2432d6cc 939sub clone {
940 my $self = shift;
941 return ref($self)->new({
942 engine => $self->engine,
943 data => $self->data,
944 type => $self->type,
945 });
946}
947
3976d8c9 948package DBM::Deep::Engine::Sector::Scalar;
949
950our @ISA = qw( DBM::Deep::Engine::Sector::Data );
951
ad4ae302 952sub free {
953 my $self = shift;
954
955 my $chain_loc = $self->chain_loc;
956
957 $self->SUPER::free();
958
959 if ( $chain_loc ) {
960 $self->engine->_load_sector( $chain_loc )->free;
961 }
962
963 return;
964}
965
3976d8c9 966sub type { $_[0]{engine}->SIG_DATA }
8fbac729 967sub _init {
968 my $self = shift;
969
970 my $engine = $self->engine;
971
3976d8c9 972 unless ( $self->offset ) {
b6fc126b 973 my $data_section = $self->size - $self->base_size - 1 * $engine->byte_size - 1;
3976d8c9 974
c0507636 975 $self->{offset} = $engine->_request_data_sector( $self->size );
ad4ae302 976
2432d6cc 977 my $data = delete $self->{data};
ad4ae302 978 my $dlen = length $data;
979 my $continue = 1;
980 my $curr_offset = $self->offset;
981 while ( $continue ) {
982
983 my $next_offset = 0;
984
985 my ($leftover, $this_len, $chunk);
986 if ( $dlen > $data_section ) {
987 $leftover = 0;
988 $this_len = $data_section;
989 $chunk = substr( $data, 0, $this_len );
990
991 $dlen -= $data_section;
c0507636 992 $next_offset = $engine->_request_data_sector( $self->size );
ad4ae302 993 $data = substr( $data, $this_len );
994 }
995 else {
996 $leftover = $data_section - $dlen;
997 $this_len = $dlen;
998 $chunk = $data;
999
1000 $continue = 0;
1001 }
1002
8af340bf 1003 $engine->storage->print_at( $curr_offset, $self->type ); # Sector type
1004 # Skip staleness
d58fd793 1005 $engine->storage->print_at( $curr_offset + $self->base_size,
ad4ae302 1006 pack( $StP{$engine->byte_size}, $next_offset ), # Chain loc
1007 pack( $StP{1}, $this_len ), # Data length
1008 $chunk, # Data to be stored in this sector
1009 chr(0) x $leftover, # Zero-fill the rest
1010 );
1011
1012 $curr_offset = $next_offset;
1013 }
3976d8c9 1014
1015 return;
1016 }
1017}
1018
1019sub data_length {
1020 my $self = shift;
1021
ad4ae302 1022 my $buffer = $self->engine->storage->read_at(
b6fc126b 1023 $self->offset + $self->base_size + $self->engine->byte_size, 1
8fbac729 1024 );
ad4ae302 1025
1026 return unpack( $StP{1}, $buffer );
1027}
1028
1029sub chain_loc {
1030 my $self = shift;
1031 my $chain_loc = $self->engine->storage->read_at(
b6fc126b 1032 $self->offset + $self->base_size, $self->engine->byte_size,
ad4ae302 1033 );
1034 return unpack( $StP{$self->engine->byte_size}, $chain_loc );
3976d8c9 1035}
1036
1037sub data {
1038 my $self = shift;
8fbac729 1039
378b4748 1040 my $data;
1041 while ( 1 ) {
1042 my $chain_loc = $self->chain_loc;
ad4ae302 1043
378b4748 1044 $data .= $self->engine->storage->read_at(
b6fc126b 1045 $self->offset + $self->base_size + $self->engine->byte_size + 1, $self->data_length,
378b4748 1046 );
ad4ae302 1047
378b4748 1048 last unless $chain_loc;
1049
1050 $self = $self->engine->_load_sector( $chain_loc );
ad4ae302 1051 }
1052
1053 return $data;
8fbac729 1054}
1055
68369f26 1056package DBM::Deep::Engine::Sector::Null;
1057
1058our @ISA = qw( DBM::Deep::Engine::Sector::Data );
1059
1060sub type { $_[0]{engine}->SIG_NULL }
1061sub data_length { 0 }
1062sub data { return }
1063
1064sub _init {
1065 my $self = shift;
1066
1067 my $engine = $self->engine;
1068
1069 unless ( $self->offset ) {
b6fc126b 1070 my $leftover = $self->size - $self->base_size - 1 * $engine->byte_size - 1;
68369f26 1071
c0507636 1072 $self->{offset} = $engine->_request_data_sector( $self->size );
8af340bf 1073 $engine->storage->print_at( $self->offset, $self->type ); # Sector type
1074 # Skip staleness counter
b6fc126b 1075 $engine->storage->print_at( $self->offset + $self->base_size,
68369f26 1076 pack( $StP{$engine->byte_size}, 0 ), # Chain loc
1077 pack( $StP{1}, $self->data_length ), # Data length
1078 chr(0) x $leftover, # Zero-fill the rest
1079 );
1080
1081 return;
1082 }
1083}
1084
3976d8c9 1085package DBM::Deep::Engine::Sector::Reference;
8fbac729 1086
3976d8c9 1087our @ISA = qw( DBM::Deep::Engine::Sector::Data );
1088
1089sub _init {
1090 my $self = shift;
1091
d58fd793 1092 my $e = $self->engine;
3976d8c9 1093
1094 unless ( $self->offset ) {
ba075714 1095 my $classname = Scalar::Util::blessed( delete $self->{data} );
d58fd793 1096 my $leftover = $self->size - $self->base_size - 2 * $e->byte_size;
d4f34951 1097
1098 my $class_offset = 0;
1099 if ( defined $classname ) {
1100 my $class_sector = DBM::Deep::Engine::Sector::Scalar->new({
d58fd793 1101 engine => $e,
d4f34951 1102 data => $classname,
1103 });
1104 $class_offset = $class_sector->offset;
1105 }
3976d8c9 1106
c0507636 1107 $self->{offset} = $e->_request_data_sector( $self->size );
d58fd793 1108 $e->storage->print_at( $self->offset, $self->type ); # Sector type
8af340bf 1109 # Skip staleness counter
d58fd793 1110 $e->storage->print_at( $self->offset + $self->base_size,
1111 pack( $StP{$e->byte_size}, 0 ), # Index/BList loc
1112 pack( $StP{$e->byte_size}, $class_offset ), # Classname loc
c0507636 1113 chr(0) x $leftover, # Zero-fill the rest
3976d8c9 1114 );
8af340bf 1115 }
1116 else {
d58fd793 1117 $self->{type} = $e->storage->read_at( $self->offset, 1 );
3976d8c9 1118 }
764e6cb9 1119
8af340bf 1120 $self->{staleness} = unpack(
d58fd793 1121 $StP{$e->STALE_SIZE},
1122 $e->storage->read_at( $self->offset + $e->SIG_SIZE, $e->STALE_SIZE ),
8af340bf 1123 );
764e6cb9 1124
1125 return;
3976d8c9 1126}
1127
d938be6a 1128sub free {
1129 my $self = shift;
1130
1131 my $blist_loc = $self->get_blist_loc;
1132 $self->engine->_load_sector( $blist_loc )->free if $blist_loc;
1133
1134 my $class_loc = $self->get_class_offset;
1135 $self->engine->_load_sector( $class_loc )->free if $class_loc;
1136
1137 $self->SUPER::free();
1138}
1139
8af340bf 1140sub staleness { $_[0]{staleness} }
1141
2432d6cc 1142sub get_data_for {
1143 my $self = shift;
1144 my ($args) = @_;
1145
1146 # Assume that the head is not allowed unless otherwise specified.
1147 $args->{allow_head} = 0 unless exists $args->{allow_head};
1148
1149 # Assume we don't create a new blist location unless otherwise specified.
1150 $args->{create} = 0 unless exists $args->{create};
1151
1152 my $blist = $self->get_bucket_list({
1153 key_md5 => $args->{key_md5},
0f4ed906 1154 key => $args->{key},
2432d6cc 1155 create => $args->{create},
1156 });
1157 return unless $blist && $blist->{found};
1158
1159 # At this point, $blist knows where the md5 is. What it -doesn't- know yet
1160 # is whether or not this transaction has this key. That's part of the next
1161 # function call.
1162 my $location = $blist->get_data_location_for({
1163 allow_head => $args->{allow_head},
1164 }) or return;
1165
1166 return $self->engine->_load_sector( $location );
1167}
1168
1169sub write_data {
1170 my $self = shift;
1171 my ($args) = @_;
1172
1173 my $blist = $self->get_bucket_list({
1174 key_md5 => $args->{key_md5},
0f4ed906 1175 key => $args->{key},
2432d6cc 1176 create => 1,
6f999f6e 1177 }) or die "How did write_data fail (no blist)?!\n";
2432d6cc 1178
1179 # Handle any transactional bookkeeping.
1180 if ( $self->engine->trans_id ) {
d938be6a 1181 if ( ! $blist->has_md5 ) {
2432d6cc 1182 $blist->mark_deleted({
1183 trans_id => 0,
1184 });
1185 }
1186 }
1187 else {
cf03415a 1188 my @trans_ids = $self->engine->get_running_txn_ids;
d938be6a 1189 if ( $blist->has_md5 ) {
2432d6cc 1190 if ( @trans_ids ) {
1191 my $old_value = $blist->get_data_for;
1192 foreach my $other_trans_id ( @trans_ids ) {
b4e17919 1193 next if $blist->get_data_location_for({
1194 trans_id => $other_trans_id,
1195 allow_head => 0,
1196 });
2432d6cc 1197 $blist->write_md5({
1198 trans_id => $other_trans_id,
1199 key => $args->{key},
1200 key_md5 => $args->{key_md5},
1201 value => $old_value->clone,
1202 });
1203 }
1204 }
1205 }
1206 else {
1207 if ( @trans_ids ) {
1208 foreach my $other_trans_id ( @trans_ids ) {
1209 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
1210 $blist->mark_deleted({
1211 trans_id => $other_trans_id,
1212 });
1213 }
1214 }
1215 }
1216 }
1217
6f999f6e 1218 #XXX Is this safe to do transactionally?
2432d6cc 1219 # Free the place we're about to write to.
1220 if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
1221 $blist->get_data_for({ allow_head => 0 })->free;
1222 }
1223
1224 $blist->write_md5({
1225 key => $args->{key},
1226 key_md5 => $args->{key_md5},
1227 value => $args->{value},
1228 });
1229}
1230
1231sub delete_key {
1232 my $self = shift;
1233 my ($args) = @_;
1234
1235 # XXX What should happen if this fails?
1236 my $blist = $self->get_bucket_list({
1237 key_md5 => $args->{key_md5},
1238 }) or die "How did delete_key fail (no blist)?!\n";
1239
6f999f6e 1240 # Save the location so that we can free the data
1241 my $location = $blist->get_data_location_for({
1242 allow_head => 0,
1243 });
26897a1c 1244 my $old_value = $location && $self->engine->_load_sector( $location );
6f999f6e 1245
1246 if ( $self->engine->trans_id == 0 ) {
cf03415a 1247 my @trans_ids = $self->engine->get_running_txn_ids;
6f999f6e 1248 if ( @trans_ids ) {
1249 foreach my $other_trans_id ( @trans_ids ) {
1250 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
1251 $blist->write_md5({
1252 trans_id => $other_trans_id,
1253 key => $args->{key},
1254 key_md5 => $args->{key_md5},
1255 value => $old_value->clone,
1256 });
1257 }
1258 }
1259 }
1260
1261 $blist->mark_deleted( $args );
1262
26897a1c 1263 my $data;
1264 if ( $old_value ) {
1265 $data = $old_value->data;
1266 $old_value->free;
1267 }
6f999f6e 1268
1269 return $data;
2432d6cc 1270}
1271
3976d8c9 1272sub get_blist_loc {
1273 my $self = shift;
1274
2432d6cc 1275 my $e = $self->engine;
b6fc126b 1276 my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
2432d6cc 1277 return unpack( $StP{$e->byte_size}, $blist_loc );
3976d8c9 1278}
1279
1280sub get_bucket_list {
1281 my $self = shift;
1282 my ($args) = @_;
4eee718c 1283 $args ||= {};
3976d8c9 1284
1285 # XXX Add in check here for recycling?
1286
1287 my $engine = $self->engine;
1288
1289 my $blist_loc = $self->get_blist_loc;
1290
1291 # There's no index or blist yet
1292 unless ( $blist_loc ) {
1293 return unless $args->{create};
1294
1295 my $blist = DBM::Deep::Engine::Sector::BucketList->new({
2432d6cc 1296 engine => $engine,
1297 key_md5 => $args->{key_md5},
3976d8c9 1298 });
2432d6cc 1299
b6fc126b 1300 $engine->storage->print_at( $self->offset + $self->base_size,
3976d8c9 1301 pack( $StP{$engine->byte_size}, $blist->offset ),
1302 );
2432d6cc 1303
3976d8c9 1304 return $blist;
1305 }
1306
d938be6a 1307 # Add searching here through the index layers, if any
1308 my $sector = $engine->_load_sector( $blist_loc )
1309 or die "Cannot read sector at $blist_loc in get_bucket_list()";
1310 my $i = 0;
1311 my $last_sector = undef;
1312 while ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
6a4f323c 1313 $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
d938be6a 1314 $last_sector = $sector;
625a24b4 1315 if ( $blist_loc ) {
1316 $sector = $engine->_load_sector( $blist_loc )
1317 or die "Cannot read sector at $blist_loc in get_bucket_list()";
1318 }
1319 else {
1320 $sector = undef;
1321 last;
1322 }
1323 }
1324
1325 # This means we went through the Index sector(s) and found an empty slot
1326 unless ( $sector ) {
1327 return unless $args->{create};
1328
1329 die "No last_sector when attempting to build a new entry"
1330 unless $last_sector;
1331
1332 my $blist = DBM::Deep::Engine::Sector::BucketList->new({
1333 engine => $engine,
1334 key_md5 => $args->{key_md5},
1335 });
1336
1337 $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
1338
1339 return $blist;
d938be6a 1340 }
1341
1342 $sector->find_md5( $args->{key_md5} );
1343
1344 # See whether or not we need to reindex the bucketlist
6a4f323c 1345 if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {
6a4f323c 1346 my $new_index = DBM::Deep::Engine::Sector::Index->new({
1347 engine => $engine,
1348 });
1349
1350 my %blist_cache;
0f4ed906 1351 foreach my $md5 ( $sector->chopped_up ) {
6a4f323c 1352 my $idx = ord( substr( $md5, $i, 1 ) );
1353
0f4ed906 1354 # XXX This is inefficient
6a4f323c 1355 my $blist = $blist_cache{$idx}
1356 ||= DBM::Deep::Engine::Sector::BucketList->new({
1357 engine => $engine,
1358 });
1359
1360 $new_index->set_entry( $idx => $blist->offset );
1361
1362 $blist->write_at_next_open( $md5 );
1363 }
1364
0f4ed906 1365 # Handle the new item separately.
1366 {
1367 my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
1368 my $blist = $blist_cache{$idx}
1369 ||= DBM::Deep::Engine::Sector::BucketList->new({
1370 engine => $engine,
1371 });
1372
1373 $new_index->set_entry( $idx => $blist->offset );
1374
1375 #XXX THIS IS HACKY!
1376 $blist->find_md5( $args->{key_md5} );
1377 $blist->write_md5({
1378 key => $args->{key},
1379 key_md5 => $args->{key_md5},
1380 value => DBM::Deep::Engine::Sector::Null->new({
1381 engine => $engine,
1382 data => undef,
1383 }),
1384 });
1385 }
1386
6a4f323c 1387 if ( $last_sector ) {
1388 $last_sector->set_entry(
1389 ord( substr( $args->{key_md5}, $i - 1, 1 ) ),
1390 $new_index->offset,
1391 );
1392 } else {
1393 $engine->storage->print_at( $self->offset + $self->base_size,
1394 pack( $StP{$engine->byte_size}, $new_index->offset ),
1395 );
1396 }
1397
1398 $sector->free;
1399
1400 $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
1401 $sector->find_md5( $args->{key_md5} );
d938be6a 1402 }
1403
1404 return $sector;
3976d8c9 1405}
1406
d938be6a 1407sub get_class_offset {
ba075714 1408 my $self = shift;
1409
d938be6a 1410 my $e = $self->engine;
1411 return unpack(
1412 $StP{$e->byte_size},
1413 $e->storage->read_at(
1414 $self->offset + $self->base_size + 1 * $e->byte_size, $e->byte_size,
1415 ),
ba075714 1416 );
d938be6a 1417}
1418
1419sub get_classname {
1420 my $self = shift;
1421
1422 my $class_offset = $self->get_class_offset;
ba075714 1423
d4f34951 1424 return unless $class_offset;
ba075714 1425
d4f34951 1426 return $self->engine->_load_sector( $class_offset )->data;
ba075714 1427}
1428
764e6cb9 1429sub data {
1430 my $self = shift;
1431
1432 my $new_obj = DBM::Deep->new({
1433 type => $self->type,
1434 base_offset => $self->offset,
8af340bf 1435 staleness => $self->staleness,
764e6cb9 1436 storage => $self->engine->storage,
c9f02899 1437 engine => $self->engine,
764e6cb9 1438 });
1439
ba075714 1440 if ( $self->engine->storage->{autobless} ) {
1441 my $classname = $self->get_classname;
1442 if ( defined $classname ) {
1443 bless $new_obj, $classname;
1444 }
1445 }
1446
764e6cb9 1447 return $new_obj;
1448}
1449
3976d8c9 1450package DBM::Deep::Engine::Sector::BucketList;
1451
1452our @ISA = qw( DBM::Deep::Engine::Sector );
1453
1454sub _init {
1455 my $self = shift;
1456
1457 my $engine = $self->engine;
1458
1459 unless ( $self->offset ) {
c000ae6e 1460 my $leftover = $self->size - $self->base_size;
3976d8c9 1461
c0507636 1462 $self->{offset} = $engine->_request_blist_sector( $self->size );
8af340bf 1463 $engine->storage->print_at( $self->offset, $engine->SIG_BLIST ); # Sector type
1464 # Skip staleness counter
b6fc126b 1465 $engine->storage->print_at( $self->offset + $self->base_size,
3976d8c9 1466 chr(0) x $leftover, # Zero-fill the data
1467 );
1468 }
8fbac729 1469
2432d6cc 1470 if ( $self->{key_md5} ) {
1471 $self->find_md5;
1472 }
1473
8fbac729 1474 return $self;
1475}
8fbac729 1476
3976d8c9 1477sub size {
1478 my $self = shift;
2432d6cc 1479 unless ( $self->{size} ) {
1480 my $e = $self->engine;
d938be6a 1481 # Base + numbuckets * bucketsize
1482 $self->{size} = $self->base_size + $e->max_buckets * $self->bucket_size;
2432d6cc 1483 }
1484 return $self->{size};
c000ae6e 1485}
1486
c0507636 1487sub free_meth { return '_add_free_blist_sector' }
1488
c000ae6e 1489sub bucket_size {
1490 my $self = shift;
2432d6cc 1491 unless ( $self->{bucket_size} ) {
1492 my $e = $self->engine;
6de4e4e9 1493 # Key + head (location) + transactions (location + staleness-counter)
17164f8a 1494 my $location_size = $e->byte_size + $e->num_txns * ( $e->byte_size + 4 );
6de4e4e9 1495 $self->{bucket_size} = $e->hash_size + $location_size;
2432d6cc 1496 }
1497 return $self->{bucket_size};
3976d8c9 1498}
8fbac729 1499
6a4f323c 1500sub chopped_up {
1501 my $self = shift;
1502
1503 my $e = $self->engine;
1504
625a24b4 1505 my @buckets;
6a4f323c 1506 foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
1507 my $md5 = $e->storage->read_at(
1508 $self->offset + $self->base_size + $idx * $self->bucket_size, $e->hash_size,
1509 );
1510
1511 last if $md5 eq $e->blank_md5;
1512
1513 my $rest = $e->storage->read_at( undef, $self->bucket_size - $e->hash_size );
625a24b4 1514 push @buckets, $md5 . $rest;
6a4f323c 1515 }
1516
625a24b4 1517 return @buckets;
6a4f323c 1518}
1519
1520sub write_at_next_open {
1521 my $self = shift;
1522 my ($md5) = @_;
1523
1524 #XXX This is such a hack!
625a24b4 1525 $self->{_next_open} = 0 unless exists $self->{_next_open};
6a4f323c 1526
1527 $self->engine->storage->print_at(
625a24b4 1528 $self->offset + $self->base_size + $self->{_next_open}++ * $self->bucket_size,
6a4f323c 1529 $md5,
1530 );
1531}
1532
3976d8c9 1533sub has_md5 {
c000ae6e 1534 my $self = shift;
2432d6cc 1535 unless ( exists $self->{found} ) {
1536 $self->find_md5;
1537 }
1538 return $self->{found};
c000ae6e 1539}
1540
1541sub find_md5 {
1542 my $self = shift;
c000ae6e 1543
2432d6cc 1544 $self->{found} = undef;
1545 $self->{idx} = -1;
c000ae6e 1546
d938be6a 1547 if ( @_ ) {
1548 $self->{key_md5} = shift;
1549 }
1550
2432d6cc 1551 # If we don't have an MD5, then what are we supposed to do?
1552 unless ( exists $self->{key_md5} ) {
6f999f6e 1553 DBM::Deep->_throw_error( "Cannot find_md5 without a key_md5 set" );
2432d6cc 1554 }
8cb9205a 1555
2432d6cc 1556 my $e = $self->engine;
1557 foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
1558 my $potential = $e->storage->read_at(
1559 $self->offset + $self->base_size + $idx * $self->bucket_size, $e->hash_size,
1560 );
1561
1562 if ( $potential eq $e->blank_md5 ) {
1563 $self->{idx} = $idx;
1564 return;
1565 }
8cb9205a 1566
2432d6cc 1567 if ( $potential eq $self->{key_md5} ) {
1568 $self->{found} = 1;
1569 $self->{idx} = $idx;
1570 return;
8cb9205a 1571 }
c000ae6e 1572 }
1573
1574 return;
3976d8c9 1575}
1576
1577sub write_md5 {
1578 my $self = shift;
2432d6cc 1579 my ($args) = @_;
2432d6cc 1580
6f999f6e 1581 DBM::Deep->_throw_error( "write_md5: no key" ) unless exists $args->{key};
1582 DBM::Deep->_throw_error( "write_md5: no key_md5" ) unless exists $args->{key_md5};
1583 DBM::Deep->_throw_error( "write_md5: no value" ) unless exists $args->{value};
3976d8c9 1584
1585 my $engine = $self->engine;
6f999f6e 1586
1587 $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
1588
2432d6cc 1589 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
6f999f6e 1590 $engine->add_entry( $args->{trans_id}, $spot );
4eee718c 1591
2432d6cc 1592 unless ($self->{found}) {
4eee718c 1593 my $key_sector = DBM::Deep::Engine::Sector::Scalar->new({
6f999f6e 1594 engine => $engine,
2432d6cc 1595 data => $args->{key},
4eee718c 1596 });
1597
1598 $engine->storage->print_at( $spot,
2432d6cc 1599 $args->{key_md5},
6f999f6e 1600 pack( $StP{$engine->byte_size}, $key_sector->offset ),
4eee718c 1601 );
1602 }
1603
6f999f6e 1604 my $loc = $spot
2432d6cc 1605 + $engine->hash_size
1606 + $engine->byte_size
17164f8a 1607 + $args->{trans_id} * ( $engine->byte_size + 4 );
6f999f6e 1608
1609 $engine->storage->print_at( $loc,
2432d6cc 1610 pack( $StP{$engine->byte_size}, $args->{value}->offset ),
6de4e4e9 1611 pack( 'N', $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
2432d6cc 1612 );
1613}
1614
1615sub mark_deleted {
1616 my $self = shift;
1617 my ($args) = @_;
6f999f6e 1618 $args ||= {};
1619
1620 my $engine = $self->engine;
1621
1622 $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
2432d6cc 1623
1624 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
6f999f6e 1625 $engine->add_entry( $args->{trans_id}, $spot );
1626
1627 my $loc = $spot
1628 + $engine->hash_size
1629 + $engine->byte_size
17164f8a 1630 + $args->{trans_id} * ( $engine->byte_size + 4 );
6f999f6e 1631
1632 $engine->storage->print_at( $loc,
1633 pack( $StP{$engine->byte_size}, 1 ), # 1 is the marker for deleted
6de4e4e9 1634 pack( 'N', $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
3976d8c9 1635 );
1636}
1637
e86cef36 1638sub delete_md5 {
3976d8c9 1639 my $self = shift;
2432d6cc 1640 my ($args) = @_;
3976d8c9 1641
e86cef36 1642 my $engine = $self->engine;
2432d6cc 1643 return undef unless $self->{found};
4eee718c 1644
1645 # Save the location so that we can free the data
2432d6cc 1646 my $location = $self->get_data_location_for({
1647 allow_head => 0,
1648 });
1649 my $key_sector = $self->get_key_for;
4eee718c 1650
6f999f6e 1651 #XXX This isn't going to work right and you know it! This eradicates data
1652 # that we're not ready to eradicate just yet.
2432d6cc 1653 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
4eee718c 1654 $engine->storage->print_at( $spot,
1655 $engine->storage->read_at(
1656 $spot + $self->bucket_size,
2432d6cc 1657 $self->bucket_size * ( $engine->num_txns - $self->{idx} - 1 ),
4eee718c 1658 ),
1659 chr(0) x $self->bucket_size,
e86cef36 1660 );
1661
3ed26433 1662 $key_sector->free;
1663
ed38e772 1664 my $data_sector = $self->engine->_load_sector( $location );
1665 my $data = $data_sector->data;
ed38e772 1666 $data_sector->free;
5c0f86e1 1667
1668 return $data;
e86cef36 1669}
1670
ed38e772 1671sub get_data_location_for {
e86cef36 1672 my $self = shift;
2432d6cc 1673 my ($args) = @_;
1674 $args ||= {};
1675
1676 $args->{allow_head} = 0 unless exists $args->{allow_head};
1677 $args->{trans_id} = $self->engine->trans_id unless exists $args->{trans_id};
1678 $args->{idx} = $self->{idx} unless exists $args->{idx};
e86cef36 1679
6de4e4e9 1680 my $e = $self->engine;
1681
1682 my $spot = $self->offset + $self->base_size
2432d6cc 1683 + $args->{idx} * $self->bucket_size
6de4e4e9 1684 + $e->hash_size
1685 + $e->byte_size
17164f8a 1686 + $args->{trans_id} * ( $e->byte_size + 4 );
6de4e4e9 1687
1688 my $buffer = $e->storage->read_at(
1689 $spot,
17164f8a 1690 $e->byte_size + 4,
3976d8c9 1691 );
6de4e4e9 1692 my ($loc, $staleness) = unpack( $StP{$e->byte_size} . ' N', $buffer );
1693
1694 # We have found an entry that is old, so get rid of it
41e27db3 1695 if ( $staleness != (my $s = $e->get_txn_staleness_counter( $args->{trans_id} ) ) ) {
6de4e4e9 1696 $e->storage->print_at(
1697 $spot,
1698 pack( $StP{$e->byte_size} . ' N', (0) x 2 ),
1699 );
1700 $loc = 0;
1701 }
8cb9205a 1702
1703 # If we're in a transaction and we never wrote to this location, try the
1704 # HEAD instead.
2432d6cc 1705 if ( $args->{trans_id} && !$loc && $args->{allow_head} ) {
1706 return $self->get_data_location_for({
1707 trans_id => 0,
1708 allow_head => 1,
17164f8a 1709 idx => $args->{idx},
2432d6cc 1710 });
8cb9205a 1711 }
2432d6cc 1712 return $loc <= 1 ? 0 : $loc;
e86cef36 1713}
1714
1715sub get_data_for {
1716 my $self = shift;
2432d6cc 1717 my ($args) = @_;
1718 $args ||= {};
e86cef36 1719
2432d6cc 1720 return unless $self->{found};
1721 my $location = $self->get_data_location_for({
1722 allow_head => $args->{allow_head},
1723 });
ed38e772 1724 return $self->engine->_load_sector( $location );
1725}
1726
1727sub get_key_for {
1728 my $self = shift;
1729 my ($idx) = @_;
2432d6cc 1730 $idx = $self->{idx} unless defined $idx;
ed38e772 1731
0f4ed906 1732 if ( $idx >= $self->engine->max_buckets ) {
1733 DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx" );
1734 }
1735
ed38e772 1736 my $location = $self->engine->storage->read_at(
1737 $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
1738 $self->engine->byte_size,
1739 );
1740 $location = unpack( $StP{$self->engine->byte_size}, $location );
1741 return unless $location;
68369f26 1742 return $self->engine->_load_sector( $location );
3976d8c9 1743}
696cadb7 1744
8af340bf 1745package DBM::Deep::Engine::Sector::Index;
76c68c87 1746
1747our @ISA = qw( DBM::Deep::Engine::Sector );
1748
1749sub _init {
1750 my $self = shift;
1751
1752 my $engine = $self->engine;
1753
1754 unless ( $self->offset ) {
1755 my $leftover = $self->size - $self->base_size;
1756
c0507636 1757 $self->{offset} = $engine->_request_index_sector( $self->size );
6a4f323c 1758 $engine->storage->print_at( $self->offset, $engine->SIG_INDEX ); # Sector type
8af340bf 1759 # Skip staleness counter
b6fc126b 1760 $engine->storage->print_at( $self->offset + $self->base_size,
d58fd793 1761 chr(0) x $leftover, # Zero-fill the rest
76c68c87 1762 );
1763 }
1764
76c68c87 1765 return $self;
1766}
1767
76c68c87 1768sub size {
1769 my $self = shift;
1770 unless ( $self->{size} ) {
1771 my $e = $self->engine;
d58fd793 1772 $self->{size} = $self->base_size + $e->byte_size * $e->hash_chars;
76c68c87 1773 }
1774 return $self->{size};
1775}
1776
c0507636 1777sub free_meth { return '_add_free_index_sector' }
1778
d938be6a 1779sub free {
1780 my $self = shift;
1781 my $e = $self->engine;
1782
1783 for my $i ( 0 .. $e->hash_chars - 1 ) {
1784 my $l = $self->location_for( $i ) or next;
1785 $e->_load_sector( $l )->free;
1786 }
1787
1788 $self->SUPER::free();
1789}
1790
6a4f323c 1791sub _loc_for {
1792 my $self = shift;
1793 my ($idx) = @_;
1794 return $self->offset + $self->base_size + $idx * $self->engine->byte_size;
1795}
1796
1797sub get_entry {
d938be6a 1798 my $self = shift;
1799 my ($idx) = @_;
1800
1801 my $e = $self->engine;
1802
0f4ed906 1803 die "get_entry: Out of range ($idx)"
1804 if $idx < 0 || $idx >= $e->hash_chars;
1805
d938be6a 1806 return unpack(
1807 $StP{$e->byte_size},
6a4f323c 1808 $e->storage->read_at( $self->_loc_for( $idx ), $e->byte_size ),
1809 );
1810}
1811
1812sub set_entry {
1813 my $self = shift;
1814 my ($idx, $loc) = @_;
1815
0f4ed906 1816 my $e = $self->engine;
1817
1818 die "set_entry: Out of range ($idx)"
1819 if $idx < 0 || $idx >= $e->hash_chars;
1820
6a4f323c 1821 $self->engine->storage->print_at(
1822 $self->_loc_for( $idx ),
0f4ed906 1823 pack( $StP{$e->byte_size}, $loc ),
d938be6a 1824 );
1825}
1826
76c68c87 18271;
1828__END__