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   940 use 5.008_004;
  50         179  
4              
5 50     50   298 use strict;
  50         113  
  50         1334  
6 50     50   350 use warnings FATAL => 'all';
  50         144  
  50         2066  
7 50     50   338 no warnings 'recursion';
  50         97  
  50         2052  
8              
9 50     50   336 use base qw( DBM::Deep::Engine );
  50         147  
  50         4521  
10              
11 50     50   389 use Scalar::Util ();
  50         136  
  50         1137  
12              
13 50     50   23239 use DBM::Deep::Null ();
  50         140  
  50         1110  
14 50     50   24610 use DBM::Deep::Sector::File ();
  50         139  
  50         1185  
15 50     50   25865 use DBM::Deep::Storage::File ();
  50         153  
  50         293883  
16              
17 23034     23034 1 79567 sub sector_type { 'DBM::Deep::Sector::File' }
18 316     316 1 16241 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 890 my $class = shift;
61 388         907 my ($args) = @_;
62              
63             $args->{storage} = DBM::Deep::Storage::File->new( $args )
64 388 50       2883 unless exists $args->{storage};
65              
66 387         3853 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         846 delete $args->{byte_size};
86 387 100       1074 if ( defined $args->{pack_size} ) {
87 3 100       15 if ( lc $args->{pack_size} eq 'small' ) {
    100          
    50          
88 1         3 $args->{byte_size} = 2;
89             }
90             elsif ( lc $args->{pack_size} eq 'medium' ) {
91 1         2 $args->{byte_size} = 4;
92             }
93             elsif ( lc $args->{pack_size} eq 'large' ) {
94 1         3 $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         1954 foreach my $param ( keys %$self ) {
103 4257 100       8261 next unless exists $args->{$param};
104 687         1269 $self->{$param} = $args->{$param};
105             }
106              
107 387         2250 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     11177 if ( !defined $self->{$attr}
    100 100        
      100        
115             || !length $self->{$attr}
116             || $self->{$attr} =~ /\D/
117             || $self->{$attr} < $c->{floor}
118             ) {
119 12 100       27 $self->{$attr} = '(undef)' if !defined $self->{$attr};
120 12         131 warn "Floor of $attr is $c->{floor}. Setting it to $c->{floor} from '$self->{$attr}'\n";
121 12         690 $self->{$attr} = $c->{floor};
122             }
123             elsif ( $self->{$attr} > $c->{ceil} ) {
124 3         38 warn "Ceiling of $attr is $c->{ceil}. Setting it to $c->{ceil} from '$self->{$attr}'\n";
125 3         173 $self->{$attr} = $c->{ceil};
126             }
127             }
128              
129 387 100       1032 if ( !$self->{digest} ) {
130 386         2403 require Digest::MD5;
131 386         1107 $self->{digest} = \&Digest::MD5::md5;
132             }
133              
134 387         2067 return $self;
135             }
136              
137             sub read_value {
138 3132     3132 1 5223 my $self = shift;
139 3132         6694 my ($obj, $key) = @_;
140              
141             # This will be a Reference sector
142 3132 100       7365 my $sector = $self->load_sector( $obj->_base_offset )
143             or return;
144              
145 3131 50       9142 if ( $sector->staleness != $obj->_staleness ) {
146 0         0 return;
147             }
148              
149 3131         8273 my $key_md5 = $self->_apply_digest( $key );
150              
151 3131         13977 my $value_sector = $sector->get_data_for({
152             key_md5 => $key_md5,
153             allow_head => 1,
154             });
155              
156 3131 100       10980 unless ( $value_sector ) {
157             return undef
158 77         304 }
159              
160 3054         9105 return $value_sector->data;
161             }
162              
163             sub get_classname {
164 12     12 1 28 my $self = shift;
165 12         30 my ($obj) = @_;
166              
167             # This will be a Reference sector
168 12 50       49 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       47 if ( $sector->staleness != $obj->_staleness ) {
172 0         0 return;
173             }
174              
175 12         48 return $sector->get_classname;
176             }
177              
178             sub make_reference {
179 29     29 1 70 my $self = shift;
180 29         62 my ($obj, $old_key, $new_key) = @_;
181              
182             # This will be a Reference sector
183 29 50       82 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         80 my $old_md5 = $self->_apply_digest( $old_key );
191              
192 29         138 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       157 if ( $value_sector->isa( 'DBM::Deep::Sector::File::Reference' ) ) {
211 6         18 $sector->write_data({
212             key => $new_key,
213             key_md5 => $self->_apply_digest( $new_key ),
214             value => $value_sector,
215             });
216 6         38 $value_sector->increment_refcount;
217             }
218             else {
219 23         63 $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         231 return;
227             }
228              
229             # exists returns '', not undefined.
230             sub key_exists {
231 127     127 1 242 my $self = shift;
232 127         369 my ($obj, $key) = @_;
233              
234             # This will be a Reference sector
235 127 100       377 my $sector = $self->load_sector( $obj->_base_offset )
236             or return '';
237              
238 126 50       434 if ( $sector->staleness != $obj->_staleness ) {
239 0         0 return '';
240             }
241              
242 126         424 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       768 return $data ? 1 : '';
249             }
250              
251             sub delete_key {
252 64     64 1 125 my $self = shift;
253 64         174 my ($obj, $key) = @_;
254              
255 64 100       187 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         208 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 3117 my $self = shift;
270 1795         3953 my ($obj, $key, $value) = @_;
271              
272 1795   100     6890 my $r = Scalar::Util::reftype( $value ) || '';
273             {
274 1795 100       2946 last if $r eq '';
  1795         4356  
275 1231 100       2488 last if $r eq 'HASH';
276 1054 100       2368 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       4461 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       5296 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         3687 my ($class, $type);
292 1789 100 100     8612 if ( !defined $value ) {
    100          
    100          
293 58         109 $class = 'DBM::Deep::Sector::File::Null';
294             }
295             elsif ( ref $value eq 'DBM::Deep::Null' ) {
296 3         13 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         2043 my $tmpvar;
304 1223 100       2408 if ( $r eq 'ARRAY' ) {
    50          
305 1049         2012 $tmpvar = tied @$value;
306             } elsif ( $r eq 'HASH' ) {
307 174         362 $tmpvar = tied %$value;
308             }
309              
310 1223 100       2771 if ( $tmpvar ) {
311 1017         1551 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
  1017         4819  
  1017         5411  
312              
313 1017 100       2601 unless ( $is_dbm_deep ) {
314 4         16 DBM::Deep->_throw_error( "Cannot store something that is tied." );
315             }
316              
317 1013 50       2451 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         3034 my $loc = $sector->get_data_location_for({
324             key_md5 => $self->_apply_digest( $key ),
325             allow_head => 1,
326             });
327              
328 1013 100 100     3433 if ( defined($loc) && $loc == $tmpvar->_base_offset ) {
329 1         11 return 1;
330             }
331              
332             #XXX Can this use $loc?
333 1012         3114 my $value_sector = $self->load_sector( $tmpvar->_base_offset );
334 1012         3494 $sector->write_data({
335             key => $key,
336             key_md5 => $self->_apply_digest( $key ),
337             value => $value_sector,
338             });
339 1012         5675 $value_sector->increment_refcount;
340              
341 1012         7780 return 1;
342             }
343              
344 206         405 $class = 'DBM::Deep::Sector::File::Reference';
345 206         567 $type = substr( $r, 0, 1 );
346             }
347             else {
348 505 50       1510 if ( tied($value) ) {
349 0         0 DBM::Deep->_throw_error( "Cannot store something that is tied." );
350             }
351 505         876 $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         4075 my $value_sector = $class->new({
357             engine => $self,
358             data => $value,
359             type => $type,
360             });
361              
362 771         2439 $sector->write_data({
363             key => $key,
364             key_md5 => $self->_apply_digest( $key ),
365             value => $value_sector,
366             });
367              
368 771         4922 $self->_descend( $value, $value_sector );
369              
370 771         4844 return 1;
371             }
372              
373             sub setup {
374 2942     2942 1 5020 my $self = shift;
375 2942         5083 my ($obj) = @_;
376              
377             # We're opening the file.
378 2942 100       7156 unless ( $obj->_base_offset ) {
379 387         1072 my $bytes_read = $self->_read_file_header;
380              
381             # Creating a new file
382 382 100       1099 unless ( $bytes_read ) {
383 94         437 $self->_write_file_header;
384              
385             # 1) Create Array/Hash entry
386 94         700 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
387             engine => $self,
388             type => $obj->_type,
389             });
390 94         354 $obj->{base_offset} = $initial_reference->offset;
391 94         563 $obj->{staleness} = $initial_reference->staleness;
392              
393 94         329 $self->storage->flush;
394             }
395             # Reading from an existing file
396             else {
397 288         586 $obj->{base_offset} = $bytes_read;
398 288         976 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
399             engine => $self,
400             offset => $obj->_base_offset,
401             });
402 288 50       957 unless ( $initial_reference ) {
403 0         0 DBM::Deep->_throw_error("Corrupted file, no master index record");
404             }
405              
406 288 100       1330 unless ($obj->_type eq $initial_reference->type) {
407 6         20 DBM::Deep->_throw_error("File type mismatch");
408             }
409              
410 282         751 $obj->{staleness} = $initial_reference->staleness;
411             }
412             }
413              
414 2931         7438 $self->storage->set_inode;
415              
416 2931         5843 return 1;
417             }
418              
419             sub begin_work {
420 275     275 1 507 my $self = shift;
421 275         536 my ($obj) = @_;
422              
423 275 50       634 unless ($self->supports('transactions')) {
424 0         0 DBM::Deep->_throw_error( "Cannot begin_work unless transactions are supported" );
425             }
426              
427 275 100       730 if ( $self->trans_id ) {
428 1         13 DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
429             }
430              
431 274         684 my @slots = $self->read_txn_slots;
432 274         802 my $found;
433 274         868 for my $i ( 0 .. $self->num_txns-2 ) {
434 32406 100       56820 next if $slots[$i];
435              
436 274         595 $slots[$i] = 1;
437 274         867 $self->set_trans_id( $i + 1 );
438 274         509 $found = 1;
439 274         522 last;
440             }
441 274 50       662 unless ( $found ) {
442 0         0 DBM::Deep->_throw_error( "Cannot allocate transaction ID" );
443             }
444 274         1119 $self->write_txn_slots( @slots );
445              
446 274 50       952 if ( !$self->trans_id ) {
447 0         0 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
448             }
449              
450 274         4711 return;
451             }
452              
453             sub rollback {
454 12     12 1 31 my $self = shift;
455 12         32 my ($obj) = @_;
456              
457 12 50       47 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         5 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         30 foreach my $entry (@{ $self->get_entries } ) {
  11         52  
468             # Remove the entry here
469 39         125 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         115 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
476 39         120 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
477 39         115 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
478              
479 39 100       144 if ( $data_loc > 1 ) {
480 27         91 $self->load_sector( $data_loc )->free;
481             }
482             }
483              
484 11         68 $self->clear_entries;
485              
486 11         41 my @slots = $self->read_txn_slots;
487 11         51 $slots[$self->trans_id-1] = 0;
488 11         73 $self->write_txn_slots( @slots );
489 11         52 $self->inc_txn_staleness_counter( $self->trans_id );
490 11         59 $self->set_trans_id( 0 );
491              
492 11         76 return 1;
493             }
494              
495             sub commit {
496 10     10 1 27 my $self = shift;
497 10         35 my ($obj) = @_;
498              
499 10 50       45 unless ($self->supports('transactions')) {
500 0         0 DBM::Deep->_throw_error( "Cannot commit unless transactions are supported" );
501             }
502              
503 10 100       41 if ( !$self->trans_id ) {
504 1         4 DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
505             }
506              
507 9         22 foreach my $entry (@{ $self->get_entries } ) {
  9         42  
508             # Overwrite the entry in head with the entry in trans_id
509 35         111 my $base = $entry
510             + $self->hash_size
511             + $self->byte_size;
512              
513 35         111 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
514 35         125 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
515              
516 35         88 my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
517 35         100 my $trans_loc = $self->storage->read_at(
518             $spot, $self->byte_size,
519             );
520              
521 35         114 $self->storage->print_at( $base, $trans_loc );
522             $self->storage->print_at(
523             $spot,
524 35         126 pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
525             );
526              
527 35 100       153 if ( $head_loc > 1 ) {
528 11         50 $self->load_sector( $head_loc )->free;
529             }
530             }
531              
532 9         61 $self->clear_entries;
533              
534 9         38 my @slots = $self->read_txn_slots;
535 9         40 $slots[$self->trans_id-1] = 0;
536 9         43 $self->write_txn_slots( @slots );
537 9         43 $self->inc_txn_staleness_counter( $self->trans_id );
538 9         61 $self->set_trans_id( 0 );
539              
540 9         72 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 3233 my $self = shift;
561 2111         4174 my $bl = $self->txn_bitfield_len;
562 2111         4100 my $num_bits = $bl * 8;
563 2111         7234 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 514 my $self = shift;
583 294         603 my $num_bits = $self->txn_bitfield_len * 8;
584 294         972 $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 2928 my $self = shift;
599 1817         3992 my @transactions = $self->read_txn_slots;
600 1817         6747 my @trans_ids = map { $_+1 } grep { $transactions[$_] } 0 .. $#transactions;
  20         128  
  14896         31548  
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 2137 my $self = shift;
612 1209         2264 my ($trans_id) = @_;
613              
614             # Hardcode staleness of 0 for the HEAD
615 1209 50       2890 return 0 unless $trans_id;
616              
617 1209         3429 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 51 my $self = shift;
634 20         55 my ($trans_id) = @_;
635              
636             # Hardcode staleness of 0 for the HEAD
637 20 50       120 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         87 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 47 my $self = shift;
655 20   100     45 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
  20         64  
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 2852 my $self = shift;
672 1877         3476 my ($trans_id, $loc) = @_;
673              
674 1877   100     5502 $self->{entries}{$trans_id} ||= {};
675 1877         8382 $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 251 my $self = shift;
690 160         660 my ($old_loc, $new_loc) = @_;
691              
692             TRANS:
693 160         272 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
  352         1301  
694 192 100       451 if ( exists $locs->{$old_loc} ) {
695 190         390 delete $locs->{$old_loc};
696 190         421 $locs->{$new_loc} = undef;
697 190         408 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         77 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   810 my $self = shift;
735              
736 94         1749 my $nt = $self->num_txns;
737 94         406 my $bl = $self->txn_bitfield_len;
738              
739 94         362 my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
740              
741 94         368 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         264 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
761             );
762              
763             #XXX Set these less fragilely
764 94         586 $self->set_trans_loc( $header_fixed + 4 );
765 94         376 $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
766              
767 94         273 $self->{v} = $this_file_version;
768              
769 94         226 return;
770             }
771              
772             sub _read_file_header {
773 390     390   655 my $self = shift;
774              
775 390         1096 my $buffer = $self->storage->read_at( 0, $header_fixed );
776 390 100       1507 return unless length($buffer);
777              
778 296         1771 my ($file_signature, $sig_header, $file_version, $size) = unpack(
779             'A4 A N N', $buffer
780             );
781              
782 296 100       1312 unless ( $file_signature eq $self->SIG_FILE ) {
783 1         46 $self->storage->close;
784 1         5 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
785             }
786              
787 295 100       1001 unless ( $sig_header eq $self->SIG_HEADER ) {
788 2         34 $self->storage->close;
789 2         11 DBM::Deep->_throw_error( "Pre-1.00 file version found" );
790             }
791              
792 293 100       688 if ( $file_version < $min_file_version ) {
793 2         8 $self->storage->close;
794 2         8 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       697 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         645 $self->{v} = $file_version;
811              
812 291         858 my $buffer2 = $self->storage->read_at( undef, $size );
813 291         1074 my @values = unpack( 'C C C C', $buffer2 );
814              
815 291 50 33     1203 if ( @values != 4 || grep { !defined } @values ) {
  1164         3078  
816 0         0 $self->storage->close;
817 0         0 DBM::Deep->_throw_error("Corrupted file - bad header");
818             }
819              
820 291 50       778 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         578 @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
  291         817  
826              
827             # These shenanigans are to allow a 256 within a C
828 291         621 $self->{max_buckets} += 1;
829 291         445 $self->{data_sector_size} += 1;
830              
831 291         755 my $bl = $self->txn_bitfield_len;
832              
833 291         735 my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
834 291 50       757 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         967 $self->set_trans_loc( $header_fixed + scalar(@values) );
840 291         730 $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
841              
842 291         824 return length($buffer) + length($buffer2);
843             }
844              
845             sub _guess_version {
846 6 100   6   17 $_[0] == 4 and return 2;
847 4 100       12 $_[0] == 3 and return '1.0003';
848 2 50       5 $_[0] == 2 and return '1.00';
849 2 50       9 $_[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   10141 my $self = shift;
865 6174         10622 my $victim = shift;
866 6174 100       24157 utf8::encode $victim if $self->{v} >= 4;
867 6174         38734 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   197 sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
882 429     429   1191 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   822 my $self = shift;
899 465         1067 my ($multiple, $offset, $size) = @_;
900              
901 465         1000 my $chains_offset = $multiple * $self->byte_size;
902              
903 465         1229 my $storage = $self->storage;
904              
905             # Increment staleness.
906             # XXX Can this increment+modulo be done by "&= 0x1" ?
907 465         1938 my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + $self->SIG_SIZE, $STALE_SIZE ) );
908 465         1638 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
909 465         2950 $storage->print_at( $offset + $self->SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
910              
911 465         1637 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         1589 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         2361 $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 606     606   1585 sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
933 5062     5062   11967 sub _request_data_sector { shift->_request_sector( 1, @_ ) }
934 10     10   50 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 5678     5678   8450 my $self = shift;
948 5678         10557 my ($multiple, $size) = @_;
949              
950 5678         10157 my $chains_offset = $multiple * $self->byte_size;
951              
952 5678         14482 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
953 5678         16319 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 5678 100       14565 unless ( $loc ) {
957 5375         13369 my $offset = $self->storage->request_space( $size );
958              
959             # Zero out the new sector. This also guarantees correct increases
960             # in the filesize.
961 5375         11677 $self->storage->print_at( $offset, chr(0) x $size );
962              
963 5375         22138 return $offset;
964             }
965              
966             # Read the new head after the signature and the staleness counter
967 303         901 my $new_head = $self->storage->read_at( $loc + $self->SIG_SIZE + $STALE_SIZE, $self->byte_size );
968 303         993 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
969             $self->storage->print_at(
970             $loc + $self->SIG_SIZE + $STALE_SIZE,
971 303         1008 pack( $StP{$self->byte_size}, 0 ),
972             );
973              
974 303         1258 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 157729     157729 1 475302 sub byte_size { $_[0]{byte_size} }
1004 52092     52092 1 147177 sub hash_size { $_[0]{hash_size} }
1005 12430     12430 1 39254 sub hash_chars { $_[0]{hash_chars} }
1006 8085     8085 1 22129 sub num_txns { $_[0]{num_txns} }
1007 18085     18085 1 61488 sub max_buckets { $_[0]{max_buckets} }
1008 15737     15737 1 33569 sub blank_md5 { chr(0) x $_[0]->hash_size }
1009 8584     8584 1 25772 sub data_sector_size { $_[0]{data_sector_size} }
1010              
1011             # This is a calculated value
1012             sub txn_bitfield_len {
1013 4019     4019 1 6619 my $self = shift;
1014 4019 100       9111 unless ( exists $self->{txn_bitfield_len} ) {
1015 382         968 my $temp = ($self->num_txns) / 8;
1016 382 100       1382 if ( $temp > int( $temp ) ) {
1017 368         1009 $temp = int( $temp ) + 1;
1018             }
1019 382         891 $self->{txn_bitfield_len} = $temp;
1020             }
1021 4019         10299 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 42251 sub trans_id { $_[0]{trans_id} }
1041 294     294 1 653 sub set_trans_id { $_[0]{trans_id} = $_[1] }
1042              
1043 3634     3634 1 12532 sub trans_loc { $_[0]{trans_loc} }
1044 385     385 1 897 sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
1045              
1046 6920     6920 1 14871 sub chains_loc { $_[0]{chains_loc} }
1047 385     385 1 747 sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
1048              
1049             sub supports {
1050 307     307 1 501 my $self = shift;
1051 307         585 my ($feature) = @_;
1052              
1053 307 100       762 if ( $feature eq 'transactions' ) {
1054 305         750 return $self->num_txns > 1;
1055             }
1056 2 100       11 return 1 if $feature eq 'singletons';
1057 1 50       12 return 1 if $feature eq 'unicode';
1058 0         0 return;
1059             }
1060              
1061             sub db_version {
1062 2 100   2 0 14 return $_[0]{v} == 3 ? '1.0003' : 2;
1063             }
1064              
1065             sub clear {
1066 217     217 1 386 my $self = shift;
1067 217         359 my $obj = shift;
1068              
1069 217 50       563 my $sector = $self->load_sector( $obj->_base_offset )
1070             or return;
1071              
1072 217 50       731 return unless $sector->staleness == $obj->_staleness;
1073              
1074 217         803 $sector->clear;
1075              
1076 217         1289 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   6 my $self = shift;
1088              
1089             # Read the header
1090 3         10 my $spot = $self->_read_file_header();
1091              
1092 3         19 my %types = (
1093             0 => 'B',
1094             1 => 'D',
1095             2 => 'I',
1096             );
1097              
1098 3         22 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         34 my $return = "";
1105              
1106             # Header values
1107 3         10 $return .= "NumTxns: " . $self->num_txns . $/;
1108              
1109             # Read the free sector chains
1110 3         8 my %sectors;
1111 3         11 foreach my $multiple ( 0 .. 2 ) {
1112 9         31 $return .= "Chains($types{$multiple}):";
1113 9         22 my $old_loc = $self->chains_loc + $multiple * $self->byte_size;
1114 9         17 while ( 1 ) {
1115             my $loc = unpack(
1116 9         23 $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       31 unless ( $loc ) {
1122 9         24 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         13 while ( $spot < $self->storage->{end} ) {
1134             # Read each sector in order.
1135 11         36 my $sector = $self->load_sector( $spot );
1136 11 50       31 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         30 $return .= sprintf "%08d: %s %04d", $spot, $sector->type, $sector->size;
1151 11 100 66     27 if ( $sector->type =~ /^[DU]\z/ ) {
    100          
    50          
1152 6         18 $return .= ' ' . $sector->data;
1153             }
1154             elsif ( $sector->type eq 'A' || $sector->type eq 'H' ) {
1155 3         13 $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         6 $return .= sprintf "%08d", unpack($StP{$self->byte_size},
1161             substr( $bucket->[-1], $self->hash_size, $self->byte_size),
1162             );
1163 3         7 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         15 $return .= sprintf " %08d", $l;
1170 3         8 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         32 $return .= $/;
1182              
1183 11         34 $spot += $sector->size;
1184             }
1185             }
1186              
1187 3         29 return $return;
1188             }
1189              
1190             1;
1191             __END__