Moved almost all direct accesses to into ::File
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine.pm
CommitLineData
a20d9a3f 1package DBM::Deep::Engine;
2
460b1067 3use 5.6.0;
4
a20d9a3f 5use strict;
460b1067 6use warnings;
a20d9a3f 7
8use Fcntl qw( :DEFAULT :flock :seek );
359a01ac 9use Scalar::Util ();
a20d9a3f 10
21838116 11# File-wide notes:
20b7f047 12# * To add to bucket_size, make sure you modify the following:
13# - calculate_sizes()
14# - _get_key_subloc()
15# - add_bucket() - where the buckets are printed
21838116 16
8db25060 17##
18# Setup file and tag signatures. These should never change.
19##
20sub SIG_FILE () { 'DPDB' }
460b1067 21sub SIG_HEADER () { 'h' }
8db25060 22sub SIG_INTERNAL () { 'i' }
23sub SIG_HASH () { 'H' }
24sub SIG_ARRAY () { 'A' }
8db25060 25sub SIG_NULL () { 'N' }
26sub SIG_DATA () { 'D' }
27sub SIG_INDEX () { 'I' }
28sub SIG_BLIST () { 'B' }
7b1e1aa1 29sub SIG_FREE () { 'F' }
8db25060 30sub SIG_SIZE () { 1 }
31
612969fb 32sub new {
33 my $class = shift;
34 my ($args) = @_;
35
36 my $self = bless {
37 long_size => 4,
38 long_pack => 'N',
39 data_size => 4,
40 data_pack => 'N',
251dfd0e 41
612969fb 42 digest => \&Digest::MD5::md5,
43 hash_size => 16,
251dfd0e 44
81d16922 45 ##
d5d7c51d 46 # Maximum number of buckets per list before another level of indexing is
e0098e7f 47 # done. Increase this value for slightly greater speed, but larger database
d5d7c51d 48 # files. DO NOT decrease this value below 16, due to risk of recursive
49 # reindex overrun.
81d16922 50 ##
612969fb 51 max_buckets => 16,
460b1067 52
53 fileobj => undef,
359a01ac 54 obj => undef,
612969fb 55 }, $class;
56
e0098e7f 57 if ( defined $args->{pack_size} ) {
58 if ( lc $args->{pack_size} eq 'small' ) {
59 $args->{long_size} = 2;
c9b6d0d8 60 $args->{long_pack} = 'n';
e0098e7f 61 }
62 elsif ( lc $args->{pack_size} eq 'medium' ) {
63 $args->{long_size} = 4;
64 $args->{long_pack} = 'N';
65 }
66 elsif ( lc $args->{pack_size} eq 'large' ) {
67 $args->{long_size} = 8;
68 $args->{long_pack} = 'Q';
69 }
70 else {
71 die "Unknown pack_size value: '$args->{pack_size}'\n";
72 }
73 }
74
fde3db1a 75 # Grab the parameters we want to use
76 foreach my $param ( keys %$self ) {
77 next unless exists $args->{$param};
3e9498a1 78 $self->{$param} = $args->{$param};
fde3db1a 79 }
359a01ac 80 Scalar::Util::weaken( $self->{obj} ) if $self->{obj};
fde3db1a 81
e0098e7f 82 if ( $self->{max_buckets} < 16 ) {
83 warn "Floor of max_buckets is 16. Setting it to 16 from '$self->{max_buckets}'\n";
84 $self->{max_buckets} = 16;
85 }
86
260a80b4 87 return $self;
88}
89
460b1067 90sub _fileobj { return $_[0]{fileobj} }
460b1067 91
260a80b4 92sub calculate_sizes {
93 my $self = shift;
94
28394a1a 95 #XXX Does this need to be updated with different hashing algorithms?
e0098e7f 96 $self->{index_size} = (2**8) * $self->{long_size};
28394a1a 97 $self->{bucket_size} = $self->{hash_size} + $self->{long_size} * 3;
e0098e7f 98 $self->{bucket_list_size} = $self->{max_buckets} * $self->{bucket_size};
612969fb 99
260a80b4 100 return;
1bf65be7 101}
102
fde3db1a 103sub write_file_header {
0d0f3d5d 104 my $self = shift;
0d0f3d5d 105
019404df 106 my $loc = $self->_fileobj->request_space( length( SIG_FILE ) + 21 );
0d0f3d5d 107
019404df 108 $self->_fileobj->print_at( $loc,
260a80b4 109 SIG_FILE,
460b1067 110 SIG_HEADER,
111 pack('N', 1), # header version
112 pack('N', 12), # header size
15ba72cc 113 pack('N', 0), # currently running transaction IDs
c9b6d0d8 114 pack('n', $self->{long_size}),
260a80b4 115 pack('A', $self->{long_pack}),
c9b6d0d8 116 pack('n', $self->{data_size}),
260a80b4 117 pack('A', $self->{data_pack}),
c9b6d0d8 118 pack('n', $self->{max_buckets}),
260a80b4 119 );
0d0f3d5d 120
20b7f047 121 $self->_fileobj->set_transaction_offset( 13 );
122
0d0f3d5d 123 return;
124}
125
fde3db1a 126sub read_file_header {
e064ccd1 127 my $self = shift;
e064ccd1 128
7dcefff3 129 my $buffer = $self->_fileobj->read_at( 0, length(SIG_FILE) + 9 );
130 return unless length($buffer);
460b1067 131
132 my ($file_signature, $sig_header, $header_version, $size) = unpack(
133 'A4 A N N', $buffer
42f79e07 134 );
e064ccd1 135
460b1067 136 unless ( $file_signature eq SIG_FILE ) {
15ba72cc 137 $self->_fileobj->close;
e96daec8 138 $self->_throw_error( "Signature not found -- file is not a Deep DB" );
460b1067 139 }
260a80b4 140
460b1067 141 unless ( $sig_header eq SIG_HEADER ) {
15ba72cc 142 $self->_fileobj->close;
e96daec8 143 $self->_throw_error( "Old file version found." );
460b1067 144 }
9b2370e0 145
7dcefff3 146 my $buffer2 = $self->_fileobj->read_at( undef, $size );
c9b6d0d8 147 my ($running_transactions, @values) = unpack( 'N n A n A n', $buffer2 );
15ba72cc 148
149 $self->_fileobj->set_transaction_offset( 13 );
150
460b1067 151 if ( @values < 5 || grep { !defined } @values ) {
15ba72cc 152 $self->_fileobj->close;
e96daec8 153 $self->_throw_error("Corrupted file - bad header");
e064ccd1 154 }
155
460b1067 156 #XXX Add warnings if values weren't set right
157 @{$self}{qw(long_size long_pack data_size data_pack max_buckets)} = @values;
158
7dcefff3 159 return length($buffer) + length($buffer2);
e064ccd1 160}
161
460b1067 162sub setup_fh {
163 my $self = shift;
164 my ($obj) = @_;
70b55428 165
7dcefff3 166 # Need to remove use of $fh here
167 my $fh = $self->_fileobj->{fh};
6fde4ed2 168 flock $fh, LOCK_EX;
118ba343 169
260a80b4 170 #XXX The duplication of calculate_sizes needs to go away
6fde4ed2 171 unless ( $obj->{base_offset} ) {
e96daec8 172 my $bytes_read = $self->read_file_header;
118ba343 173
260a80b4 174 $self->calculate_sizes;
175
118ba343 176 ##
fde3db1a 177 # File is empty -- write header and master index
118ba343 178 ##
179 if (!$bytes_read) {
aa83bc1e 180 $self->_fileobj->audit( "# Database created on" );
359a01ac 181
e96daec8 182 $self->write_file_header;
118ba343 183
019404df 184 $obj->{base_offset} = $self->_fileobj->request_space( $self->tag_size( $self->{index_size} ) );
118ba343 185
9e4f83a0 186 $self->write_tag(
e96daec8 187 $obj->_base_offset, $obj->_type,
f37c15ab 188 chr(0)x$self->{index_size},
118ba343 189 );
190
191 # Flush the filehandle
192 my $old_fh = select $fh;
193 my $old_af = $|; $| = 1; $| = $old_af;
194 select $old_fh;
195 }
196 else {
197 $obj->{base_offset} = $bytes_read;
198
199 ##
fde3db1a 200 # Get our type from master index header
118ba343 201 ##
359a01ac 202 my $tag = $self->load_tag($obj->_base_offset);
203 unless ( $tag ) {
204 flock $fh, LOCK_UN;
205 $self->_throw_error("Corrupted file, no master index record");
206 }
118ba343 207
e96daec8 208 unless ($obj->_type eq $tag->{signature}) {
359a01ac 209 flock $fh, LOCK_UN;
e96daec8 210 $self->_throw_error("File type mismatch");
118ba343 211 }
212 }
118ba343 213 }
260a80b4 214 else {
215 $self->calculate_sizes;
216 }
e06824f8 217
673464d9 218 #XXX We have to make sure we don't mess up when autoflush isn't turned on
7dcefff3 219 $self->_fileobj->set_inode;
70b55428 220
6fde4ed2 221 flock $fh, LOCK_UN;
222
70b55428 223 return 1;
224}
225
16d1ad9b 226sub tag_size {
227 my $self = shift;
228 my ($size) = @_;
229 return SIG_SIZE + $self->{data_size} + $size;
230}
231
9e4f83a0 232sub write_tag {
20f7b20c 233 ##
234 # Given offset, signature and content, create tag and write to disk
235 ##
d4b1166e 236 my $self = shift;
e96daec8 237 my ($offset, $sig, $content) = @_;
f37c15ab 238 my $size = length( $content );
20f7b20c 239
7dcefff3 240 $self->_fileobj->print_at(
241 $offset,
242 $sig, pack($self->{data_pack}, $size), $content,
243 );
20f7b20c 244
f37c15ab 245 return unless defined $offset;
246
20f7b20c 247 return {
248 signature => $sig,
249 size => $size,
8db25060 250 offset => $offset + SIG_SIZE + $self->{data_size},
20f7b20c 251 content => $content
252 };
d4b1166e 253}
254
255sub load_tag {
20f7b20c 256 ##
257 # Given offset, load single tag and return signature, size and data
258 ##
d4b1166e 259 my $self = shift;
e96daec8 260 my ($offset) = @_;
20f7b20c 261
7dcefff3 262 my $fileobj = $self->_fileobj;
20f7b20c 263
7dcefff3 264 my $s = SIG_SIZE + $self->{data_size};
265 my $b = $fileobj->read_at( $offset, $s );
251dfd0e 266 my ($sig, $size) = unpack( "A $self->{data_pack}", $b );
20f7b20c 267
7dcefff3 268 my $buffer = $fileobj->read_at( undef, $size );
20f7b20c 269
270 return {
271 signature => $sig,
272 size => $size,
8db25060 273 offset => $offset + SIG_SIZE + $self->{data_size},
20f7b20c 274 content => $buffer
275 };
d4b1166e 276}
277
56ec4340 278sub _get_dbm_object {
279 my $item = shift;
280
281 my $obj = eval {
282 local $SIG{__DIE__};
283 if ($item->isa( 'DBM::Deep' )) {
284 return $item;
285 }
286 return;
287 };
288 return $obj if $obj;
289
290 my $r = Scalar::Util::reftype( $item ) || '';
291 if ( $r eq 'HASH' ) {
292 my $obj = eval {
293 local $SIG{__DIE__};
294 my $obj = tied(%$item);
295 if ($obj->isa( 'DBM::Deep' )) {
296 return $obj;
297 }
298 return;
299 };
300 return $obj if $obj;
301 }
302 elsif ( $r eq 'ARRAY' ) {
303 my $obj = eval {
304 local $SIG{__DIE__};
305 my $obj = tied(@$item);
306 if ($obj->isa( 'DBM::Deep' )) {
307 return $obj;
308 }
309 return;
310 };
311 return $obj if $obj;
312 }
313
314 return;
315}
316
29b01632 317sub _length_needed {
318 my $self = shift;
e96daec8 319 my ($value, $key) = @_;
29b01632 320
321 my $is_dbm_deep = eval {
322 local $SIG{'__DIE__'};
323 $value->isa( 'DBM::Deep' );
324 };
325
f37c15ab 326 my $len = SIG_SIZE + $self->{data_size}
327 + $self->{data_size} + length( $key );
29b01632 328
e96daec8 329 if ( $is_dbm_deep && $value->_fileobj eq $self->_fileobj ) {
f37c15ab 330 return $len + $self->{long_size};
29b01632 331 }
332
333 my $r = Scalar::Util::reftype( $value ) || '';
e96daec8 334 if ( $self->_fileobj->{autobless} ) {
9a187d8c 335 # This is for the bit saying whether or not this thing is blessed.
336 $len += 1;
337 }
338
29b01632 339 unless ( $r eq 'HASH' || $r eq 'ARRAY' ) {
f37c15ab 340 if ( defined $value ) {
341 $len += length( $value );
342 }
343 return $len;
29b01632 344 }
345
f37c15ab 346 $len += $self->{index_size};
29b01632 347
348 # if autobless is enabled, must also take into consideration
f37c15ab 349 # the class name as it is stored after the key.
e96daec8 350 if ( $self->_fileobj->{autobless} ) {
56ec4340 351 my $c = Scalar::Util::blessed($value);
352 if ( defined $c && !$is_dbm_deep ) {
353 $len += $self->{data_size} + length($c);
29b01632 354 }
355 }
356
f37c15ab 357 return $len;
29b01632 358}
359
20f7b20c 360sub add_bucket {
361 ##
362 # Adds one key/value pair to bucket list, given offset, MD5 digest of key,
363 # plain (undigested) key and value.
364 ##
d4b1166e 365 my $self = shift;
359a01ac 366 my ($tag, $md5, $plain_key, $value, $deleted, $orig_key) = @_;
c9b6d0d8 367 $deleted ||= 0;
75be6413 368
21838116 369 local($/,$\);
370
eea0d863 371 # This verifies that only supported values will be stored.
372 {
373 my $r = Scalar::Util::reftype( $value );
374 last if !defined $r;
375
376 last if $r eq 'HASH';
377 last if $r eq 'ARRAY';
378
e96daec8 379 $self->_throw_error(
eea0d863 380 "Storage of variables of type '$r' is not supported."
381 );
382 }
383
20f7b20c 384 my $location = 0;
385 my $result = 2;
386
019404df 387 my $fileobj = $self->_fileobj;
20f7b20c 388
e96daec8 389 my $actual_length = $self->_length_needed( $value, $plain_key );
20f7b20c 390
21838116 391 #ACID - This is a mutation. Must only find the exact transaction
c9b6d0d8 392 my ($subloc, $offset, $size,$is_deleted) = $self->_find_in_buckets( $tag, $md5, 1 );
393
394 my @transactions;
019404df 395 if ( $fileobj->transaction_id == 0 ) {
396 @transactions = $fileobj->current_transactions;
c9b6d0d8 397 }
75be6413 398
e96daec8 399# $self->_release_space( $size, $subloc );
386bab6c 400 # Updating a known md5
f9c33187 401#XXX This needs updating to use _release_space
386bab6c 402 if ( $subloc ) {
403 $result = 1;
20f7b20c 404
386bab6c 405 if ($actual_length <= $size) {
406 $location = $subloc;
20f7b20c 407 }
75be6413 408 else {
019404df 409 $location = $fileobj->request_space( $actual_length );
410
411 $fileobj->print_at( $tag->{offset} + $offset + $self->{hash_size},
412 pack($self->{long_pack}, $location ),
413 pack($self->{long_pack}, $actual_length ),
414 pack('n n', $fileobj->transaction_id, $deleted ),
386bab6c 415 );
75be6413 416 }
75be6413 417 }
386bab6c 418 # Adding a new md5
419 elsif ( defined $offset ) {
019404df 420 $location = $fileobj->request_space( $actual_length );
386bab6c 421
019404df 422 $fileobj->print_at( $tag->{offset} + $offset,
423 $md5,
424 pack($self->{long_pack}, $location ),
425 pack($self->{long_pack}, $actual_length ),
426 pack('n n', $fileobj->transaction_id, $deleted ),
427 );
c9b6d0d8 428
429 for ( @transactions ) {
430 my $tag2 = $self->load_tag( $tag->{offset} - SIG_SIZE - $self->{data_size} );
019404df 431 $fileobj->{transaction_id} = $_;
359a01ac 432 $self->add_bucket( $tag2, $md5, '', '', 1, $orig_key );
019404df 433 $fileobj->{transaction_id} = 0;
c9b6d0d8 434 }
386bab6c 435 }
436 # If bucket didn't fit into list, split into a new index level
019404df 437 # split_index() will do the _fileobj->request_space() call
386bab6c 438 else {
e96daec8 439 $location = $self->split_index( $md5, $tag );
386bab6c 440 }
20f7b20c 441
359a01ac 442 $self->write_value( $location, $plain_key, $value, $orig_key );
d5d7c51d 443
444 return $result;
445}
446
447sub write_value {
448 my $self = shift;
359a01ac 449 my ($location, $key, $value, $orig_key) = @_;
d5d7c51d 450
7dcefff3 451 my $fileobj = $self->_fileobj;
d5d7c51d 452
9d4fa373 453 my $dbm_deep_obj = _get_dbm_object( $value );
7dcefff3 454 if ( $dbm_deep_obj && $dbm_deep_obj->_fileobj ne $fileobj ) {
e96daec8 455 $self->_throw_error( "Cannot cross-reference. Use export() instead" );
9d4fa373 456 }
d5d7c51d 457
20f7b20c 458 ##
d5d7c51d 459 # Write signature based on content type, set content length and write
460 # actual value.
20f7b20c 461 ##
9d4fa373 462 my $r = Scalar::Util::reftype( $value ) || '';
463 if ( $dbm_deep_obj ) {
7dcefff3 464 $self->write_tag( $location, SIG_INTERNAL,pack($self->{long_pack}, $dbm_deep_obj->_base_offset) );
f37c15ab 465 }
466 elsif ($r eq 'HASH') {
9d4fa373 467 if ( !$dbm_deep_obj && tied %{$value} ) {
e96daec8 468 $self->_throw_error( "Cannot store something that is tied" );
019ab3a1 469 }
7dcefff3 470 $self->write_tag( $location, SIG_HASH, chr(0)x$self->{index_size} );
f37c15ab 471 }
472 elsif ($r eq 'ARRAY') {
9d4fa373 473 if ( !$dbm_deep_obj && tied @{$value} ) {
e96daec8 474 $self->_throw_error( "Cannot store something that is tied" );
019ab3a1 475 }
7dcefff3 476 $self->write_tag( $location, SIG_ARRAY, chr(0)x$self->{index_size} );
f37c15ab 477 }
478 elsif (!defined($value)) {
7dcefff3 479 $self->write_tag( $location, SIG_NULL, '' );
d5d7c51d 480 }
481 else {
7dcefff3 482 $self->write_tag( $location, SIG_DATA, $value );
d5d7c51d 483 }
20f7b20c 484
d5d7c51d 485 ##
486 # Plain key is stored AFTER value, as keys are typically fetched less often.
487 ##
7dcefff3 488 $fileobj->print_at( undef, pack($self->{data_pack}, length($key)) . $key );
20f7b20c 489
9a187d8c 490 # Internal references don't care about autobless
9d4fa373 491 return 1 if $dbm_deep_obj;
9a187d8c 492
d5d7c51d 493 ##
494 # If value is blessed, preserve class name
495 ##
7dcefff3 496 if ( $fileobj->{autobless} ) {
56ec4340 497 my $c = Scalar::Util::blessed($value);
498 if ( defined $c && !$dbm_deep_obj ) {
7dcefff3 499 $fileobj->print_at( undef, chr(1), pack($self->{data_pack}, length($c)) . $c );
20f7b20c 500 }
d5d7c51d 501 else {
7dcefff3 502 $fileobj->print_at( undef, chr(0) );
20f7b20c 503 }
d5d7c51d 504 }
20f7b20c 505
d5d7c51d 506 ##
56ec4340 507 # Tie the passed in reference so that changes to it are reflected in the
508 # datafile. The use of $location as the base_offset will act as the
509 # the linkage between parent and child.
510 #
511 # The overall assignment is a hack around the fact that just tying doesn't
512 # store the values. This may not be the wrong thing to do.
d5d7c51d 513 ##
9d4fa373 514 if ($r eq 'HASH') {
515 my %x = %$value;
516 tie %$value, 'DBM::Deep', {
517 base_offset => $location,
7dcefff3 518 fileobj => $fileobj,
359a01ac 519 parent => $self->{obj},
520 parent_key => $orig_key,
9d4fa373 521 };
522 %$value = %x;
523 }
524 elsif ($r eq 'ARRAY') {
525 my @x = @$value;
526 tie @$value, 'DBM::Deep', {
527 base_offset => $location,
7dcefff3 528 fileobj => $fileobj,
359a01ac 529 parent => $self->{obj},
530 parent_key => $orig_key,
9d4fa373 531 };
532 @$value = @x;
20f7b20c 533 }
d4b1166e 534
d5d7c51d 535 return 1;
d4b1166e 536}
537
75be6413 538sub split_index {
539 my $self = shift;
e96daec8 540 my ($md5, $tag) = @_;
75be6413 541
019404df 542 my $fileobj = $self->_fileobj;
21838116 543
019404df 544 my $loc = $fileobj->request_space(
e96daec8 545 $self->tag_size( $self->{index_size} ),
16d1ad9b 546 );
547
019404df 548 $fileobj->print_at( $tag->{ref_loc}, pack($self->{long_pack}, $loc) );
75be6413 549
9e4f83a0 550 my $index_tag = $self->write_tag(
e96daec8 551 $loc, SIG_INDEX,
f37c15ab 552 chr(0)x$self->{index_size},
75be6413 553 );
554
019404df 555 my $newtag_loc = $fileobj->request_space(
e96daec8 556 $self->tag_size( $self->{bucket_list_size} ),
f9c33187 557 );
75be6413 558
7b1e1aa1 559 my $keys = $tag->{content}
f9c33187 560 . $md5 . pack($self->{long_pack}, $newtag_loc)
28394a1a 561 . pack($self->{long_pack}, 0) # size
20b7f047 562 . pack($self->{long_pack}, 0); # transaction ID
75be6413 563
f9c33187 564 my @newloc = ();
75be6413 565 BUCKET:
566 for (my $i = 0; $i <= $self->{max_buckets}; $i++) {
9a187d8c 567 my ($key, $old_subloc, $size) = $self->_get_key_subloc( $keys, $i );
75be6413 568
f9c33187 569 die "[INTERNAL ERROR]: No key in split_index()\n" unless $key;
570 die "[INTERNAL ERROR]: No subloc in split_index()\n" unless $old_subloc;
75be6413 571
75be6413 572 my $num = ord(substr($key, $tag->{ch} + 1, 1));
573
f9c33187 574 if ($newloc[$num]) {
7dcefff3 575 my $subkeys = $fileobj->read_at( $newloc[$num], $self->{bucket_list_size} );
75be6413 576
f9c33187 577 # This is looking for the first empty spot
7b1e1aa1 578 my ($subloc, $offset, $size) = $self->_find_in_buckets(
f9c33187 579 { content => $subkeys }, '',
7b1e1aa1 580 );
75be6413 581
019404df 582 $fileobj->print_at( $newloc[$num] + $offset, $key . pack($self->{long_pack}, $old_subloc) );
7b1e1aa1 583
584 next;
75be6413 585 }
75be6413 586
019404df 587 my $loc = $fileobj->request_space(
e96daec8 588 $self->tag_size( $self->{bucket_list_size} ),
7b1e1aa1 589 );
2603d86e 590
019404df 591 $fileobj->print_at(
592 $index_tag->{offset} + ($num * $self->{long_size}),
593 pack($self->{long_pack}, $loc),
594 );
75be6413 595
7b1e1aa1 596 my $blist_tag = $self->write_tag(
e96daec8 597 $loc, SIG_BLIST,
7b1e1aa1 598 chr(0)x$self->{bucket_list_size},
599 );
600
019404df 601 $fileobj->print_at( $blist_tag->{offset}, $key . pack($self->{long_pack}, $old_subloc) );
7b1e1aa1 602
f9c33187 603 $newloc[$num] = $blist_tag->{offset};
7b1e1aa1 604 }
605
606 $self->_release_space(
e96daec8 607 $self->tag_size( $self->{bucket_list_size} ),
7b1e1aa1 608 $tag->{offset} - SIG_SIZE - $self->{data_size},
609 );
75be6413 610
f9c33187 611 return $newtag_loc;
75be6413 612}
613
8db25060 614sub read_from_loc {
615 my $self = shift;
359a01ac 616 my ($subloc, $orig_key) = @_;
8db25060 617
7dcefff3 618 my $fileobj = $self->_fileobj;
8db25060 619
620 ##
621 # Found match -- seek to offset and read signature
622 ##
7dcefff3 623 my $signature = $fileobj->read_at( $subloc, SIG_SIZE );
8db25060 624
625 ##
626 # If value is a hash or array, return new DBM::Deep object with correct offset
627 ##
628 if (($signature eq SIG_HASH) || ($signature eq SIG_ARRAY)) {
685e40f1 629 my $new_obj = DBM::Deep->new({
359a01ac 630 type => $signature,
8db25060 631 base_offset => $subloc,
e96daec8 632 fileobj => $self->_fileobj,
359a01ac 633 parent => $self->{obj},
634 parent_key => $orig_key,
685e40f1 635 });
8db25060 636
460b1067 637 if ($new_obj->_fileobj->{autobless}) {
8db25060 638 ##
639 # Skip over value and plain key to see if object needs
640 # to be re-blessed
641 ##
7dcefff3 642 $fileobj->increment_pointer( $self->{data_size} + $self->{index_size} );
8db25060 643
7dcefff3 644 my $size = $fileobj->read_at( undef, $self->{data_size} );
c6ea6b6c 645 $size = unpack($self->{data_pack}, $size);
7dcefff3 646 if ($size) { $fileobj->increment_pointer( $size ); }
8db25060 647
7dcefff3 648 my $bless_bit = $fileobj->read_at( undef, 1 );
8db25060 649 if (ord($bless_bit)) {
650 ##
651 # Yes, object needs to be re-blessed
652 ##
7dcefff3 653 my $size = $fileobj->read_at( undef, $self->{data_size} );
c6ea6b6c 654 $size = unpack($self->{data_pack}, $size);
7dcefff3 655
656 my $class_name;
657 if ($size) { $class_name = $fileobj->read_at( undef, $size ); }
658 if (defined $class_name) { $new_obj = bless( $new_obj, $class_name ); }
8db25060 659 }
660 }
661
685e40f1 662 return $new_obj;
8db25060 663 }
664 elsif ( $signature eq SIG_INTERNAL ) {
7dcefff3 665 my $size = $fileobj->read_at( undef, $self->{data_size} );
8db25060 666 $size = unpack($self->{data_pack}, $size);
667
668 if ( $size ) {
7dcefff3 669 my $new_loc = $fileobj->read_at( undef, $size );
670 $new_loc = unpack( $self->{long_pack}, $new_loc );
359a01ac 671 return $self->read_from_loc( $new_loc, $orig_key );
8db25060 672 }
673 else {
674 return;
675 }
676 }
677 ##
678 # Otherwise return actual value
679 ##
460b1067 680 elsif ( $signature eq SIG_DATA ) {
7dcefff3 681 my $size = $fileobj->read_at( undef, $self->{data_size} );
8db25060 682 $size = unpack($self->{data_pack}, $size);
683
684 my $value = '';
7dcefff3 685 if ($size) { $value = $fileobj->read_at( undef, $size ); }
8db25060 686 return $value;
687 }
688
689 ##
690 # Key exists, but content is null
691 ##
692 return;
693}
694
9020ee8c 695sub get_bucket_value {
beac1dff 696 ##
697 # Fetch single value given tag and MD5 digested key.
698 ##
699 my $self = shift;
359a01ac 700 my ($tag, $md5, $orig_key) = @_;
9020ee8c 701
21838116 702 #ACID - This is a read. Can find exact or HEAD
c9b6d0d8 703 my ($subloc, $offset, $size,$is_deleted) = $self->_find_in_buckets( $tag, $md5 );
704 if ( $subloc && !$is_deleted ) {
359a01ac 705 return $self->read_from_loc( $subloc, $orig_key );
386bab6c 706 }
beac1dff 707 return;
9020ee8c 708}
ab0e4957 709
710sub delete_bucket {
beac1dff 711 ##
712 # Delete single key/value pair given tag and MD5 digested key.
713 ##
714 my $self = shift;
a97c8f67 715 my ($tag, $md5, $orig_key) = @_;
ab0e4957 716
21838116 717 #ACID - This is a mutation. Must only find the exact transaction
718 my ($subloc, $offset, $size) = $self->_find_in_buckets( $tag, $md5, 1 );
019404df 719#XXX This needs _release_space() for the value and anything below
386bab6c 720 if ( $subloc ) {
019404df 721 $self->_fileobj->print_at(
722 $tag->{offset} + $offset,
723 substr($tag->{content}, $offset + $self->{bucket_size} ),
724 chr(0) x $self->{bucket_size},
725 );
d0b74c17 726
ab0e4957 727 return 1;
386bab6c 728 }
beac1dff 729 return;
ab0e4957 730}
731
912d50b1 732sub bucket_exists {
beac1dff 733 ##
734 # Check existence of single key given tag and MD5 digested key.
735 ##
736 my $self = shift;
e96daec8 737 my ($tag, $md5) = @_;
912d50b1 738
21838116 739 #ACID - This is a read. Can find exact or HEAD
c9b6d0d8 740 my ($subloc, $offset, $size, $is_deleted) = $self->_find_in_buckets( $tag, $md5 );
741 return ($subloc && !$is_deleted) && 1;
912d50b1 742}
743
6736c116 744sub find_bucket_list {
beac1dff 745 ##
746 # Locate offset for bucket list, given digested key
747 ##
748 my $self = shift;
e96daec8 749 my ($offset, $md5, $args) = @_;
d0b74c17 750 $args = {} unless $args;
751
21838116 752 local($/,$\);
753
beac1dff 754 ##
755 # Locate offset for bucket list using digest index system
756 ##
e96daec8 757 my $tag = $self->load_tag( $offset )
758 or $self->_throw_error( "INTERNAL ERROR - Cannot find tag" );
d0b74c17 759
e5fc7e69 760 my $ch = 0;
8db25060 761 while ($tag->{signature} ne SIG_BLIST) {
d0b74c17 762 my $num = ord substr($md5, $ch, 1);
763
764 my $ref_loc = $tag->{offset} + ($num * $self->{long_size});
e96daec8 765 $tag = $self->index_lookup( $tag, $num );
d0b74c17 766
767 if (!$tag) {
29b01632 768 return if !$args->{create};
d0b74c17 769
019404df 770 my $loc = $self->_fileobj->request_space(
e96daec8 771 $self->tag_size( $self->{bucket_list_size} ),
16d1ad9b 772 );
773
019404df 774 $self->_fileobj->print_at( $ref_loc, pack($self->{long_pack}, $loc) );
d0b74c17 775
9e4f83a0 776 $tag = $self->write_tag(
e96daec8 777 $loc, SIG_BLIST,
f37c15ab 778 chr(0)x$self->{bucket_list_size},
d5d7c51d 779 );
780
781 $tag->{ref_loc} = $ref_loc;
782 $tag->{ch} = $ch;
783
784 last;
d0b74c17 785 }
786
16d1ad9b 787 $tag->{ch} = $ch++;
d0b74c17 788 $tag->{ref_loc} = $ref_loc;
beac1dff 789 }
d0b74c17 790
beac1dff 791 return $tag;
6736c116 792}
793
d0b74c17 794sub index_lookup {
795 ##
796 # Given index tag, lookup single entry in index and return .
797 ##
798 my $self = shift;
e96daec8 799 my ($tag, $index) = @_;
d0b74c17 800
801 my $location = unpack(
802 $self->{long_pack},
803 substr(
804 $tag->{content},
805 $index * $self->{long_size},
806 $self->{long_size},
807 ),
808 );
809
810 if (!$location) { return; }
811
e96daec8 812 return $self->load_tag( $location );
d0b74c17 813}
814
6736c116 815sub traverse_index {
beac1dff 816 ##
817 # Scan index and recursively step into deeper levels, looking for next key.
818 ##
6736c116 819 my $self = shift;
820 my ($obj, $offset, $ch, $force_return_next) = @_;
d0b74c17 821
e96daec8 822 my $tag = $self->load_tag( $offset );
6736c116 823
8db25060 824 if ($tag->{signature} ne SIG_BLIST) {
beac1dff 825 my $content = $tag->{content};
e5fc7e69 826 my $start = $obj->{return_next} ? 0 : ord(substr($obj->{prev_md5}, $ch, 1));
d0b74c17 827
d5d7c51d 828 for (my $idx = $start; $idx < (2**8); $idx++) {
e5fc7e69 829 my $subloc = unpack(
830 $self->{long_pack},
e06824f8 831 substr(
832 $content,
833 $idx * $self->{long_size},
834 $self->{long_size},
835 ),
e5fc7e69 836 );
837
beac1dff 838 if ($subloc) {
e5fc7e69 839 my $result = $self->traverse_index(
840 $obj, $subloc, $ch + 1, $force_return_next,
841 );
842
beac1dff 843 if (defined($result)) { return $result; }
844 }
845 } # index loop
d0b74c17 846
beac1dff 847 $obj->{return_next} = 1;
848 } # tag is an index
d0b74c17 849
e5fc7e69 850 else {
beac1dff 851 my $keys = $tag->{content};
852 if ($force_return_next) { $obj->{return_next} = 1; }
d0b74c17 853
beac1dff 854 ##
855 # Iterate through buckets, looking for a key match
856 ##
8db25060 857 for (my $i = 0; $i < $self->{max_buckets}; $i++) {
9cec1360 858 my ($key, $subloc) = $self->_get_key_subloc( $keys, $i );
d0b74c17 859
8db25060 860 # End of bucket list -- return to outer loop
beac1dff 861 if (!$subloc) {
beac1dff 862 $obj->{return_next} = 1;
863 last;
864 }
8db25060 865 # Located previous key -- return next one found
beac1dff 866 elsif ($key eq $obj->{prev_md5}) {
beac1dff 867 $obj->{return_next} = 1;
868 next;
869 }
8db25060 870 # Seek to bucket location and skip over signature
beac1dff 871 elsif ($obj->{return_next}) {
7dcefff3 872 my $fileobj = $self->_fileobj;
d0b74c17 873
beac1dff 874 # Skip over value to get to plain key
7dcefff3 875 my $sig = $fileobj->read_at( $subloc, SIG_SIZE );
8db25060 876
7dcefff3 877 my $size = $fileobj->read_at( undef, $self->{data_size} );
e5fc7e69 878 $size = unpack($self->{data_pack}, $size);
7dcefff3 879 if ($size) { $fileobj->increment_pointer( $size ); }
d0b74c17 880
beac1dff 881 # Read in plain key and return as scalar
7dcefff3 882 $size = $fileobj->read_at( undef, $self->{data_size} );
e5fc7e69 883 $size = unpack($self->{data_pack}, $size);
7dcefff3 884 my $plain_key;
885 if ($size) { $plain_key = $fileobj->read_at( undef, $size); }
d0b74c17 886
beac1dff 887 return $plain_key;
888 }
8db25060 889 }
d0b74c17 890
beac1dff 891 $obj->{return_next} = 1;
892 } # tag is a bucket list
d0b74c17 893
beac1dff 894 return;
6736c116 895}
896
897sub get_next_key {
beac1dff 898 ##
899 # Locate next key, given digested previous one
900 ##
6736c116 901 my $self = shift;
902 my ($obj) = @_;
d0b74c17 903
beac1dff 904 $obj->{prev_md5} = $_[1] ? $_[1] : undef;
905 $obj->{return_next} = 0;
d0b74c17 906
beac1dff 907 ##
908 # If the previous key was not specifed, start at the top and
909 # return the first one found.
910 ##
911 if (!$obj->{prev_md5}) {
912 $obj->{prev_md5} = chr(0) x $self->{hash_size};
913 $obj->{return_next} = 1;
914 }
d0b74c17 915
beac1dff 916 return $self->traverse_index( $obj, $obj->_base_offset, 0 );
6736c116 917}
918
75be6413 919# Utilities
920
9cec1360 921sub _get_key_subloc {
75be6413 922 my $self = shift;
923 my ($keys, $idx) = @_;
924
c9b6d0d8 925 my ($key, $subloc, $size, $transaction_id, $is_deleted) = unpack(
28394a1a 926 # This is 'a', not 'A'. Please read the pack() documentation for the
927 # difference between the two and why it's important.
c9b6d0d8 928 "a$self->{hash_size} $self->{long_pack}2 n2",
75be6413 929 substr(
930 $keys,
9cec1360 931 ($idx * $self->{bucket_size}),
932 $self->{bucket_size},
75be6413 933 ),
934 );
935
c9b6d0d8 936 return ($key, $subloc, $size, $transaction_id, $is_deleted);
75be6413 937}
938
d608b06e 939sub _find_in_buckets {
940 my $self = shift;
21838116 941 my ($tag, $md5, $exact) = @_;
d608b06e 942
28394a1a 943 my $trans_id = $self->_fileobj->transaction_id;
944
21838116 945 my @zero;
946
d608b06e 947 BUCKET:
948 for ( my $i = 0; $i < $self->{max_buckets}; $i++ ) {
c9b6d0d8 949 my ($key, $subloc, $size, $transaction_id, $is_deleted) = $self->_get_key_subloc(
9a187d8c 950 $tag->{content}, $i,
951 );
d608b06e 952
c9b6d0d8 953 my @rv = ($subloc, $i * $self->{bucket_size}, $size, $is_deleted);
21838116 954
955 unless ( $subloc ) {
20b7f047 956 if ( !$exact && @zero and $trans_id ) {
c9b6d0d8 957 @rv = ($zero[2], $zero[0] * $self->{bucket_size},$zero[3],$is_deleted);
20b7f047 958 }
21838116 959 return @rv;
960 }
961
962 next BUCKET if $key ne $md5;
d608b06e 963
21838116 964 # Save off the HEAD in case we need it.
c9b6d0d8 965 @zero = ($i,$key,$subloc,$size,$transaction_id,$is_deleted) if $transaction_id == 0;
d608b06e 966
21838116 967 next BUCKET if $transaction_id != $trans_id;
968
969 return @rv;
d608b06e 970 }
971
972 return;
973}
974
994ccd8e 975sub _release_space {
976 my $self = shift;
e96daec8 977 my ($size, $loc) = @_;
994ccd8e 978
7b1e1aa1 979 my $next_loc = 0;
980
019404df 981 $self->_fileobj->print_at( $loc,
982 SIG_FREE,
983 pack($self->{long_pack}, $size ),
984 pack($self->{long_pack}, $next_loc ),
7b1e1aa1 985 );
986
994ccd8e 987 return;
988}
989
e96daec8 990sub _throw_error {
991 die "DBM::Deep: $_[1]\n";
992}
993
a20d9a3f 9941;
995__END__