File Coverage

blib/lib/DBM/Deep/Sector/File/Reference.pm
Criterion Covered Total %
statement 217 217 100.0
branch 89 98 90.8
condition 25 30 83.3
subroutine 22 22 100.0
pod 0 16 0.0
total 353 383 92.1


line stmt bran cond sub pod time code
1             package DBM::Deep::Sector::File::Reference;
2              
3 50     50   909 use 5.008_004;
  50         182  
4              
5 50     50   321 use strict;
  50         120  
  50         1345  
6 50     50   297 use warnings FATAL => 'all';
  50         127  
  50         2042  
7              
8 50     50   317 use base qw( DBM::Deep::Sector::File::Data );
  50         113  
  50         4636  
9              
10 50     50   446 use Scalar::Util;
  50         152  
  50         135657  
11              
12             my $STALE_SIZE = 2;
13              
14             # Please refer to the pack() documentation for further information
15             my %StP = (
16             1 => 'C', # Unsigned char value (no order needed as it's just one byte)
17             2 => 'n', # Unsigned short in "network" (big-endian) order
18             4 => 'N', # Unsigned long in "network" (big-endian) order
19             8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
20             );
21              
22             sub _init {
23 9564     9564   15353 my $self = shift;
24              
25 9564         21484 my $e = $self->engine;
26              
27 9564 100       22003 unless ( $self->offset ) {
28 300         950 my $classname = Scalar::Util::blessed( delete $self->{data} );
29 300         1060 my $leftover = $self->size - $self->base_size - 3 * $e->byte_size;
30              
31 300         634 my $class_offset = 0;
32 300 100       876 if ( defined $classname ) {
33 18         100 my $class_sector = DBM::Deep::Sector::File::Scalar->new({
34             engine => $e,
35             data => $classname,
36             });
37 18         63 $class_offset = $class_sector->offset;
38             }
39              
40 300         809 $self->{offset} = $e->_request_data_sector( $self->size );
41 300         1092 $e->storage->print_at( $self->offset, $self->type ); # Sector type
42             # Skip staleness counter
43             $e->storage->print_at( $self->offset + $self->base_size,
44             pack( $StP{$e->byte_size}, 0 ), # Index/BList loc
45             pack( $StP{$e->byte_size}, $class_offset ), # Classname loc
46 300         1103 pack( $StP{$e->byte_size}, 1 ), # Initial refcount
47             chr(0) x $leftover, # Zero-fill the rest
48             );
49             }
50             else {
51 9264         25170 $self->{type} = $e->storage->read_at( $self->offset, 1 );
52             }
53              
54             $self->{staleness} = unpack(
55 9564         37206 $StP{$STALE_SIZE},
56             $e->storage->read_at( $self->offset + $e->SIG_SIZE, $STALE_SIZE ),
57             );
58              
59 9564         28977 return;
60             }
61              
62             sub get_data_location_for {
63 4299     4299 0 7350 my $self = shift;
64 4299         7231 my ($args) = @_;
65              
66             # Assume that the head is not allowed unless otherwise specified.
67 4299 50       9341 $args->{allow_head} = 0 unless exists $args->{allow_head};
68              
69             # Assume we don't create a new blist location unless otherwise specified.
70 4299 50       10659 $args->{create} = 0 unless exists $args->{create};
71              
72             my $blist = $self->get_bucket_list({
73             key_md5 => $args->{key_md5},
74             key => $args->{key},
75             create => $args->{create},
76 4299         16400 });
77 4299 100 100     22883 return unless $blist && $blist->{found};
78              
79             # At this point, $blist knows where the md5 is. What it -doesn't- know yet
80             # is whether or not this transaction has this key. That's part of the next
81             # function call.
82             my $location = $blist->get_data_location_for({
83             allow_head => $args->{allow_head},
84 3199 100       12917 }) or return;
85              
86 3146         15654 return $location;
87             }
88              
89             sub get_data_for {
90 3286     3286 0 5952 my $self = shift;
91 3286         6331 my ($args) = @_;
92              
93 3286 100       7210 my $location = $self->get_data_location_for( $args )
94             or return;
95              
96 3142         8729 return $self->engine->load_sector( $location );
97             }
98              
99             sub write_data {
100 1812     1812 0 3421 my $self = shift;
101 1812         3829 my ($args) = @_;
102              
103             my $blist = $self->get_bucket_list({
104             key_md5 => $args->{key_md5},
105             key => $args->{key},
106 1812 50       7273 create => 1,
107             }) or DBM::Deep->_throw_error( "How did write_data fail (no blist)?!" );
108              
109             # Handle any transactional bookkeeping.
110 1812 100       6699 if ( $self->engine->trans_id ) {
111 67 100       171 if ( ! $blist->has_md5 ) {
112 40         171 $blist->mark_deleted({
113             trans_id => 0,
114             });
115             }
116             }
117             else {
118 1745         3847 my @trans_ids = $self->engine->get_running_txn_ids;
119 1745 100       5790 if ( $blist->has_md5 ) {
120 163 100       683 if ( @trans_ids ) {
121 4         15 my $old_value = $blist->get_data_for;
122 4         17 foreach my $other_trans_id ( @trans_ids ) {
123 4 100       23 next if $blist->get_data_location_for({
124             trans_id => $other_trans_id,
125             allow_head => 0,
126             });
127             $blist->write_md5({
128             trans_id => $other_trans_id,
129             key => $args->{key},
130             key_md5 => $args->{key_md5},
131 3         52 value => $old_value->clone,
132             });
133             }
134             }
135             }
136             else {
137 1582 100       4194 if ( @trans_ids ) {
138 2         6 foreach my $other_trans_id ( @trans_ids ) {
139             #XXX This doesn't seem to possible to ever happen . . .
140 2 50       13 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
141 2         15 $blist->mark_deleted({
142             trans_id => $other_trans_id,
143             });
144             }
145             }
146             }
147             }
148              
149             #XXX Is this safe to do transactionally?
150             # Free the place we're about to write to.
151 1812 100       7513 if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
152 170         812 $blist->get_data_for({ allow_head => 0 })->free;
153             }
154              
155             $blist->write_md5({
156             key => $args->{key},
157             key_md5 => $args->{key_md5},
158             value => $args->{value},
159 1812         11294 });
160             }
161              
162             sub delete_key {
163 63     63 0 159 my $self = shift;
164 63         142 my ($args) = @_;
165              
166             # This can return nothing if we are deleting an entry in a hashref that was
167             # auto-vivified as part of the delete process. For example:
168             # my $x = {};
169             # delete $x->{foo}{bar};
170             my $blist = $self->get_bucket_list({
171             key_md5 => $args->{key_md5},
172 63 100       254 }) or return;
173              
174             # Save the location so that we can free the data
175 62         311 my $location = $blist->get_data_location_for({
176             allow_head => 0,
177             });
178 62   66     336 my $old_value = $location && $self->engine->load_sector( $location );
179              
180 62         199 my @trans_ids = $self->engine->get_running_txn_ids;
181              
182             # If we're the HEAD and there are running txns, then we need to clone this
183             # value to the other transactions to preserve Isolation.
184 62 100       253 if ( $self->engine->trans_id == 0 ) {
185 51 100       145 if ( @trans_ids ) {
186 1         5 foreach my $other_trans_id ( @trans_ids ) {
187 1 50       8 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
188             $blist->write_md5({
189             trans_id => $other_trans_id,
190             key => $args->{key},
191             key_md5 => $args->{key_md5},
192 1         11 value => $old_value->clone,
193             });
194             }
195             }
196             }
197              
198 62         125 my $data;
199 62 100       172 if ( @trans_ids ) {
200 12         58 $blist->mark_deleted( $args );
201              
202 12 100       60 if ( $old_value ) {
203             #XXX Is this export => 1 actually doing anything?
204 1         20 $data = $old_value->data({ export => 1 });
205 1         7 $old_value->free;
206             }
207             }
208             else {
209 50         181 $data = $blist->delete_md5( $args );
210             }
211              
212 62         471 return $data;
213             }
214              
215             sub write_blist_loc {
216 249     249 0 479 my $self = shift;
217 249         544 my ($loc) = @_;
218              
219 249         645 my $engine = $self->engine;
220             $engine->storage->print_at( $self->offset + $self->base_size,
221 249         798 pack( $StP{$engine->byte_size}, $loc ),
222             );
223             }
224              
225             sub get_blist_loc {
226 6581     6581 0 10486 my $self = shift;
227              
228 6581         13496 my $e = $self->engine;
229 6581         16804 my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
230 6581         24505 return unpack( $StP{$e->byte_size}, $blist_loc );
231             }
232              
233             sub get_bucket_list {
234 6174     6174 0 10003 my $self = shift;
235 6174         10564 my ($args) = @_;
236 6174   50     13908 $args ||= {};
237              
238             # XXX Add in check here for recycling?
239              
240 6174         15668 my $engine = $self->engine;
241              
242 6174         12881 my $blist_loc = $self->get_blist_loc;
243              
244             # There's no index or blist yet
245 6174 100       15645 unless ( $blist_loc ) {
246 275 100       769 return unless $args->{create};
247              
248             my $blist = DBM::Deep::Sector::File::BucketList->new({
249             engine => $engine,
250             key_md5 => $args->{key_md5},
251 249         1674 });
252              
253 249         744 $self->write_blist_loc( $blist->offset );
254             # $engine->storage->print_at( $self->offset + $self->base_size,
255             # pack( $StP{$engine->byte_size}, $blist->offset ),
256             # );
257              
258 249         1129 return $blist;
259             }
260              
261 5899 50       17051 my $sector = $engine->load_sector( $blist_loc )
262             or DBM::Deep->_throw_error( "Cannot read sector at $blist_loc in get_bucket_list()" );
263 5899         10856 my $i = 0;
264 5899         9342 my $last_sector = undef;
265 5899         22676 while ( $sector->isa( 'DBM::Deep::Sector::File::Index' ) ) {
266 4074         15070 $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
267 4074         9218 $last_sector = $sector;
268 4074 100       10260 if ( $blist_loc ) {
269 3582 50       11306 $sector = $engine->load_sector( $blist_loc )
270             or DBM::Deep->_throw_error( "Cannot read sector at $blist_loc in get_bucket_list()" );
271             }
272             else {
273 492         955 $sector = undef;
274 492         904 last;
275             }
276             }
277              
278             # This means we went through the Index sector(s) and found an empty slot
279 5899 100       13498 unless ( $sector ) {
280 492 100       1847 return unless $args->{create};
281              
282 253 50       615 DBM::Deep->_throw_error( "No last_sector when attempting to build a new entry" )
283             unless $last_sector;
284              
285             my $blist = DBM::Deep::Sector::File::BucketList->new({
286             engine => $engine,
287             key_md5 => $args->{key_md5},
288 253         1243 });
289              
290 253         1052 $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
291              
292 253         1677 return $blist;
293             }
294              
295 5407         18437 $sector->find_md5( $args->{key_md5} );
296              
297             # See whether or not we need to reindex the bucketlist
298             # Yes, the double-braces are there for a reason. if() doesn't create a
299             # redo-able block, so we have to create a bare block within the if() for
300             # redo-purposes.
301             # Patch and idea submitted by sprout@cpan.org. -RobK, 2008-01-09
302 5407 100 100     16292 if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
      100        
303 7         27 my $redo;
  10         21  
304              
305 10         91 my $new_index = DBM::Deep::Sector::File::Index->new({
306             engine => $engine,
307             });
308              
309 10         23 my %blist_cache;
310             #XXX q.v. the comments for this function.
311 10         47 foreach my $entry ( $sector->chopped_up ) {
312 160         290 my ($spot, $md5) = @{$entry};
  160         327  
313 160         360 my $idx = ord( substr( $md5, $i, 1 ) );
314              
315             # XXX This is inefficient
316 160   66     780 my $blist = $blist_cache{$idx}
317             ||= DBM::Deep::Sector::File::BucketList->new({
318             engine => $engine,
319             });
320              
321 160         464 $new_index->set_entry( $idx => $blist->offset );
322              
323 160         642 my $new_spot = $blist->write_at_next_open( $md5 );
324 160         509 $engine->reindex_entry( $spot => $new_spot );
325             }
326              
327             # Handle the new item separately.
328             {
329 10         59 my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
  10         35  
330              
331             # If all the previous blist's items have been thrown into one
332             # blist and the new item belongs in there too, we need
333             # another index.
334 10 100 66     77 if ( keys %blist_cache == 1 and each %blist_cache == $idx ) {
335 3         9 ++$i, ++$redo;
336             } else {
337 7   66     77 my $blist = $blist_cache{$idx}
338             ||= DBM::Deep::Sector::File::BucketList->new({
339             engine => $engine,
340             });
341            
342 7         34 $new_index->set_entry( $idx => $blist->offset );
343            
344             #XXX THIS IS HACKY!
345 7         52 $blist->find_md5( $args->{key_md5} );
346             $blist->write_md5({
347             key => $args->{key},
348             key_md5 => $args->{key_md5},
349 7         132 value => DBM::Deep::Sector::File::Null->new({
350             engine => $engine,
351             data => undef,
352             }),
353             });
354             }
355             }
356              
357 10 100       82 if ( $last_sector ) {
358             $last_sector->set_entry(
359 3         14 ord( substr( $args->{key_md5}, $i - 1, 1 ) ),
360             $new_index->offset,
361             );
362             } else {
363             $engine->storage->print_at( $self->offset + $self->base_size,
364 7         30 pack( $StP{$engine->byte_size}, $new_index->offset ),
365             );
366             }
367              
368 10         84 $sector->wipe;
369 10         68 $sector->free;
370              
371 10 100       45 if ( $redo ) {
372 3         17 (undef, $sector) = %blist_cache;
373 3         8 $last_sector = $new_index;
374 3         8 redo;
375             }
376              
377 7         53 $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
378 7         33 $sector->find_md5( $args->{key_md5} );
379             }}
380              
381 5407         20123 return $sector;
382             }
383              
384             sub get_class_offset {
385 2371     2371 0 3977 my $self = shift;
386              
387 2371         5224 my $e = $self->engine;
388             return unpack(
389 2371         6226 $StP{$e->byte_size},
390             $e->storage->read_at(
391             $self->offset + $self->base_size + 1 * $e->byte_size, $e->byte_size,
392             ),
393             );
394             }
395              
396             sub get_classname {
397 2338     2338 0 4177 my $self = shift;
398              
399 2338         4937 my $class_offset = $self->get_class_offset;
400              
401 2338 100       8307 return unless $class_offset;
402              
403 43         148 return $self->engine->load_sector( $class_offset )->data;
404             }
405              
406             # Look to hoist this method into a ::Reference trait
407             sub data {
408 2353     2353 0 4151 my $self = shift;
409 2353         5145 my ($args) = @_;
410 2353   100     10964 $args ||= {};
411              
412 2353         5700 my $engine = $self->engine;
413 2353   100     6263 my $cache_entry = $engine->cache->{ $self->offset } ||= {};
414 2353         6677 my $trans_id = $engine->trans_id;
415 2353         3656 my $obj;
416 2353 100       6354 if ( !defined $$cache_entry{ $trans_id } ) {
417 2334         5633 $obj = DBM::Deep->new({
418             type => $self->type,
419             base_offset => $self->offset,
420             staleness => $self->staleness,
421             storage => $engine->storage,
422             engine => $engine,
423             });
424              
425 2334         7999 $$cache_entry{ $trans_id } = $obj;
426 2334         7180 Scalar::Util::weaken($$cache_entry{ $trans_id });
427             }
428             else {
429 19         37 $obj = $$cache_entry{ $trans_id };
430             }
431              
432             # We're not exporting, so just return.
433 2353 100       5635 unless ( $args->{export} ) {
434 2339 100       5262 if ( $engine->storage->{autobless} ) {
435 2326         5532 my $classname = $self->get_classname;
436 2326 100       5527 if ( defined $classname ) {
437 39         131 bless $obj, $classname;
438             }
439             }
440              
441 2339         14087 return $obj;
442             }
443              
444             # We shouldn't export if this is still referred to.
445 14 100       176 if ( $self->get_refcount > 1 ) {
446 8         33 return $obj;
447             }
448              
449 6         51 return $obj->export;
450             }
451              
452             sub free {
453 60     60 0 124 my $self = shift;
454              
455             # We're not ready to be removed yet.
456 60 100       154 return if $self->decrement_refcount > 0;
457              
458 33         117 my $e = $self->engine;
459              
460             # Rebless the object into DBM::Deep::Null.
461             # In external_refs mode, this will already have been removed from
462             # the cache, so we can skip this.
463 33 100       109 if(!$e->{external_refs}) {
464             # eval { %{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
465             # eval { @{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
466 28         108 my $cache = $e->cache;
467 28         76 my $off = $self->offset;
468 28 100 100     228 if( exists $cache->{ $off }
469             and exists $cache->{ $off }{ my $trans_id = $e->trans_id } ) {
470             bless $cache->{ $off }{ $trans_id }, 'DBM::Deep::Null'
471 21 100       101 if defined $cache->{ $off }{ $trans_id };
472 21         68 delete $cache->{ $off }{ $trans_id };
473             }
474             }
475              
476 33         94 my $blist_loc = $self->get_blist_loc;
477 33 100       139 $e->load_sector( $blist_loc )->free if $blist_loc;
478              
479 33         180 my $class_loc = $self->get_class_offset;
480 33 100       468 $e->load_sector( $class_loc )->free if $class_loc;
481              
482 33         153 $self->SUPER::free();
483             }
484              
485             sub increment_refcount {
486 1033     1033 0 1888 my $self = shift;
487              
488 1033         2553 my $refcount = $self->get_refcount;
489              
490 1033         2141 $refcount++;
491              
492 1033         3072 $self->write_refcount( $refcount );
493              
494 1033         2946 return $refcount;
495             }
496              
497             sub decrement_refcount {
498 60     60 0 112 my $self = shift;
499              
500 60         140 my $refcount = $self->get_refcount;
501              
502 60         145 $refcount--;
503              
504 60         215 $self->write_refcount( $refcount );
505              
506 60         462 return $refcount;
507             }
508              
509             sub get_refcount {
510 1110     1110 0 1745 my $self = shift;
511              
512 1110         2721 my $e = $self->engine;
513             return unpack(
514 1110         2918 $StP{$e->byte_size},
515             $e->storage->read_at(
516             $self->offset + $self->base_size + 2 * $e->byte_size, $e->byte_size,
517             ),
518             );
519             }
520              
521             sub write_refcount {
522 1093     1093 0 1843 my $self = shift;
523 1093         2109 my ($num) = @_;
524              
525 1093         2624 my $e = $self->engine;
526             $e->storage->print_at(
527             $self->offset + $self->base_size + 2 * $e->byte_size,
528 1093         3415 pack( $StP{$e->byte_size}, $num ),
529             );
530             }
531              
532             sub clear {
533 217     217 0 396 my $self = shift;
534              
535 217 100       628 my $blist_loc = $self->get_blist_loc or return;
536              
537 10         48 my $engine = $self->engine;
538              
539             # This won't work with autoblessed items.
540 10 100       35 if ($engine->get_running_txn_ids) {
541             # ~~~ Temporary; the code below this block needs to be modified to
542             # take transactions into account.
543 2         13 $self->data->_get_self->_clear;
544 2         19 return;
545             }
546              
547 8 50       32 my $sector = $engine->load_sector( $blist_loc )
548             or DBM::Deep->_throw_error(
549             "Cannot read sector at $blist_loc in clear()"
550             );
551              
552             # Set blist offset to 0
553             $engine->storage->print_at( $self->offset + $self->base_size,
554 8         27 pack( $StP{$engine->byte_size}, 0 ),
555             );
556              
557             # Free the blist
558 8         49 $sector->free;
559              
560 8         30 return;
561             }
562              
563             1;
564             __END__