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   884 use 5.008_004;
  50         172  
4              
5 50     50   269 use strict;
  50         138  
  50         1287  
6 50     50   272 use warnings FATAL => 'all';
  50         139  
  50         2016  
7              
8 50     50   311 use base qw( DBM::Deep::Sector::File::Data );
  50         127  
  50         4136  
9              
10 50     50   382 use Scalar::Util;
  50         123  
  50         139333  
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   15750 my $self = shift;
24              
25 9564         20680 my $e = $self->engine;
26              
27 9564 100       20467 unless ( $self->offset ) {
28 300         994 my $classname = Scalar::Util::blessed( delete $self->{data} );
29 300         1008 my $leftover = $self->size - $self->base_size - 3 * $e->byte_size;
30              
31 300         639 my $class_offset = 0;
32 300 100       790 if ( defined $classname ) {
33 18         101 my $class_sector = DBM::Deep::Sector::File::Scalar->new({
34             engine => $e,
35             data => $classname,
36             });
37 18         58 $class_offset = $class_sector->offset;
38             }
39              
40 300         769 $self->{offset} = $e->_request_data_sector( $self->size );
41 300         1142 $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         1080 pack( $StP{$e->byte_size}, 1 ), # Initial refcount
47             chr(0) x $leftover, # Zero-fill the rest
48             );
49             }
50             else {
51 9264         23044 $self->{type} = $e->storage->read_at( $self->offset, 1 );
52             }
53              
54             $self->{staleness} = unpack(
55 9564         34116 $StP{$STALE_SIZE},
56             $e->storage->read_at( $self->offset + $e->SIG_SIZE, $STALE_SIZE ),
57             );
58              
59 9564         27412 return;
60             }
61              
62             sub get_data_location_for {
63 4299     4299 0 6601 my $self = shift;
64 4299         7078 my ($args) = @_;
65              
66             # Assume that the head is not allowed unless otherwise specified.
67 4299 50       9358 $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       11253 $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         15351 });
77 4299 100 100     20565 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       13023 }) or return;
85              
86 3146         14384 return $location;
87             }
88              
89             sub get_data_for {
90 3286     3286 0 5405 my $self = shift;
91 3286         6094 my ($args) = @_;
92              
93 3286 100       6705 my $location = $self->get_data_location_for( $args )
94             or return;
95              
96 3142         8617 return $self->engine->load_sector( $location );
97             }
98              
99             sub write_data {
100 1812     1812 0 3361 my $self = shift;
101 1812         3542 my ($args) = @_;
102              
103             my $blist = $self->get_bucket_list({
104             key_md5 => $args->{key_md5},
105             key => $args->{key},
106 1812 50       7009 create => 1,
107             }) or DBM::Deep->_throw_error( "How did write_data fail (no blist)?!" );
108              
109             # Handle any transactional bookkeeping.
110 1812 100       6529 if ( $self->engine->trans_id ) {
111 67 100       170 if ( ! $blist->has_md5 ) {
112 40         199 $blist->mark_deleted({
113             trans_id => 0,
114             });
115             }
116             }
117             else {
118 1745         3788 my @trans_ids = $self->engine->get_running_txn_ids;
119 1745 100       5323 if ( $blist->has_md5 ) {
120 163 100       747 if ( @trans_ids ) {
121 4         16 my $old_value = $blist->get_data_for;
122 4         23 foreach my $other_trans_id ( @trans_ids ) {
123 4 100       19 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         60 value => $old_value->clone,
132             });
133             }
134             }
135             }
136             else {
137 1582 100       4145 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       19 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
141 2         33 $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       7382 if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
152 170         813 $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         10757 });
160             }
161              
162             sub delete_key {
163 63     63 0 172 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       208 }) or return;
173              
174             # Save the location so that we can free the data
175 62         317 my $location = $blist->get_data_location_for({
176             allow_head => 0,
177             });
178 62   66     392 my $old_value = $location && $self->engine->load_sector( $location );
179              
180 62         207 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       247 if ( $self->engine->trans_id == 0 ) {
185 51 100       141 if ( @trans_ids ) {
186 1         4 foreach my $other_trans_id ( @trans_ids ) {
187 1 50       6 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         15 value => $old_value->clone,
193             });
194             }
195             }
196             }
197              
198 62         124 my $data;
199 62 100       182 if ( @trans_ids ) {
200 12         45 $blist->mark_deleted( $args );
201              
202 12 100       55 if ( $old_value ) {
203             #XXX Is this export => 1 actually doing anything?
204 1         10 $data = $old_value->data({ export => 1 });
205 1         9 $old_value->free;
206             }
207             }
208             else {
209 50         170 $data = $blist->delete_md5( $args );
210             }
211              
212 62         376 return $data;
213             }
214              
215             sub write_blist_loc {
216 249     249 0 479 my $self = shift;
217 249         534 my ($loc) = @_;
218              
219 249         623 my $engine = $self->engine;
220             $engine->storage->print_at( $self->offset + $self->base_size,
221 249         726 pack( $StP{$engine->byte_size}, $loc ),
222             );
223             }
224              
225             sub get_blist_loc {
226 6581     6581 0 9675 my $self = shift;
227              
228 6581         12471 my $e = $self->engine;
229 6581         15575 my $blist_loc = $e->storage->read_at( $self->offset + $self->base_size, $e->byte_size );
230 6581         23410 return unpack( $StP{$e->byte_size}, $blist_loc );
231             }
232              
233             sub get_bucket_list {
234 6174     6174 0 9965 my $self = shift;
235 6174         9844 my ($args) = @_;
236 6174   50     13144 $args ||= {};
237              
238             # XXX Add in check here for recycling?
239              
240 6174         14226 my $engine = $self->engine;
241              
242 6174         12608 my $blist_loc = $self->get_blist_loc;
243              
244             # There's no index or blist yet
245 6174 100       17280 unless ( $blist_loc ) {
246 275 100       818 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         1636 });
252              
253 249         719 $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         1087 return $blist;
259             }
260              
261 5899 50       15543 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         10072 my $i = 0;
264 5899         9183 my $last_sector = undef;
265 5899         22004 while ( $sector->isa( 'DBM::Deep::Sector::File::Index' ) ) {
266 4074         13125 $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
267 4074         8449 $last_sector = $sector;
268 4074 100       9678 if ( $blist_loc ) {
269 3581 50       10411 $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 493         938 $sector = undef;
274 493         908 last;
275             }
276             }
277              
278             # This means we went through the Index sector(s) and found an empty slot
279 5899 100       12999 unless ( $sector ) {
280 493 100       1749 return unless $args->{create};
281              
282 254 50       585 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 254         1177 });
289              
290 254         962 $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
291              
292 254         1539 return $blist;
293             }
294              
295 5406         17283 $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 5406 100 100     14368 if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
      100        
