1 package DBM::Deep::Engine::File;
6 use warnings FATAL => 'all';
8 use base qw( DBM::Deep::Engine );
10 # Never import symbols into our namespace. We are a class, not a library.
13 use DBM::Deep::Storage::File ();
15 use DBM::Deep::Engine::Sector::Data ();
16 use DBM::Deep::Engine::Sector::BucketList ();
17 use DBM::Deep::Engine::Sector::Index ();
18 use DBM::Deep::Engine::Sector::Null ();
19 use DBM::Deep::Engine::Sector::Reference ();
20 use DBM::Deep::Engine::Sector::Scalar ();
21 use DBM::Deep::Null ();
25 # Please refer to the pack() documentation for further information
27 1 => 'C', # Unsigned char value (no order needed as it's just one byte)
28 2 => 'n', # Unsigned short in "network" (big-endian) order
29 4 => 'N', # Unsigned long in "network" (big-endian) order
30 8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent)
35 DBM::Deep::Engine::File
39 This is the engine for use with L<DBM::Deep::Storage::File/>.
41 =head1 EXTERNAL METHODS
45 This takes a set of args. These args are described in the documentation for
54 $args->{storage} = DBM::Deep::Storage::File->new( $args )
55 unless exists $args->{storage};
61 hash_size => 16, # In bytes
62 hash_chars => 256, # Number of chars the algorithm uses per byte
64 num_txns => 1, # The HEAD
65 trans_id => 0, # Default to the HEAD
67 data_sector_size => 64, # Size in bytes of each data sector
69 entries => {}, # This is the list of entries for transactions
73 # Never allow byte_size to be set directly.
74 delete $args->{byte_size};
75 if ( defined $args->{pack_size} ) {
76 if ( lc $args->{pack_size} eq 'small' ) {
77 $args->{byte_size} = 2;
79 elsif ( lc $args->{pack_size} eq 'medium' ) {
80 $args->{byte_size} = 4;
82 elsif ( lc $args->{pack_size} eq 'large' ) {
83 $args->{byte_size} = 8;
86 DBM::Deep->_throw_error( "Unknown pack_size value: '$args->{pack_size}'" );
90 # Grab the parameters we want to use
91 foreach my $param ( keys %$self ) {
92 next unless exists $args->{$param};
93 $self->{$param} = $args->{$param};
97 max_buckets => { floor => 16, ceil => 256 },
98 num_txns => { floor => 1, ceil => 255 },
99 data_sector_size => { floor => 32, ceil => 256 },
102 while ( my ($attr, $c) = each %validations ) {
103 if ( !defined $self->{$attr}
104 || !length $self->{$attr}
105 || $self->{$attr} =~ /\D/
106 || $self->{$attr} < $c->{floor}
108 $self->{$attr} = '(undef)' if !defined $self->{$attr};
109 warn "Floor of $attr is $c->{floor}. Setting it to $c->{floor} from '$self->{$attr}'\n";
110 $self->{$attr} = $c->{floor};
112 elsif ( $self->{$attr} > $c->{ceil} ) {
113 warn "Ceiling of $attr is $c->{ceil}. Setting it to $c->{ceil} from '$self->{$attr}'\n";
114 $self->{$attr} = $c->{ceil};
118 if ( !$self->{digest} ) {
120 $self->{digest} = \&Digest::MD5::md5;
126 =head2 read_value( $obj, $key )
128 This takes an object that provides _base_offset() and a string. It returns the
129 value stored in the corresponding Sector::Value's data section.
135 my ($obj, $key) = @_;
137 # This will be a Reference sector
138 my $sector = $self->_load_sector( $obj->_base_offset )
141 if ( $sector->staleness != $obj->_staleness ) {
145 my $key_md5 = $self->_apply_digest( $key );
147 my $value_sector = $sector->get_data_for({
152 unless ( $value_sector ) {
153 $value_sector = DBM::Deep::Engine::Sector::Null->new({
158 $sector->write_data({
161 value => $value_sector,
165 return $value_sector->data;
168 =head2 get_classname( $obj )
170 This takes an object that provides _base_offset() and returns the classname (if
171 any) associated with it.
173 It delegates to Sector::Reference::get_classname() for the heavy lifting.
175 It performs a staleness check.
183 # This will be a Reference sector
184 my $sector = $self->_load_sector( $obj->_base_offset )
185 or DBM::Deep->_throw_error( "How did get_classname fail (no sector for '$obj')?!" );
187 if ( $sector->staleness != $obj->_staleness ) {
191 return $sector->get_classname;
194 =head2 make_reference( $obj, $old_key, $new_key )
196 This takes an object that provides _base_offset() and two strings. The
197 strings correspond to the old key and new key, respectively. This operation
198 is equivalent to (given C<< $db->{foo} = []; >>) C<< $db->{bar} = $db->{foo} >>.
200 This returns nothing.
206 my ($obj, $old_key, $new_key) = @_;
208 # This will be a Reference sector
209 my $sector = $self->_load_sector( $obj->_base_offset )
210 or DBM::Deep->_throw_error( "How did make_reference fail (no sector for '$obj')?!" );
212 if ( $sector->staleness != $obj->_staleness ) {
216 my $old_md5 = $self->_apply_digest( $old_key );
218 my $value_sector = $sector->get_data_for({
223 unless ( $value_sector ) {
224 $value_sector = DBM::Deep::Engine::Sector::Null->new({
229 $sector->write_data({
232 value => $value_sector,
236 if ( $value_sector->isa( 'DBM::Deep::Engine::Sector::Reference' ) ) {
237 $sector->write_data({
239 key_md5 => $self->_apply_digest( $new_key ),
240 value => $value_sector,
242 $value_sector->increment_refcount;
245 $sector->write_data({
247 key_md5 => $self->_apply_digest( $new_key ),
248 value => $value_sector->clone,
255 =head2 key_exists( $obj, $key )
257 This takes an object that provides _base_offset() and a string for
258 the key to be checked. This returns 1 for true and "" for false.
264 my ($obj, $key) = @_;
266 # This will be a Reference sector
267 my $sector = $self->_load_sector( $obj->_base_offset )
270 if ( $sector->staleness != $obj->_staleness ) {
274 my $data = $sector->get_data_for({
275 key_md5 => $self->_apply_digest( $key ),
279 # exists() returns 1 or '' for true/false.
280 return $data ? 1 : '';
283 =head2 delete_key( $obj, $key )
285 This takes an object that provides _base_offset() and a string for
286 the key to be deleted. This returns the result of the Sector::Reference
293 my ($obj, $key) = @_;
295 my $sector = $self->_load_sector( $obj->_base_offset )
298 if ( $sector->staleness != $obj->_staleness ) {
302 return $sector->delete_key({
303 key_md5 => $self->_apply_digest( $key ),
308 =head2 write_value( $obj, $key, $value )
310 This takes an object that provides _base_offset(), a string for the
311 key, and a value. This value can be anything storable within L<DBM::Deep/>.
313 This returns 1 upon success.
319 my ($obj, $key, $value) = @_;
321 my $r = Scalar::Util::reftype( $value ) || '';
324 last if $r eq 'HASH';
325 last if $r eq 'ARRAY';
327 DBM::Deep->_throw_error(
328 "Storage of references of type '$r' is not supported."
332 # This will be a Reference sector
333 my $sector = $self->_load_sector( $obj->_base_offset )
334 or DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
336 if ( $sector->staleness != $obj->_staleness ) {
337 DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
341 if ( !defined $value ) {
342 $class = 'DBM::Deep::Engine::Sector::Null';
344 elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
346 if ( $r eq 'ARRAY' ) {
347 $tmpvar = tied @$value;
348 } elsif ( $r eq 'HASH' ) {
349 $tmpvar = tied %$value;
353 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
355 unless ( $is_dbm_deep ) {
356 DBM::Deep->_throw_error( "Cannot store something that is tied." );
359 unless ( $tmpvar->_engine->storage == $self->storage ) {
360 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
363 # First, verify if we're storing the same thing to this spot. If we are, then
364 # this should be a no-op. -EJS, 2008-05-19
365 my $loc = $sector->get_data_location_for({
366 key_md5 => $self->_apply_digest( $key ),
370 if ( defined($loc) && $loc == $tmpvar->_base_offset ) {
374 #XXX Can this use $loc?
375 my $value_sector = $self->_load_sector( $tmpvar->_base_offset );
376 $sector->write_data({
378 key_md5 => $self->_apply_digest( $key ),
379 value => $value_sector,
381 $value_sector->increment_refcount;
386 $class = 'DBM::Deep::Engine::Sector::Reference';
387 $type = substr( $r, 0, 1 );
390 if ( tied($value) ) {
391 DBM::Deep->_throw_error( "Cannot store something that is tied." );
393 $class = 'DBM::Deep::Engine::Sector::Scalar';
396 # Create this after loading the reference sector in case something bad happens.
397 # This way, we won't allocate value sector(s) needlessly.
398 my $value_sector = $class->new({
404 $sector->write_data({
406 key_md5 => $self->_apply_digest( $key ),
407 value => $value_sector,
410 # This code is to make sure we write all the values in the $value to the disk
411 # and to make sure all changes to $value after the assignment are reflected
412 # on disk. This may be counter-intuitive at first, but it is correct dwimmery.
413 # NOTE - simply tying $value won't perform a STORE on each value. Hence, the
414 # copy to a temp value.
415 if ( $r eq 'ARRAY' ) {
417 tie @$value, 'DBM::Deep', {
418 base_offset => $value_sector->offset,
419 staleness => $value_sector->staleness,
420 storage => $self->storage,
424 bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
426 elsif ( $r eq 'HASH' ) {
428 tie %$value, 'DBM::Deep', {
429 base_offset => $value_sector->offset,
430 staleness => $value_sector->staleness,
431 storage => $self->storage,
436 bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
442 =head2 setup_fh( $obj )
444 This takes an object that provides _base_offset(). It will do everything needed
445 in order to properly initialize all values for necessary functioning. If this is
446 called upon an already initialized object, this will also reset the inode.
456 # We're opening the file.
457 unless ( $obj->_base_offset ) {
458 my $bytes_read = $self->_read_file_header;
460 # Creating a new file
461 unless ( $bytes_read ) {
462 $self->_write_file_header;
464 # 1) Create Array/Hash entry
465 my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
469 $obj->{base_offset} = $initial_reference->offset;
470 $obj->{staleness} = $initial_reference->staleness;
472 $self->storage->flush;
474 # Reading from an existing file
476 $obj->{base_offset} = $bytes_read;
477 my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
479 offset => $obj->_base_offset,
481 unless ( $initial_reference ) {
482 DBM::Deep->_throw_error("Corrupted file, no master index record");
485 unless ($obj->_type eq $initial_reference->type) {
486 DBM::Deep->_throw_error("File type mismatch");
489 $obj->{staleness} = $initial_reference->staleness;
493 $self->storage->set_inode;
498 =head2 begin_work( $obj )
500 This takes an object that provides _base_offset(). It will set up all necessary
501 bookkeeping in order to run all work within a transaction.
503 If $obj is already within a transaction, an error wiill be thrown. If there are
504 no more available transactions, an error will be thrown.
514 if ( $self->trans_id ) {
515 DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
518 my @slots = $self->read_txn_slots;
520 for my $i ( 0 .. $#slots ) {
524 $self->set_trans_id( $i + 1 );
529 DBM::Deep->_throw_error( "Cannot allocate transaction ID" );
531 $self->write_txn_slots( @slots );
533 if ( !$self->trans_id ) {
534 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
540 =head2 rollback( $obj )
542 This takes an object that provides _base_offset(). It will revert all
543 actions taken within the running transaction.
545 If $obj is not within a transaction, an error will be thrown.
555 if ( !$self->trans_id ) {
556 DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
559 # Each entry is the file location for a bucket that has a modification for
560 # this transaction. The entries need to be expunged.
561 foreach my $entry (@{ $self->get_entries } ) {
562 # Remove the entry here
563 my $read_loc = $entry
567 + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
569 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
570 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
571 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
573 if ( $data_loc > 1 ) {
574 $self->_load_sector( $data_loc )->free;
578 $self->clear_entries;
580 my @slots = $self->read_txn_slots;
581 $slots[$self->trans_id-1] = 0;
582 $self->write_txn_slots( @slots );
583 $self->inc_txn_staleness_counter( $self->trans_id );
584 $self->set_trans_id( 0 );
589 =head2 commit( $obj )
591 This takes an object that provides _base_offset(). It will apply all
592 actions taken within the transaction to the HEAD.
594 If $obj is not within a transaction, an error will be thrown.
604 if ( !$self->trans_id ) {
605 DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
608 foreach my $entry (@{ $self->get_entries } ) {
609 # Overwrite the entry in head with the entry in trans_id
614 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
615 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
617 my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
618 my $trans_loc = $self->storage->read_at(
619 $spot, $self->byte_size,
622 $self->storage->print_at( $base, $trans_loc );
623 $self->storage->print_at(
625 pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
628 if ( $head_loc > 1 ) {
629 $self->_load_sector( $head_loc )->free;
633 $self->clear_entries;
635 my @slots = $self->read_txn_slots;
636 $slots[$self->trans_id-1] = 0;
637 $self->write_txn_slots( @slots );
638 $self->inc_txn_staleness_counter( $self->trans_id );
639 $self->set_trans_id( 0 );
644 =head2 lock_exclusive()
646 This takes an object that provides _base_offset(). It will guarantee that
647 the storage has taken precautions to be safe for a write.
649 This returns nothing.
656 return $self->storage->lock_exclusive( $obj );
661 This takes an object that provides _base_offset(). It will guarantee that
662 the storage has taken precautions to be safe for a read.
664 This returns nothing.
671 return $self->storage->lock_shared( $obj );
676 This takes an object that provides _base_offset(). It will guarantee that
677 the storage has released all locks taken.
679 This returns nothing.
687 my $rv = $self->storage->unlock( $obj );
694 =head1 INTERNAL METHODS
696 The following methods are internal-use-only to DBM::Deep::Engine::File.
700 =head2 read_txn_slots()
702 This takes no arguments.
704 This will return an array with a 1 or 0 in each slot. Each spot represents one
705 available transaction. If the slot is 1, that transaction is taken. If it is 0,
706 the transaction is available.
712 my $bl = $self->txn_bitfield_len;
713 my $num_bits = $bl * 8;
714 return split '', unpack( 'b'.$num_bits,
715 $self->storage->read_at(
716 $self->trans_loc, $bl,
721 =head2 write_txn_slots( @slots )
723 This takes an array of 1's and 0's. This array represents the transaction slots
724 returned by L</read_txn_slots()>. In other words, the following is true:
726 @x = read_txn_slots( write_txn_slots( @x ) );
728 (With the obviously missing object referents added back in.)
732 sub write_txn_slots {
734 my $num_bits = $self->txn_bitfield_len * 8;
735 $self->storage->print_at( $self->trans_loc,
736 pack( 'b'.$num_bits, join('', @_) ),
740 =head2 get_running_txn_ids()
742 This takes no arguments.
744 This will return an array of taken transaction IDs. This wraps L</read_txn_slots()>.
748 sub get_running_txn_ids {
750 my @transactions = $self->read_txn_slots;
751 my @trans_ids = map { $_+1 } grep { $transactions[$_] } 0 .. $#transactions;
754 =head2 get_txn_staleness_counter( $trans_id )
756 This will return the staleness counter for the given transaction ID. Please see
757 L</TRANSACTION STALENESS> for more information.
761 sub get_txn_staleness_counter {
765 # Hardcode staleness of 0 for the HEAD
766 return 0 unless $trans_id;
768 return unpack( $StP{$STALE_SIZE},
769 $self->storage->read_at(
770 $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
776 =head2 inc_txn_staleness_counter( $trans_id )
778 This will increment the staleness counter for the given transaction ID. Please see
779 L</TRANSACTION STALENESS> for more information.
783 sub inc_txn_staleness_counter {
787 # Hardcode staleness of 0 for the HEAD
788 return 0 unless $trans_id;
790 $self->storage->print_at(
791 $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
792 pack( $StP{$STALE_SIZE}, $self->get_txn_staleness_counter( $trans_id ) + 1 ),
798 This takes no arguments.
800 This returns a list of all the sectors that have been modified by this transaction.
806 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
809 =head2 add_entry( $trans_id, $location )
811 This takes a transaction ID and a file location and marks the sector at that
812 location as having been modified by the transaction identified by $trans_id.
814 This returns nothing.
816 B<NOTE>: Unlike all the other _entries() methods, there are several cases where
817 C<< $trans_id != $self->trans_id >> for this method.
823 my ($trans_id, $loc) = @_;
825 $self->{entries}{$trans_id} ||= {};
826 $self->{entries}{$trans_id}{$loc} = undef;
829 =head2 reindex_entry( $old_loc, $new_loc )
831 This takes two locations (old and new, respectively). If a location that has
832 been modified by this transaction is subsequently reindexed due to a bucketlist
833 overflowing, then the entries hash needs to be made aware of this change.
835 This returns nothing.
841 my ($old_loc, $new_loc) = @_;
844 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
845 if ( exists $locs->{$old_loc} ) {
846 delete $locs->{$old_loc};
847 $locs->{$new_loc} = undef;
853 =head2 clear_entries()
855 This takes no arguments. It will clear the entries list for the running
858 This returns nothing.
864 delete $self->{entries}{$self->trans_id};
867 =head2 _write_file_header()
869 This writes the file header for a new file. This will write the various settings
870 that set how the file is interpreted.
872 =head2 _read_file_header()
874 This reads the file header from an existing file. This will read the various
875 settings that set how the file is interpreted.
880 my $header_fixed = length( __PACKAGE__->SIG_FILE ) + 1 + 4 + 4;
881 my $this_file_version = 3;
883 sub _write_file_header {
886 my $nt = $self->num_txns;
887 my $bl = $self->txn_bitfield_len;
889 my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
891 my $loc = $self->storage->request_space( $header_fixed + $header_var );
893 $self->storage->print_at( $loc,
896 pack('N', $this_file_version), # At this point, we're at 9 bytes
897 pack('N', $header_var), # header size
898 # --- Above is $header_fixed. Below is $header_var
899 pack('C', $self->byte_size),
901 # These shenanigans are to allow a 256 within a C
902 pack('C', $self->max_buckets - 1),
903 pack('C', $self->data_sector_size - 1),
906 pack('C' . $bl, 0 ), # Transaction activeness bitfield
907 pack($StP{$STALE_SIZE}.($nt-1), 0 x ($nt-1) ), # Transaction staleness counters
908 pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
909 pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
910 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
913 #XXX Set these less fragilely
914 $self->set_trans_loc( $header_fixed + 4 );
915 $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
920 sub _read_file_header {
923 my $buffer = $self->storage->read_at( 0, $header_fixed );
924 return unless length($buffer);
926 my ($file_signature, $sig_header, $file_version, $size) = unpack(
930 unless ( $file_signature eq $self->SIG_FILE ) {
931 $self->storage->close;
932 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
935 unless ( $sig_header eq $self->SIG_HEADER ) {
936 $self->storage->close;
937 DBM::Deep->_throw_error( "Pre-1.00 file version found" );
940 unless ( $file_version == $this_file_version ) {
941 $self->storage->close;
942 DBM::Deep->_throw_error(
943 "Wrong file version found - " . $file_version .
944 " - expected " . $this_file_version
948 my $buffer2 = $self->storage->read_at( undef, $size );
949 my @values = unpack( 'C C C C', $buffer2 );
951 if ( @values != 4 || grep { !defined } @values ) {
952 $self->storage->close;
953 DBM::Deep->_throw_error("Corrupted file - bad header");
956 #XXX Add warnings if values weren't set right
957 @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
959 # These shenangians are to allow a 256 within a C
960 $self->{max_buckets} += 1;
961 $self->{data_sector_size} += 1;
963 my $bl = $self->txn_bitfield_len;
965 my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
966 unless ( $size == $header_var ) {
967 $self->storage->close;
968 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
971 $self->set_trans_loc( $header_fixed + scalar(@values) );
972 $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
974 return length($buffer) + length($buffer2);
978 =head2 _load_sector( $offset )
980 This will instantiate and return the sector object that represents the data found
989 # Add a catch for offset of 0 or 1
990 return if !$offset || $offset <= 1;
992 my $type = $self->storage->read_at( $offset, 1 );
993 return if $type eq chr(0);
995 if ( $type eq $self->SIG_ARRAY || $type eq $self->SIG_HASH ) {
996 return DBM::Deep::Engine::Sector::Reference->new({
1002 # XXX Don't we need key_md5 here?
1003 elsif ( $type eq $self->SIG_BLIST ) {
1004 return DBM::Deep::Engine::Sector::BucketList->new({
1010 elsif ( $type eq $self->SIG_INDEX ) {
1011 return DBM::Deep::Engine::Sector::Index->new({
1017 elsif ( $type eq $self->SIG_NULL ) {
1018 return DBM::Deep::Engine::Sector::Null->new({
1024 elsif ( $type eq $self->SIG_DATA ) {
1025 return DBM::Deep::Engine::Sector::Scalar->new({
1031 # This was deleted from under us, so just return and let the caller figure it out.
1032 elsif ( $type eq $self->SIG_FREE ) {
1036 DBM::Deep->_throw_error( "'$offset': Don't know what to do with type '$type'" );
1039 =head2 _apply_digest( @stuff )
1041 This will apply the digest methd (default to Digest::MD5::md5) to the arguments
1042 passed in and return the result.
1048 return $self->{digest}->(@_);
1051 =head2 _add_free_blist_sector( $offset, $size )
1053 =head2 _add_free_data_sector( $offset, $size )
1055 =head2 _add_free_index_sector( $offset, $size )
1057 These methods are all wrappers around _add_free_sector(), providing the proper
1058 chain offset ($multiple) for the sector type.
1062 sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
1063 sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
1064 sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
1066 =head2 _add_free_sector( $multiple, $offset, $size )
1068 _add_free_sector() takes the offset into the chains location, the offset of the
1069 sector, and the size of that sector. It will mark the sector as a free sector
1070 and put it into the list of sectors that are free of this type for use later.
1072 This returns nothing.
1074 B<NOTE>: $size is unused?
1078 sub _add_free_sector {
1080 my ($multiple, $offset, $size) = @_;
1082 my $chains_offset = $multiple * $self->byte_size;
1084 my $storage = $self->storage;
1086 # Increment staleness.
1087 # XXX Can this increment+modulo be done by "&= 0x1" ?
1088 my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + $self->SIG_SIZE, $STALE_SIZE ) );
1089 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
1090 $storage->print_at( $offset + $self->SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
1092 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
1094 $storage->print_at( $self->chains_loc + $chains_offset,
1095 pack( $StP{$self->byte_size}, $offset ),
1098 # Record the old head in the new sector after the signature and staleness counter
1099 $storage->print_at( $offset + $self->SIG_SIZE + $STALE_SIZE, $old_head );
1102 =head2 _request_blist_sector( $size )
1104 =head2 _request_data_sector( $size )
1106 =head2 _request_index_sector( $size )
1108 These methods are all wrappers around _request_sector(), providing the proper
1109 chain offset ($multiple) for the sector type.
1113 sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
1114 sub _request_data_sector { shift->_request_sector( 1, @_ ) }
1115 sub _request_index_sector { shift->_request_sector( 2, @_ ) }
1117 =head2 _request_sector( $multiple $size )
1119 This takes the offset into the chains location and the size of that sector.
1121 This returns the object with the sector. If there is an available free sector of
1122 that type, then it will be reused. If there isn't one, then a new one will be
1127 sub _request_sector {
1129 my ($multiple, $size) = @_;
1131 my $chains_offset = $multiple * $self->byte_size;
1133 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
1134 my $loc = unpack( $StP{$self->byte_size}, $old_head );
1136 # We don't have any free sectors of the right size, so allocate a new one.
1138 my $offset = $self->storage->request_space( $size );
1140 # Zero out the new sector. This also guarantees correct increases
1142 $self->storage->print_at( $offset, chr(0) x $size );
1147 # Read the new head after the signature and the staleness counter
1148 my $new_head = $self->storage->read_at( $loc + $self->SIG_SIZE + $STALE_SIZE, $self->byte_size );
1149 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
1150 $self->storage->print_at(
1151 $loc + $self->SIG_SIZE + $STALE_SIZE,
1152 pack( $StP{$self->byte_size}, 0 ),
1160 This takes no arguments. It will do everything necessary to flush all things to
1161 disk. This is usually called during unlock() and setup_fh().
1163 This returns nothing.
1170 # Why do we need to have the storage flush? Shouldn't autoflush take care of things?
1172 $self->storage->flush;
1177 The following are readonly attributes.
1195 =item * data_sector_size
1197 =item * txn_bitfield_len
1203 sub storage { $_[0]{storage} }
1204 sub byte_size { $_[0]{byte_size} }
1205 sub hash_size { $_[0]{hash_size} }
1206 sub hash_chars { $_[0]{hash_chars} }
1207 sub num_txns { $_[0]{num_txns} }
1208 sub max_buckets { $_[0]{max_buckets} }
1209 sub blank_md5 { chr(0) x $_[0]->hash_size }
1210 sub data_sector_size { $_[0]{data_sector_size} }
1212 # This is a calculated value
1213 sub txn_bitfield_len {
1215 unless ( exists $self->{txn_bitfield_len} ) {
1216 my $temp = ($self->num_txns) / 8;
1217 if ( $temp > int( $temp ) ) {
1218 $temp = int( $temp ) + 1;
1220 $self->{txn_bitfield_len} = $temp;
1222 return $self->{txn_bitfield_len};
1227 The following are read/write attributes.
1231 =item * trans_id / set_trans_id( $new_id )
1233 =item * trans_loc / set_trans_loc( $new_loc )
1235 =item * chains_loc / set_chains_loc( $new_loc )
1241 sub trans_id { $_[0]{trans_id} }
1242 sub set_trans_id { $_[0]{trans_id} = $_[1] }
1244 sub trans_loc { $_[0]{trans_loc} }
1245 sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
1247 sub chains_loc { $_[0]{chains_loc} }
1248 sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
1250 sub cache { $_[0]{cache} ||= {} }
1251 sub clear_cache { %{$_[0]->cache} = () }
1255 This method takes no arguments. It's used to print out a textual representation
1256 of the DBM::Deep DB file. It assumes the file is not-corrupted.
1264 my $spot = $self->_read_file_header();
1273 'D' => $self->data_sector_size,
1274 'B' => DBM::Deep::Engine::Sector::BucketList->new({engine=>$self,offset=>1})->size,
1275 'I' => DBM::Deep::Engine::Sector::Index->new({engine=>$self,offset=>1})->size,
1281 $return .= "NumTxns: " . $self->num_txns . $/;
1283 # Read the free sector chains
1285 foreach my $multiple ( 0 .. 2 ) {
1286 $return .= "Chains($types{$multiple}):";
1287 my $old_loc = $self->chains_loc + $multiple * $self->byte_size;
1290 $StP{$self->byte_size},
1291 $self->storage->read_at( $old_loc, $self->byte_size ),
1294 # We're now out of free sectors of this kind.
1299 $sectors{ $types{$multiple} }{ $loc } = undef;
1300 $old_loc = $loc + $self->SIG_SIZE + $STALE_SIZE;
1307 while ( $spot < $self->storage->{end} ) {
1308 # Read each sector in order.
1309 my $sector = $self->_load_sector( $spot );
1311 # Find it in the free-sectors that were found already
1312 foreach my $type ( keys %sectors ) {
1313 if ( exists $sectors{$type}{$spot} ) {
1314 my $size = $sizes{$type};
1315 $return .= sprintf "%08d: %s %04d\n", $spot, 'F' . $type, $size;
1321 die "********\n$return\nDidn't find free sector for $spot in chains\n********\n";
1324 $return .= sprintf "%08d: %s %04d", $spot, $sector->type, $sector->size;
1325 if ( $sector->type eq 'D' ) {
1326 $return .= ' ' . $sector->data;
1328 elsif ( $sector->type eq 'A' || $sector->type eq 'H' ) {
1329 $return .= ' REF: ' . $sector->get_refcount;
1331 elsif ( $sector->type eq 'B' ) {
1332 foreach my $bucket ( $sector->chopped_up ) {
1334 $return .= sprintf "%08d", unpack($StP{$self->byte_size},
1335 substr( $bucket->[-1], $self->hash_size, $self->byte_size),
1337 my $l = unpack( $StP{$self->byte_size},
1338 substr( $bucket->[-1],
1339 $self->hash_size + $self->byte_size,
1343 $return .= sprintf " %08d", $l;
1344 foreach my $txn ( 0 .. $self->num_txns - 2 ) {
1345 my $l = unpack( $StP{$self->byte_size},
1346 substr( $bucket->[-1],
1347 $self->hash_size + 2 * $self->byte_size + $txn * ($self->byte_size + $STALE_SIZE),
1351 $return .= sprintf " %08d", $l;
1357 $spot += $sector->size;