File Coverage

blib/lib/DBM/Deep/Engine/File.pm
Criterion Covered Total %
statement 387 427 90.6
branch 134 172 77.9
condition 24 27 88.8
subroutine 60 61 98.3
pod 38 39 97.4
total 643 726 88.5


line stmt bran cond sub pod time code
1             package DBM::Deep::Engine::File;
2              
3 50     50   944 use 5.008_004;
  50         178  
4              
5 50     50   279 use strict;
  50         106  
  50         1443  
6 50     50   853 use warnings FATAL => 'all';
  50         103  
  50         2147  
7 50     50   315 no warnings 'recursion';
  50         183  
  50         1998  
8              
9 50     50   373 use base qw( DBM::Deep::Engine );
  50         136  
  50         3952  
10              
11 50     50   363 use Scalar::Util ();
  50         121  
  50         1249  
12              
13 50     50   23427 use DBM::Deep::Null ();
  50         139  
  50         1015  
14 50     50   23361 use DBM::Deep::Sector::File ();
  50         179  
  50         1102  
15 50     50   24957 use DBM::Deep::Storage::File ();
  50         150  
  50         296750  
16              
17 23033     23033 1 78669 sub sector_type { 'DBM::Deep::Sector::File' }
18 316     316 1 14785 sub iterator_class { 'DBM::Deep::Iterator::File' }
19              
20             my $STALE_SIZE = 2;
21              
22             # Setup file and tag signatures. These should never change.
23             sub SIG_FILE () { 'DPDB' }
24             sub SIG_HEADER () { 'h' }
25             sub SIG_NULL () { 'N' }
26             sub SIG_DATA () { 'D' }
27             sub SIG_UNIDATA () { 'U' }
28             sub SIG_INDEX () { 'I' }
29             sub SIG_BLIST () { 'B' }
30             sub SIG_FREE () { 'F' }
31             sub SIG_SIZE () { 1 }
32             # SIG_HASH and SIG_ARRAY are defined in DBM::Deep::Engine
33              
34             # Please refer to the pack() documentation for further information
35             my %StP = (
36             1 => 'C', # Unsigned char value (no order needed as it's just one byte)
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)
40             );
41              
42             =head1 NAME
43              
44             DBM::Deep::Engine::File - engine for use with DBM::Deep::Storage::File
45              
46             =head1 PURPOSE
47              
48             This is the engine for use with L.
49              
50             =head1 EXTERNAL METHODS
51              
52             =head2 new()
53              
54             This takes a set of args. These args are described in the documentation for
55             L.
56              
57             =cut
58              
59             sub new {
60 388     388 1 807 my $class = shift;
61 388         769 my ($args) = @_;
62              
63             $args->{storage} = DBM::Deep::Storage::File->new( $args )
64 388 50       2636 unless exists $args->{storage};
65              
66 387         3466 my $self = bless {
67             byte_size => 4,
68              
69             digest => undef,
70             hash_size => 16, # In bytes
71             hash_chars => 256, # Number of chars the algorithm uses per byte
72             max_buckets => 16,
73             num_txns => 1, # The HEAD
74             trans_id => 0, # Default to the HEAD
75              
76             data_sector_size => 64, # Size in bytes of each data sector
77              
78             entries => {}, # This is the list of entries for transactions
79             storage => undef,
80              
81             external_refs => undef,
82             }, $class;
83              
84             # Never allow byte_size to be set directly.
85 387         808 delete $args->{byte_size};
86 387 100       987 if ( defined $args->{pack_size} ) {
87 3 100       17 if ( lc $args->{pack_size} eq 'small' ) {
    100          
    50          
88 1         2 $args->{byte_size} = 2;
89             }
90             elsif ( lc $args->{pack_size} eq 'medium' ) {
91 1         3 $args->{byte_size} = 4;
92             }
93             elsif ( lc $args->{pack_size} eq 'large' ) {
94 1         2 $args->{byte_size} = 8;
95             }
96             else {
97 0         0 DBM::Deep->_throw_error( "Unknown pack_size value: '$args->{pack_size}'" );
98             }
99             }
100              
101             # Grab the parameters we want to use
102 387         1805 foreach my $param ( keys %$self ) {
103 4257 100       7718 next unless exists $args->{$param};
104 687         1159 $self->{$param} = $args->{$param};
105             }
106              
107 387         2223 my %validations = (
108             max_buckets => { floor => 16, ceil => 256 },
109             num_txns => { floor => 1, ceil => 255 },
110             data_sector_size => { floor => 32, ceil => 256 },
111             );
112              
113 387         1506 while ( my ($attr, $c) = each %validations ) {
114 1161 100 100     10387 if ( !defined $self->{$attr}
    100 100        
      100        
115             || !length $self->{$attr}
116             || $self->{$attr} =~ /\D/
117             || $self->{$attr} < $c->{floor}
118             ) {
119 12 100       32 $self->{$attr} = '(undef)' if !defined $self->{$attr};
120 12         137 warn "Floor of $attr is $c->{floor}. Setting it to $c->{floor} from '$self->{$attr}'\n";
121 12         735 $self->{$attr} = $c->{floor};
122             }
123             elsif ( $self->{$attr} > $c->{ceil} ) {
124 3         48 warn "Ceiling of $attr is $c->{ceil}. Setting it to $c->{ceil} from '$self->{$attr}'\n";
125 3         180 $self->{$attr} = $c->{ceil};
126             }
127             }
128              
129 387 100       964 if ( !$self->{digest} ) {
130 386         2127 require Digest::MD5;
131 386         1083 $self->{digest} = \&Digest::MD5::md5;
132             }
133              
134 387         1776 return $self;
135             }
136              
137             sub read_value {
138 3132     3132 1 5341 my $self = shift;
139 3132         6207 my ($obj, $key) = @_;
140              
141             # This will be a Reference sector
142 3132 100       7666 my $sector = $self->load_sector( $obj->_base_offset )
143             or return;
144              
145 3131 50       8491 if ( $sector->staleness != $obj->_staleness ) {
146 0         0 return;
147             }
148              
149 3131         8074 my $key_md5 = $self->_apply_digest( $key );
150              
151 3131         12548 my $value_sector = $sector->get_data_for({
152             key_md5 => $key_md5,
153             allow_head => 1,
154             });
155              
156 3131 100       9995 unless ( $value_sector ) {
157             return undef
158 77         348 }
159              
160 3054         8180 return $value_sector->data;
161             }
162              
163             sub get_classname {
164 12     12 1 24 my $self = shift;
165 12         39 my ($obj) = @_;
166              
167             # This will be a Reference sector
168 12 50       39 my $sector = $self->load_sector( $obj->_base_offset )
169             or DBM::Deep->_throw_error( "How did get_classname fail (no sector for '$obj')?!" );
170              
171 12 50       51 if ( $sector->staleness != $obj->_staleness ) {
172 0         0 return;
173             }
174              
175 12         52 return $sector->get_classname;
176             }
177              
178             sub make_reference {
179 29     29 1 45 my $self = shift;
180 29         58 my ($obj, $old_key, $new_key) = @_;
181              
182             # This will be a Reference sector
183 29 50       69 my $sector = $self->load_sector( $obj->_base_offset )
184             or DBM::Deep->_throw_error( "How did make_reference fail (no sector for '$obj')?!" );
185              
186 29 50       93 if ( $sector->staleness != $obj->_staleness ) {
187 0         0 return;
188             }
189              
190 29         79 my $old_md5 = $self->_apply_digest( $old_key );
191              
192 29         132 my $value_sector = $sector->get_data_for({
193             key_md5 => $old_md5,
194             allow_head => 1,
195             });
196              
197 29 50       99 unless ( $value_sector ) {
198 0         0 $value_sector = DBM::Deep::Sector::File::Null->new({
199             engine => $self,
200             data => undef,
201             });
202              
203 0         0 $sector->write_data({
204             key_md5 => $old_md5,
205             key => $old_key,
206             value => $value_sector,
207             });
208             }
209              
210 29 100       164 if ( $value_sector->isa( 'DBM::Deep::Sector::File::Reference' ) ) {
211 6         21 $sector->write_data({
212             key => $new_key,
213             key_md5 => $self->_apply_digest( $new_key ),
214             value => $value_sector,
215             });
216 6         36 $value_sector->increment_refcount;
217             }
218             else {
219 23         70 $sector->write_data({
220             key => $new_key,
221             key_md5 => $self->_apply_digest( $new_key ),
222             value => $value_sector->clone,
223             });
224             }
225              
226 29         221 return;
227             }
228              
229             # exists returns '', not undefined.
230             sub key_exists {
231 127     127 1 240 my $self = shift;
232 127         391 my ($obj, $key) = @_;
233              
234             # This will be a Reference sector
235 127 100       341 my $sector = $self->load_sector( $obj->_base_offset )
236             or return '';
237              
238 126 50       413 if ( $sector->staleness != $obj->_staleness ) {
239 0         0 return '';
240             }
241              
242 126         429 my $data = $sector->get_data_for({
243             key_md5 => $self->_apply_digest( $key ),
244             allow_head => 1,
245             });
246              
247             # exists() returns 1 or '' for true/false.
248 126 100       662 return $data ? 1 : '';
249             }
250              
251             sub delete_key {
252 64     64 1 149 my $self = shift;
253 64         156 my ($obj, $key) = @_;
254              
255 64 100       164 my $sector = $self->load_sector( $obj->_base_offset )
256             or return;
257              
258 63 50       207 if ( $sector->staleness != $obj->_staleness ) {
259 0         0 return;
260             }
261              
262 63         211 return $sector->delete_key({
263             key_md5 => $self->_apply_digest( $key ),
264             allow_head => 0,
265             });
266             }
267              
268             sub write_value {
269 1795     1795 1 3408 my $self = shift;
270 1795         3949 my ($obj, $key, $value) = @_;
271              
272 1795   100     7036 my $r = Scalar::Util::reftype( $value ) || '';
273             {
274 1795 100       2793 last if $r eq '';
  1795         4219  
275 1231 100       2577 last if $r eq 'HASH';
276 1054 100       2501 last if $r eq 'ARRAY';
277              
278 5         25 DBM::Deep->_throw_error(
279             "Storage of references of type '$r' is not supported."
280             );
281             }
282              
283             # This will be a Reference sector
284 1790 100       4275 my $sector = $self->load_sector( $obj->_base_offset )
285             or DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
286              
287 1789 50       5218 if ( $sector->staleness != $obj->_staleness ) {
288 0         0 DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
289             }
290              
291 1789         3429 my ($class, $type);
292 1789 100 100     8190 if ( !defined $value ) {
    100          
    100          
293 58         138 $class = 'DBM::Deep::Sector::File::Null';
294             }
295             elsif ( ref $value eq 'DBM::Deep::Null' ) {
296 3         15 DBM::Deep::_warnif(
297             'uninitialized', 'Assignment of stale reference'
298             );
299 2         9 $class = 'DBM::Deep::Sector::File::Null';
300 2         5 $value = undef;
301             }
302             elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
303 1223         1810 my $tmpvar;
304 1223 100       2336 if ( $r eq 'ARRAY' ) {
    50          
305 1049         2047 $tmpvar = tied @$value;
306             } elsif ( $r eq 'HASH' ) {
307 174         337 $tmpvar = tied %$value;
308             }
309              
310 1223 100       2677 if ( $tmpvar ) {
311 1017         1425 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
  1017         4562  
  1017         4994  
312              
313 1017 100       2402 unless ( $is_dbm_deep ) {
314 4         36 DBM::Deep->_throw_error( "Cannot store something that is tied." );
315             }
316              
317 1013 50       2357 unless ( $tmpvar->_engine->storage == $self->storage ) {
318 0         0 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
319             }
320              
321             # First, verify if we're storing the same thing to this spot. If we
322             # are, then this should be a no-op. -EJS, 2008-05-19
323 1013         3003 my $loc = $sector->get_data_location_for({
324             key_md5 => $self->_apply_digest( $key ),
325             allow_head => 1,
326             });
327              
328 1013 100 100     3543 if ( defined($loc) && $loc == $tmpvar->_base_offset ) {
329 1         6 return 1;
330             }
331              
332             #XXX Can this use $loc?
333 1012         3163 my $value_sector = $self->load_sector( $tmpvar->_base_offset );
334 1012         3063 $sector->write_data({
335             key => $key,
336             key_md5 => $self->_apply_digest( $key ),
337             value => $value_sector,
338             });
339 1012         5279 $value_sector->increment_refcount;
340              
341 1012         7211 return 1;
342             }
343              
344 206         344 $class = 'DBM::Deep::Sector::File::Reference';
345 206         602 $type = substr( $r, 0, 1 );
346             }
347             else {
348 505 50       1277 if ( tied($value) ) {
349 0         0 DBM::Deep->_throw_error( "Cannot store something that is tied." );
350             }
351 505         937 $class = 'DBM::Deep::Sector::File::Scalar';
352             }
353              
354             # Create this after loading the reference sector in case something bad
355             # happens. This way, we won't allocate value sector(s) needlessly.
356 771         3751 my $value_sector = $class->new({
357             engine => $self,
358             data => $value,
359             type => $type,
360             });
361              
362 771         2374 $sector->write_data({
363             key => $key,
364             key_md5 => $self->_apply_digest( $key ),
365             value => $value_sector,
366             });
367              
368 771         4733 $self->_descend( $value, $value_sector );
369              
370 771         4906 return 1;
371             }
372              
373             sub setup {
374 2942     2942 1 4772 my $self = shift;
375 2942         5537 my ($obj) = @_;
376              
377             # We're opening the file.
378 2942 100       7380 unless ( $obj->_base_offset ) {
379 387         1026 my $bytes_read = $self->_read_file_header;
380              
381             # Creating a new file
382 382 100       963 unless ( $bytes_read ) {
383 94         515 $self->_write_file_header;
384              
385             # 1) Create Array/Hash entry
386 94         755 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
387             engine => $self,
388             type => $obj->_type,
389             });
390 94         331 $obj->{base_offset} = $initial_reference->offset;
391 94         659 $obj->{staleness} = $initial_reference->staleness;
392              
393 94         316 $self->storage->flush;
394             }
395             # Reading from an existing file
396             else {
397 288         572 $obj->{base_offset} = $bytes_read;
398 288         827 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
399             engine => $self,
400             offset => $obj->_base_offset,
401             });
402 288 50       769 unless ( $initial_reference ) {
403 0         0 DBM::Deep->_throw_error("Corrupted file, no master index record");
404             }
405              
406 288 100       1267 unless ($obj->_type eq $initial_reference->type) {
407 6         18 DBM::Deep->_throw_error("File type mismatch");
408             }
409              
410 282         681 $obj->{staleness} = $initial_reference->staleness;
411             }
412             }
413              
414 2931         6816 $self->storage->set_inode;
415              
416 2931         5490 return 1;
417             }
418              
419             sub begin_work {
420 275     275 1 459 my $self = shift;
421 275         485 my ($obj) = @_;
422              
423 275 50       664 unless ($self->supports('transactions')) {
424 0         0 DBM::Deep->_throw_error( "Cannot begin_work unless transactions are supported" );
425             }
426              
427 275 100       641 if ( $self->trans_id ) {
428 1         3 DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
429             }
430              
431 274         671 my @slots = $self->read_txn_slots;
432 274         806 my $found;
433 274         715 for my $i ( 0 .. $self->num_txns-2 ) {
434 32406 100       53789 next if $slots[$i];
435              
436 274         505 $slots[$i] = 1;
437 274         764 $self->set_trans_id( $i + 1 );
438 274         454 $found = 1;
439 274         472 last;
440             }
441 274 50       699 unless ( $found ) {
442 0         0 DBM::Deep->_throw_error( "Cannot allocate transaction ID" );
443             }
444 274         1137 $self->write_txn_slots( @slots );
445              
446 274 50       964 if ( !$self->trans_id ) {
447 0         0 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
448             }
449              
450 274         4358 return;
451             }
452              
453             sub rollback {
454 12     12 1 33 my $self = shift;
455 12         31 my ($obj) = @_;
456              
457 12 50       56 unless ($self->supports('transactions')) {
458 0         0 DBM::Deep->_throw_error( "Cannot rollback unless transactions are supported" );
459             }
460              
461 12 100       41 if ( !$self->trans_id ) {
462 1         6 DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
463             }
464              
465             # Each entry is the file location for a bucket that has a modification for
466             # this transaction. The entries need to be expunged.
467 11         44 foreach my $entry (@{ $self->get_entries } ) {
  11         52  
468             # Remove the entry here
469 39         106 my $read_loc = $entry
470             + $self->hash_size
471             + $self->byte_size
472             + $self->byte_size
473             + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
474              
475 39         113 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
476 39         467 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
477 39         135 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
478              
479 39 100       154 if ( $data_loc > 1 ) {
480 27         102 $self->load_sector( $data_loc )->free;
481             }
482             }
483              
484 11         74 $self->clear_entries;
485              
486 11         36 my @slots = $self->read_txn_slots;
487 11         45 $slots[$self->trans_id-1] = 0;
488 11         58 $self->write_txn_slots( @slots );
489 11         54 $self->inc_txn_staleness_counter( $self->trans_id );
490 11         78 $self->set_trans_id( 0 );
491              
492 11         76 return 1;
493             }
494              
495             sub commit {
496 10     10 1 29 my $self = shift;
497 10         24 my ($obj) = @_;
498              
499 10 50       38 unless ($self->supports('transactions')) {
500 0         0 DBM::Deep->_throw_error( "Cannot commit unless transactions are supported" );
501             }
502              
503 10 100       33 if ( !$self->trans_id ) {
504 1         5 DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
505             }
506              
507 9         34 foreach my $entry (@{ $self->get_entries } ) {
  9         36  
508             # Overwrite the entry in head with the entry in trans_id
509 35         108 my $base = $entry
510             + $self->hash_size
511             + $self->byte_size;
512              
513 35         104 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
514 35         118 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
515              
516 35         80 my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
517 35         120 my $trans_loc = $self->storage->read_at(
518             $spot, $self->byte_size,
519             );
520              
521 35         154 $self->storage->print_at( $base, $trans_loc );
522             $self->storage->print_at(
523             $spot,
524 35         139 pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
525             );
526              
527 35 100       145 if ( $head_loc > 1 ) {
528 11         49 $self->load_sector( $head_loc )->free;
529             }
530             }
531              
532 9         51 $self->clear_entries;
533              
534 9         50 my @slots = $self->read_txn_slots;
535 9         62 $slots[$self->trans_id-1] = 0;
536 9         37 $self->write_txn_slots( @slots );
537 9         57 $self->inc_txn_staleness_counter( $self->trans_id );
538 9         76 $self->set_trans_id( 0 );
539              
540 9         61 return 1;
541             }
542              
543             =head1 INTERNAL METHODS
544              
545             The following methods are internal-use-only to DBM::Deep::Engine::File.
546              
547             =cut
548              
549             =head2 read_txn_slots()
550              
551             This takes no arguments.
552              
553             This will return an array with a 1 or 0 in each slot. Each spot represents one
554             available transaction. If the slot is 1, that transaction is taken. If it is 0,
555             the transaction is available.
556              
557             =cut
558              
559             sub read_txn_slots {
560 2111     2111 1 3048 my $self = shift;
561 2111         4300 my $bl = $self->txn_bitfield_len;
562 2111         3759 my $num_bits = $bl * 8;
563 2111         6573 return split '', unpack( 'b'.$num_bits,
564             $self->storage->read_at(
565             $self->trans_loc, $bl,
566             )
567             );
568             }
569              
570             =head2 write_txn_slots( @slots )
571              
572             This takes an array of 1's and 0's. This array represents the transaction slots
573             returned by L. In other words, the following is true:
574              
575             @x = read_txn_slots( write_txn_slots( @x ) );
576              
577             (With the obviously missing object referents added back in.)
578              
579             =cut
580              
581             sub write_txn_slots {
582 294     294 1 486 my $self = shift;
583 294         584 my $num_bits = $self->txn_bitfield_len * 8;
584 294         871 $self->storage->print_at( $self->trans_loc,
585             pack( 'b'.$num_bits, join('', @_) ),
586             );
587             }
588              
589             =head2 get_running_txn_ids()
590              
591             This takes no arguments.
592              
593             This will return an array of taken transaction IDs. This wraps L.
594              
595             =cut
596              
597             sub get_running_txn_ids {
598 1817     1817 1 2926 my $self = shift;
599 1817         3950 my @transactions = $self->read_txn_slots;
600 1817         6882 my @trans_ids = map { $_+1 } grep { $transactions[$_] } 0 .. $#transactions;
  20         121  
  14896         29845  
601             }
602              
603             =head2 get_txn_staleness_counter( $trans_id )
604              
605             This will return the staleness counter for the given transaction ID. Please see
606             L for more information.
607              
608             =cut
609              
610             sub get_txn_staleness_counter {
611 1209     1209 1 1982 my $self = shift;
612 1209         2044 my ($trans_id) = @_;
613              
614             # Hardcode staleness of 0 for the HEAD
615 1209 50       2353 return 0 unless $trans_id;
616              
617 1209         3304 return unpack( $StP{$STALE_SIZE},
618             $self->storage->read_at(
619             $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
620             $STALE_SIZE,
621             )
622             );
623             }
624              
625             =head2 inc_txn_staleness_counter( $trans_id )
626              
627             This will increment the staleness counter for the given transaction ID. Please see
628             L for more information.
629              
630             =cut
631              
632             sub inc_txn_staleness_counter {
633 20     20 1 40 my $self = shift;
634 20         65 my ($trans_id) = @_;
635              
636             # Hardcode staleness of 0 for the HEAD
637 20 50       69 return 0 unless $trans_id;
638              
639             $self->storage->print_at(
640             $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
641 20         84 pack( $StP{$STALE_SIZE}, $self->get_txn_staleness_counter( $trans_id ) + 1 ),
642             );
643             }
644              
645             =head2 get_entries()
646              
647             This takes no arguments.
648              
649             This returns a list of all the sectors that have been modified by this transaction.
650              
651             =cut
652              
653             sub get_entries {
654 20     20 1 45 my $self = shift;
655 20   100     41 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
  20         60  
656             }
657              
658             =head2 add_entry( $trans_id, $location )
659              
660             This takes a transaction ID and a file location and marks the sector at that
661             location as having been modified by the transaction identified by $trans_id.
662              
663             This returns nothing.
664              
665             B: Unlike all the other _entries() methods, there are several cases where
666             C<< $trans_id != $self->trans_id >> for this method.
667              
668             =cut
669              
670             sub add_entry {
671 1877     1877 1 2830 my $self = shift;
672 1877         3326 my ($trans_id, $loc) = @_;
673              
674 1877   100     5504 $self->{entries}{$trans_id} ||= {};
675 1877         10100 $self->{entries}{$trans_id}{$loc} = undef;
676             }
677              
678             =head2 reindex_entry( $old_loc, $new_loc )
679              
680             This takes two locations (old and new, respectively). If a location that has
681             been modified by this transaction is subsequently reindexed due to a bucketlist
682             overflowing, then the entries hash needs to be made aware of this change.
683              
684             This returns nothing.
685              
686             =cut
687              
688             sub reindex_entry {
689 160     160 1 246 my $self = shift;
690 160         295 my ($old_loc, $new_loc) = @_;
691              
692             TRANS:
693 160         269 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
  352         1215  
694 192 100       447 if ( exists $locs->{$old_loc} ) {
695 190         411 delete $locs->{$old_loc};
696 190         566 $locs->{$new_loc} = undef;
697 190         413 next TRANS;
698             }
699             }
700             }
701              
702             =head2 clear_entries()
703              
704             This takes no arguments. It will clear the entries list for the running
705             transaction.
706              
707             This returns nothing.
708              
709             =cut
710              
711             sub clear_entries {
712 20     20 1 50 my $self = shift;
713 20         67 delete $self->{entries}{$self->trans_id};
714             }
715              
716             =head2 _write_file_header()
717              
718             This writes the file header for a new file. This will write the various settings
719             that set how the file is interpreted.
720              
721             =head2 _read_file_header()
722              
723             This reads the file header from an existing file. This will read the various
724             settings that set how the file is interpreted.
725              
726             =cut
727              
728             {
729             my $header_fixed = length( __PACKAGE__->SIG_FILE ) + 1 + 4 + 4;
730             my $this_file_version = 4;
731             my $min_file_version = 3;
732              
733             sub _write_file_header {
734 94     94   784 my $self = shift;
735              
736 94         1789 my $nt = $self->num_txns;
737 94         404 my $bl = $self->txn_bitfield_len;
738              
739 94         441 my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
740              
741 94         352 my $loc = $self->storage->request_space( $header_fixed + $header_var );
742              
743             $self->storage->print_at( $loc,
744             $self->SIG_FILE,
745             $self->SIG_HEADER,
746             pack('N', $this_file_version), # At this point, we're at 9 bytes
747             pack('N', $header_var), # header size
748             # --- Above is $header_fixed. Below is $header_var
749             pack('C', $self->byte_size),
750              
751             # These shenanigans are to allow a 256 within a C
752             pack('C', $self->max_buckets - 1),
753             pack('C', $self->data_sector_size - 1),
754              
755             pack('C', $nt),
756             pack('C' . $bl, 0 ), # Transaction activeness bitfield
757             pack($StP{$STALE_SIZE}.($nt-1), 0 x ($nt-1) ), # Transaction staleness counters
758             pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
759             pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
760 94         319 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
761             );
762              
763             #XXX Set these less fragilely
764 94         540 $self->set_trans_loc( $header_fixed + 4 );
765 94         352 $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
766              
767 94         195 $self->{v} = $this_file_version;
768              
769 94         193 return;
770             }
771              
772             sub _read_file_header {
773 390     390   611 my $self = shift;
774              
775 390         1029 my $buffer = $self->storage->read_at( 0, $header_fixed );
776 390 100       1441 return unless length($buffer);
777              
778 296         1594 my ($file_signature, $sig_header, $file_version, $size) = unpack(
779             'A4 A N N', $buffer
780             );
781              
782 296 100       1233 unless ( $file_signature eq $self->SIG_FILE ) {
783 1         38 $self->storage->close;
784 1         5 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
785             }
786              
787 295 100       933 unless ( $sig_header eq $self->SIG_HEADER ) {
788 2         41 $self->storage->close;
789 2         8 DBM::Deep->_throw_error( "Pre-1.00 file version found" );
790             }
791              
792 293 100       628 if ( $file_version < $min_file_version ) {
793 2         11 $self->storage->close;
794 2         6 DBM::Deep->_throw_error(
795             "This file version is too old - "
796             . _guess_version($file_version) .
797             " - expected " . _guess_version($min_file_version)
798             . " to " . _guess_version($this_file_version)
799             );
800             }
801 291 50       604 if ( $file_version > $this_file_version ) {
802 0         0 $self->storage->close;
803 0         0 DBM::Deep->_throw_error(
804             "This file version is too new - probably "
805             . _guess_version($file_version) .
806             " - expected " . _guess_version($min_file_version)
807             . " to " . _guess_version($this_file_version)
808             );
809             }
810 291         670 $self->{v} = $file_version;
811              
812 291         816 my $buffer2 = $self->storage->read_at( undef, $size );
813 291         1035 my @values = unpack( 'C C C C', $buffer2 );
814              
815 291 50 33     1112 if ( @values != 4 || grep { !defined } @values ) {
  1164         2722  
816 0         0 $self->storage->close;
817 0         0 DBM::Deep->_throw_error("Corrupted file - bad header");
818             }
819              
820 291 50       777 if ($values[3] != $self->{num_txns}) {
821 0         0 warn "num_txns ($self->{num_txns}) is different from the file ($values[3])\n";
822             }
823              
824             #XXX Add warnings if values weren't set right
825 291         489 @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
  291         803  
826              
827             # These shenanigans are to allow a 256 within a C
828 291         519 $self->{max_buckets} += 1;
829 291         427 $self->{data_sector_size} += 1;
830              
831 291         796 my $bl = $self->txn_bitfield_len;
832              
833 291         682 my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
834 291 50       771 unless ( $size == $header_var ) {
835 0         0 $self->storage->close;
836 0         0 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
837             }
838              
839 291         790 $self->set_trans_loc( $header_fixed + scalar(@values) );
840 291         628 $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
841              
842 291         717 return length($buffer) + length($buffer2);
843             }
844              
845             sub _guess_version {
846 6 100   6   24 $_[0] == 4 and return 2;
847 4 100       12 $_[0] == 3 and return '1.0003';
848 2 50       6 $_[0] == 2 and return '1.00';
849 2 50       15 $_[0] == 1 and return '0.99';
850 0 0       0 $_[0] == 0 and return '0.91';
851              
852 0         0 return $_[0]-2;
853             }
854             }
855              
856             =head2 _apply_digest( @stuff )
857              
858             This will apply the digest method (default to Digest::MD5::md5) to the arguments
859             passed in and return the result.
860              
861             =cut
862              
863             sub _apply_digest {
864 6174     6174   9935 my $self = shift;
865 6174         10315 my $victim = shift;
866 6174 100       23192 utf8::encode $victim if $self->{v} >= 4;
867 6174         35493 return $self->{digest}->($victim);
868             }
869              
870             =head2 _add_free_blist_sector( $offset, $size )
871              
872             =head2 _add_free_data_sector( $offset, $size )
873              
874             =head2 _add_free_index_sector( $offset, $size )
875              
876             These methods are all wrappers around _add_free_sector(), providing the proper
877             chain offset ($multiple) for the sector type.
878              
879             =cut
880              
881 36     36   188 sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
882 429     429   1246 sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
883 0     0   0 sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
884              
885             =head2 _add_free_sector( $multiple, $offset, $size )
886              
887             _add_free_sector() takes the offset into the chains location, the offset of the
888             sector, and the size of that sector. It will mark the sector as a free sector
889             and put it into the list of sectors that are free of this type for use later.
890              
891             This returns nothing.
892              
893             B: $size is unused?
894              
895             =cut
896              
897             sub _add_free_sector {
898 465     465   808 my $self = shift;
899 465         1079 my ($multiple, $offset, $size) = @_;
900              
901 465         991 my $chains_offset = $multiple * $self->byte_size;
902              
903 465         1218 my $storage = $self->storage;
904              
905             # Increment staleness.
906             # XXX Can this increment+modulo be done by "&= 0x1" ?
907 465         1797 my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + $self->SIG_SIZE, $STALE_SIZE ) );
908 465         1710 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
909 465         2900 $storage->print_at( $offset + $self->SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
910              
911 465         1618 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
912              
913             $storage->print_at( $self->chains_loc + $chains_offset,
914 465         1586 pack( $StP{$self->byte_size}, $offset ),
915             );
916              
917             # Record the old head in the new sector after the signature and staleness counter
918 465         2229 $storage->print_at( $offset + $self->SIG_SIZE + $STALE_SIZE, $old_head );
919             }
920              
921             =head2 _request_blist_sector( $size )
922              
923             =head2 _request_data_sector( $size )
924              
925             =head2 _request_index_sector( $size )
926              
927             These methods are all wrappers around _request_sector(), providing the proper
928             chain offset ($multiple) for the sector type.
929              
930             =cut
931              
932 607     607   1592 sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
933 5062     5062   12247 sub _request_data_sector { shift->_request_sector( 1, @_ ) }
934 10     10   36 sub _request_index_sector { shift->_request_sector( 2, @_ ) }
935              
936             =head2 _request_sector( $multiple $size )
937              
938             This takes the offset into the chains location and the size of that sector.
939              
940             This returns the object with the sector. If there is an available free sector of
941             that type, then it will be reused. If there isn't one, then a new one will be
942             allocated.
943              
944             =cut
945              
946             sub _request_sector {
947 5679     5679   8182 my $self = shift;
948 5679         10351 my ($multiple, $size) = @_;
949              
950 5679         10279 my $chains_offset = $multiple * $self->byte_size;
951              
952 5679         13939 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
953 5679         15921 my $loc = unpack( $StP{$self->byte_size}, $old_head );
954              
955             # We don't have any free sectors of the right size, so allocate a new one.
956 5679 100       14127 unless ( $loc ) {
957 5377         12848 my $offset = $self->storage->request_space( $size );
958              
959             # Zero out the new sector. This also guarantees correct increases
960             # in the filesize.
961 5377         11358 $self->storage->print_at( $offset, chr(0) x $size );
962              
963 5377         21673 return $offset;
964             }
965              
966             # Read the new head after the signature and the staleness counter
967 302         859 my $new_head = $self->storage->read_at( $loc + $self->SIG_SIZE + $STALE_SIZE, $self->byte_size );
968 302         1001 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
969             $self->storage->print_at(
970             $loc + $self->SIG_SIZE + $STALE_SIZE,
971 302         998 pack( $StP{$self->byte_size}, 0 ),
972             );
973              
974 302         1265 return $loc;
975             }
976              
977             =head2 ACCESSORS
978              
979             The following are readonly attributes.
980              
981             =over 4
982              
983             =item * byte_size
984              
985             =item * hash_size
986              
987             =item * hash_chars
988              
989             =item * num_txns
990              
991             =item * max_buckets
992              
993             =item * blank_md5
994              
995             =item * data_sector_size
996              
997             =item * txn_bitfield_len
998              
999             =back
1000              
1001             =cut
1002              
1003 157732     157732 1 448798 sub byte_size { $_[0]{byte_size} }
1004 52096     52096 1 134870 sub hash_size { $_[0]{hash_size} }
1005 12431     12431 1 37010 sub hash_chars { $_[0]{hash_chars} }
1006 8085     8085 1 21775 sub num_txns { $_[0]{num_txns} }
1007 18086     18086 1 57256 sub max_buckets { $_[0]{max_buckets} }
1008 15739     15739 1 32063 sub blank_md5 { chr(0) x $_[0]->hash_size }
1009 8584     8584 1 24379 sub data_sector_size { $_[0]{data_sector_size} }
1010              
1011             # This is a calculated value
1012             sub txn_bitfield_len {
1013 4019     4019 1 5962 my $self = shift;
1014 4019 100       8731 unless ( exists $self->{txn_bitfield_len} ) {
1015 382         936 my $temp = ($self->num_txns) / 8;
1016 382 100       1285 if ( $temp > int( $temp ) ) {
1017 368         655 $temp = int( $temp ) + 1;
1018             }
1019 382         811 $self->{txn_bitfield_len} = $temp;
1020             }
1021 4019         9141 return $self->{txn_bitfield_len};
1022             }
1023              
1024             =pod
1025              
1026             The following are read/write attributes.
1027              
1028             =over 4
1029              
1030             =item * trans_id / set_trans_id( $new_id )
1031              
1032             =item * trans_loc / set_trans_loc( $new_loc )
1033              
1034             =item * chains_loc / set_chains_loc( $new_loc )
1035              
1036             =back
1037              
1038             =cut
1039              
1040 17637     17637 1 38222 sub trans_id { $_[0]{trans_id} }
1041 294     294 1 578 sub set_trans_id { $_[0]{trans_id} = $_[1] }
1042              
1043 3634     3634 1 11621 sub trans_loc { $_[0]{trans_loc} }
1044 385     385 1 814 sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
1045              
1046 6920     6920 1 14788 sub chains_loc { $_[0]{chains_loc} }
1047 385     385 1 661 sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
1048              
1049             sub supports {
1050 307     307 1 472 my $self = shift;
1051 307         568 my ($feature) = @_;
1052              
1053 307 100       736 if ( $feature eq 'transactions' ) {
1054 305         633 return $self->num_txns > 1;
1055             }
1056 2 100       6 return 1 if $feature eq 'singletons';
1057 1 50       14 return 1 if $feature eq 'unicode';
1058 0         0 return;
1059             }
1060              
1061             sub db_version {
1062 2 100   2 0 13 return $_[0]{v} == 3 ? '1.0003' : 2;
1063             }
1064              
1065             sub clear {
1066 217     217 1 396 my $self = shift;
1067 217         330 my $obj = shift;
1068              
1069 217 50       506 my $sector = $self->load_sector( $obj->_base_offset )
1070             or return;
1071              
1072 217 50       711 return unless $sector->staleness == $obj->_staleness;
1073              
1074 217         837 $sector->clear;
1075              
1076 217         1261 return;
1077             }
1078              
1079             =head2 _dump_file()
1080              
1081             This method takes no arguments. It's used to print out a textual representation
1082             of the DBM::Deep DB file. It assumes the file is not-corrupted.
1083              
1084             =cut
1085              
1086             sub _dump_file {
1087 3     3   7 my $self = shift;
1088              
1089             # Read the header
1090 3         6 my $spot = $self->_read_file_header();
1091              
1092 3         12 my %types = (
1093             0 => 'B',
1094             1 => 'D',
1095             2 => 'I',
1096             );
1097              
1098 3         7 my %sizes = (
1099             'D' => $self->data_sector_size,
1100             'B' => DBM::Deep::Sector::File::BucketList->new({engine=>$self,offset=>1})->size,
1101             'I' => DBM::Deep::Sector::File::Index->new({engine=>$self,offset=>1})->size,
1102             );
1103              
1104 3         14 my $return = "";
1105              
1106             # Header values
1107 3         7 $return .= "NumTxns: " . $self->num_txns . $/;
1108              
1109             # Read the free sector chains
1110 3         5 my %sectors;
1111 3         9 foreach my $multiple ( 0 .. 2 ) {
1112 9         24 $return .= "Chains($types{$multiple}):";
1113 9         18 my $old_loc = $self->chains_loc + $multiple * $self->byte_size;
1114 9         12 while ( 1 ) {
1115             my $loc = unpack(
1116 9         17 $StP{$self->byte_size},
1117             $self->storage->read_at( $old_loc, $self->byte_size ),
1118             );
1119              
1120             # We're now out of free sectors of this kind.
1121 9 50       38 unless ( $loc ) {
1122 9         13 last;
1123             }
1124              
1125 0         0 $sectors{ $types{$multiple} }{ $loc } = undef;
1126 0         0 $old_loc = $loc + $self->SIG_SIZE + $STALE_SIZE;
1127 0         0 $return .= " $loc";
1128             }
1129 9         26 $return .= $/;
1130             }
1131              
1132             SECTOR:
1133 3         11 while ( $spot < $self->storage->{end} ) {
1134             # Read each sector in order.
1135 11         31 my $sector = $self->load_sector( $spot );
1136 11 50       26 if ( !$sector ) {
1137             # Find it in the free-sectors that were found already
1138 0         0 foreach my $type ( keys %sectors ) {
1139 0 0       0 if ( exists $sectors{$type}{$spot} ) {
1140 0         0 my $size = $sizes{$type};
1141 0         0 $return .= sprintf "%08d: %s %04d\n", $spot, 'F' . $type, $size;
1142 0         0 $spot += $size;
1143 0         0 next SECTOR;
1144             }
1145             }
1146              
1147 0         0 die "********\n$return\nDidn't find free sector for $spot in chains\n********\n";
1148             }
1149             else {
1150 11         31 $return .= sprintf "%08d: %s %04d", $spot, $sector->type, $sector->size;
1151 11 100 66     27 if ( $sector->type =~ /^[DU]\z/ ) {
    100          
    50          
1152 6         15 $return .= ' ' . $sector->data;
1153             }
1154             elsif ( $sector->type eq 'A' || $sector->type eq 'H' ) {
1155 3         12 $return .= ' REF: ' . $sector->get_refcount;
1156             }
1157             elsif ( $sector->type eq 'B' ) {
1158 2         6 foreach my $bucket ( $sector->chopped_up ) {
1159 3         6 $return .= "\n ";
1160 3         7 $return .= sprintf "%08d", unpack($StP{$self->byte_size},
1161             substr( $bucket->[-1], $self->hash_size, $self->byte_size),
1162             );
1163 3         8 my $l = unpack( $StP{$self->byte_size},
1164             substr( $bucket->[-1],
1165             $self->hash_size + $self->byte_size,
1166             $self->byte_size,
1167             ),
1168             );
1169 3         10 $return .= sprintf " %08d", $l;
1170 3         7 foreach my $txn ( 0 .. $self->num_txns - 2 ) {
1171 0         0 my $l = unpack( $StP{$self->byte_size},
1172             substr( $bucket->[-1],
1173             $self->hash_size + 2 * $self->byte_size + $txn * ($self->byte_size + $STALE_SIZE),
1174             $self->byte_size,
1175             ),
1176             );
1177 0         0 $return .= sprintf " %08d", $l;
1178             }
1179             }
1180             }
1181 11         30 $return .= $/;
1182              
1183 11         36 $spot += $sector->size;
1184             }
1185             }
1186              
1187 3         58 return $return;
1188             }
1189              
1190             1;
1191             __END__