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 54     54   935 use 5.008_004;
  54         211  
4              
5 54     54   274 use strict;
  54         99  
  54         1969  
6 54     54   268 use warnings FATAL => 'all';
  54         100  
  54         3284  
7              
8 54     54   343 use base qw( DBM::Deep::Sector::File::Data );
  54         125  
  54         5449  
9              
10 54     54   354 use Scalar::Util;
  54         120  
  54         163021  
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 11446     11446   18530 my $self = shift;
24              
25 11446         30958 my $e = $self->engine;
26              
27 11446 100       26239 unless ( $self->offset ) {
28 308         767 my $classname = Scalar::Util::blessed( delete $self->{data} );
29 308         1244 my $leftover = $self->size - $self->base_size - 3 * $e->byte_size;
30              
31 308         656 my $class_offset = 0;
32 308 100       773 if ( defined $classname ) {
33 18         110 my $class_sector = DBM::Deep::Sector::File::Scalar->new({
34             engine => $e,
35             data => $classname,
36             });
37 18         62 $class_offset = $class_sector->offset;
38             }
39              
40 308         858 $self->{offset} = $e->_request_data_sector( $self->size );
41 308         1026 $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 308         981 pack( $StP{$e->byte_size}, 1 ), # Initial refcount
47             chr(0) x $leftover, # Zero-fill the rest
48             );
49             }
50             else {
51 11138         33767 $self->{type} = $e->storage->read_at( $self->offset, 1 );
52             }
53              
54             $self->{staleness} = unpack(
55 11446         43862 $StP{$STALE_SIZE},
56             $e->storage->read_at( $self->offset + $e->SIG_SIZE, $STALE_SIZE ),
57             );
58              
59 11446         33890 return;
60             }
61              
62             sub get_data_location_for {
63 5044     5044 0 8936 my $self = shift;
64 5044         9634 my ($args) = @_;
65              
66             # Assume that the head is not allowed unless otherwise specified.
67 5044 50       13053 $args->{allow_head} = 0 unless exists $args->{allow_head};
68              
69             # Assume we don't create a new blist location unless otherwise specified.
70 5044 50       15187 $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 5044         24847 });
77 5044 100 100     31897 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 3930 100       18450 }) or return;
85              
86 3877         28088 return $location;
87             }
88              
89             sub get_data_for {
90 4031     4031 0 7509 my $self = shift;
91 4031         8986 my ($args) = @_;
92              
93 4031 100       10826 my $location = $self->get_data_location_for( $args )
94             or return;
95              
96 3873         12181 return $self->engine->load_sector( $location );
97             }
98              
99             sub write_data {
100 2900     2900 0 4899 my $self = shift;
101 2900         6114 my ($args) = @_;
102              
103             my $blist = $self->get_bucket_list({
104             key_md5 => $args->{key_md5},
105             key => $args->{key},
106 2900 50       22248 create => 1,
107             }) or DBM::Deep->_throw_error( "How did write_data fail (no blist)?!" );
108              
109             # Handle any transactional bookkeeping.
110 2900 100       12226 if ( $self->engine->trans_id ) {
111 67 100       125 if ( ! $blist->has_md5 ) {
112 40         144 $blist->mark_deleted({
113             trans_id => 0,
114             });
115             }
116             }
117             else {
118 2833         6661 my @trans_ids = $self->engine->get_running_txn_ids;
119 2833 100       9846 if ( $blist->has_md5 ) {
120 524 100       1775 if ( @trans_ids ) {
121 4         18 my $old_value = $blist->get_data_for;
122 4         14 foreach my $other_trans_id ( @trans_ids ) {
123 4 100       27 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         49 value => $old_value->clone,
132             });
133             }
134             }
135             }
136             else {
137 2309 100       6023 if ( @trans_ids ) {
138 2         8 foreach my $other_trans_id ( @trans_ids ) {
139             #XXX This doesn't seem to possible to ever happen . . .
140 2 50       31 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
141 2         17 $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 2900 100       14285 if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
152 531         2852 $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 2900         23286 });
160             }
161              
162             sub delete_key {
163 65     65 0 177 my $self = shift;
164 65         196 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 65 100       296 }) or return;
173              
174             # Save the location so that we can free the data
175 64         392 my $location = $blist->get_data_location_for({
176             allow_head => 0,
177             });
178 64   66     508 my $old_value = $location && $self->engine->load_sector( $location );
179              
180 64         215 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 64 100       234 if ( $self->engine->trans_id == 0 ) {
185 53 100       225 if ( @trans_ids ) {
186 1         19 foreach my $other_trans_id ( @trans_ids ) {
187 1 50       9 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         9 value => $old_value->clone,
193             });
194             }
195             }
196             }
197              
198 64         184 my $data;
199 64 100       178 if ( @trans_ids ) {
200 12         63 $blist->mark_deleted( $args );
201              
202 12 100       63 if ( $old_value ) {
203             #XXX Is this export => 1 actually doing anything?
204 1         7 $data = $old_value->data({ export => 1 });
205 1         6 $old_value->free;
206             }
207             }
208             else {
209 52         259 $data = $blist->delete_md5( $args );
210             }
211              
212 64         568 return $data;
213             }
214              
215             sub write_blist_loc {
216 257     257 0 593 my $self = shift;
217 257         544 my ($loc) = @_;
218              
219 257         617 my $engine = $self->engine;
220             $engine->storage->print_at( $self->offset + $self->base_size,
221 257         1049 pack( $StP{$engine->byte_size}, $loc ),
222             );
223             }
224              
225             sub get_blist_loc {
226 8421     8421 0 12878 my $self = shift;
227              
228 8421         17726 my $e = $self->engine;
229 8421         24120 my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
230 8421         27661 return unpack( $StP{$e->byte_size}, $blist_loc );
231             }
232              
233             sub get_bucket_list {
234 8009     8009 0 14270 my $self = shift;
235 8009         17360 my ($args) = @_;
236 8009   50     19825 $args ||= {};
237              
238             # XXX Add in check here for recycling?
239              
240 8009         21713 my $engine = $self->engine;
241              
242 8009         19275 my $blist_loc = $self->get_blist_loc;
243              
244             # There's no index or blist yet
245 8009 100       21803 unless ( $blist_loc ) {
246 285 100       941 return unless $args->{create};
247              
248             my $blist = DBM::Deep::Sector::File::BucketList->new({
249             engine => $engine,
250             key_md5 => $args->{key_md5},
251 257         2713 });
252              
253 257         684 $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 257         1252 return $blist;
259             }
260              
261 7724 50       23045 my $sector = $engine->load_sector( $blist_loc )
262             or DBM::Deep->_throw_error( "Cannot read sector at $blist_loc in get_bucket_list()" );
263 7724         14780 my $i = 0;
264 7724         14869 my $last_sector = undef;
265 7724         36289 while ( $sector->isa( 'DBM::Deep::Sector::File::Index' ) ) {
266 5721         24798 $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
267 5721         13559 $last_sector = $sector;
268 5721 100       12512 if ( $blist_loc ) {
269 4773 50       14279 $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 948         1573 $sector = undef;
274 948         1925 last;
275             }
276             }
277              
278             # This means we went through the Index sector(s) and found an empty slot
279 7724 100       17869 unless ( $sector ) {
280 948 100       3311 return unless $args->{create};
281              
282 709 50       1558 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 709         3639 });
289              
290 709         3082 $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
291              
292 709         5222 return $blist;
293             }
294              
295 6776         27796 $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 6776 100 100     22494 if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
      100        
