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