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)
39 This is an internal-use-only object for L<DBM::Deep/>. It mediates the low-level
40 mapping between the L<DBM::Deep/> objects and the storage medium.
42 The purpose of this documentation is to provide low-level documentation for
43 developers. It is B<not> intended to be used by the general public. This
44 documentation and what it documents can and will change without notice.
48 The engine exposes an API to the DBM::Deep objects (DBM::Deep, DBM::Deep::Array,
49 and DBM::Deep::Hash) for their use to access the actual stored values. This API
60 =item * make_reference
78 =item * lock_exclusive
86 They are explained in their own sections below. These methods, in turn, may
87 provide some bounds-checking, but primarily act to instantiate objects in the
88 Engine::Sector::* hierarchy and dispatch to them.
92 Transactions in DBM::Deep are implemented using a variant of MVCC. This attempts
93 to keep the amount of actual work done against the file low while stil providing
94 Atomicity, Consistency, and Isolation. Durability, unfortunately, cannot be done
99 If another process uses a transaction slot and writes stuff to it, then
100 terminates, the data that process wrote it still within the file. In order to
101 address this, there is also a transaction staleness counter associated within
102 every write. Each time a transaction is started, that process increments that
103 transaction's staleness counter. If, when it reads a value, the staleness
104 counters aren't identical, DBM::Deep will consider the value on disk to be stale
109 The fourth leg of ACID is Durability, the guarantee that when a commit returns,
110 the data will be there the next time you read from it. This should be regardless
111 of any crashes or powerdowns in between the commit and subsequent read.
112 DBM::Deep does provide that guarantee; once the commit returns, all of the data
113 has been transferred from the transaction shadow to the HEAD. The issue arises
114 with partial commits - a commit that is interrupted in some fashion. In keeping
115 with DBM::Deep's "tradition" of very light error-checking and non-existent
116 error-handling, there is no way to recover from a partial commit. (This is
117 probably a failure in Consistency as well as Durability.)
119 Other DBMSes use transaction logs (a separate file, generally) to achieve
120 Durability. As DBM::Deep is a single-file, we would have to do something
121 similar to what SQLite and BDB do in terms of committing using synchonized
122 writes. To do this, we would have to use a much higher RAM footprint and some
123 serious programming that make my head hurts just to think about it.
125 =head1 EXTERNAL METHODS
129 This takes a set of args. These args are described in the documentation for
138 $args->{storage} = DBM::Deep::Storage::File->new( $args )
139 unless exists $args->{storage};
145 hash_size => 16, # In bytes
146 hash_chars => 256, # Number of chars the algorithm uses per byte
148 num_txns => 1, # The HEAD
149 trans_id => 0, # Default to the HEAD
151 data_sector_size => 64, # Size in bytes of each data sector
153 entries => {}, # This is the list of entries for transactions
157 # Never allow byte_size to be set directly.
158 delete $args->{byte_size};
159 if ( defined $args->{pack_size} ) {
160 if ( lc $args->{pack_size} eq 'small' ) {
161 $args->{byte_size} = 2;
163 elsif ( lc $args->{pack_size} eq 'medium' ) {
164 $args->{byte_size} = 4;
166 elsif ( lc $args->{pack_size} eq 'large' ) {
167 $args->{byte_size} = 8;
170 DBM::Deep->_throw_error( "Unknown pack_size value: '$args->{pack_size}'" );
174 # Grab the parameters we want to use
175 foreach my $param ( keys %$self ) {
176 next unless exists $args->{$param};
177 $self->{$param} = $args->{$param};
181 max_buckets => { floor => 16, ceil => 256 },
182 num_txns => { floor => 1, ceil => 255 },
183 data_sector_size => { floor => 32, ceil => 256 },
186 while ( my ($attr, $c) = each %validations ) {
187 if ( !defined $self->{$attr}
188 || !length $self->{$attr}
189 || $self->{$attr} =~ /\D/
190 || $self->{$attr} < $c->{floor}
192 $self->{$attr} = '(undef)' if !defined $self->{$attr};
193 warn "Floor of $attr is $c->{floor}. Setting it to $c->{floor} from '$self->{$attr}'\n";
194 $self->{$attr} = $c->{floor};
196 elsif ( $self->{$attr} > $c->{ceil} ) {
197 warn "Ceiling of $attr is $c->{ceil}. Setting it to $c->{ceil} from '$self->{$attr}'\n";
198 $self->{$attr} = $c->{ceil};
202 if ( !$self->{digest} ) {
204 $self->{digest} = \&Digest::MD5::md5;
210 =head2 read_value( $obj, $key )
212 This takes an object that provides _base_offset() and a string. It returns the
213 value stored in the corresponding Sector::Value's data section.
219 my ($obj, $key) = @_;
221 # This will be a Reference sector
222 my $sector = $self->_load_sector( $obj->_base_offset )
225 if ( $sector->staleness != $obj->_staleness ) {
229 my $key_md5 = $self->_apply_digest( $key );
231 my $value_sector = $sector->get_data_for({
236 unless ( $value_sector ) {
237 $value_sector = DBM::Deep::Engine::Sector::Null->new({
242 $sector->write_data({
245 value => $value_sector,
249 return $value_sector->data;
252 =head2 get_classname( $obj )
254 This takes an object that provides _base_offset() and returns the classname (if
255 any) associated with it.
257 It delegates to Sector::Reference::get_classname() for the heavy lifting.
259 It performs a staleness check.
267 # This will be a Reference sector
268 my $sector = $self->_load_sector( $obj->_base_offset )
269 or DBM::Deep->_throw_error( "How did get_classname fail (no sector for '$obj')?!" );
271 if ( $sector->staleness != $obj->_staleness ) {
275 return $sector->get_classname;
278 =head2 make_reference( $obj, $old_key, $new_key )
280 This takes an object that provides _base_offset() and two strings. The
281 strings correspond to the old key and new key, respectively. This operation
282 is equivalent to (given C<< $db->{foo} = []; >>) C<< $db->{bar} = $db->{foo} >>.
284 This returns nothing.
290 my ($obj, $old_key, $new_key) = @_;
292 # This will be a Reference sector
293 my $sector = $self->_load_sector( $obj->_base_offset )
294 or DBM::Deep->_throw_error( "How did make_reference fail (no sector for '$obj')?!" );
296 if ( $sector->staleness != $obj->_staleness ) {
300 my $old_md5 = $self->_apply_digest( $old_key );
302 my $value_sector = $sector->get_data_for({
307 unless ( $value_sector ) {
308 $value_sector = DBM::Deep::Engine::Sector::Null->new({
313 $sector->write_data({
316 value => $value_sector,
320 if ( $value_sector->isa( 'DBM::Deep::Engine::Sector::Reference' ) ) {
321 $sector->write_data({
323 key_md5 => $self->_apply_digest( $new_key ),
324 value => $value_sector,
326 $value_sector->increment_refcount;
329 $sector->write_data({
331 key_md5 => $self->_apply_digest( $new_key ),
332 value => $value_sector->clone,
339 =head2 key_exists( $obj, $key )
341 This takes an object that provides _base_offset() and a string for
342 the key to be checked. This returns 1 for true and "" for false.
348 my ($obj, $key) = @_;
350 # This will be a Reference sector
351 my $sector = $self->_load_sector( $obj->_base_offset )
354 if ( $sector->staleness != $obj->_staleness ) {
358 my $data = $sector->get_data_for({
359 key_md5 => $self->_apply_digest( $key ),
363 # exists() returns 1 or '' for true/false.
364 return $data ? 1 : '';
367 =head2 delete_key( $obj, $key )
369 This takes an object that provides _base_offset() and a string for
370 the key to be deleted. This returns the result of the Sector::Reference
377 my ($obj, $key) = @_;
379 my $sector = $self->_load_sector( $obj->_base_offset )
382 if ( $sector->staleness != $obj->_staleness ) {
386 return $sector->delete_key({
387 key_md5 => $self->_apply_digest( $key ),
392 =head2 write_value( $obj, $key, $value )
394 This takes an object that provides _base_offset(), a string for the
395 key, and a value. This value can be anything storable within L<DBM::Deep/>.
397 This returns 1 upon success.
403 my ($obj, $key, $value) = @_;
405 my $r = Scalar::Util::reftype( $value ) || '';
408 last if $r eq 'HASH';
409 last if $r eq 'ARRAY';
411 DBM::Deep->_throw_error(
412 "Storage of references of type '$r' is not supported."
416 # This will be a Reference sector
417 my $sector = $self->_load_sector( $obj->_base_offset )
418 or DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
420 if ( $sector->staleness != $obj->_staleness ) {
421 DBM::Deep->_throw_error( "Cannot write to a deleted spot in DBM::Deep." );
425 if ( !defined $value ) {
426 $class = 'DBM::Deep::Engine::Sector::Null';
428 elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) {
430 if ( $r eq 'ARRAY' ) {
431 $tmpvar = tied @$value;
432 } elsif ( $r eq 'HASH' ) {
433 $tmpvar = tied %$value;
437 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
439 unless ( $is_dbm_deep ) {
440 DBM::Deep->_throw_error( "Cannot store something that is tied." );
443 unless ( $tmpvar->_engine->storage == $self->storage ) {
444 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
447 # First, verify if we're storing the same thing to this spot. If we are, then
448 # this should be a no-op. -EJS, 2008-05-19
449 my $loc = $sector->get_data_location_for({
450 key_md5 => $self->_apply_digest( $key ),
454 if ( defined($loc) && $loc == $tmpvar->_base_offset ) {
458 #XXX Can this use $loc?
459 my $value_sector = $self->_load_sector( $tmpvar->_base_offset );
460 $sector->write_data({
462 key_md5 => $self->_apply_digest( $key ),
463 value => $value_sector,
465 $value_sector->increment_refcount;
470 $class = 'DBM::Deep::Engine::Sector::Reference';
471 $type = substr( $r, 0, 1 );
474 if ( tied($value) ) {
475 DBM::Deep->_throw_error( "Cannot store something that is tied." );
477 $class = 'DBM::Deep::Engine::Sector::Scalar';
480 # Create this after loading the reference sector in case something bad happens.
481 # This way, we won't allocate value sector(s) needlessly.
482 my $value_sector = $class->new({
488 $sector->write_data({
490 key_md5 => $self->_apply_digest( $key ),
491 value => $value_sector,
494 # This code is to make sure we write all the values in the $value to the disk
495 # and to make sure all changes to $value after the assignment are reflected
496 # on disk. This may be counter-intuitive at first, but it is correct dwimmery.
497 # NOTE - simply tying $value won't perform a STORE on each value. Hence, the
498 # copy to a temp value.
499 if ( $r eq 'ARRAY' ) {
501 tie @$value, 'DBM::Deep', {
502 base_offset => $value_sector->offset,
503 staleness => $value_sector->staleness,
504 storage => $self->storage,
508 bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
510 elsif ( $r eq 'HASH' ) {
512 tie %$value, 'DBM::Deep', {
513 base_offset => $value_sector->offset,
514 staleness => $value_sector->staleness,
515 storage => $self->storage,
520 bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
526 =head2 setup_fh( $obj )
528 This takes an object that provides _base_offset(). It will do everything needed
529 in order to properly initialize all values for necessary functioning. If this is
530 called upon an already initialized object, this will also reset the inode.
540 # We're opening the file.
541 unless ( $obj->_base_offset ) {
542 my $bytes_read = $self->_read_file_header;
544 # Creating a new file
545 unless ( $bytes_read ) {
546 $self->_write_file_header;
548 # 1) Create Array/Hash entry
549 my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
553 $obj->{base_offset} = $initial_reference->offset;
554 $obj->{staleness} = $initial_reference->staleness;
556 $self->storage->flush;
558 # Reading from an existing file
560 $obj->{base_offset} = $bytes_read;
561 my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({
563 offset => $obj->_base_offset,
565 unless ( $initial_reference ) {
566 DBM::Deep->_throw_error("Corrupted file, no master index record");
569 unless ($obj->_type eq $initial_reference->type) {
570 DBM::Deep->_throw_error("File type mismatch");
573 $obj->{staleness} = $initial_reference->staleness;
577 $self->storage->set_inode;
582 =head2 begin_work( $obj )
584 This takes an object that provides _base_offset(). It will set up all necessary
585 bookkeeping in order to run all work within a transaction.
587 If $obj is already within a transaction, an error wiill be thrown. If there are
588 no more available transactions, an error will be thrown.
598 if ( $self->trans_id ) {
599 DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
602 my @slots = $self->read_txn_slots;
604 for my $i ( 0 .. $#slots ) {
608 $self->set_trans_id( $i + 1 );
613 DBM::Deep->_throw_error( "Cannot allocate transaction ID" );
615 $self->write_txn_slots( @slots );
617 if ( !$self->trans_id ) {
618 DBM::Deep->_throw_error( "Cannot begin_work - no available transactions" );
624 =head2 rollback( $obj )
626 This takes an object that provides _base_offset(). It will revert all
627 actions taken within the running transaction.
629 If $obj is not within a transaction, an error will be thrown.
639 if ( !$self->trans_id ) {
640 DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
643 # Each entry is the file location for a bucket that has a modification for
644 # this transaction. The entries need to be expunged.
645 foreach my $entry (@{ $self->get_entries } ) {
646 # Remove the entry here
647 my $read_loc = $entry
651 + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
653 my $data_loc = $self->storage->read_at( $read_loc, $self->byte_size );
654 $data_loc = unpack( $StP{$self->byte_size}, $data_loc );
655 $self->storage->print_at( $read_loc, pack( $StP{$self->byte_size}, 0 ) );
657 if ( $data_loc > 1 ) {
658 $self->_load_sector( $data_loc )->free;
662 $self->clear_entries;
664 my @slots = $self->read_txn_slots;
665 $slots[$self->trans_id-1] = 0;
666 $self->write_txn_slots( @slots );
667 $self->inc_txn_staleness_counter( $self->trans_id );
668 $self->set_trans_id( 0 );
673 =head2 commit( $obj )
675 This takes an object that provides _base_offset(). It will apply all
676 actions taken within the transaction to the HEAD.
678 If $obj is not within a transaction, an error will be thrown.
688 if ( !$self->trans_id ) {
689 DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
692 foreach my $entry (@{ $self->get_entries } ) {
693 # Overwrite the entry in head with the entry in trans_id
698 my $head_loc = $self->storage->read_at( $base, $self->byte_size );
699 $head_loc = unpack( $StP{$self->byte_size}, $head_loc );
701 my $spot = $base + $self->byte_size + ($self->trans_id - 1) * ( $self->byte_size + $STALE_SIZE );
702 my $trans_loc = $self->storage->read_at(
703 $spot, $self->byte_size,
706 $self->storage->print_at( $base, $trans_loc );
707 $self->storage->print_at(
709 pack( $StP{$self->byte_size} . ' ' . $StP{$STALE_SIZE}, (0) x 2 ),
712 if ( $head_loc > 1 ) {
713 $self->_load_sector( $head_loc )->free;
717 $self->clear_entries;
719 my @slots = $self->read_txn_slots;
720 $slots[$self->trans_id-1] = 0;
721 $self->write_txn_slots( @slots );
722 $self->inc_txn_staleness_counter( $self->trans_id );
723 $self->set_trans_id( 0 );
728 =head2 lock_exclusive()
730 This takes an object that provides _base_offset(). It will guarantee that
731 the storage has taken precautions to be safe for a write.
733 This returns nothing.
740 return $self->storage->lock_exclusive( $obj );
745 This takes an object that provides _base_offset(). It will guarantee that
746 the storage has taken precautions to be safe for a read.
748 This returns nothing.
755 return $self->storage->lock_shared( $obj );
760 This takes an object that provides _base_offset(). It will guarantee that
761 the storage has released all locks taken.
763 This returns nothing.
771 my $rv = $self->storage->unlock( $obj );
778 =head1 INTERNAL METHODS
780 The following methods are internal-use-only to DBM::Deep::Engine::File.
784 =head2 read_txn_slots()
786 This takes no arguments.
788 This will return an array with a 1 or 0 in each slot. Each spot represents one
789 available transaction. If the slot is 1, that transaction is taken. If it is 0,
790 the transaction is available.
796 my $bl = $self->txn_bitfield_len;
797 my $num_bits = $bl * 8;
798 return split '', unpack( 'b'.$num_bits,
799 $self->storage->read_at(
800 $self->trans_loc, $bl,
805 =head2 write_txn_slots( @slots )
807 This takes an array of 1's and 0's. This array represents the transaction slots
808 returned by L</read_txn_slots()>. In other words, the following is true:
810 @x = read_txn_slots( write_txn_slots( @x ) );
812 (With the obviously missing object referents added back in.)
816 sub write_txn_slots {
818 my $num_bits = $self->txn_bitfield_len * 8;
819 $self->storage->print_at( $self->trans_loc,
820 pack( 'b'.$num_bits, join('', @_) ),
824 =head2 get_running_txn_ids()
826 This takes no arguments.
828 This will return an array of taken transaction IDs. This wraps L</read_txn_slots()>.
832 sub get_running_txn_ids {
834 my @transactions = $self->read_txn_slots;
835 my @trans_ids = map { $_+1 } grep { $transactions[$_] } 0 .. $#transactions;
838 =head2 get_txn_staleness_counter( $trans_id )
840 This will return the staleness counter for the given transaction ID. Please see
841 L</TRANSACTION STALENESS> for more information.
845 sub get_txn_staleness_counter {
849 # Hardcode staleness of 0 for the HEAD
850 return 0 unless $trans_id;
852 return unpack( $StP{$STALE_SIZE},
853 $self->storage->read_at(
854 $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
860 =head2 inc_txn_staleness_counter( $trans_id )
862 This will increment the staleness counter for the given transaction ID. Please see
863 L</TRANSACTION STALENESS> for more information.
867 sub inc_txn_staleness_counter {
871 # Hardcode staleness of 0 for the HEAD
872 return 0 unless $trans_id;
874 $self->storage->print_at(
875 $self->trans_loc + $self->txn_bitfield_len + $STALE_SIZE * ($trans_id - 1),
876 pack( $StP{$STALE_SIZE}, $self->get_txn_staleness_counter( $trans_id ) + 1 ),
882 This takes no arguments.
884 This returns a list of all the sectors that have been modified by this transaction.
890 return [ keys %{ $self->{entries}{$self->trans_id} ||= {} } ];
893 =head2 add_entry( $trans_id, $location )
895 This takes a transaction ID and a file location and marks the sector at that
896 location as having been modified by the transaction identified by $trans_id.
898 This returns nothing.
900 B<NOTE>: Unlike all the other _entries() methods, there are several cases where
901 C<< $trans_id != $self->trans_id >> for this method.
907 my ($trans_id, $loc) = @_;
909 $self->{entries}{$trans_id} ||= {};
910 $self->{entries}{$trans_id}{$loc} = undef;
913 =head2 reindex_entry( $old_loc, $new_loc )
915 This takes two locations (old and new, respectively). If a location that has
916 been modified by this transaction is subsequently reindexed due to a bucketlist
917 overflowing, then the entries hash needs to be made aware of this change.
919 This returns nothing.
925 my ($old_loc, $new_loc) = @_;
928 while ( my ($trans_id, $locs) = each %{ $self->{entries} } ) {
929 if ( exists $locs->{$old_loc} ) {
930 delete $locs->{$old_loc};
931 $locs->{$new_loc} = undef;
937 =head2 clear_entries()
939 This takes no arguments. It will clear the entries list for the running
942 This returns nothing.
948 delete $self->{entries}{$self->trans_id};
951 =head2 _write_file_header()
953 This writes the file header for a new file. This will write the various settings
954 that set how the file is interpreted.
956 =head2 _read_file_header()
958 This reads the file header from an existing file. This will read the various
959 settings that set how the file is interpreted.
964 my $header_fixed = length( __PACKAGE__->SIG_FILE ) + 1 + 4 + 4;
965 my $this_file_version = 3;
967 sub _write_file_header {
970 my $nt = $self->num_txns;
971 my $bl = $self->txn_bitfield_len;
973 my $header_var = 1 + 1 + 1 + 1 + $bl + $STALE_SIZE * ($nt - 1) + 3 * $self->byte_size;
975 my $loc = $self->storage->request_space( $header_fixed + $header_var );
977 $self->storage->print_at( $loc,
980 pack('N', $this_file_version), # At this point, we're at 9 bytes
981 pack('N', $header_var), # header size
982 # --- Above is $header_fixed. Below is $header_var
983 pack('C', $self->byte_size),
985 # These shenanigans are to allow a 256 within a C
986 pack('C', $self->max_buckets - 1),
987 pack('C', $self->data_sector_size - 1),
990 pack('C' . $bl, 0 ), # Transaction activeness bitfield
991 pack($StP{$STALE_SIZE}.($nt-1), 0 x ($nt-1) ), # Transaction staleness counters
992 pack($StP{$self->byte_size}, 0), # Start of free chain (blist size)
993 pack($StP{$self->byte_size}, 0), # Start of free chain (data size)
994 pack($StP{$self->byte_size}, 0), # Start of free chain (index size)
997 #XXX Set these less fragilely
998 $self->set_trans_loc( $header_fixed + 4 );
999 $self->set_chains_loc( $header_fixed + 4 + $bl + $STALE_SIZE * ($nt-1) );
1004 sub _read_file_header {
1007 my $buffer = $self->storage->read_at( 0, $header_fixed );
1008 return unless length($buffer);
1010 my ($file_signature, $sig_header, $file_version, $size) = unpack(
1014 unless ( $file_signature eq $self->SIG_FILE ) {
1015 $self->storage->close;
1016 DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" );
1019 unless ( $sig_header eq $self->SIG_HEADER ) {
1020 $self->storage->close;
1021 DBM::Deep->_throw_error( "Pre-1.00 file version found" );
1024 unless ( $file_version == $this_file_version ) {
1025 $self->storage->close;
1026 DBM::Deep->_throw_error(
1027 "Wrong file version found - " . $file_version .
1028 " - expected " . $this_file_version
1032 my $buffer2 = $self->storage->read_at( undef, $size );
1033 my @values = unpack( 'C C C C', $buffer2 );
1035 if ( @values != 4 || grep { !defined } @values ) {
1036 $self->storage->close;
1037 DBM::Deep->_throw_error("Corrupted file - bad header");
1040 #XXX Add warnings if values weren't set right
1041 @{$self}{qw(byte_size max_buckets data_sector_size num_txns)} = @values;
1043 # These shenangians are to allow a 256 within a C
1044 $self->{max_buckets} += 1;
1045 $self->{data_sector_size} += 1;
1047 my $bl = $self->txn_bitfield_len;
1049 my $header_var = scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) + 3 * $self->byte_size;
1050 unless ( $size == $header_var ) {
1051 $self->storage->close;
1052 DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." );
1055 $self->set_trans_loc( $header_fixed + scalar(@values) );
1056 $self->set_chains_loc( $header_fixed + scalar(@values) + $bl + $STALE_SIZE * ($self->num_txns - 1) );
1058 return length($buffer) + length($buffer2);
1062 =head2 _load_sector( $offset )
1064 This will instantiate and return the sector object that represents the data found
1073 # Add a catch for offset of 0 or 1
1074 return if !$offset || $offset <= 1;
1076 my $type = $self->storage->read_at( $offset, 1 );
1077 return if $type eq chr(0);
1079 if ( $type eq $self->SIG_ARRAY || $type eq $self->SIG_HASH ) {
1080 return DBM::Deep::Engine::Sector::Reference->new({
1086 # XXX Don't we need key_md5 here?
1087 elsif ( $type eq $self->SIG_BLIST ) {
1088 return DBM::Deep::Engine::Sector::BucketList->new({
1094 elsif ( $type eq $self->SIG_INDEX ) {
1095 return DBM::Deep::Engine::Sector::Index->new({
1101 elsif ( $type eq $self->SIG_NULL ) {
1102 return DBM::Deep::Engine::Sector::Null->new({
1108 elsif ( $type eq $self->SIG_DATA ) {
1109 return DBM::Deep::Engine::Sector::Scalar->new({
1115 # This was deleted from under us, so just return and let the caller figure it out.
1116 elsif ( $type eq $self->SIG_FREE ) {
1120 DBM::Deep->_throw_error( "'$offset': Don't know what to do with type '$type'" );
1123 =head2 _apply_digest( @stuff )
1125 This will apply the digest methd (default to Digest::MD5::md5) to the arguments
1126 passed in and return the result.
1132 return $self->{digest}->(@_);
1135 =head2 _add_free_blist_sector( $offset, $size )
1137 =head2 _add_free_data_sector( $offset, $size )
1139 =head2 _add_free_index_sector( $offset, $size )
1141 These methods are all wrappers around _add_free_sector(), providing the proper
1142 chain offset ($multiple) for the sector type.
1146 sub _add_free_blist_sector { shift->_add_free_sector( 0, @_ ) }
1147 sub _add_free_data_sector { shift->_add_free_sector( 1, @_ ) }
1148 sub _add_free_index_sector { shift->_add_free_sector( 2, @_ ) }
1150 =head2 _add_free_sector( $multiple, $offset, $size )
1152 _add_free_sector() takes the offset into the chains location, the offset of the
1153 sector, and the size of that sector. It will mark the sector as a free sector
1154 and put it into the list of sectors that are free of this type for use later.
1156 This returns nothing.
1158 B<NOTE>: $size is unused?
1162 sub _add_free_sector {
1164 my ($multiple, $offset, $size) = @_;
1166 my $chains_offset = $multiple * $self->byte_size;
1168 my $storage = $self->storage;
1170 # Increment staleness.
1171 # XXX Can this increment+modulo be done by "&= 0x1" ?
1172 my $staleness = unpack( $StP{$STALE_SIZE}, $storage->read_at( $offset + $self->SIG_SIZE, $STALE_SIZE ) );
1173 $staleness = ($staleness + 1 ) % ( 2 ** ( 8 * $STALE_SIZE ) );
1174 $storage->print_at( $offset + $self->SIG_SIZE, pack( $StP{$STALE_SIZE}, $staleness ) );
1176 my $old_head = $storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
1178 $storage->print_at( $self->chains_loc + $chains_offset,
1179 pack( $StP{$self->byte_size}, $offset ),
1182 # Record the old head in the new sector after the signature and staleness counter
1183 $storage->print_at( $offset + $self->SIG_SIZE + $STALE_SIZE, $old_head );
1186 =head2 _request_blist_sector( $size )
1188 =head2 _request_data_sector( $size )
1190 =head2 _request_index_sector( $size )
1192 These methods are all wrappers around _request_sector(), providing the proper
1193 chain offset ($multiple) for the sector type.
1197 sub _request_blist_sector { shift->_request_sector( 0, @_ ) }
1198 sub _request_data_sector { shift->_request_sector( 1, @_ ) }
1199 sub _request_index_sector { shift->_request_sector( 2, @_ ) }
1201 =head2 _request_sector( $multiple $size )
1203 This takes the offset into the chains location and the size of that sector.
1205 This returns the object with the sector. If there is an available free sector of
1206 that type, then it will be reused. If there isn't one, then a new one will be
1211 sub _request_sector {
1213 my ($multiple, $size) = @_;
1215 my $chains_offset = $multiple * $self->byte_size;
1217 my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size );
1218 my $loc = unpack( $StP{$self->byte_size}, $old_head );
1220 # We don't have any free sectors of the right size, so allocate a new one.
1222 my $offset = $self->storage->request_space( $size );
1224 # Zero out the new sector. This also guarantees correct increases
1226 $self->storage->print_at( $offset, chr(0) x $size );
1231 # Read the new head after the signature and the staleness counter
1232 my $new_head = $self->storage->read_at( $loc + $self->SIG_SIZE + $STALE_SIZE, $self->byte_size );
1233 $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head );
1234 $self->storage->print_at(
1235 $loc + $self->SIG_SIZE + $STALE_SIZE,
1236 pack( $StP{$self->byte_size}, 0 ),
1244 This takes no arguments. It will do everything necessary to flush all things to
1245 disk. This is usually called during unlock() and setup_fh().
1247 This returns nothing.
1254 # Why do we need to have the storage flush? Shouldn't autoflush take care of things?
1256 $self->storage->flush;
1261 The following are readonly attributes.
1279 =item * data_sector_size
1281 =item * txn_bitfield_len
1287 sub storage { $_[0]{storage} }
1288 sub byte_size { $_[0]{byte_size} }
1289 sub hash_size { $_[0]{hash_size} }
1290 sub hash_chars { $_[0]{hash_chars} }
1291 sub num_txns { $_[0]{num_txns} }
1292 sub max_buckets { $_[0]{max_buckets} }
1293 sub blank_md5 { chr(0) x $_[0]->hash_size }
1294 sub data_sector_size { $_[0]{data_sector_size} }
1296 # This is a calculated value
1297 sub txn_bitfield_len {
1299 unless ( exists $self->{txn_bitfield_len} ) {
1300 my $temp = ($self->num_txns) / 8;
1301 if ( $temp > int( $temp ) ) {
1302 $temp = int( $temp ) + 1;
1304 $self->{txn_bitfield_len} = $temp;
1306 return $self->{txn_bitfield_len};
1311 The following are read/write attributes.
1315 =item * trans_id / set_trans_id( $new_id )
1317 =item * trans_loc / set_trans_loc( $new_loc )
1319 =item * chains_loc / set_chains_loc( $new_loc )
1325 sub trans_id { $_[0]{trans_id} }
1326 sub set_trans_id { $_[0]{trans_id} = $_[1] }
1328 sub trans_loc { $_[0]{trans_loc} }
1329 sub set_trans_loc { $_[0]{trans_loc} = $_[1] }
1331 sub chains_loc { $_[0]{chains_loc} }
1332 sub set_chains_loc { $_[0]{chains_loc} = $_[1] }
1334 sub cache { $_[0]{cache} ||= {} }
1335 sub clear_cache { %{$_[0]->cache} = () }
1339 This method takes no arguments. It's used to print out a textual representation
1340 of the DBM::Deep DB file. It assumes the file is not-corrupted.
1348 my $spot = $self->_read_file_header();
1357 'D' => $self->data_sector_size,
1358 'B' => DBM::Deep::Engine::Sector::BucketList->new({engine=>$self,offset=>1})->size,
1359 'I' => DBM::Deep::Engine::Sector::Index->new({engine=>$self,offset=>1})->size,
1365 $return .= "NumTxns: " . $self->num_txns . $/;
1367 # Read the free sector chains
1369 foreach my $multiple ( 0 .. 2 ) {
1370 $return .= "Chains($types{$multiple}):";
1371 my $old_loc = $self->chains_loc + $multiple * $self->byte_size;
1374 $StP{$self->byte_size},
1375 $self->storage->read_at( $old_loc, $self->byte_size ),
1378 # We're now out of free sectors of this kind.
1383 $sectors{ $types{$multiple} }{ $loc } = undef;
1384 $old_loc = $loc + $self->SIG_SIZE + $STALE_SIZE;
1391 while ( $spot < $self->storage->{end} ) {
1392 # Read each sector in order.
1393 my $sector = $self->_load_sector( $spot );
1395 # Find it in the free-sectors that were found already
1396 foreach my $type ( keys %sectors ) {
1397 if ( exists $sectors{$type}{$spot} ) {
1398 my $size = $sizes{$type};
1399 $return .= sprintf "%08d: %s %04d\n", $spot, 'F' . $type, $size;
1405 die "********\n$return\nDidn't find free sector for $spot in chains\n********\n";
1408 $return .= sprintf "%08d: %s %04d", $spot, $sector->type, $sector->size;
1409 if ( $sector->type eq 'D' ) {
1410 $return .= ' ' . $sector->data;
1412 elsif ( $sector->type eq 'A' || $sector->type eq 'H' ) {
1413 $return .= ' REF: ' . $sector->get_refcount;
1415 elsif ( $sector->type eq 'B' ) {
1416 foreach my $bucket ( $sector->chopped_up ) {
1418 $return .= sprintf "%08d", unpack($StP{$self->byte_size},
1419 substr( $bucket->[-1], $self->hash_size, $self->byte_size),
1421 my $l = unpack( $StP{$self->byte_size},
1422 substr( $bucket->[-1],
1423 $self->hash_size + $self->byte_size,
1427 $return .= sprintf " %08d", $l;
1428 foreach my $txn ( 0 .. $self->num_txns - 2 ) {
1429 my $l = unpack( $StP{$self->byte_size},
1430 substr( $bucket->[-1],
1431 $self->hash_size + 2 * $self->byte_size + $txn * ($self->byte_size + $STALE_SIZE),
1435 $return .= sprintf " %08d", $l;
1441 $spot += $sector->size;