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   875 use 5.008_004;
  50         178  
4              
5 50     50   304 use strict;
  50         133  
  50         1265  
6 50     50   256 use warnings FATAL => 'all';
  50         139  
  50         2011  
7              
8 50     50   300 use base qw( DBM::Deep::Sector::File::Data );
  50         99  
  50         4158  
9              
10 50     50   388 use Scalar::Util;
  50         145  
  50         135659  
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   14873 my $self = shift;
24              
25 9564         19639 my $e = $self->engine;
26              
27 9564 100       22396 unless ( $self->offset ) {
28 300         894 my $classname = Scalar::Util::blessed( delete $self->{data} );
29 300         975 my $leftover = $self->size - $self->base_size - 3 * $e->byte_size;
30              
31 300         624 my $class_offset = 0;
32 300 100       785 if ( defined $classname ) {
33 18         117 my $class_sector = DBM::Deep::Sector::File::Scalar->new({
34             engine => $e,
35             data => $classname,
36             });
37 18         57 $class_offset = $class_sector->offset;
38             }
39              
40 300         771 $self->{offset} = $e->_request_data_sector( $self->size );
41 300         1014 $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         1084 pack( $StP{$e->byte_size}, 1 ), # Initial refcount
47             chr(0) x $leftover, # Zero-fill the rest
48             );
49             }
50             else {
51 9264         22933 $self->{type} = $e->storage->read_at( $self->offset, 1 );
52             }
53              
54             $self->{staleness} = unpack(
55 9564         33688 $StP{$STALE_SIZE},
56             $e->storage->read_at( $self->offset + $e->SIG_SIZE, $STALE_SIZE ),
57             );
58              
59 9564         27265 return;
60             }
61              
62             sub get_data_location_for {
63 4299     4299 0 6338 my $self = shift;
64 4299         6791 my ($args) = @_;
65              
66             # Assume that the head is not allowed unless otherwise specified.
67 4299 50       9184 $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       10087 $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         15150 });
77 4299 100 100     21933 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       12199 }) or return;
85              
86 3146         14161 return $location;
87             }
88              
89             sub get_data_for {
90 3286     3286 0 5658 my $self = shift;
91 3286         6432 my ($args) = @_;
92              
93 3286 100       7105 my $location = $self->get_data_location_for( $args )
94             or return;
95              
96 3142         8750 return $self->engine->load_sector( $location );
97             }
98              
99             sub write_data {
100 1812     1812 0 3161 my $self = shift;
101 1812         3561 my ($args) = @_;
102              
103             my $blist = $self->get_bucket_list({
104             key_md5 => $args->{key_md5},
105             key => $args->{key},
106 1812 50       7103 create => 1,
107             }) or DBM::Deep->_throw_error( "How did write_data fail (no blist)?!" );
108              
109             # Handle any transactional bookkeeping.
110 1812 100       6382 if ( $self->engine->trans_id ) {
111 67 100       155 if ( ! $blist->has_md5 ) {
112 40         173 $blist->mark_deleted({
113             trans_id => 0,
114             });
115             }
116             }
117             else {
118 1745         3659 my @trans_ids = $self->engine->get_running_txn_ids;
119 1745 100       5291 if ( $blist->has_md5 ) {
120 163 100       644 if ( @trans_ids ) {
121 4         25 my $old_value = $blist->get_data_for;
122 4         22 foreach my $other_trans_id ( @trans_ids ) {
123 4 100       30 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         53 value => $old_value->clone,
132             });
133             }
134             }
135             }
136             else {
137 1582 100       4086 if ( @trans_ids ) {
138 2         7 foreach my $other_trans_id ( @trans_ids ) {
139             #XXX This doesn't seem to possible to ever happen . . .
140 2 50       12 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
141 2         24 $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       6897 if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
152 170         849 $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         10693 });
160             }
161              
162             sub delete_key {
163 63     63 0 169 my $self = shift;
164 63         135 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       205 }) or return;
173              
174             # Save the location so that we can free the data
175 62         322 my $location = $blist->get_data_location_for({
176             allow_head => 0,
177             });
178 62   66     360 my $old_value = $location && $self->engine->load_sector( $location );
179              
180 62         227 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       237 if ( $self->engine->trans_id == 0 ) {
185 51 100       145 if ( @trans_ids ) {
186 1         3 foreach my $other_trans_id ( @trans_ids ) {
187 1 50       7 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         12 value => $old_value->clone,
193             });
194             }
195             }
196             }
197              
198 62         117 my $data;
199 62 100       163 if ( @trans_ids ) {
200 12         51 $blist->mark_deleted( $args );
201              
202 12 100       47 if ( $old_value ) {
203             #XXX Is this export => 1 actually doing anything?
204 1         5 $data = $old_value->data({ export => 1 });
205 1         6 $old_value->free;
206             }
207             }
208             else {
209 50         171 $data = $blist->delete_md5( $args );
210             }
211              
212 62         400 return $data;
213             }
214              
215             sub write_blist_loc {
216 249     249 0 443 my $self = shift;
217 249         537 my ($loc) = @_;
218              
219 249         602 my $engine = $self->engine;
220             $engine->storage->print_at( $self->offset + $self->base_size,
221 249         731 pack( $StP{$engine->byte_size}, $loc ),
222             );
223             }
224              
225             sub get_blist_loc {
226 6581     6581 0 9898 my $self = shift;
227              
228 6581         12461 my $e = $self->engine;
229 6581         15512 my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
230 6581         22048 return unpack( $StP{$e->byte_size}, $blist_loc );
231             }
232              
233             sub get_bucket_list {
234 6174     6174 0 9363 my $self = shift;
235 6174         10103 my ($args) = @_;
236 6174   50     12736 $args ||= {};
237              
238             # XXX Add in check here for recycling?
239              
240 6174         13696 my $engine = $self->engine;
241              
242 6174         12158 my $blist_loc = $self->get_blist_loc;
243              
244             # There's no index or blist yet
245 6174 100       15246 unless ( $blist_loc ) {
246 275 100       781 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         1770 });
252              
253 249         738 $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         1102 return $blist;
259             }
260              
261 5899 50       16005 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         9670 my $i = 0;
264 5899         9325 my $last_sector = undef;
265 5899         22512 while ( $sector->isa( 'DBM::Deep::Sector::File::Index' ) ) {
266 4074         13782 $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
267 4074         8623 $last_sector = $sector;
268 4074 100       8903 if ( $blist_loc ) {
269 3580 50       10455 $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 494         870 $sector = undef;
274 494         887 last;
275             }
276             }
277              
278             # This means we went through the Index sector(s) and found an empty slot
279 5899 100       12729 unless ( $sector ) {
280 494 100       1809 return unless $args->{create};
281              
282 255 50       652 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 255         1251 });
289              
290 255         1065 $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
291              
292 255         1473 return $blist;
293             }
294              
295 5405         17110 $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 5405 100 100     16234 if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
      100        
