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