File Coverage

blib/lib/DBM/Deep/Sector/File/BucketList.pm
Criterion Covered Total %
statement 161 164 98.1
branch 60 70 85.7
condition 10 12 83.3
subroutine 20 20 100.0
pod 0 15 0.0
total 251 281 89.3


line stmt bran cond sub pod time code
1             package DBM::Deep::Sector::File::BucketList;
2              
3 54     54   912 use 5.008_004;
  54         208  
4              
5 54     54   339 use strict;
  54         351  
  54         5013  
6 54     54   379 use warnings FATAL => 'all';
  54         140  
  54         3392  
7              
8 54     54   448 use base qw( DBM::Deep::Sector::File );
  54         175  
  54         149530  
9              
10             my $STALE_SIZE = 2;
11              
12             # Please refer to the pack() documentation for further information
13             my %StP = (
14             1 => 'C', # Unsigned char value (no order needed as it's just one byte)
15             2 => 'n', # Unsigned short in "network" (big-endian) order
16             4 => 'N', # Unsigned long in "network" (big-endian) order
17             8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
18             );
19              
20             sub _init {
21 8277     8277   15491 my $self = shift;
22              
23 8277         20724 my $engine = $self->engine;
24              
25 8277 100       19464 unless ( $self->offset ) {
26 1137         2978 my $leftover = $self->size - $self->base_size;
27              
28 1137         2851 $self->{offset} = $engine->_request_blist_sector( $self->size );
29 1137         3798 $engine->storage->print_at( $self->offset, $engine->SIG_BLIST ); # Sector type
30             # Skip staleness counter
31 1137         3696 $engine->storage->print_at( $self->offset + $self->base_size,
32             chr(0) x $leftover, # Zero-fill the data
33             );
34             }
35              
36 8277 100       22785 if ( $self->{key_md5} ) {
37 966         2945 $self->find_md5;
38             }
39              
40 8277         17334 return $self;
41             }
42              
43             sub wipe {
44 14     14 0 32 my $self = shift;
45 14         53 $self->engine->storage->print_at( $self->offset + $self->base_size,
46             chr(0) x ($self->size - $self->base_size), # Zero-fill the data
47             );
48             }
49              
50             sub size {
51 2379     2379 0 3639 my $self = shift;
52 2379 100       5101 unless ( $self->{size} ) {
53 1181         2490 my $e = $self->engine;
54             # Base + numbuckets * bucketsize
55 1181         3243 $self->{size} = $self->base_size + $e->max_buckets * $self->bucket_size;
56             }
57 2379         7578 return $self->{size};
58             }
59              
60 42     42 0 159 sub free_meth { '_add_free_blist_sector' }
61              
62             sub free {
63 42     42 0 143 my $self = shift;
64              
65 42         148 my $e = $self->engine;
66 42         154 foreach my $bucket ( $self->chopped_up ) {
67 68         167 my $rest = $bucket->[-1];
68              
69             # Delete the keysector
70 68         197 my $l = unpack( $StP{$e->byte_size}, substr( $rest, $e->hash_size, $e->byte_size ) );
71 68 50       277 my $s = $e->load_sector( $l ); $s->free if $s;
  68         329  
72              
73             # Delete the HEAD sector
74 68         223 $l = unpack( $StP{$e->byte_size},
75             substr( $rest,
76             $e->hash_size + $e->byte_size,
77             $e->byte_size,
78             ),
79             );
80 68 100       274 $s = $e->load_sector( $l ); $s->free if $s;
  68         412  
81              
82 68         262 foreach my $txn ( 0 .. $e->num_txns - 2 ) {
83 4         10 my $l = unpack( $StP{$e->byte_size},
84             substr( $rest,
85             $e->hash_size + 2 * $e->byte_size + $txn * ($e->byte_size + $STALE_SIZE),
86             $e->byte_size,
87             ),
88             );
89 4 100       12 my $s = $e->load_sector( $l ); $s->free if $s;
  4         21  
90             }
91             }
92              
93 42         359 $self->SUPER::free();
94             }
95              
96             sub bucket_size {
97 37544     37544 0 59241 my $self = shift;
98 37544 100       83408 unless ( $self->{bucket_size} ) {
99 8277         16949 my $e = $self->engine;
100             # Key + head (location) + transactions (location + staleness-counter)
101 8277         20918 my $location_size = $e->byte_size + $e->byte_size + ($e->num_txns - 1) * ($e->byte_size + $STALE_SIZE);
102 8277         22730 $self->{bucket_size} = $e->hash_size + $location_size;
103             }
104 37544         106012 return $self->{bucket_size};
105             }
106              
107             # XXX This is such a poor hack. I need to rethink this code.
108             sub chopped_up {
109 58     58 0 120 my $self = shift;
110              
111 58         194 my $e = $self->engine;
112              
113 58         148 my @buckets;
114 58         252 foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
115 339         903 my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size;
116 339         891 my $md5 = $e->storage->read_at( $spot, $e->hash_size );
117              
118             #XXX If we're chopping, why would we ever have the blank_md5?
119 339 100       948 last if $md5 eq $e->blank_md5;
120              
121 295         855 my $rest = $e->storage->read_at( undef, $self->bucket_size - $e->hash_size );
122 295         1150 push @buckets, [ $spot, $md5 . $rest ];
123             }
124              
125 58         276 return @buckets;
126             }
127              
128             sub write_at_next_open {
129 224     224 0 356 my $self = shift;
130 224         436 my ($entry) = @_;
131              
132             #XXX This is such a hack!
133 224 100       638 $self->{_next_open} = 0 unless exists $self->{_next_open};
134              
135 224         543 my $spot = $self->offset + $self->base_size + $self->{_next_open}++ * $self->bucket_size;
136 224         491 $self->engine->storage->print_at( $spot, $entry );
137              
138 224         604 return $spot;
139             }
140              
141             sub has_md5 {
142 9676     9676 0 16685 my $self = shift;
143 9676 50       23823 unless ( exists $self->{found} ) {
144 0         0 $self->find_md5;
145             }
146 9676         37255 return $self->{found};
147             }
148              
149             sub find_md5 {
150 7764     7764 0 13899 my $self = shift;
151              
152 7764         18401 $self->{found} = undef;
153 7764         17955 $self->{idx} = -1;
154              
155 7764 100       19426 if ( @_ ) {
156 6798         16185 $self->{key_md5} = shift;
157             }
158              
159             # If we don't have an MD5, then what are we supposed to do?
160 7764 50       19464 unless ( exists $self->{key_md5} ) {
161 0         0 DBM::Deep->_throw_error( "Cannot find_md5 without a key_md5 set" );
162             }
163              
164 7764         18190 my $e = $self->engine;
165 7764         24802 foreach my $idx ( 0 .. $e->max_buckets - 1 ) {
166 18226         47842 my $potential = $e->storage->read_at(
167             $self->offset + $self->base_size + $idx * $self->bucket_size, $e->hash_size,
168             );
169              
170 18226 100       56405 if ( $potential eq $e->blank_md5 ) {
171 3209         6334 $self->{idx} = $idx;
172 3209         9153 return;
173             }
174              
175 15017 100       49069 if ( $potential eq $self->{key_md5} ) {
176 4543         9201 $self->{found} = 1;
177 4543         8123 $self->{idx} = $idx;
178 4543         14692 return;
179             }
180             }
181              
182 12         42 return;
183             }
184              
185             sub write_md5 {
186 2915     2915 0 5194 my $self = shift;
187 2915         5600 my ($args) = @_;
188              
189 2915 50       7306 DBM::Deep->_throw_error( "write_md5: no key" ) unless exists $args->{key};
190 2915 50       7189 DBM::Deep->_throw_error( "write_md5: no key_md5" ) unless exists $args->{key_md5};
191 2915 50       6536 DBM::Deep->_throw_error( "write_md5: no value" ) unless exists $args->{value};
192              
193 2915         7846 my $engine = $self->engine;
194              
195 2915 100       10515 $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
196              
197 2915         7104 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
198 2915         11156 $engine->add_entry( $args->{trans_id}, $spot );
199              
200 2915 100       7341 unless ($self->{found}) {
201             my $key_sector = DBM::Deep::Sector::File::Scalar->new({
202             engine => $engine,
203             data => $args->{key},
204 2360         24093 });
205              
206             $engine->storage->print_at( $spot,
207             $args->{key_md5},
208 2356         7520 pack( $StP{$engine->byte_size}, $key_sector->offset ),
209             );
210             }
211              
212 2911         9899 my $loc = $spot
213             + $engine->hash_size
214             + $engine->byte_size;
215              
216 2911 100       7877 if ( $args->{trans_id} ) {
217 73         156 $loc += $engine->byte_size + ($args->{trans_id} - 1) * ( $engine->byte_size + $STALE_SIZE );
218              
219             $engine->storage->print_at( $loc,
220             pack( $StP{$engine->byte_size}, $args->{value}->offset ),
221 73         184 pack( $StP{$STALE_SIZE}, $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
222             );
223             }
224             else {
225             $engine->storage->print_at( $loc,
226 2838         7927 pack( $StP{$engine->byte_size}, $args->{value}->offset ),
227             );
228             }
229             }
230              
231             sub mark_deleted {
232 54     54 0 83 my $self = shift;
233 54         95 my ($args) = @_;
234 54   50     126 $args ||= {};
235              
236 54         100 my $engine = $self->engine;
237              
238 54 100       145 $args->{trans_id} = $engine->trans_id unless exists $args->{trans_id};
239              
240 54         105 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
241 54         185 $engine->add_entry( $args->{trans_id}, $spot );
242              
243 54         109 my $loc = $spot
244             + $engine->hash_size
245             + $engine->byte_size;
246              
247 54 100       117 if ( $args->{trans_id} ) {
248 13         32 $loc += $engine->byte_size + ($args->{trans_id} - 1) * ( $engine->byte_size + $STALE_SIZE );
249              
250             $engine->storage->print_at( $loc,
251             pack( $StP{$engine->byte_size}, 1 ), # 1 is the marker for deleted
252 13         46 pack( $StP{$STALE_SIZE}, $engine->get_txn_staleness_counter( $args->{trans_id} ) ),
253             );
254             }
255             else {
256             $engine->storage->print_at( $loc,
257 41         94 pack( $StP{$engine->byte_size}, 1 ), # 1 is the marker for deleted
258             );
259             }
260             }
261              
262             sub delete_md5 {
263 52     52 0 119 my $self = shift;
264 52         150 my ($args) = @_;
265              
266 52         170 my $engine = $self->engine;
267 52 100       263 return undef unless $self->{found};
268              
269             # Save the location so that we can free the data
270 50         226 my $location = $self->get_data_location_for({
271             allow_head => 0,
272             });
273 50         307 my $key_sector = $self->get_key_for;
274              
275 50         221 my $spot = $self->offset + $self->base_size + $self->{idx} * $self->bucket_size;
276             $engine->storage->print_at( $spot,
277             $engine->storage->read_at(
278             $spot + $self->bucket_size,
279 50         186 $self->bucket_size * ( $engine->max_buckets - $self->{idx} - 1 ),
280             ),
281             chr(0) x $self->bucket_size,
282             );
283              
284 50         316 $key_sector->free;
285              
286 50         234 my $data_sector = $self->engine->load_sector( $location );
287 50         328 my $data = $data_sector->data({ export => 1 });
288 50         314 $data_sector->free;
289              
290 50         394 return $data;
291             }
292              
293             sub get_data_location_for {
294 13676     13676 0 24032 my $self = shift;
295 13676         29564 my ($args) = @_;
296 13676   50     32276 $args ||= {};
297              
298 13676 50       29237 $args->{allow_head} = 0 unless exists $args->{allow_head};
299 13676 100       49336 $args->{trans_id} = $self->engine->trans_id unless exists $args->{trans_id};
300 13676 100       35283 $args->{idx} = $self->{idx} unless exists $args->{idx};
301              
302 13676         31696 my $e = $self->engine;
303              
304             my $spot = $self->offset + $self->base_size
305 13676         29056 + $args->{idx} * $self->bucket_size
306             + $e->hash_size
307             + $e->byte_size;
308              
309 13676 100       32627 if ( $args->{trans_id} ) {
310 1103         2364 $spot += $e->byte_size + ($args->{trans_id} - 1) * ( $e->byte_size + $STALE_SIZE );
311             }
312              
313 13676         35790 my $buffer = $e->storage->read_at(
314             $spot,
315             $e->byte_size + $STALE_SIZE,
316             );
317 13676         38305 my ($loc, $staleness) = unpack( $StP{$e->byte_size} . ' ' . $StP{$STALE_SIZE}, $buffer );
318              
319             # XXX Merge the two if-clauses below
320 13676 100       41051 if ( $args->{trans_id} ) {
321             # We have found an entry that is old, so get rid of it
322 1103 100       3133 if ( $staleness != (my $s = $e->get_txn_staleness_counter( $args->{trans_id} ) ) ) {
323             $e->storage->print_at(
324             $spot,
325 283         775 pack( $StP{$e->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
326             );
327 283         695 $loc = 0;
328             }
329             }
330              
331             # If we're in a transaction and we never wrote to this location, try the
332             # HEAD instead.
333 13676 100 100     39243 if ( $args->{trans_id} && !$loc && $args->{allow_head} ) {
      100        
334             return $self->get_data_location_for({
335             trans_id => 0,
336             allow_head => 1,
337             idx => $args->{idx},
338 908         3798 });
339             }
340              
341 12768 100       56106 return $loc <= 1 ? 0 : $loc;
342             }
343              
344             sub get_data_for {
345 535     535 0 1139 my $self = shift;
346 535         1151 my ($args) = @_;
347 535   100     1476 $args ||= {};
348              
349 535 50       1558 return unless $self->{found};
350             my $location = $self->get_data_location_for({
351             allow_head => $args->{allow_head},
352 535         1996 });
353 535         2350 return $self->engine->load_sector( $location );
354             }
355              
356             sub get_key_for {
357 434     434 0 828 my $self = shift;
358 434         926 my ($idx) = @_;
359 434 100       1224 $idx = $self->{idx} unless defined $idx;
360              
361 434 50       1223 if ( $idx >= $self->engine->max_buckets ) {
362 0         0 DBM::Deep->_throw_error( "get_key_for(): Attempting to retrieve $idx" );
363             }
364              
365 434         1111 my $location = $self->engine->storage->read_at(
366             $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size,
367             $self->engine->byte_size,
368             );
369 434         1508 $location = unpack( $StP{$self->engine->byte_size}, $location );
370 434 50       1240 DBM::Deep->_throw_error( "get_key_for: No location?" ) unless $location;
371              
372 434         1068 return $self->engine->load_sector( $location );
373             }
374              
375             1;
376             __END__