303 7         25 my $redo;
  10         23  
304              
305 10         85 my $new_index = DBM::Deep::Sector::File::Index->new({
306             engine => $engine,
307             });
308              
309 10         25 my %blist_cache;
310             #XXX q.v. the comments for this function.
311 10         64 foreach my $entry ( $sector->chopped_up ) {
312 160         260 my ($spot, $md5) = @{$entry};
  160         304  
313 160         350 my $idx = ord( substr( $md5, $i, 1 ) );
314              
315             # XXX This is inefficient
316 160   66     859 my $blist = $blist_cache{$idx}
317             ||= DBM::Deep::Sector::File::BucketList->new({
318             engine => $engine,
319             });
320              
321 160         445 $new_index->set_entry( $idx => $blist->offset );
322              
323 160         579 my $new_spot = $blist->write_at_next_open( $md5 );
324 160         497 $engine->reindex_entry( $spot => $new_spot );
325             }
326              
327             # Handle the new item separately.
328             {
329 10         48 my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
  10         40  
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     68 if ( keys %blist_cache == 1 and each %blist_cache == $idx ) {
335 3         24 ++$i, ++$redo;
336             } else {
337 7   66     67 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         58 $blist->find_md5( $args->{key_md5} );
346             $blist->write_md5({
347             key => $args->{key},
348             key_md5 => $args->{key_md5},
349 7         124 value => DBM::Deep::Sector::File::Null->new({
350             engine => $engine,
351             data => undef,
352             }),
353             });
354             }
355             }
356              
357 10 100       86 if ( $last_sector ) {
358             $last_sector->set_entry(
359 3         12 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         34 pack( $StP{$engine->byte_size}, $new_index->offset ),
365             );
366             }
367              
368 10         92 $sector->wipe;
369 10         72 $sector->free;
370              
371 10 100       59 if ( $redo ) {
372 3         16 (undef, $sector) = %blist_cache;
373 3         9 $last_sector = $new_index;
374 3         9 redo;
375             }
376              
377 7         66 $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
378 7         33 $sector->find_md5( $args->{key_md5} );
379             }}
380              
381 5405         19262 return $sector;
382             }
383              
384             sub get_class_offset {
385 2371     2371 0 3346 my $self = shift;
386              
387 2371         5460 my $e = $self->engine;
388             return unpack(
389 2371         6099 $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 3669 my $self = shift;
398              
399 2338         4533 my $class_offset = $self->get_class_offset;
400              
401 2338 100       7539 return unless $class_offset;
402              
403 43         138 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 3984 my $self = shift;
409 2353         4609 my ($args) = @_;
410 2353   100     10368 $args ||= {};
411              
412 2353         5737 my $engine = $self->engine;
413 2353   100     5962 my $cache_entry = $engine->cache->{ $self->offset } ||= {};
414 2353         6055 my $trans_id = $engine->trans_id;
415 2353         3568 my $obj;
416 2353 100       5992 if ( !defined $$cache_entry{ $trans_id } ) {
417 2334         5753 $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         7614 $$cache_entry{ $trans_id } = $obj;
426 2334         6666 Scalar::Util::weaken($$cache_entry{ $trans_id });
427             }
428             else {
429 19         32 $obj = $$cache_entry{ $trans_id };
430             }
431              
432             # We're not exporting, so just return.
433 2353 100       5588 unless ( $args->{export} ) {
434 2339 100       5434 if ( $engine->storage->{autobless} ) {
435 2326         5112 my $classname = $self->get_classname;
436 2326 100       5611 if ( defined $classname ) {
437 39         138 bless $obj, $classname;
438             }
439             }
440              
441 2339         13445 return $obj;
442             }
443              
444             # We shouldn't export if this is still referred to.
445 14 100       175 if ( $self->get_refcount > 1 ) {
446 8         29 return $obj;
447             }
448              
449 6         66 return $obj->export;
450             }
451              
452             sub free {
453 60     60 0 120 my $self = shift;
454              
455             # We're not ready to be removed yet.
456 60 100       151 return if $self->decrement_refcount > 0;
457              
458 33         112 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       93 if(!$e->{external_refs}) {
464             # eval { %{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
465             # eval { @{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
466 28         98 my $cache = $e->cache;
467 28         95 my $off = $self->offset;
468 28 100 100     179 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       69 if defined $cache->{ $off }{ $trans_id };
472 21         60 delete $cache->{ $off }{ $trans_id };
473             }
474             }
475              
476 33         109 my $blist_loc = $self->get_blist_loc;
477 33 100       128 $e->load_sector( $blist_loc )->free if $blist_loc;
478              
479 33         118 my $class_loc = $self->get_class_offset;
480 33 100       139 $e->load_sector( $class_loc )->free if $class_loc;
481              
482 33         179 $self->SUPER::free();
483             }
484              
485             sub increment_refcount {
486 1033     1033 0 1687 my $self = shift;
487              
488 1033         2338 my $refcount = $self->get_refcount;
489              
490 1033         2205 $refcount++;
491              
492 1033         2900 $self->write_refcount( $refcount );
493              
494 1033         2709 return $refcount;
495             }
496              
497             sub decrement_refcount {
498 60     60 0 104 my $self = shift;
499              
500 60         153 my $refcount = $self->get_refcount;
501              
502 60         172 $refcount--;
503              
504 60         220 $self->write_refcount( $refcount );
505              
506 60         476 return $refcount;
507             }
508              
509             sub get_refcount {
510 1110     1110 0 1659 my $self = shift;
511              
512 1110         2733 my $e = $self->engine;
513             return unpack(
514 1110         2854 $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 1756 my $self = shift;
523 1093         1977 my ($num) = @_;
524              
525 1093         2811 my $e = $self->engine;
526             $e->storage->print_at(
527             $self->offset + $self->base_size + 2 * $e->byte_size,
528 1093         2841 pack( $StP{$e->byte_size}, $num ),
529             );
530             }
531              
532             sub clear {
533 217     217 0 379 my $self = shift;
534              
535 217 100       483 my $blist_loc = $self->get_blist_loc or return;
536              
537 10         49 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         11 $self->data->_get_self->_clear;
544 2         21 return;
545             }
546              
547 8 50       35 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         38 pack( $StP{$engine->byte_size}, 0 ),
555             );
556              
557             # Free the blist
558 8         47 $sector->free;
559              
560 8         33 return;
561             }
562              
563             1;
564             __END__