File Coverage

blib/lib/DBM/Deep/Engine/File.pm
Criterion Covered Total %
statement 389 429 90.6
branch 134 172 77.9
condition 24 27 88.8
subroutine 60 61 98.3
pod 38 39 97.4
total 645 728 88.6


line stmt bran cond sub pod time code
1             package DBM::Deep::Engine::File;
2              
3 54     54   956 use 5.008_004;
  54         228  
4              
5 54     54   1395 use strict;
  54         116  
  54         1920  
6 54     54   267 use warnings FATAL => 'all';
  54         103  
  54         3285  
7 54     54   329 no warnings 'recursion';
  54         96  
  54         2403  
8              
9 54     54   260 use base qw( DBM::Deep::Engine );
  54         140  
  54         8927  
10              
11 54     54   379 use Scalar::Util ();
  54         142  
  54         1155  
12              
13 54     54   29937 use DBM::Deep::Null ();
  54         209  
  54         1317  
14 54     54   29120 use DBM::Deep::Sector::File ();
  54         254  
  54         1667  
15 54     54   30594 use DBM::Deep::Storage::File ();
  54         219  
  54         364322  
16              
17 29012     29012 1 121254 sub sector_type { 'DBM::Deep::Sector::File' }
18 316     316 1 20472 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 405     405 1 922 my $class = shift;
61 405         913 my ($args) = @_;
62              
63             $args->{storage} = DBM::Deep::Storage::File->new( $args )
64 405 50       3311 unless exists $args->{storage};
65              
66 404         4915 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 404         3993 delete $args->{byte_size};
86 404 100       1151 if ( defined $args->{pack_size} ) {
87 7 100       28 if ( lc $args->{pack_size} eq 'small' ) {
    100          
    50          
88 5         14 $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         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 404         2216 foreach my $param ( keys %$self ) {
103 4444 100       8530 next unless exists $args->{$param};
104 710         3624 $self->{$param} = $args->{$param};
105             }
106              
107 404         2946 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 404         1890 while ( my ($attr, $c) = each %validations ) {
114 1212 100 100     12517 if ( !defined $self->{$attr}
    100 100        
      100        
115             || !length $self->{$attr}
116             || $self->{$attr} =~ /\D/
117             || $self->{$attr} < $c->{floor}
118             ) {
119 12 100       64 $self->{$attr} = '(undef)' if !defined $self->{$attr};
120 12         187 warn "Floor of $attr is $c->{floor}. Setting it to $c->{floor} from '$self->{$attr}'\n";
121 12         983 $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         214 $self->{$attr} = $c->{ceil};
126             }
127             }
128              
129 404 100       1166 if ( !$self->{digest} ) {
130 403         2591 require Digest::MD5;
131 403         1025 $self->{digest} = \&Digest::MD5::md5;
132             }
133              
134 404         2573 return $self;
135             }
136              
137             sub read_value {
138 3877     3877 1 6948 my $self = shift;
139 3877         9315 my ($obj, $key) = @_;
140              
141             # This will be a Reference sector
142 3877 100       11020 my $sector = $self->load_sector( $obj->_base_offset )
143             or return;
144              
145 3876 50       11149 if ( $sector->staleness != $obj->_staleness ) {
146 0         0 return;
147             }
148              
149 3876         12230 my $key_md5 = $self->_apply_digest( $key );
150              
151 3876         25763 my $value_sector = $sector->get_data_for({
152             key_md5 => $key_md5,
153             allow_head => 1,
154             });
155              
156 3876 100       19529 unless ( $value_sector ) {
157             return undef
158 91         513 }
159              
160 3785         12388 return $value_sector->data;
161             }
162              
163             sub get_classname {
164 12     12 1 30 my $self = shift;
165 12         33 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       52 if ( $sector->staleness != $obj->_staleness ) {
172 0         0 return;
173             }
174              
175 12         54 return $sector->get_classname;
176             }
177              
178             sub make_reference {
179 29     29 1 58 my $self = shift;
180 29         151 my ($obj, $old_key, $new_key) = @_;
181              
182             # This will be a Reference sector
183 29 50       93 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       76 if ( $sector->staleness != $obj->_staleness ) {
187 0         0 return;
188             }
189              
190 29         103 my $old_md5 = $self->_apply_digest( $old_key );
191              
192 29         158 my $value_sector = $sector->get_data_for({
193             key_md5 => $old_md5,
194             allow_head => 1,
195             });
196              
197 29 50       120 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       183 if ( $value_sector->isa( 'DBM::Deep::Sector::File::Reference' ) ) {
211 6         50 $sector->write_data({
212             key => $new_key,
213             key_md5 => $self->_apply_digest( $new_key ),
214             value => $value_sector,
215             });
216 6         49 $value_sector->increment_refcount;
217             }
218             else {
219 23         74 $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         293 return;
227             }
228              
229             # exists returns '', not undefined.
230             sub key_exists {
231 127     127 1 1045 my $self = shift;
232 127         281 my ($obj, $key) = @_;
233              
234             # This will be a Reference sector
235 127 100       358 my $sector = $self->load_sector( $obj->_base_offset )
236             or return '';
237              
238 126 50       327 if ( $sector->staleness != $obj->_staleness ) {
239 0         0 return '';
240             }
241              
242 126         383 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       676 return $data ? 1 : '';
249             }
250              
251             sub delete_key {
252 66     66 1 137 my $self = shift;
253 66         182 my ($obj, $key) = @_;
254              
255 66 100       209 my $sector = $self->load_sector( $obj->_base_offset )
256             or return;
257              
258 65 50       1019 if ( $sector->staleness != $obj->_staleness ) {
259 0         0 return;
260             }
261              
262 65         281 return $sector->delete_key({
263             key_md5 => $self->_apply_digest( $key ),
264             allow_head => 0,
265             });
266             }
267              
268             sub write_value {
269 2887     2887 1 4738 my $self = shift;
270 2887         7281 my ($obj, $key, $value) = @_;
271              
272 2887   100     9720 my $r = Scalar::Util::reftype( $value ) || '';
273             {
274 2887 100       4863 last if $r eq '';
  2887         7336  
275 1234 100       3000 last if $r eq 'HASH';
276 1056 100       3063 last if $r eq 'ARRAY';
277              
278 5         19 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 2882 100       8436 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 2881 50       7749 if ( $sector->staleness != $obj->_staleness ) {
288 0         0 DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
289             }
290              
291 2881         5973 my ($class, $type);
292 2881 100 100     15313 if ( !defined $value ) {
    100          
    100          
293 58         120 $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         15 $class = 'DBM::Deep::Sector::File::Null';
300 2         37 $value = undef;
301             }
302             elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
303 1226         2004 my $tmpvar;
304 1226 100       2848 if ( $r eq 'ARRAY' ) {
    50          
305 1051         2161 $tmpvar = tied @$value;
306             } elsif ( $r eq 'HASH' ) {
307 175         333 $tmpvar = tied %$value;
308             }
309              
310 1226 100       3189 if ( $tmpvar ) {
311 1017         1914 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
  1017         6055  
  1017         5913  
312              
313 1017 100       2654 unless ( $is_dbm_deep ) {
314 4         10 DBM::Deep->_throw_error( "Cannot store something that is tied." );
315             }
316              
317 1013 50       2938 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         3328 my $loc = $sector->get_data_location_for({
324             key_md5 => $self->_apply_digest( $key ),
325             allow_head => 1,
326             });
327              
328 1013 100 100     4194 if ( defined($loc) && $loc == $tmpvar->_base_offset ) {
329 1         5 return 1;
330             }
331              
332             #XXX Can this use $loc?
333 1012         4215 my $value_sector = $self->load_sector( $tmpvar->_base_offset );
334 1012         3593 $sector->write_data({
335             key => $key,
336             key_md5 => $self->_apply_digest( $key ),
337             value => $value_sector,
338             });
339 1012         7281 $value_sector->increment_refcount;
340              
341 1012         10780 return 1;
342             }
343              
344 209         387 $class = 'DBM::Deep::Sector::File::Reference';
345 209         469 $type = substr( $r, 0, 1 );
346             }
347             else {
348 1594 50       3770 if ( tied($value) ) {
349 0         0 DBM::Deep->_throw_error( "Cannot store something that is tied." );
350             }
351 1594         2824 $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 1863         10100 my $value_sector = $class->new({
357             engine => $self,
358             data => $value,
359             type => $type,
360             });
361              
362 1859         6213 $sector->write_data({
363             key => $key,
364             key_md5 => $self->_apply_digest( $key ),
365             value => $value_sector,
366             });
367              
368 1855         12751 $self->_descend( $value, $value_sector );
369              
370 1855         14747 return 1;
371             }
372              
373             sub setup {
374 2964     2964 1 5117 my $self = shift;
375 2964         5905 my ($obj) = @_;
376              
377             # We're opening the file.
378 2964 100       9996 unless ( $obj->_base_offset ) {
379 404         1362 my $bytes_read = $self->_read_file_header;
380              
381             # Creating a new file
382 399 100       1018 unless ( $bytes_read ) {
383 99         352 $self->{storage}->{byte_size} = $self->{byte_size};
384 99         930 $self->_write_file_header;
385              
386             # 1) Create Array/Hash entry
387 99         906 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
388             engine => $self,
389             type => $obj->_type,
390             });
391 99         347 $obj->{base_offset} = $initial_reference->offset;
392 99         705 $obj->{staleness} = $initial_reference->staleness;
393              
394 99         431 $self->storage->flush;
395             }
396             # Reading from an existing file
397             else {
398 300         674 $obj->{base_offset} = $bytes_read;
399 300         1005 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
400             engine => $self,
401             offset => $obj->_base_offset,
402             });
403 300 50       834 unless ( $initial_reference ) {
404 0         0 DBM::Deep->_throw_error("Corrupted file, no master index record");
405             }
406              
407 300 100       1362 unless ($obj->_type eq $initial_reference->type) {
408 6         33 DBM::Deep->_throw_error("File type mismatch");
409             }
410              
411 294         740 $obj->{staleness} = $initial_reference->staleness;
412 294         1637 $self->{storage}->{byte_size} = $self->{byte_size};
413             }
414             }
415              
416 2953         8994 $self->storage->set_inode;
417              
418 2953         6064 return 1;
419             }
420              
421             sub begin_work {
422 275     275 1 500 my $self = shift;
423 275         604 my ($obj) = @_;
424              
425 275 50       1078 unless ($self->supports('transactions')) {
426 0         0 DBM::Deep->_throw_error( "Cannot begin_work unless transactions are supported" );
427             }
428              
429 275 100       666 if ( $self->trans_id ) {
430 1         5 DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
431             }
432              
433 274         734 my @slots = $self->read_txn_slots;
434 274         825 my $found;
435 274         784 for my $i ( 0 .. $self->num_txns-2 ) {
436 32406 100       51676 next if $slots[$i];
437              
438 274         543 $slots[$i] = 1;
439 274         915 $self->set_trans_id( $i + 1 );
440 274         426 $found = 1;
441 274         403 last;
442             }
443 274 50       642 unless ( $found ) {
444 0         0 DBM::Deep->_throw_error( "Cannot allocate transaction ID" );
445             }
446 274         1191 $self->write_txn_slots( @slots );
447              
448 274 50       732 if ( !$self->trans_id ) {
449 0         0 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
450             }
451              
452 274         4733 return;
453             }
454              
455             sub rollback {
456 12     12 1 29 my $self = shift;
457 12         31 my ($obj) = @_;
458              
459 12 50       48 unless ($self->supports('transactions')) {
460 0         0 DBM::Deep->_throw_error( "Cannot rollback unless transactions are supported" );
461             }
462              
463 12 100       38 if ( !$self->trans_id ) {
464 1         6 DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
465             }
466              
467             # Each entry is the file location for a bucket that has a modification for
468             # this transaction. The entries need to be expunged.
469 11         25 foreach my $entry (@{ $self->get_entries } ) {
  11         42  
470             # Remove the entry here
471 39         100 my $read_loc = $entry
472             + $self->hash_size
473             + $self->byte_size
474             + $self->byte_size
475             + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
476              
477 39         88 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
478 39         83 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
479 39         76 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
480              
481 39 100       110 if ( $data_loc > 1 ) {
482 27         65 $self->load_sector( $data_loc )->free;
483             }
484             }
485              
486 11         94 $self->clear_entries;
487              
488 11         59 my @slots = $self->read_txn_slots;
489 11         55 $slots[$self->trans_id-1] = 0;
490 11         47 $self->write_txn_slots( @slots );
491 11         39 $self->inc_txn_staleness_counter( $self->trans_id );
492 11         50 $self->set_trans_id( 0 );
493              
494 11         84 return 1;
495             }
496              
497             sub commit {
498 10     10 1 23 my $self = shift;
499 10         26 my ($obj) = @_;
500              
501 10 50       42 unless ($self->supports('transactions')) {
502 0         0 DBM::Deep->_throw_error( "Cannot commit unless transactions are supported" );
503             }
504              
505 10 100       68 if ( !$self->trans_id ) {
506 1         4 DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
507             }
508              
509 9         19 foreach my $entry (@{ $self->get_entries } ) {
  9         33  
510             # Overwrite the entry in head with the entry in trans_id
511 35         79 my $base = $entry
512             + $self->hash_size
513             + $self->byte_size;
514              
515 35         102 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
516 35         71 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
517              
518 35         132 my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
519 35         85 my $trans_loc = $self->storage->read_at(
520             $spot, $self->byte_size,
521             );
522              
523 35         79 $self->storage->print_at( $base, $trans_loc );
524             $self->storage->print_at(
525             $spot,
526 35         80 pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
527             );
528              
529 35 100       104 if ( $head_loc > 1 ) {
530 11         36 $self->load_sector( $head_loc )->free;
531             }
532             }
533              
534 9         51 $self->clear_entries;
535              
536 9         33 my @slots = $self->read_txn_slots;
537 9         32 $slots[$self->trans_id-1] = 0;
538 9         39 $self->write_txn_slots( @slots );
539 9         32 $self->inc_txn_staleness_counter( $self->trans_id );
540 9         41 $self->set_trans_id( 0 );
541              
542 9         73 return 1;
543             }
544              
545             =head1 INTERNAL METHODS
546              
547             The following methods are internal-use-only to DBM::Deep::Engine::File.
548              
549             =cut
550              
551             =head2 read_txn_slots()
552              
553             This takes no arguments.
554              
555             This will return an array with a 1 or 0 in each slot. Each spot represents one
556             available transaction. If the slot is 1, that transaction is taken. If it is 0,
557             the transaction is available.
558              
559             =cut
560              
561             sub read_txn_slots {
562 3201     3201 1 6180 my $self = shift;
563 3201         7663 my $bl = $self->txn_bitfield_len;
564 3201         6226 my $num_bits = $bl * 8;
565 3201         11358 return split '', unpack( 'b'.$num_bits,
566             $self->storage->read_at(
567             $self->trans_loc, $bl,
568             )
569             );
570             }
571              
572             =head2 write_txn_slots( @slots )
573              
574             This takes an array of 1's and 0's. This array represents the transaction slots
575             returned by L. In other words, the following is true:
576              
577             @x = read_txn_slots( write_txn_slots( @x ) );
578              
579             (With the obviously missing object referents added back in.)
580              
581             =cut
582              
583             sub write_txn_slots {
584 294     294 1 475 my $self = shift;
585 294         528 my $num_bits = $self->txn_bitfield_len * 8;
586 294         842 $self->storage->print_at( $self->trans_loc,
587             pack( 'b'.$num_bits, join('', @_) ),
588             );
589             }
590              
591             =head2 get_running_txn_ids()
592              
593             This takes no arguments.
594              
595             This will return an array of taken transaction IDs. This wraps L.
596              
597             =cut
598              
599             sub get_running_txn_ids {
600 2907     2907 1 4823 my $self = shift;
601 2907         8018 my @transactions = $self->read_txn_slots;
602 2907         12416 my @trans_ids = map { $_+1 } grep { $transactions[$_] } 0 .. $#transactions;
  20         164  
  23616         51236  
603             }
604              
605             =head2 get_txn_staleness_counter( $trans_id )
606              
607             This will return the staleness counter for the given transaction ID. Please see
608             L for more information.
609              
610             =cut
611              
612             sub get_txn_staleness_counter {
613 1209     1209 1 1857 my $self = shift;
614 1209         2082 my ($trans_id) = @_;
615              
616             # Hardcode staleness of 0 for the HEAD
617 1209 50       2462 return 0 unless $trans_id;
618              
619 1209         3311 return unpack( $StP{$STALE_SIZE},
620             $self->storage->read_at(
621             $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
622             $STALE_SIZE,
623             )
624             );
625             }
626              
627             =head2 inc_txn_staleness_counter( $trans_id )
628              
629             This will increment the staleness counter for the given transaction ID. Please see
630             L for more information.
631              
632             =cut
633              
634             sub inc_txn_staleness_counter {
635 20     20 1 44 my $self = shift;
636 20         45 my ($trans_id) = @_;
637              
638             # Hardcode staleness of 0 for the HEAD
639 20 50       59 return 0 unless $trans_id;
640              
641             $self->storage->print_at(
642             $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
643 20         138 pack( $StP{$STALE_SIZE}, $self->get_txn_staleness_counter( $trans_id ) + 1 ),
644             );
645             }
646              
647             =head2 get_entries()
648              
649             This takes no arguments.
650              
651             This returns a list of all the sectors that have been modified by this transaction.
652              
653             =cut
654              
655             sub get_entries {
656 20     20 1 78 my $self = shift;
657 20   100     40 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
  20         71  
658             }
659              
660             =head2 add_entry( $trans_id, $location )
661              
662             This takes a transaction ID and a file location and marks the sector at that
663             location as having been modified by the transaction identified by $trans_id.
664              
665             This returns nothing.
666              
667             B: Unlike all the other _entries() methods, there are several cases where
668             C<< $trans_id != $self->trans_id >> for this method.
669              
670             =cut
671              
672             sub add_entry {
673 2969     2969 1 4504 my $self = shift;
674 2969         6248 my ($trans_id, $loc) = @_;
675              
676 2969   100     10047 $self->{entries}{$trans_id} ||= {};
677 2969         13508 $self->{entries}{$trans_id}{$loc} = undef;
678             }
679              
680             =head2 reindex_entry( $old_loc, $new_loc )
681              
682             This takes two locations (old and new, respectively). If a location that has
683             been modified by this transaction is subsequently reindexed due to a bucketlist
684             overflowing, then the entries hash needs to be made aware of this change.
685              
686             This returns nothing.
687              
688             =cut
689              
690             sub reindex_entry {
691 224     224 1 384 my $self = shift;
692 224         416 my ($old_loc, $new_loc) = @_;
693              
694             TRANS:
695 224         348 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
  480         2112  
696 256 100       647 if ( exists $locs->{$old_loc} ) {
697 254         547 delete $locs->{$old_loc};
698 254         633 $locs->{$new_loc} = undef;
699 254         626 next TRANS;
700             }
701             }
702             }
703              
704             =head2 clear_entries()
705              
706             This takes no arguments. It will clear the entries list for the running
707             transaction.
708              
709             This returns nothing.
710              
711             =cut
712              
713             sub clear_entries {
714 20     20 1 78 my $self = shift;
715 20         69 delete $self->{entries}{$self->trans_id};
716             }
717              
718             =head2 _write_file_header()
719              
720             This writes the file header for a new file. This will write the various settings
721             that set how the file is interpreted.
722              
723             =head2 _read_file_header()
724              
725             This reads the file header from an existing file. This will read the various
726             settings that set how the file is interpreted.
727              
728             =cut
729              
730             {
731             my $header_fixed = length( __PACKAGE__->SIG_FILE ) + 1 + 4 + 4;
732             my $this_file_version = 4;
733             my $min_file_version = 3;
734              
735             sub _write_file_header {
736 99     99   235 my $self = shift;
737              
738 99         330 my $nt = $self->num_txns;
739 99         507 my $bl = $self->txn_bitfield_len;
740              
741 99         650 my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
742              
743 99         416 my $loc = $self->storage->request_space( $header_fixed + $header_var );
744              
745             $self->storage->print_at( $loc,
746             $self->SIG_FILE,
747             $self->SIG_HEADER,
748             pack('N', $this_file_version), # At this point, we're at 9 bytes
749             pack('N', $header_var), # header size
750             # --- Above is $header_fixed. Below is $header_var
751             pack('C', $self->byte_size),
752              
753             # These shenanigans are to allow a 256 within a C
754             pack('C', $self->max_buckets - 1),
755             pack('C', $self->data_sector_size - 1),
756              
757             pack('C', $nt),
758             pack('C' . $bl, 0 ), # Transaction activeness bitfield
759             pack($StP{$STALE_SIZE}.($nt-1), 0 x ($nt-1) ), # Transaction staleness counters
760             pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
761             pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
762 99         322 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
763             );
764              
765             #XXX Set these less fragilely
766 99         662 $self->set_trans_loc( $header_fixed + 4 );
767 99         422 $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
768              
769 99         242 $self->{v} = $this_file_version;
770              
771 99         226 return;
772             }
773              
774             sub _read_file_header {
775 407     407   646 my $self = shift;
776              
777 407         1088 my $buffer = $self->storage->read_at( 0, $header_fixed );
778 407 100       1482 return unless length($buffer);
779              
780 308         1661 my ($file_signature, $sig_header, $file_version, $size) = unpack(
781             'A4 A N N', $buffer
782             );
783              
784 308 100       1421 unless ( $file_signature eq $self->SIG_FILE ) {
785 1         3 $self->storage->close;
786 1         5 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
787             }
788              
789 307 100       976 unless ( $sig_header eq $self->SIG_HEADER ) {
790 2         6 $self->storage->close;
791 2         34 DBM::Deep->_throw_error( "Pre-1.00 file version found" );
792             }
793              
794 305 100       828 if ( $file_version < $min_file_version ) {
795 2         9 $self->storage->close;
796 2         10 DBM::Deep->_throw_error(
797             "This file version is too old - "
798             . _guess_version($file_version) .
799             " - expected " . _guess_version($min_file_version)
800             . " to " . _guess_version($this_file_version)
801             );
802             }
803 303 50       619 if ( $file_version > $this_file_version ) {
804 0         0 $self->storage->close;
805 0         0 DBM::Deep->_throw_error(
806             "This file version is too new - probably "
807             . _guess_version($file_version) .
808             " - expected " . _guess_version($min_file_version)
809             . " to " . _guess_version($this_file_version)
810             );
811             }
812 303         812 $self->{v} = $file_version;
813              
814 303         778 my $buffer2 = $self->storage->read_at( undef, $size );
815 303         1098 my @values = unpack( 'C C C C', $buffer2 );
816              
817 303 50 33     1222 if ( @values != 4 || grep { !defined } @values ) {
  1212         2898  
818 0         0 $self->storage->close;
819 0         0 DBM::Deep->_throw_error("Corrupted file - bad header");
820             }
821              
822 303 50       819 if ($values[3] != $self->{num_txns}) {
823 0         0 warn "num_txns ($self->{num_txns}) is different from the file ($values[3])\n";
824             }
825              
826             #XXX Add warnings if values weren't set right
827 303         529 @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
  303         886  
828              
829             # These shenanigans are to allow a 256 within a C
830 303         648 $self->{max_buckets} += 1;
831 303         490 $self->{data_sector_size} += 1;
832              
833 303         783 my $bl = $self->txn_bitfield_len;
834              
835 303         779 my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
836 303 50       751 unless ( $size == $header_var ) {
837 0         0 $self->storage->close;
838 0         0 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
839             }
840              
841 303         1118 $self->set_trans_loc( $header_fixed + scalar(@values) );
842 303         748 $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
843              
844 303         834 return length($buffer) + length($buffer2);
845             }
846              
847             sub _guess_version {
848 6 100   6   28 $_[0] == 4 and return 2;
849 4 100       13 $_[0] == 3 and return '1.0003';
850 2 50       7 $_[0] == 2 and return '1.00';
851 2 50       23 $_[0] == 1 and return '0.99';
852 0 0       0 $_[0] == 0 and return '0.91';
853              
854 0         0 return $_[0]-2;
855             }
856             }
857              
858             =head2 _apply_digest( @stuff )
859              
860             This will apply the digest method (default to Digest::MD5::md5) to the arguments
861             passed in and return the result.
862              
863             =cut
864              
865             sub _apply_digest {
866 8009     8009   13560 my $self = shift;
867 8009         15365 my $victim = shift;
868 8009 100       44135 utf8::encode $victim if $self->{v} >= 4;
869 8009         67948 return $self->{digest}->($victim);
870             }
871              
872             =head2 _add_free_blist_sector( $offset, $size )
873              
874             =head2 _add_free_data_sector( $offset, $size )
875              
876             =head2 _add_free_index_sector( $offset, $size )
877              
878             These methods are all wrappers around _add_free_sector(), providing the proper
879             chain offset ($multiple) for the sector type.
880              
881             =cut
882              
883 42     42   166 sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
884 800     800   2924 sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
885 0     0   0 sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
886              
887             =head2 _add_free_sector( $multiple, $offset, $size )
888              
889             _add_free_sector() takes the offset into the chains location, the offset of the
890             sector, and the size of that sector. It will mark the sector as a free sector
891             and put it into the list of sectors that are free of this type for use later.
892              
893             This returns nothing.
894              
895             B: $size is unused?
896              
897             =cut
898              
899             sub _add_free_sector {
900 842     842   1485 my $self = shift;
901 842         2289 my ($multiple, $offset, $size) = @_;
902              
903 842         2136 my $chains_offset = $multiple * $self->byte_size;
904              
905 842         2861 my $storage = $self->storage;
906              
907             # Increment staleness.
908             # XXX Can this increment+modulo be done by "&= 0x1" ?
909 842         4434 my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + $self->SIG_SIZE, $STALE_SIZE ) );
910 842         3220 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
911 842         6163 $storage->print_at( $offset + $self->SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
912              
913 842         3035 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
914              
915             $storage->print_at( $self->chains_loc + $chains_offset,
916 842         2442 pack( $StP{$self->byte_size}, $offset ),
917             );
918              
919             # Record the old head in the new sector after the signature and staleness counter
920 842         4588 $storage->print_at( $offset + $self->SIG_SIZE + $STALE_SIZE, $old_head );
921             }
922              
923             =head2 _request_blist_sector( $size )
924              
925             =head2 _request_data_sector( $size )
926              
927             =head2 _request_index_sector( $size )
928              
929             These methods are all wrappers around _request_sector(), providing the proper
930             chain offset ($multiple) for the sector type.
931              
932             =cut
933              
934 1137     1137   4512 sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
935 6894     6894   20362 sub _request_data_sector { shift->_request_sector( 1, @_ ) }
936 14     14   68 sub _request_index_sector { shift->_request_sector( 2, @_ ) }
937              
938             =head2 _request_sector( $multiple $size )
939              
940             This takes the offset into the chains location and the size of that sector.
941              
942             This returns the object with the sector. If there is an available free sector of
943             that type, then it will be reused. If there isn't one, then a new one will be
944             allocated.
945              
946             =cut
947              
948             sub _request_sector {
949 8045     8045   11214 my $self = shift;
950 8045         14795 my ($multiple, $size) = @_;
951              
952 8045         15282 my $chains_offset = $multiple * $self->byte_size;
953              
954 8045         21077 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
955 8045         18618 my $loc = unpack( $StP{$self->byte_size}, $old_head );
956              
957             # We don't have any free sectors of the right size, so allocate a new one.
958 8045 100       19054 unless ( $loc ) {
959 7368         15619 my $offset = $self->storage->request_space( $size );
960              
961             # Zero out the new sector. This also guarantees correct increases
962             # in the filesize.
963 7368         14480 $self->storage->print_at( $offset, chr(0) x $size );
964              
965 7362         29144 return $offset;
966             }
967              
968             # Read the new head after the signature and the staleness counter
969 677         2317 my $new_head = $self->storage->read_at( $loc + $self->SIG_SIZE + $STALE_SIZE, $self->byte_size );
970 677         2181 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
971             $self->storage->print_at(
972             $loc + $self->SIG_SIZE + $STALE_SIZE,
973 677         2694 pack( $StP{$self->byte_size}, 0 ),
974             );
975              
976 677         3615 return $loc;
977             }
978              
979             =head2 ACCESSORS
980              
981             The following are readonly attributes.
982              
983             =over 4
984              
985             =item * byte_size
986              
987             =item * hash_size
988              
989             =item * hash_chars
990              
991             =item * num_txns
992              
993             =item * max_buckets
994              
995             =item * blank_md5
996              
997             =item * data_sector_size
998              
999             =item * txn_bitfield_len
1000              
1001             =back
1002              
1003             =cut
1004              
1005 199027     199027 1 655433 sub byte_size { $_[0]{byte_size} }
1006 62997     62997 1 198363 sub hash_size { $_[0]{hash_size} }
1007 14605     14605 1 45443 sub hash_chars { $_[0]{hash_chars} }
1008 10036     10036 1 31032 sub num_txns { $_[0]{num_txns} }
1009 20482     20482 1 72249 sub max_buckets { $_[0]{max_buckets} }
1010 18565     18565 1 40110 sub blank_md5 { chr(0) x $_[0]->hash_size }
1011 12995     12995 1 43191 sub data_sector_size { $_[0]{data_sector_size} }
1012              
1013             # This is a calculated value
1014             sub txn_bitfield_len {
1015 5126     5126 1 7984 my $self = shift;
1016 5126 100       12165 unless ( exists $self->{txn_bitfield_len} ) {
1017 399         1020 my $temp = ($self->num_txns) / 8;
1018 399 100       1455 if ( $temp > int( $temp ) ) {
1019 385         712 $temp = int( $temp ) + 1;
1020             }
1021 399         981 $self->{txn_bitfield_len} = $temp;
1022             }
1023 5126         14425 return $self->{txn_bitfield_len};
1024             }
1025              
1026             =pod
1027              
1028             The following are read/write attributes.
1029              
1030             =over 4
1031              
1032             =item * trans_id / set_trans_id( $new_id )
1033              
1034             =item * trans_loc / set_trans_loc( $new_loc )
1035              
1036             =item * chains_loc / set_chains_loc( $new_loc )
1037              
1038             =back
1039              
1040             =cut
1041              
1042 22007     22007 1 57399 sub trans_id { $_[0]{trans_id} }
1043 294     294 1 578 sub set_trans_id { $_[0]{trans_id} = $_[1] }
1044              
1045 4724     4724 1 18122 sub trans_loc { $_[0]{trans_loc} }
1046 402     402 1 952 sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
1047              
1048 10415     10415 1 26042 sub chains_loc { $_[0]{chains_loc} }
1049 402     402 1 836 sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
1050              
1051             sub supports {
1052 307     307 1 606 my $self = shift;
1053 307         673 my ($feature) = @_;
1054              
1055 307 100       799 if ( $feature eq 'transactions' ) {
1056 305         792 return $self->num_txns > 1;
1057             }
1058 2 100       9 return 1 if $feature eq 'singletons';
1059 1 50       5 return 1 if $feature eq 'unicode';
1060 0         0 return;
1061             }
1062              
1063             sub db_version {
1064 2 100   2 0 11 return $_[0]{v} == 3 ? '1.0003' : 2;
1065             }
1066              
1067             sub clear {
1068 220     220 1 343 my $self = shift;
1069 220         374 my $obj = shift;
1070              
1071 220 50       558 my $sector = $self->load_sector( $obj->_base_offset )
1072             or return;
1073              
1074 220 50       617 return unless $sector->staleness == $obj->_staleness;
1075              
1076 220         963 $sector->clear;
1077              
1078 220         1268 return;
1079             }
1080              
1081             =head2 _dump_file()
1082              
1083             This method takes no arguments. It's used to print out a textual representation
1084             of the DBM::Deep DB file. It assumes the file is not-corrupted.
1085              
1086             =cut
1087              
1088             sub _dump_file {
1089 3     3   6 my $self = shift;
1090              
1091             # Read the header
1092 3         10 my $spot = $self->_read_file_header();
1093              
1094 3         26 my %types = (
1095             0 => 'B',
1096             1 => 'D',
1097             2 => 'I',
1098             );
1099              
1100 3         9 my %sizes = (
1101             'D' => $self->data_sector_size,
1102             'B' => DBM::Deep::Sector::File::BucketList->new({engine=>$self,offset=>1})->size,
1103             'I' => DBM::Deep::Sector::File::Index->new({engine=>$self,offset=>1})->size,
1104             );
1105              
1106 3         21 my $return = "";
1107              
1108             # Header values
1109 3         9 $return .= "NumTxns: " . $self->num_txns . $/;
1110              
1111             # Read the free sector chains
1112 3         7 my %sectors;
1113 3         9 foreach my $multiple ( 0 .. 2 ) {
1114 9         28 $return .= "Chains($types{$multiple}):";
1115 9         21 my $old_loc = $self->chains_loc + $multiple * $self->byte_size;
1116 9         16 while ( 1 ) {
1117             my $loc = unpack(
1118 9         17 $StP{$self->byte_size},
1119             $self->storage->read_at( $old_loc, $self->byte_size ),
1120             );
1121              
1122             # We're now out of free sectors of this kind.
1123 9 50       31 unless ( $loc ) {
1124 9         17 last;
1125             }
1126              
1127 0         0 $sectors{ $types{$multiple} }{ $loc } = undef;
1128 0         0 $old_loc = $loc + $self->SIG_SIZE + $STALE_SIZE;
1129 0         0 $return .= " $loc";
1130             }
1131 9         60 $return .= $/;
1132             }
1133              
1134             SECTOR:
1135 3         10 while ( $spot < $self->storage->{end} ) {
1136             # Read each sector in order.
1137 11         34 my $sector = $self->load_sector( $spot );
1138 11 50       31 if ( !$sector ) {
1139             # Find it in the free-sectors that were found already
1140 0         0 foreach my $type ( keys %sectors ) {
1141 0 0       0 if ( exists $sectors{$type}{$spot} ) {
1142 0         0 my $size = $sizes{$type};
1143 0         0 $return .= sprintf "%08d: %s %04d\n", $spot, 'F' . $type, $size;
1144 0         0 $spot += $size;
1145 0         0 next SECTOR;
1146             }
1147             }
1148              
1149 0         0 die "********\n$return\nDidn't find free sector for $spot in chains\n********\n";
1150             }
1151             else {
1152 11         31 $return .= sprintf "%08d: %s %04d", $spot, $sector->type, $sector->size;
1153 11 100 66     33 if ( $sector->type =~ /^[DU]\z/ ) {
    100          
    50          
1154 6         33 $return .= ' ' . $sector->data;
1155             }
1156             elsif ( $sector->type eq 'A' || $sector->type eq 'H' ) {
1157 3         10 $return .= ' REF: ' . $sector->get_refcount;
1158             }
1159             elsif ( $sector->type eq 'B' ) {
1160 2         9 foreach my $bucket ( $sector->chopped_up ) {
1161 3         6 $return .= "\n ";
1162 3         9 $return .= sprintf "%08d", unpack($StP{$self->byte_size},
1163             substr( $bucket->[-1], $self->hash_size, $self->byte_size),
1164             );
1165 3         9 my $l = unpack( $StP{$self->byte_size},
1166             substr( $bucket->[-1],
1167             $self->hash_size + $self->byte_size,
1168             $self->byte_size,
1169             ),
1170             );
1171 3         9 $return .= sprintf " %08d", $l;
1172 3         8 foreach my $txn ( 0 .. $self->num_txns - 2 ) {
1173 0         0 my $l = unpack( $StP{$self->byte_size},
1174             substr( $bucket->[-1],
1175             $self->hash_size + 2 * $self->byte_size + $txn * ($self->byte_size + $STALE_SIZE),
1176             $self->byte_size,
1177             ),
1178             );
1179 0         0 $return .= sprintf " %08d", $l;
1180             }
1181             }
1182             }
1183 11         39 $return .= $/;
1184              
1185 11         33 $spot += $sector->size;
1186             }
1187             }
1188              
1189 3         36 return $return;
1190             }
1191              
1192             1;
1193             __END__