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
CommitLineData
065b45be 1package DBM::Deep::Engine::Sector::Reference;
2
9c7d9738 3use 5.006_000;
065b45be 4
5use strict;
6use warnings FATAL => 'all';
7
8use Scalar::Util ();
9
10use DBM::Deep::Null;
11
12use DBM::Deep::Engine::Sector::Data;
13our @ISA = qw( DBM::Deep::Engine::Sector::Data );
14
15sub _init {
16 my $self = shift;
17
18 my $e = $self->engine;
19
20 unless ( $self->offset ) {
9c7d9738 21 $self->{offset} = $e->_request_data_sector( $self->size );
065b45be 22
23 my $class_offset = 0;
9c7d9738 24 my $classname = Scalar::Util::blessed( delete $self->{data} );
065b45be 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
a8d2331c 33 $self->write( 0, $self->type );
34 $self->write( $self->base_size,
9c7d9738 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
065b45be 38 );
39 }
40 else {
a8d2331c 41 $self->{type} = $self->read( 0, $e->SIG_SIZE );
9c7d9738 42 }
065b45be 43
576320ff 44 $self->{staleness} = unpack(
45 $e->StP($DBM::Deep::Engine::STALE_SIZE),
a8d2331c 46 $self->read( $e->SIG_SIZE, $DBM::Deep::Engine::STALE_SIZE ),
576320ff 47 );
48
065b45be 49 return;
50}
51
52sub staleness { $_[0]{staleness} }
53
54sub 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
81sub 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
91sub 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
154sub 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
203sub get_blist_loc {
204 my $self = shift;
205
206 my $e = $self->engine;
a8d2331c 207 return unpack(
208 $e->StP($e->byte_size),
209 $self->read( $self->base_size, $e->byte_size ),
210 );
065b45be 211}
212
213sub 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
a8d2331c 233 $self->write( $self->base_size, pack( $engine->StP($engine->byte_size), $blist->offset ) );
065b45be 234
235 return $blist;
236 }
237
238 my $sector = $engine->_load_sector( $blist_loc )
00d9bd0b 239 or DBM::Deep->_throw_error( "1: Cannot read sector at $blist_loc in get_bucket_list()" );
065b45be 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 )
00d9bd0b 247 or DBM::Deep->_throw_error( "2: Cannot read sector at $blist_loc in get_bucket_list()" );
065b45be 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 }
5c0756fc 331
332#XXX Why is this code here? -RobK, 2008-06-15
065b45be 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 {
a8d2331c 358 $self->write( $self->base_size, pack( $engine->StP($engine->byte_size), $new_index->offset ) );
065b45be 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
377sub get_class_offset {
378 my $self = shift;
379
380 my $e = $self->engine;
381 return unpack(
382 $e->StP($e->byte_size),
a8d2331c 383 $self->read(
384 $self->base_size + 1 * $e->byte_size,
385 $e->byte_size,
065b45be 386 ),
387 );
388}
389
390sub 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
400sub 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
438sub 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
461sub 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
473sub 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
485sub get_refcount {
486 my $self = shift;
487
488 my $e = $self->engine;
489 return unpack(
490 $e->StP($e->byte_size),
a8d2331c 491 $self->read(
492 $self->base_size + 2 * $e->byte_size,
493 $e->byte_size,
065b45be 494 ),
495 );
496}
497
498sub write_refcount {
499 my $self = shift;
500 my ($num) = @_;
501
502 my $e = $self->engine;
a8d2331c 503 $self->write(
504 $self->base_size + 2 * $e->byte_size,
065b45be 505 pack( $e->StP($e->byte_size), $num ),
506 );
507}
508
509
5101;
511__END__
512