303 7         35 my $redo;
  10         32  
304              
305 10         78 my $new_index = DBM::Deep::Sector::File::Index->new({
306             engine => $engine,
307             });
308              
309 10         21 my %blist_cache;
310             #XXX q.v. the comments for this function.
311 10         68 foreach my $entry ( $sector->chopped_up ) {
312 160         254 my ($spot, $md5) = @{$entry};
  160         339  
313 160         368 my $idx = ord( substr( $md5, $i, 1 ) );
314              
315             # XXX This is inefficient
316 160   66     845 my $blist = $blist_cache{$idx}
317             ||= DBM::Deep::Sector::File::BucketList->new({
318             engine => $engine,
319             });
320              
321 160         444 $new_index->set_entry( $idx => $blist->offset );
322              
323 160         573 my $new_spot = $blist->write_at_next_open( $md5 );
324 160         494 $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         37  
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     65 if ( keys %blist_cache == 1 and each %blist_cache == $idx ) {
335 3         21 ++$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         38 $new_index->set_entry( $idx => $blist->offset );
343            
344             #XXX THIS IS HACKY!
345 7         53 $blist->find_md5( $args->{key_md5} );
346             $blist->write_md5({
347             key => $args->{key},
348             key_md5 => $args->{key_md5},
349 7         144 value => DBM::Deep::Sector::File::Null->new({
350             engine => $engine,
351             data => undef,
352             }),
353             });
354             }
355             }
356              
357 10 100       116 if ( $last_sector ) {
358             $last_sector->set_entry(
359 3         30 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         79 $sector->wipe;
369 10         68 $sector->free;
370              
371 10 100       78 if ( $redo ) {
372 3         15 (undef, $sector) = %blist_cache;
373 3         8 $last_sector = $new_index;
374 3         8 redo;
375             }
376              
377 7         50 $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
378 7         30 $sector->find_md5( $args->{key_md5} );
379             }}
380              
381 5406         18679 return $sector;
382             }
383              
384             sub get_class_offset {
385 2371     2371 0 3542 my $self = shift;
386              
387 2371         5558 my $e = $self->engine;
388             return unpack(
389 2371         6365 $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 3898 my $self = shift;
398              
399 2338         4533 my $class_offset = $self->get_class_offset;
400              
401 2338 100       7632 return unless $class_offset;
402              
403 43         145 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 3822 my $self = shift;
409 2353         4478 my ($args) = @_;
410 2353   100     10250 $args ||= {};
411              
412 2353         5511 my $engine = $self->engine;
413 2353   100     6104 my $cache_entry = $engine->cache->{ $self->offset } ||= {};
414 2353         6384 my $trans_id = $engine->trans_id;
415 2353         4028 my $obj;
416 2353 100       6078 if ( !defined $$cache_entry{ $trans_id } ) {
417 2334         5385 $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         7364 $$cache_entry{ $trans_id } = $obj;
426 2334         6881 Scalar::Util::weaken($$cache_entry{ $trans_id });
427             }
428             else {
429 19         42 $obj = $$cache_entry{ $trans_id };
430             }
431              
432             # We're not exporting, so just return.
433 2353 100       5684 unless ( $args->{export} ) {
434 2339 100       5512 if ( $engine->storage->{autobless} ) {
435 2326         5425 my $classname = $self->get_classname;
436 2326 100       5314 if ( defined $classname ) {
437 39         157 bless $obj, $classname;
438             }
439             }
440              
441 2339         13341 return $obj;
442             }
443              
444             # We shouldn't export if this is still referred to.
445 14 100       159 if ( $self->get_refcount > 1 ) {
446 8         36 return $obj;
447             }
448              
449 6         48 return $obj->export;
450             }
451              
452             sub free {
453 60     60 0 130 my $self = shift;
454              
455             # We're not ready to be removed yet.
456 60 100       150 return if $self->decrement_refcount > 0;
457              
458 33         145 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       124 if(!$e->{external_refs}) {
464             # eval { %{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
465             # eval { @{ $e->cache->{ $self->offset }{ $e->trans_id } } = (); };
466 28         105 my $cache = $e->cache;
467 28         77 my $off = $self->offset;
468 28 100 100     159 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       74 if defined $cache->{ $off }{ $trans_id };
472 21         56 delete $cache->{ $off }{ $trans_id };
473             }
474             }
475              
476 33         103 my $blist_loc = $self->get_blist_loc;
477 33 100       160 $e->load_sector( $blist_loc )->free if $blist_loc;
478              
479 33         130 my $class_loc = $self->get_class_offset;
480 33 100       123 $e->load_sector( $class_loc )->free if $class_loc;
481              
482 33         162 $self->SUPER::free();
483             }
484              
485             sub increment_refcount {
486 1033     1033 0 1804 my $self = shift;
487              
488 1033         2271 my $refcount = $self->get_refcount;
489              
490 1033         2330 $refcount++;
491              
492 1033         3089 $self->write_refcount( $refcount );
493              
494 1033         2727 return $refcount;
495             }
496              
497             sub decrement_refcount {
498 60     60 0 104 my $self = shift;
499              
500 60         150 my $refcount = $self->get_refcount;
501              
502 60         149 $refcount--;
503              
504 60         209 $self->write_refcount( $refcount );
505              
506 60         428 return $refcount;
507             }
508              
509             sub get_refcount {
510 1110     1110 0 1662 my $self = shift;
511              
512 1110         2684 my $e = $self->engine;
513             return unpack(
514 1110         3153 $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 1680 my $self = shift;
523 1093         1955 my ($num) = @_;
524              
525 1093         2865 my $e = $self->engine;
526             $e->storage->print_at(
527             $self->offset + $self->base_size + 2 * $e->byte_size,
528 1093         2886 pack( $StP{$e->byte_size}, $num ),
529             );
530             }
531              
532             sub clear {
533 217     217 0 374 my $self = shift;
534              
535 217 100       496 my $blist_loc = $self->get_blist_loc or return;
536              
537 10         44 my $engine = $self->engine;
538              
539             # This won't work with autoblessed items.
540 10 100       41 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         12 $self->data->_get_self->_clear;
544 2         20 return;
545             }
546              
547 8 50       36 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         44 $sector->free;
559              
560 8         32 return;
561             }
562              
563             1;
564             __END__