303 11         25 my $redo;
  14         26  
304              
305 14         131 my $new_index = DBM::Deep::Sector::File::Index->new({
306             engine => $engine,
307             });
308              
309 14         31 my %blist_cache;
310             #XXX q.v. the comments for this function.
311 14         62 foreach my $entry ( $sector->chopped_up ) {
312 224         352 my ($spot, $md5) = @{$entry};
  224         446  
313 224         502 my $idx = ord( substr( $md5, $i, 1 ) );
314              
315             # XXX This is inefficient
316 224   66     1314 my $blist = $blist_cache{$idx}
317             ||= DBM::Deep::Sector::File::BucketList->new({
318             engine => $engine,
319             });
320              
321 224         676 $new_index->set_entry( $idx => $blist->offset );
322              
323 224         855 my $new_spot = $blist->write_at_next_open( $md5 );
324 224         702 $engine->reindex_entry( $spot => $new_spot );
325             }
326              
327             # Handle the new item separately.
328             {
329 14         85 my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
  14         60  
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 14 100 66     137 if ( keys %blist_cache == 1 and each %blist_cache == $idx ) {
335 3         12 ++$i, ++$redo;
336             } else {
337 11   66     94 my $blist = $blist_cache{$idx}
338             ||= DBM::Deep::Sector::File::BucketList->new({
339             engine => $engine,
340             });
341            
342 11         39 $new_index->set_entry( $idx => $blist->offset );
343            
344             #XXX THIS IS HACKY!
345 11         64 $blist->find_md5( $args->{key_md5} );
346             $blist->write_md5({
347             key => $args->{key},
348             key_md5 => $args->{key_md5},
349 11         222 value => DBM::Deep::Sector::File::Null->new({
350             engine => $engine,
351             data => undef,
352             }),
353             });
354             }
355             }
356              
357 14 100       119 if ( $last_sector ) {
358             $last_sector->set_entry(
359 3         71 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 11         64 pack( $StP{$engine->byte_size}, $new_index->offset ),
365             );
366             }
367              
368 14         84 $sector->wipe;
369 14         112 $sector->free;
370              
371 14 100       52 if ( $redo ) {
372 3         27 (undef, $sector) = %blist_cache;
373 3         13 $last_sector = $new_index;
374 3         12 redo;
375             }
376              
377 11         88 $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
378 11         48 $sector->find_md5( $args->{key_md5} );
379             }}
380              
381 6776         31743 return $sector;
382             }
383              
384             sub get_class_offset {
385 2375     2375 0 3751 my $self = shift;
386              
387 2375         7195 my $e = $self->engine;
388             return unpack(
389 2375         8963 $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 2340     2340 0 4030 my $self = shift;
398              
399 2340         7276 my $class_offset = $self->get_class_offset;
400              
401 2340 100       8229 return unless $class_offset;
402              
403 43         125 return $self->engine->load_sector( $class_offset )->data;
404             }
405              
406             # Look to hoist this method into a ::Reference trait
407             sub data {
408 2357     2357 0 4144 my $self = shift;
409 2357         5350 my ($args) = @_;
410 2357   100     11779 $args ||= {};
411              
412 2357         6698 my $engine = $self->engine;
413 2357   100     7184 my $cache_entry = $engine->cache->{ $self->offset } ||= {};
414 2357         7593 my $trans_id = $engine->trans_id;
415 2357         3908 my $obj;
416 2357 100       7468 if ( !defined $$cache_entry{ $trans_id } ) {
417 2336         6201 $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 2336         10463 $$cache_entry{ $trans_id } = $obj;
426 2336         6156 Scalar::Util::weaken($$cache_entry{ $trans_id });
427             }
428             else {
429 21         47 $obj = $$cache_entry{ $trans_id };
430             }
431              
432             # We're not exporting, so just return.
433 2357 100       6241 unless ( $args->{export} ) {
434 2341 100       6382 if ( $engine->storage->{autobless} ) {
435 2328         7301 my $classname = $self->get_classname;
436 2328 100       6128 if ( defined $classname ) {
437 39         153 bless $obj, $classname;
438             }
439             }
440              
441 2341         18440 return $obj;
442             }
443              
444             # We shouldn't export if this is still referred to.
445 16 100       66 if ( $self->get_refcount > 1 ) {
446 10         56 return $obj;
447             }
448              
449 6         54 return $obj->export;
450             }
451              
452             sub free {
453 69     69 0 155 my $self = shift;
454              
455             # We're not ready to be removed yet.
456 69 100       312 return if $self->decrement_refcount > 0;
457              
458 35         115 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 35 100       146 if(!$e->{external_refs}) {
464             # eval { %{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
465             # eval { @{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
466 28         145 my $cache = $e->cache;
467 28         107 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       92 if defined $cache->{ $off }{ $trans_id };
472 21         59 delete $cache->{ $off }{ $trans_id };
473             }
474             }
475              
476 35         115 my $blist_loc = $self->get_blist_loc;
477 35 100       146 $e->load_sector( $blist_loc )->free if $blist_loc;
478              
479 35         229 my $class_loc = $self->get_class_offset;
480 35 100       156 $e->load_sector( $class_loc )->free if $class_loc;
481              
482 35         228 $self->SUPER::free();
483             }
484              
485             sub increment_refcount {
486 1040     1040 0 1850 my $self = shift;
487              
488 1040         2840 my $refcount = $self->get_refcount;
489              
490 1040         2076 $refcount++;
491              
492 1040         3523 $self->write_refcount( $refcount );
493              
494 1040         3208 return $refcount;
495             }
496              
497             sub decrement_refcount {
498 69     69 0 117 my $self = shift;
499              
500 69         207 my $refcount = $self->get_refcount;
501              
502 69         159 $refcount--;
503              
504 69         264 $self->write_refcount( $refcount );
505              
506 69         698 return $refcount;
507             }
508              
509             sub get_refcount {
510 1128     1128 0 1738 my $self = shift;
511              
512 1128         3193 my $e = $self->engine;
513             return unpack(
514 1128         3336 $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 1109     1109 0 1969 my $self = shift;
523 1109         2114 my ($num) = @_;
524              
525 1109         2898 my $e = $self->engine;
526             $e->storage->print_at(
527             $self->offset + $self->base_size + 2 * $e->byte_size,
528 1109         3324 pack( $StP{$e->byte_size}, $num ),
529             );
530             }
531              
532             sub clear {
533 220     220 0 535 my $self = shift;
534              
535 220 100       679 my $blist_loc = $self->get_blist_loc or return;
536              
537 10         36 my $engine = $self->engine;
538              
539             # This won't work with autoblessed items.
540 10 100       45 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         10 $self->data->_get_self->_clear;
544 2         19 return;
545             }
546              
547 8 50       57 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         33 pack( $StP{$engine->byte_size}, 0 ),
555             );
556              
557             # Free the blist
558 8         55 $sector->free;
559              
560 8         50 return;
561             }
562              
563             1;
564             __END__