The header now has its own sector. A lot needs to be moved over to it, but it's there.
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine / Sector / Reference.pm
1 package DBM::Deep::Engine::Sector::Reference;
2
3 use 5.006_000;
4
5 use strict;
6 use warnings FATAL => 'all';
7
8 use Scalar::Util ();
9
10 use DBM::Deep::Null;
11
12 use DBM::Deep::Engine::Sector::Data;
13 our @ISA = qw( DBM::Deep::Engine::Sector::Data );
14
15 sub _init {
16     my $self = shift;
17
18     my $e = $self->engine;
19
20     unless ( $self->offset ) {
21         $self->{offset} = $e->_request_data_sector( $self->size );
22
23         my $class_offset = 0;
24         my $classname = Scalar::Util::blessed( delete $self->{data} );
25         if ( defined $classname ) {
26             my $class_sector = DBM::Deep::Engine::Sector::Scalar->new({
27                 engine => $e,
28                 data   => $classname,
29             });
30             $class_offset = $class_sector->offset;
31         }
32
33         $self->write( 0, $self->type );
34         $self->write( $self->base_size, 
35             pack( $e->StP($e->byte_size), 0 )             # Index/BList loc
36           . pack( $e->StP($e->byte_size), $class_offset ) # Classname loc
37           . pack( $e->StP($e->byte_size), 1 )             # Initial refcount
38         );
39     }
40     else {
41         $self->{type} = $self->read( 0, $e->SIG_SIZE );
42     }
43
44     $self->{staleness} = unpack(
45         $e->StP($DBM::Deep::Engine::STALE_SIZE),
46         $self->read( $e->SIG_SIZE, $DBM::Deep::Engine::STALE_SIZE ),
47     );
48
49     return;
50 }
51
52 sub staleness { $_[0]{staleness} }
53
54 sub get_data_location_for {
55     my $self = shift;
56     my ($args) = @_;
57
58     # Assume that the head is not allowed unless otherwise specified.
59     $args->{allow_head} = 0 unless exists $args->{allow_head};
60
61     # Assume we don't create a new blist location unless otherwise specified.
62     $args->{create} = 0 unless exists $args->{create};
63
64     my $blist = $self->get_bucket_list({
65         key_md5 => $args->{key_md5},
66         key => $args->{key},
67         create  => $args->{create},
68     });
69     return unless $blist && $blist->{found};
70
71     # At this point, $blist knows where the md5 is. What it -doesn't- know yet
72     # is whether or not this transaction has this key. That's part of the next
73     # function call.
74     my $location = $blist->get_data_location_for({
75         allow_head => $args->{allow_head},
76     }) or return;
77
78     return $location;
79 }
80
81 sub get_data_for {
82     my $self = shift;
83     my ($args) = @_;
84
85     my $location = $self->get_data_location_for( $args )
86         or return;
87
88     return $self->engine->_load_sector( $location );
89 }
90
91 sub write_data {
92     my $self = shift;
93     my ($args) = @_;
94
95     my $blist = $self->get_bucket_list({
96         key_md5 => $args->{key_md5},
97         key => $args->{key},
98         create  => 1,
99     }) or DBM::Deep->_throw_error( "How did write_data fail (no blist)?!" );
100
101     # Handle any transactional bookkeeping.
102     if ( $self->engine->trans_id ) {
103         if ( ! $blist->has_md5 ) {
104             $blist->mark_deleted({
105                 trans_id => 0,
106             });
107         }
108     }
109     else {
110         my @trans_ids = $self->engine->get_running_txn_ids;
111         if ( $blist->has_md5 ) {
112             if ( @trans_ids ) {
113                 my $old_value = $blist->get_data_for;
114                 foreach my $other_trans_id ( @trans_ids ) {
115                     next if $blist->get_data_location_for({
116                         trans_id   => $other_trans_id,
117                         allow_head => 0,
118                     });
119                     $blist->write_md5({
120                         trans_id => $other_trans_id,
121                         key      => $args->{key},
122                         key_md5  => $args->{key_md5},
123                         value    => $old_value->clone,
124                     });
125                 }
126             }
127         }
128         else {
129             if ( @trans_ids ) {
130                 foreach my $other_trans_id ( @trans_ids ) {
131                     #XXX This doesn't seem to possible to ever happen . . .
132                     next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
133                     $blist->mark_deleted({
134                         trans_id => $other_trans_id,
135                     });
136                 }
137             }
138         }
139     }
140
141     #XXX Is this safe to do transactionally?
142     # Free the place we're about to write to.
143     if ( $blist->get_data_location_for({ allow_head => 0 }) ) {
144         $blist->get_data_for({ allow_head => 0 })->free;
145     }
146
147     $blist->write_md5({
148         key      => $args->{key},
149         key_md5  => $args->{key_md5},
150         value    => $args->{value},
151     });
152 }
153
154 sub delete_key {
155     my $self = shift;
156     my ($args) = @_;
157
158     # XXX What should happen if this fails?
159     my $blist = $self->get_bucket_list({
160         key_md5 => $args->{key_md5},
161     }) or DBM::Deep->_throw_error( "How did delete_key fail (no blist)?!" );
162
163     # Save the location so that we can free the data
164     my $location = $blist->get_data_location_for({
165         allow_head => 0,
166     });
167     my $old_value = $location && $self->engine->_load_sector( $location );
168
169     my @trans_ids = $self->engine->get_running_txn_ids;
170
171     # If we're the HEAD and there are running txns, then we need to clone this value to the other
172     # transactions to preserve Isolation.
173     if ( $self->engine->trans_id == 0 ) {
174         if ( @trans_ids ) {
175             foreach my $other_trans_id ( @trans_ids ) {
176                 next if $blist->get_data_location_for({ trans_id => $other_trans_id, allow_head => 0 });
177                 $blist->write_md5({
178                     trans_id => $other_trans_id,
179                     key      => $args->{key},
180                     key_md5  => $args->{key_md5},
181                     value    => $old_value->clone,
182                 });
183             }
184         }
185     }
186
187     my $data;
188     if ( @trans_ids ) {
189         $blist->mark_deleted( $args );
190
191         if ( $old_value ) {
192             $data = $old_value->data({ export => 1 });
193             $old_value->free;
194         }
195     }
196     else {
197         $data = $blist->delete_md5( $args );
198     }
199
200     return $data;
201 }
202
203 sub get_blist_loc {
204     my $self = shift;
205
206     my $e = $self->engine;
207     return unpack(
208         $e->StP($e->byte_size),
209         $self->read( $self->base_size, $e->byte_size ),
210     );
211 }
212
213 sub get_bucket_list {
214     my $self = shift;
215     my ($args) = @_;
216     $args ||= {};
217
218     # XXX Add in check here for recycling?
219
220     my $engine = $self->engine;
221
222     my $blist_loc = $self->get_blist_loc;
223
224     # There's no index or blist yet
225     unless ( $blist_loc ) {
226         return unless $args->{create};
227
228         my $blist = DBM::Deep::Engine::Sector::BucketList->new({
229             engine  => $engine,
230             key_md5 => $args->{key_md5},
231         });
232
233         $self->write( $self->base_size, pack( $engine->StP($engine->byte_size), $blist->offset ) );
234
235         return $blist;
236     }
237
238     my $sector = $engine->_load_sector( $blist_loc )
239         or DBM::Deep->_throw_error( "1: Cannot read sector at $blist_loc in get_bucket_list()" );
240     my $i = 0;
241     my $last_sector = undef;
242     while ( $sector->isa( 'DBM::Deep::Engine::Sector::Index' ) ) {
243         $blist_loc = $sector->get_entry( ord( substr( $args->{key_md5}, $i++, 1 ) ) );
244         $last_sector = $sector;
245         if ( $blist_loc ) {
246             $sector = $engine->_load_sector( $blist_loc )
247                 or DBM::Deep->_throw_error( "2: Cannot read sector at $blist_loc in get_bucket_list()" );
248         }
249         else {
250             $sector = undef;
251             last;
252         }
253     }
254
255     # This means we went through the Index sector(s) and found an empty slot
256     unless ( $sector ) {
257         return unless $args->{create};
258
259         DBM::Deep->_throw_error( "No last_sector when attempting to build a new entry" )
260             unless $last_sector;
261
262         my $blist = DBM::Deep::Engine::Sector::BucketList->new({
263             engine  => $engine,
264             key_md5 => $args->{key_md5},
265         });
266
267         $last_sector->set_entry( ord( substr( $args->{key_md5}, $i - 1, 1 ) ) => $blist->offset );
268
269         return $blist;
270     }
271
272     $sector->find_md5( $args->{key_md5} );
273
274     # See whether or not we need to reindex the bucketlist
275     # Yes, the double-braces are there for a reason. if() doesn't create a redo-able block,
276     # so we have to create a bare block within the if() for redo-purposes. Patch and idea
277     # submitted by sprout@cpan.org. -RobK, 2008-01-09
278     if ( !$sector->has_md5 && $args->{create} && $sector->{idx} == -1 ) {{
279         my $redo;
280
281         my $new_index = DBM::Deep::Engine::Sector::Index->new({
282             engine => $engine,
283         });
284
285         my %blist_cache;
286         #XXX q.v. the comments for this function.
287         foreach my $entry ( $sector->chopped_up ) {
288             my ($spot, $md5) = @{$entry};
289             my $idx = ord( substr( $md5, $i, 1 ) );
290
291             # XXX This is inefficient
292             my $blist = $blist_cache{$idx}
293                 ||= DBM::Deep::Engine::Sector::BucketList->new({
294                     engine => $engine,
295                 });
296
297             $new_index->set_entry( $idx => $blist->offset );
298
299             my $new_spot = $blist->write_at_next_open( $md5 );
300             $engine->reindex_entry( $spot => $new_spot );
301         }
302
303         # Handle the new item separately.
304         {
305             my $idx = ord( substr( $args->{key_md5}, $i, 1 ) );
306
307             # If all the previous blist's items have been thrown into one
308             # blist and the new item belongs in there too, we need
309             # another index.
310             if ( keys %blist_cache == 1 and each %blist_cache == $idx ) {
311                 ++$i, ++$redo;
312             } else {
313                 my $blist = $blist_cache{$idx}
314                     ||= DBM::Deep::Engine::Sector::BucketList->new({
315                         engine => $engine,
316                     });
317     
318                 $new_index->set_entry( $idx => $blist->offset );
319     
320                 #XXX THIS IS HACKY!
321                 $blist->find_md5( $args->{key_md5} );
322                 $blist->write_md5({
323                     key     => $args->{key},
324                     key_md5 => $args->{key_md5},
325                     value   => DBM::Deep::Engine::Sector::Null->new({
326                         engine => $engine,
327                         data   => undef,
328                     }),
329                 });
330             }
331
332 #XXX Why is this code here? -RobK, 2008-06-15
333 #            my $blist = $blist_cache{$idx}
334 #                ||= DBM::Deep::Engine::Sector::BucketList->new({
335 #                    engine => $engine,
336 #                });
337 #
338 #            $new_index->set_entry( $idx => $blist->offset );
339 #
340 #            #XXX THIS IS HACKY!
341 #            $blist->find_md5( $args->{key_md5} );
342 #            $blist->write_md5({
343 #                key     => $args->{key},
344 #                key_md5 => $args->{key_md5},
345 #                value   => DBM::Deep::Engine::Sector::Null->new({
346 #                    engine => $engine,
347 #                    data   => undef,
348 #                }),
349 #            });
350         }
351
352         if ( $last_sector ) {
353             $last_sector->set_entry(
354                 ord( substr( $args->{key_md5}, $i - 1, 1 ) ),
355                 $new_index->offset,
356             );
357         } else {
358             $self->write( $self->base_size, pack( $engine->StP($engine->byte_size), $new_index->offset ) );
359         }
360
361         $sector->clear;
362         $sector->free;
363
364         if ( $redo ) {
365             (undef, $sector) = %blist_cache;
366             $last_sector = $new_index;
367             redo;
368         }
369
370         $sector = $blist_cache{ ord( substr( $args->{key_md5}, $i, 1 ) ) };
371         $sector->find_md5( $args->{key_md5} );
372     }}
373
374     return $sector;
375 }
376
377 sub get_class_offset {
378     my $self = shift;
379
380     my $e = $self->engine;
381     return unpack(
382         $e->StP($e->byte_size),
383         $self->read(
384             $self->base_size + 1 * $e->byte_size,
385             $e->byte_size,
386         ),
387     );
388 }
389
390 sub get_classname {
391     my $self = shift;
392
393     my $class_offset = $self->get_class_offset;
394
395     return unless $class_offset;
396
397     return $self->engine->_load_sector( $class_offset )->data;
398 }
399
400 sub data {
401     my $self = shift;
402     my ($args) = @_;
403     $args ||= {};
404
405     my $obj;
406     unless ( $obj = $self->engine->cache->{ $self->offset } ) {
407         $obj = DBM::Deep->new({
408             type        => $self->type,
409             base_offset => $self->offset,
410             staleness   => $self->staleness,
411             storage     => $self->engine->storage,
412             engine      => $self->engine,
413         });
414
415         if ( $self->engine->storage->{autobless} ) {
416             my $classname = $self->get_classname;
417             if ( defined $classname ) {
418                 bless $obj, $classname;
419             }
420         }
421
422         $self->engine->cache->{$self->offset} = $obj;
423     }
424
425     # We're not exporting, so just return.
426     unless ( $args->{export} ) {
427         return $obj;
428     }
429
430     # We shouldn't export if this is still referred to.
431     if ( $self->get_refcount > 1 ) {
432         return $obj;
433     }
434
435     return $obj->export;
436 }
437
438 sub free {
439     my $self = shift;
440
441     # We're not ready to be removed yet.
442     if ( $self->decrement_refcount > 0 ) {
443         return;
444     }
445
446     # Rebless the object into DBM::Deep::Null.
447     eval { %{ $self->engine->cache->{ $self->offset } } = (); };
448     eval { @{ $self->engine->cache->{ $self->offset } } = (); };
449     bless $self->engine->cache->{ $self->offset }, 'DBM::Deep::Null';
450     delete $self->engine->cache->{ $self->offset };
451
452     my $blist_loc = $self->get_blist_loc;
453     $self->engine->_load_sector( $blist_loc )->free if $blist_loc;
454
455     my $class_loc = $self->get_class_offset;
456     $self->engine->_load_sector( $class_loc )->free if $class_loc;
457
458     $self->SUPER::free();
459 }
460
461 sub increment_refcount {
462     my $self = shift;
463
464     my $refcount = $self->get_refcount;
465
466     $refcount++;
467
468     $self->write_refcount( $refcount );
469
470     return $refcount;
471 }
472
473 sub decrement_refcount {
474     my $self = shift;
475
476     my $refcount = $self->get_refcount;
477
478     $refcount--;
479
480     $self->write_refcount( $refcount );
481
482     return $refcount;
483 }
484
485 sub get_refcount {
486     my $self = shift;
487
488     my $e = $self->engine;
489     return unpack(
490         $e->StP($e->byte_size),
491         $self->read(
492             $self->base_size + 2 * $e->byte_size,
493             $e->byte_size,
494         ),
495     );
496 }
497
498 sub write_refcount {
499     my $self = shift;
500     my ($num) = @_;
501
502     my $e = $self->engine;
503     $self->write(
504         $self->base_size + 2 * $e->byte_size,
505         pack( $e->StP($e->byte_size), $num ),
506     );
507 }
508
509
510 1;
511 __END__
512