Removed extraneous slashes from POD
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine / File.pm
CommitLineData
51e9eee3 1package DBM::Deep::Engine::File;
a20d9a3f 2
2120a181 3use 5.006_000;
460b1067 4
a20d9a3f 5use strict;
065b45be 6use warnings FATAL => 'all';
a20d9a3f 7
51e9eee3 8use base qw( DBM::Deep::Engine );
9
75a6a379 10use Scalar::Util ();
d6d8e27e 11
f0276afb 12use DBM::Deep::Null ();
2c70efe1 13use DBM::Deep::Sector::File ();
14use DBM::Deep::Storage::File ();
f0276afb 15
d6ecf579 16sub sector_type { 'DBM::Deep::Sector::File' }
19b913ce 17sub iterator_class { 'DBM::Deep::Iterator::File' }
d6ecf579 18
9c87a079 19my $STALE_SIZE = 2;
8db25060 20
18bc2fa6 21# Setup file and tag signatures. These should never change.
22sub SIG_FILE () { 'DPDB' }
23sub SIG_HEADER () { 'h' }
18bc2fa6 24sub SIG_NULL () { 'N' }
25sub SIG_DATA () { 'D' }
26sub SIG_INDEX () { 'I' }
27sub SIG_BLIST () { 'B' }
28sub SIG_FREE () { 'F' }
29sub SIG_SIZE () { 1 }
a4d36ff6 30# SIG_HASH and SIG_ARRAY are defined in DBM::Deep::Engine
18bc2fa6 31
2120a181 32# Please refer to the pack() documentation for further information
33my %StP = (
e9b0b5f0 34 1 => 'C', # Unsigned char value (no order needed as it's just one byte)
2120a181 35 2 => 'n', # Unsigned short in "network" (big-endian) order
36 4 => 'N', # Unsigned long in "network" (big-endian) order
37 8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
38);
00d9bd0b 39
ae6c15b8 40=head1 NAME
41
64a531e5 42DBM::Deep::Engine::File
ae6c15b8 43
44=head1 PURPOSE
45
1c62d370 46This is the engine for use with L<DBM::Deep::Storage::File>.
ae6c15b8 47
48=head1 EXTERNAL METHODS
49
ae6c15b8 50=head2 new()
51
52This takes a set of args. These args are described in the documentation for
53L<DBM::Deep/new>.
54
55=cut
56
612969fb 57sub new {
58 my $class = shift;
59 my ($args) = @_;
60
d426259c 61 $args->{storage} = DBM::Deep::Storage::File->new( $args )
f1879fdc 62 unless exists $args->{storage};
63
612969fb 64 my $self = bless {
2120a181 65 byte_size => 4,
66
67 digest => undef,
68 hash_size => 16, # In bytes
69 hash_chars => 256, # Number of chars the algorithm uses per byte
612969fb 70 max_buckets => 16,
e9b0b5f0 71 num_txns => 1, # The HEAD
2120a181 72 trans_id => 0, # Default to the HEAD
460b1067 73
e9b0b5f0 74 data_sector_size => 64, # Size in bytes of each data sector
75
2120a181 76 entries => {}, # This is the list of entries for transactions
83371fe3 77 storage => undef,
612969fb 78 }, $class;
79
e9b0b5f0 80 # Never allow byte_size to be set directly.
81 delete $args->{byte_size};
e0098e7f 82 if ( defined $args->{pack_size} ) {
83 if ( lc $args->{pack_size} eq 'small' ) {
2120a181 84 $args->{byte_size} = 2;
e0098e7f 85 }
86 elsif ( lc $args->{pack_size} eq 'medium' ) {
2120a181 87 $args->{byte_size} = 4;
e0098e7f 88 }
89 elsif ( lc $args->{pack_size} eq 'large' ) {
2120a181 90 $args->{byte_size} = 8;
e0098e7f 91 }
92 else {
2120a181 93 DBM::Deep->_throw_error( "Unknown pack_size value: '$args->{pack_size}'" );
e0098e7f 94 }
95 }
96
fde3db1a 97 # Grab the parameters we want to use
98 foreach my $param ( keys %$self ) {
99 next unless exists $args->{$param};
3e9498a1 100 $self->{$param} = $args->{$param};
fde3db1a 101 }
102
e9b0b5f0 103 my %validations = (
104 max_buckets => { floor => 16, ceil => 256 },
105 num_txns => { floor => 1, ceil => 255 },
106 data_sector_size => { floor => 32, ceil => 256 },
107 );
108
109 while ( my ($attr, $c) = each %validations ) {
110 if ( !defined $self->{$attr}
111 || !length $self->{$attr}
112 || $self->{$attr} =~ /\D/
113 || $self->{$attr} < $c->{floor}
114 ) {
115 $self->{$attr} = '(undef)' if !defined $self->{$attr};
116 warn "Floor of $attr is $c->{floor}. Setting it to $c->{floor} from '$self->{$attr}'\n";
117 $self->{$attr} = $c->{floor};
118 }
119 elsif ( $self->{$attr} > $c->{ceil} ) {
120 warn "Ceiling of $attr is $c->{ceil}. Setting it to $c->{ceil} from '$self->{$attr}'\n";
121 $self->{$attr} = $c->{ceil};
122 }
e0098e7f 123 }
124
2120a181 125 if ( !$self->{digest} ) {
126 require Digest::MD5;
127 $self->{digest} = \&Digest::MD5::md5;
128 }
129
260a80b4 130 return $self;
131}
132
2120a181 133sub read_value {
c3aafc14 134 my $self = shift;
2120a181 135 my ($obj, $key) = @_;
136
137 # This will be a Reference sector
d6ecf579 138 my $sector = $self->load_sector( $obj->_base_offset )
2120a181 139 or return;
140
141 if ( $sector->staleness != $obj->_staleness ) {
142 return;
143 }
144
145 my $key_md5 = $self->_apply_digest( $key );
146
147 my $value_sector = $sector->get_data_for({
148 key_md5 => $key_md5,
149 allow_head => 1,
150 });
151
152 unless ( $value_sector ) {
2c70efe1 153 $value_sector = DBM::Deep::Sector::File::Null->new({
2120a181 154 engine => $self,
155 data => undef,
156 });
157
158 $sector->write_data({
159 key_md5 => $key_md5,
160 key => $key,
161 value => $value_sector,
162 });
163 }
164
165 return $value_sector->data;
c3aafc14 166}
167
2120a181 168sub get_classname {
260a80b4 169 my $self = shift;
2120a181 170 my ($obj) = @_;
260a80b4 171
2120a181 172 # This will be a Reference sector
d6ecf579 173 my $sector = $self->load_sector( $obj->_base_offset )
2120a181 174 or DBM::Deep->_throw_error( "How did get_classname fail (no sector for '$obj')?!" );
612969fb 175
2120a181 176 if ( $sector->staleness != $obj->_staleness ) {
177 return;
178 }
ea2f6d67 179
2120a181 180 return $sector->get_classname;
1bf65be7 181}
182
1cff45d7 183sub make_reference {
184 my $self = shift;
185 my ($obj, $old_key, $new_key) = @_;
186
187 # This will be a Reference sector
d6ecf579 188 my $sector = $self->load_sector( $obj->_base_offset )
ae6c15b8 189 or DBM::Deep->_throw_error( "How did make_reference fail (no sector for '$obj')?!" );
1cff45d7 190
191 if ( $sector->staleness != $obj->_staleness ) {
192 return;
193 }
194
195 my $old_md5 = $self->_apply_digest( $old_key );
196
197 my $value_sector = $sector->get_data_for({
198 key_md5 => $old_md5,
199 allow_head => 1,
200 });
201
202 unless ( $value_sector ) {
2c70efe1 203 $value_sector = DBM::Deep::Sector::File::Null->new({
1cff45d7 204 engine => $self,
205 data => undef,
206 });
207
208 $sector->write_data({
209 key_md5 => $old_md5,
210 key => $old_key,
211 value => $value_sector,
212 });
213 }
214
2c70efe1 215 if ( $value_sector->isa( 'DBM::Deep::Sector::File::Reference' ) ) {
1cff45d7 216 $sector->write_data({
217 key => $new_key,
218 key_md5 => $self->_apply_digest( $new_key ),
219 value => $value_sector,
220 });
221 $value_sector->increment_refcount;
222 }
223 else {
224 $sector->write_data({
225 key => $new_key,
226 key_md5 => $self->_apply_digest( $new_key ),
227 value => $value_sector->clone,
228 });
229 }
ae6c15b8 230
231 return;
1cff45d7 232}
233
a4d36ff6 234# exists returns '', not undefined.
2120a181 235sub key_exists {
0d0f3d5d 236 my $self = shift;
2120a181 237 my ($obj, $key) = @_;
0d0f3d5d 238
2120a181 239 # This will be a Reference sector
d6ecf579 240 my $sector = $self->load_sector( $obj->_base_offset )
2120a181 241 or return '';
0d0f3d5d 242
2120a181 243 if ( $sector->staleness != $obj->_staleness ) {
244 return '';
245 }
0d0f3d5d 246
2120a181 247 my $data = $sector->get_data_for({
248 key_md5 => $self->_apply_digest( $key ),
249 allow_head => 1,
250 });
20b7f047 251
2120a181 252 # exists() returns 1 or '' for true/false.
253 return $data ? 1 : '';
0d0f3d5d 254}
255
2120a181 256sub delete_key {
e064ccd1 257 my $self = shift;
2120a181 258 my ($obj, $key) = @_;
e064ccd1 259
d6ecf579 260 my $sector = $self->load_sector( $obj->_base_offset )
2120a181 261 or return;
460b1067 262
2120a181 263 if ( $sector->staleness != $obj->_staleness ) {
264 return;
265 }
266
267 return $sector->delete_key({
268 key_md5 => $self->_apply_digest( $key ),
269 allow_head => 0,
270 });
271}
272
273sub write_value {
274 my $self = shift;
275 my ($obj, $key, $value) = @_;
276
277 my $r = Scalar::Util::reftype( $value ) || '';
278 {
279 last if $r eq '';
280 last if $r eq 'HASH';
281 last if $r eq 'ARRAY';
e064ccd1 282
2120a181 283 DBM::Deep->_throw_error(
284 "Storage of references of type '$r' is not supported."
285 );
460b1067 286 }
260a80b4 287
1cff45d7 288 # This will be a Reference sector
d6ecf579 289 my $sector = $self->load_sector( $obj->_base_offset )
9c87a079 290 or DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
1cff45d7 291
292 if ( $sector->staleness != $obj->_staleness ) {
9c87a079 293 DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
1cff45d7 294 }
295
2120a181 296 my ($class, $type);
297 if ( !defined $value ) {
2c70efe1 298 $class = 'DBM::Deep::Sector::File::Null';
2120a181 299 }
300 elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
75a6a379 301 my $tmpvar;
302 if ( $r eq 'ARRAY' ) {
303 $tmpvar = tied @$value;
304 } elsif ( $r eq 'HASH' ) {
305 $tmpvar = tied %$value;
306 }
5ef7542f 307
edd45134 308 if ( $tmpvar ) {
309 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
310
311 unless ( $is_dbm_deep ) {
312 DBM::Deep->_throw_error( "Cannot store something that is tied." );
313 }
314
d6d8e27e 315 unless ( $tmpvar->_engine->storage == $self->storage ) {
75a6a379 316 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
5ef7542f 317 }
5ef7542f 318
2c70efe1 319 # First, verify if we're storing the same thing to this spot. If we
320 # are, then this should be a no-op. -EJS, 2008-05-19
d6d8e27e 321 my $loc = $sector->get_data_location_for({
322 key_md5 => $self->_apply_digest( $key ),
323 allow_head => 1,
324 });
325
326 if ( defined($loc) && $loc == $tmpvar->_base_offset ) {
1cff45d7 327 return 1;
328 }
329
edd45134 330 #XXX Can this use $loc?
d6ecf579 331 my $value_sector = $self->load_sector( $tmpvar->_base_offset );
d6d8e27e 332 $sector->write_data({
333 key => $key,
334 key_md5 => $self->_apply_digest( $key ),
335 value => $value_sector,
336 });
337 $value_sector->increment_refcount;
338
339 return 1;
1cff45d7 340 }
edd45134 341
2c70efe1 342 $class = 'DBM::Deep::Sector::File::Reference';
2120a181 343 $type = substr( $r, 0, 1 );
344 }
345 else {
1cff45d7 346 if ( tied($value) ) {
347 DBM::Deep->_throw_error( "Cannot store something that is tied." );
348 }
2c70efe1 349 $class = 'DBM::Deep::Sector::File::Scalar';
460b1067 350 }
9b2370e0 351
2c70efe1 352 # Create this after loading the reference sector in case something bad
353 # happens. This way, we won't allocate value sector(s) needlessly.
2120a181 354 my $value_sector = $class->new({
355 engine => $self,
356 data => $value,
357 type => $type,
358 });
75a6a379 359
2120a181 360 $sector->write_data({
361 key => $key,
362 key_md5 => $self->_apply_digest( $key ),
363 value => $value_sector,
364 });
365
2c70efe1 366 # This code is to make sure we write all the values in the $value to the
367 # disk and to make sure all changes to $value after the assignment are
368 # reflected on disk. This may be counter-intuitive at first, but it is
369 # correct dwimmery.
370 # NOTE - simply tying $value won't perform a STORE on each value. Hence,
371 # the copy to a temp value.
2120a181 372 if ( $r eq 'ARRAY' ) {
373 my @temp = @$value;
374 tie @$value, 'DBM::Deep', {
375 base_offset => $value_sector->offset,
376 staleness => $value_sector->staleness,
377 storage => $self->storage,
378 engine => $self,
379 };
380 @$value = @temp;
381 bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
e064ccd1 382 }
2120a181 383 elsif ( $r eq 'HASH' ) {
384 my %temp = %$value;
385 tie %$value, 'DBM::Deep', {
386 base_offset => $value_sector->offset,
387 staleness => $value_sector->staleness,
388 storage => $self->storage,
389 engine => $self,
390 };
e064ccd1 391
2120a181 392 %$value = %temp;
393 bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
394 }
460b1067 395
2120a181 396 return 1;
e064ccd1 397}
398
f4d0ac97 399sub setup {
2120a181 400 my $self = shift;
401 my ($obj) = @_;
359a01ac 402
9c87a079 403 # We're opening the file.
404 unless ( $obj->_base_offset ) {
405 my $bytes_read = $self->_read_file_header;
118ba343 406
9c87a079 407 # Creating a new file
408 unless ( $bytes_read ) {
409 $self->_write_file_header;
118ba343 410
9c87a079 411 # 1) Create Array/Hash entry
2c70efe1 412 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
9c87a079 413 engine => $self,
414 type => $obj->_type,
415 });
416 $obj->{base_offset} = $initial_reference->offset;
417 $obj->{staleness} = $initial_reference->staleness;
118ba343 418
9c87a079 419 $self->storage->flush;
118ba343 420 }
9c87a079 421 # Reading from an existing file
422 else {
423 $obj->{base_offset} = $bytes_read;
2c70efe1 424 my $initial_reference = DBM::Deep::Sector::File::Reference->new({
9c87a079 425 engine => $self,
426 offset => $obj->_base_offset,
427 });
428 unless ( $initial_reference ) {
429 DBM::Deep->_throw_error("Corrupted file, no master index record");
430 }
2120a181 431
9c87a079 432 unless ($obj->_type eq $initial_reference->type) {
433 DBM::Deep->_throw_error("File type mismatch");
434 }
f1879fdc 435
9c87a079 436 $obj->{staleness} = $initial_reference->staleness;
437 }
118ba343 438 }
2120a181 439
a5bdb1ac 440 $self->storage->set_inode;
441
2120a181 442 return 1;
443}
444
445sub begin_work {
446 my $self = shift;
447 my ($obj) = @_;
448
449 if ( $self->trans_id ) {
450 DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
451 }
452
453 my @slots = $self->read_txn_slots;
e9b0b5f0 454 my $found;
455 for my $i ( 0 .. $#slots ) {
2120a181 456 next if $slots[$i];
e9b0b5f0 457
2120a181 458 $slots[$i] = 1;
e9b0b5f0 459 $self->set_trans_id( $i + 1 );
460 $found = 1;
2120a181 461 last;
462 }
e9b0b5f0 463 unless ( $found ) {
464 DBM::Deep->_throw_error( "Cannot allocate transaction ID" );
465 }
2120a181 466 $self->write_txn_slots( @slots );
467
468 if ( !$self->trans_id ) {
469 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
470 }
471
472 return;
473}
474
475sub rollback {
476 my $self = shift;
477 my ($obj) = @_;
478
479 if ( !$self->trans_id ) {
480 DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
481 }
482
9c87a079 483 # Each entry is the file location for a bucket that has a modification for
484 # this transaction. The entries need to be expunged.
485 foreach my $entry (@{ $self->get_entries } ) {
486 # Remove the entry here
487 my $read_loc = $entry
488 + $self->hash_size
489 + $self->byte_size
490 + $self->byte_size
491 + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
492
493 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
494 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
495 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
496
497 if ( $data_loc > 1 ) {
d6ecf579 498 $self->load_sector( $data_loc )->free;
9c87a079 499 }
db2eb673 500 }
501
2120a181 502 $self->clear_entries;
70b55428 503
2120a181 504 my @slots = $self->read_txn_slots;
e9b0b5f0 505 $slots[$self->trans_id-1] = 0;
2120a181 506 $self->write_txn_slots( @slots );
507 $self->inc_txn_staleness_counter( $self->trans_id );
508 $self->set_trans_id( 0 );
6fde4ed2 509
70b55428 510 return 1;
511}
512
2120a181 513sub commit {
16d1ad9b 514 my $self = shift;
2120a181 515 my ($obj) = @_;
516
517 if ( !$self->trans_id ) {
518 DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
519 }
520
9c87a079 521 foreach my $entry (@{ $self->get_entries } ) {
522 # Overwrite the entry in head with the entry in trans_id
523 my $base = $entry
524 + $self->hash_size
525 + $self->byte_size;
526
527 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
528 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
529
530 my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
531 my $trans_loc = $self->storage->read_at(
532 $spot, $self->byte_size,
533 );
534
535 $self->storage->print_at( $base, $trans_loc );
536 $self->storage->print_at(
537 $spot,
538 pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
539 );
540
541 if ( $head_loc > 1 ) {
d6ecf579 542 $self->load_sector( $head_loc )->free;
9c87a079 543 }
db2eb673 544 }
545
2120a181 546 $self->clear_entries;
547
548 my @slots = $self->read_txn_slots;
e9b0b5f0 549 $slots[$self->trans_id-1] = 0;
2120a181 550 $self->write_txn_slots( @slots );
551 $self->inc_txn_staleness_counter( $self->trans_id );
552 $self->set_trans_id( 0 );
553
554 return 1;
16d1ad9b 555}
556
ae6c15b8 557=head1 INTERNAL METHODS
558
51e9eee3 559The following methods are internal-use-only to DBM::Deep::Engine::File.
ae6c15b8 560
561=cut
562
563=head2 read_txn_slots()
564
565This takes no arguments.
566
567This will return an array with a 1 or 0 in each slot. Each spot represents one
568available transaction. If the slot is 1, that transaction is taken. If it is 0,
569the transaction is available.
570
571=cut
572
2120a181 573sub read_txn_slots {
d4b1166e 574 my $self = shift;
9c87a079 575 my $bl = $self->txn_bitfield_len;
576 my $num_bits = $bl * 8;
577 return split '', unpack( 'b'.$num_bits,
578 $self->storage->read_at(
579 $self->trans_loc, $bl,
580 )
581 );
2120a181 582}
20f7b20c 583
ae6c15b8 584=head2 write_txn_slots( @slots )
585
586This takes an array of 1's and 0's. This array represents the transaction slots
587returned by L</read_txn_slots()>. In other words, the following is true:
588
589 @x = read_txn_slots( write_txn_slots( @x ) );
590
591(With the obviously missing object referents added back in.)
592
593=cut
594
2120a181 595sub write_txn_slots {
596 my $self = shift;
9c87a079 597 my $num_bits = $self->txn_bitfield_len * 8;
598 $self->storage->print_at( $self->trans_loc,
599 pack( 'b'.$num_bits, join('', @_) ),
600 );
2120a181 601}
602
ae6c15b8 603=head2 get_running_txn_ids()
604
605This takes no arguments.
606
607This will return an array of taken transaction IDs. This wraps L</read_txn_slots()>.
608
609=cut
610
2120a181 611sub get_running_txn_ids {
612 my $self = shift;
613 my @transactions = $self->read_txn_slots;
ae6c15b8 614 my @trans_ids = map { $_+1 } grep { $transactions[$_] } 0 .. $#transactions;
2120a181 615}
616
ae6c15b8 617=head2 get_txn_staleness_counter( $trans_id )
618
619This will return the staleness counter for the given transaction ID. Please see
620L</TRANSACTION STALENESS> for more information.
621
622=cut
623
2120a181 624sub get_txn_staleness_counter {
625 my $self = shift;
9c87a079 626 my ($trans_id) = @_;
627
628 # Hardcode staleness of 0 for the HEAD
629 return 0 unless $trans_id;
630
631 return unpack( $StP{$STALE_SIZE},
632 $self->storage->read_at(
633 $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
634 $STALE_SIZE,
635 )
636 );
d4b1166e 637}
638
ae6c15b8 639=head2 inc_txn_staleness_counter( $trans_id )
640
641This will increment the staleness counter for the given transaction ID. Please see
642L</TRANSACTION STALENESS> for more information.
643
644=cut
645
2120a181 646sub inc_txn_staleness_counter {
d4b1166e 647 my $self = shift;
9c87a079 648 my ($trans_id) = @_;
649
650 # Hardcode staleness of 0 for the HEAD
651 return 0 unless $trans_id;
652
653 $self->storage->print_at(
654 $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
655 pack( $StP{$STALE_SIZE}, $self->get_txn_staleness_counter( $trans_id ) + 1 ),
656 );
2120a181 657}
20f7b20c 658
ae6c15b8 659=head2 get_entries()
660
661This takes no arguments.
662
663This returns a list of all the sectors that have been modified by this transaction.
664
665=cut
666
2120a181 667sub get_entries {
668 my $self = shift;
669 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
d4b1166e 670}
671
ae6c15b8 672=head2 add_entry( $trans_id, $location )
673
3b90cc56 674This takes a transaction ID and a file location and marks the sector at that
675location as having been modified by the transaction identified by $trans_id.
ae6c15b8 676
677This returns nothing.
678
679B<NOTE>: Unlike all the other _entries() methods, there are several cases where
680C<< $trans_id != $self->trans_id >> for this method.
681
682=cut
683
2120a181 684sub add_entry {
ea2f6d67 685 my $self = shift;
9c87a079 686 my ($trans_id, $loc) = @_;
97d40a0a 687
2120a181 688 $self->{entries}{$trans_id} ||= {};
9c87a079 689 $self->{entries}{$trans_id}{$loc} = undef;
2120a181 690}
ea2f6d67 691
ae6c15b8 692=head2 reindex_entry( $old_loc, $new_loc )
693
3b90cc56 694This takes two locations (old and new, respectively). If a location that has
695been modified by this transaction is subsequently reindexed due to a bucketlist
ae6c15b8 696overflowing, then the entries hash needs to be made aware of this change.
697
698This returns nothing.
699
700=cut
701
2120a181 702sub reindex_entry {
703 my $self = shift;
9c87a079 704 my ($old_loc, $new_loc) = @_;
2120a181 705
706 TRANS:
707 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
9c87a079 708 if ( exists $locs->{$old_loc} ) {
709 delete $locs->{$old_loc};
710 $locs->{$new_loc} = undef;
695c88b1 711 next TRANS;
2120a181 712 }
ea2f6d67 713 }
ea2f6d67 714}
715
ae6c15b8 716=head2 clear_entries()
717
3b90cc56 718This takes no arguments. It will clear the entries list for the running
719transaction.
ae6c15b8 720
721This returns nothing.
722
723=cut
724
2120a181 725sub clear_entries {
d4b1166e 726 my $self = shift;
2120a181 727 delete $self->{entries}{$self->trans_id};
728}
eea0d863 729
ae6c15b8 730=head2 _write_file_header()
731
732This writes the file header for a new file. This will write the various settings
733that set how the file is interpreted.
734
735=head2 _read_file_header()
736
737This reads the file header from an existing file. This will read the various
738settings that set how the file is interpreted.
739
740=cut
741
9c87a079 742{
51e9eee3 743 my $header_fixed = length( __PACKAGE__->SIG_FILE ) + 1 + 4 + 4;
9c87a079 744 my $this_file_version = 3;
16d1ad9b 745
9c87a079 746 sub _write_file_header {
747 my $self = shift;
75be6413 748
9c87a079 749 my $nt = $self->num_txns;
750 my $bl = $self->txn_bitfield_len;
75be6413 751
9c87a079 752 my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
75be6413 753
9c87a079 754 my $loc = $self->storage->request_space( $header_fixed + $header_var );
75be6413 755
9c87a079 756 $self->storage->print_at( $loc,
51e9eee3 757 $self->SIG_FILE,
758 $self->SIG_HEADER,
9c87a079 759 pack('N', $this_file_version), # At this point, we're at 9 bytes
760 pack('N', $header_var), # header size
761 # --- Above is $header_fixed. Below is $header_var
762 pack('C', $self->byte_size),
75be6413 763
9c87a079 764 # These shenanigans are to allow a 256 within a C
765 pack('C', $self->max_buckets - 1),
766 pack('C', $self->data_sector_size - 1),
75be6413 767
9c87a079 768 pack('C', $nt),
769 pack('C' . $bl, 0 ), # Transaction activeness bitfield
770 pack($StP{$STALE_SIZE}.($nt-1), 0 x ($nt-1) ), # Transaction staleness counters
771 pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
772 pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
773 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
774 );
75be6413 775
9c87a079 776 #XXX Set these less fragilely
777 $self->set_trans_loc( $header_fixed + 4 );
778 $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
75be6413 779
9c87a079 780 return;
badf847c 781 }
7b1e1aa1 782
9c87a079 783 sub _read_file_header {
badf847c 784 my $self = shift;
75be6413 785
9c87a079 786 my $buffer = $self->storage->read_at( 0, $header_fixed );
787 return unless length($buffer);
2603d86e 788
9c87a079 789 my ($file_signature, $sig_header, $file_version, $size) = unpack(
790 'A4 A N N', $buffer
791 );
75be6413 792
51e9eee3 793 unless ( $file_signature eq $self->SIG_FILE ) {
9c87a079 794 $self->storage->close;
795 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
796 }
7b1e1aa1 797
51e9eee3 798 unless ( $sig_header eq $self->SIG_HEADER ) {
9c87a079 799 $self->storage->close;
800 DBM::Deep->_throw_error( "Pre-1.00 file version found" );
801 }
802
803 unless ( $file_version == $this_file_version ) {
804 $self->storage->close;
805 DBM::Deep->_throw_error(
806 "Wrong file version found - " . $file_version .
807 " - expected " . $this_file_version
808 );
809 }
810
811 my $buffer2 = $self->storage->read_at( undef, $size );
812 my @values = unpack( 'C C C C', $buffer2 );
813
814 if ( @values != 4 || grep { !defined } @values ) {
815 $self->storage->close;
816 DBM::Deep->_throw_error("Corrupted file - bad header");
817 }
818
819 #XXX Add warnings if values weren't set right
820 @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
821
822 # These shenangians are to allow a 256 within a C
823 $self->{max_buckets} += 1;
824 $self->{data_sector_size} += 1;
825
826 my $bl = $self->txn_bitfield_len;
827
828 my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
829 unless ( $size == $header_var ) {
830 $self->storage->close;
831 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
badf847c 832 }
7b1e1aa1 833
9c87a079 834 $self->set_trans_loc( $header_fixed + scalar(@values) );
835 $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
836
837 return length($buffer) + length($buffer2);
7b1e1aa1 838 }
75be6413 839}
840
ae6c15b8 841=head2 _apply_digest( @stuff )
842
843This will apply the digest methd (default to Digest::MD5::md5) to the arguments
844passed in and return the result.
845
846=cut
847
9c87a079 848sub _apply_digest {
a8d2331c 849 my $self = shift;
9c87a079 850 return $self->{digest}->(@_);
a8d2331c 851}
852
ae6c15b8 853=head2 _add_free_blist_sector( $offset, $size )
854
855=head2 _add_free_data_sector( $offset, $size )
856
857=head2 _add_free_index_sector( $offset, $size )
858
859These methods are all wrappers around _add_free_sector(), providing the proper
860chain offset ($multiple) for the sector type.
861
862=cut
863
9c87a079 864sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
865sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
866sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
867
ae6c15b8 868=head2 _add_free_sector( $multiple, $offset, $size )
869
870_add_free_sector() takes the offset into the chains location, the offset of the
871sector, and the size of that sector. It will mark the sector as a free sector
872and put it into the list of sectors that are free of this type for use later.
873
874This returns nothing.
875
876B<NOTE>: $size is unused?
877
878=cut
879
9c87a079 880sub _add_free_sector {
00d9bd0b 881 my $self = shift;
9c87a079 882 my ($multiple, $offset, $size) = @_;
883
884 my $chains_offset = $multiple * $self->byte_size;
885
886 my $storage = $self->storage;
887
888 # Increment staleness.
889 # XXX Can this increment+modulo be done by "&= 0x1" ?
51e9eee3 890 my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + $self->SIG_SIZE, $STALE_SIZE ) );
9c87a079 891 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
51e9eee3 892 $storage->print_at( $offset + $self->SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
9c87a079 893
894 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
895
896 $storage->print_at( $self->chains_loc + $chains_offset,
897 pack( $StP{$self->byte_size}, $offset ),
898 );
899
900 # Record the old head in the new sector after the signature and staleness counter
51e9eee3 901 $storage->print_at( $offset + $self->SIG_SIZE + $STALE_SIZE, $old_head );
00d9bd0b 902}
903
ae6c15b8 904=head2 _request_blist_sector( $size )
905
906=head2 _request_data_sector( $size )
907
908=head2 _request_index_sector( $size )
909
910These methods are all wrappers around _request_sector(), providing the proper
911chain offset ($multiple) for the sector type.
912
913=cut
914
9c87a079 915sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
916sub _request_data_sector { shift->_request_sector( 1, @_ ) }
917sub _request_index_sector { shift->_request_sector( 2, @_ ) }
918
ae6c15b8 919=head2 _request_sector( $multiple $size )
920
921This takes the offset into the chains location and the size of that sector.
922
923This returns the object with the sector. If there is an available free sector of
924that type, then it will be reused. If there isn't one, then a new one will be
925allocated.
926
927=cut
928
9c87a079 929sub _request_sector {
a8d2331c 930 my $self = shift;
9c87a079 931 my ($multiple, $size) = @_;
932
933 my $chains_offset = $multiple * $self->byte_size;
934
935 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
936 my $loc = unpack( $StP{$self->byte_size}, $old_head );
937
938 # We don't have any free sectors of the right size, so allocate a new one.
939 unless ( $loc ) {
940 my $offset = $self->storage->request_space( $size );
941
942 # Zero out the new sector. This also guarantees correct increases
943 # in the filesize.
944 $self->storage->print_at( $offset, chr(0) x $size );
a8d2331c 945
9c87a079 946 return $offset;
947 }
948
949 # Read the new head after the signature and the staleness counter
51e9eee3 950 my $new_head = $self->storage->read_at( $loc + $self->SIG_SIZE + $STALE_SIZE, $self->byte_size );
9c87a079 951 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
952 $self->storage->print_at(
51e9eee3 953 $loc + $self->SIG_SIZE + $STALE_SIZE,
9c87a079 954 pack( $StP{$self->byte_size}, 0 ),
955 );
956
957 return $loc;
a8d2331c 958}
959
f0276afb 960=head2 ACCESSORS
961
962The following are readonly attributes.
963
964=over 4
965
f0276afb 966=item * byte_size
967
968=item * hash_size
969
970=item * hash_chars
971
972=item * num_txns
973
974=item * max_buckets
975
976=item * blank_md5
977
978=item * data_sector_size
979
980=item * txn_bitfield_len
981
982=back
983
984=cut
f1879fdc 985
2120a181 986sub byte_size { $_[0]{byte_size} }
987sub hash_size { $_[0]{hash_size} }
988sub hash_chars { $_[0]{hash_chars} }
989sub num_txns { $_[0]{num_txns} }
990sub max_buckets { $_[0]{max_buckets} }
991sub blank_md5 { chr(0) x $_[0]->hash_size }
e9b0b5f0 992sub data_sector_size { $_[0]{data_sector_size} }
993
994# This is a calculated value
995sub txn_bitfield_len {
996 my $self = shift;
997 unless ( exists $self->{txn_bitfield_len} ) {
998 my $temp = ($self->num_txns) / 8;
999 if ( $temp > int( $temp ) ) {
1000 $temp = int( $temp ) + 1;
1001 }
1002 $self->{txn_bitfield_len} = $temp;
1003 }
1004 return $self->{txn_bitfield_len};
1005}
8db25060 1006
f0276afb 1007=pod
1008
1009The following are read/write attributes.
1010
1011=over 4
1012
1013=item * trans_id / set_trans_id( $new_id )
1014
1015=item * trans_loc / set_trans_loc( $new_loc )
1016
1017=item * chains_loc / set_chains_loc( $new_loc )
1018
1019=back
1020
1021=cut
1022
2120a181 1023sub trans_id { $_[0]{trans_id} }
1024sub set_trans_id { $_[0]{trans_id} = $_[1] }
8db25060 1025
2120a181 1026sub trans_loc { $_[0]{trans_loc} }
1027sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
1028
1029sub chains_loc { $_[0]{chains_loc} }
1030sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
1031
580e5ee2 1032sub supports {
1033 shift;
1034 my ($feature) = @_;
1035
1036 return 1 if $feature eq 'transactions';
1037 return;
1038}
1039
f0276afb 1040=head2 _dump_file()
1041
3b90cc56 1042This method takes no arguments. It's used to print out a textual representation
1043of the DBM::Deep DB file. It assumes the file is not-corrupted.
f0276afb 1044
1045=cut
1046
888453b9 1047sub _dump_file {
1048 my $self = shift;
1049
1050 # Read the header
9c87a079 1051 my $spot = $self->_read_file_header();
888453b9 1052
1053 my %types = (
1054 0 => 'B',
1055 1 => 'D',
1056 2 => 'I',
1057 );
1058
1059 my %sizes = (
1060 'D' => $self->data_sector_size,
2c70efe1 1061 'B' => DBM::Deep::Sector::File::BucketList->new({engine=>$self,offset=>1})->size,
1062 'I' => DBM::Deep::Sector::File::Index->new({engine=>$self,offset=>1})->size,
888453b9 1063 );
1064
1065 my $return = "";
c57b19c6 1066
1067 # Header values
1068 $return .= "NumTxns: " . $self->num_txns . $/;
1069
888453b9 1070 # Read the free sector chains
1071 my %sectors;
1072 foreach my $multiple ( 0 .. 2 ) {
1073 $return .= "Chains($types{$multiple}):";
1074 my $old_loc = $self->chains_loc + $multiple * $self->byte_size;
1075 while ( 1 ) {
1076 my $loc = unpack(
1077 $StP{$self->byte_size},
1078 $self->storage->read_at( $old_loc, $self->byte_size ),
1079 );
1080
1081 # We're now out of free sectors of this kind.
1082 unless ( $loc ) {
1083 last;
1084 }
1085
1086 $sectors{ $types{$multiple} }{ $loc } = undef;
51e9eee3 1087 $old_loc = $loc + $self->SIG_SIZE + $STALE_SIZE;
888453b9 1088 $return .= " $loc";
1089 }
1090 $return .= $/;
1091 }
1092
1093 SECTOR:
1094 while ( $spot < $self->storage->{end} ) {
1095 # Read each sector in order.
d6ecf579 1096 my $sector = $self->load_sector( $spot );
888453b9 1097 if ( !$sector ) {
1098 # Find it in the free-sectors that were found already
1099 foreach my $type ( keys %sectors ) {
1100 if ( exists $sectors{$type}{$spot} ) {
1101 my $size = $sizes{$type};
1102 $return .= sprintf "%08d: %s %04d\n", $spot, 'F' . $type, $size;
1103 $spot += $size;
1104 next SECTOR;
1105 }
1106 }
1107
1108 die "********\n$return\nDidn't find free sector for $spot in chains\n********\n";
1109 }
1110 else {
1111 $return .= sprintf "%08d: %s %04d", $spot, $sector->type, $sector->size;
1112 if ( $sector->type eq 'D' ) {
1113 $return .= ' ' . $sector->data;
1114 }
1115 elsif ( $sector->type eq 'A' || $sector->type eq 'H' ) {
1116 $return .= ' REF: ' . $sector->get_refcount;
1117 }
1118 elsif ( $sector->type eq 'B' ) {
1119 foreach my $bucket ( $sector->chopped_up ) {
1120 $return .= "\n ";
1121 $return .= sprintf "%08d", unpack($StP{$self->byte_size},
1122 substr( $bucket->[-1], $self->hash_size, $self->byte_size),
1123 );
c57b19c6 1124 my $l = unpack( $StP{$self->byte_size},
1125 substr( $bucket->[-1],
1126 $self->hash_size + $self->byte_size,
1127 $self->byte_size,
1128 ),
1129 );
1130 $return .= sprintf " %08d", $l;
1131 foreach my $txn ( 0 .. $self->num_txns - 2 ) {
888453b9 1132 my $l = unpack( $StP{$self->byte_size},
1133 substr( $bucket->[-1],
c57b19c6 1134 $self->hash_size + 2 * $self->byte_size + $txn * ($self->byte_size + $STALE_SIZE),
888453b9 1135 $self->byte_size,
1136 ),
1137 );
1138 $return .= sprintf " %08d", $l;
1139 }
1140 }
1141 }
1142 $return .= $/;
1143
1144 $spot += $sector->size;
1145 }
1146 }
1147
1148 return $return;
1149}
1150
a20d9a3f 11511;
1152__END__