Moved all constants into DBM::Deep::Engine
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine.pm
CommitLineData
a20d9a3f 1package DBM::Deep::Engine;
2
3use strict;
4
5use Fcntl qw( :DEFAULT :flock :seek );
6
1bf65be7 7##
8# Set to 4 and 'N' for 32-bit offset tags (default). Theoretical limit of 4 GB per file.
9# (Perl must be compiled with largefile support for files > 2 GB)
10#
11# Set to 8 and 'Q' for 64-bit offsets. Theoretical limit of 16 XB per file.
12# (Perl must be compiled with largefile and 64-bit long support)
13##
14##
15# Set to 4 and 'N' for 32-bit data length prefixes. Limit of 4 GB for each key/value.
16# Upgrading this is possible (see above) but probably not necessary. If you need
17# more than 4 GB for a single key or value, this module is really not for you :-)
18##
19our ($LONG_SIZE, $LONG_PACK, $DATA_LENGTH_SIZE, $DATA_LENGTH_PACK);
20##
21# Maximum number of buckets per list before another level of indexing is done.
22# Increase this value for slightly greater speed, but larger database files.
23# DO NOT decrease this value below 16, due to risk of recursive reindex overrun.
24##
25our $MAX_BUCKETS = 16;
26our ($DIGEST_FUNC, $HASH_SIZE);
27our ($INDEX_SIZE, $BUCKET_SIZE, $BUCKET_LIST_SIZE);
28set_digest();
29
30sub _precalc_sizes {
31 ##
32 # Precalculate index, bucket and bucket list sizes
33 ##
34
35 #XXX I don't like this ...
36 set_pack() unless defined $LONG_SIZE;
37
38 $INDEX_SIZE = 256 * $LONG_SIZE;
39 $BUCKET_SIZE = $HASH_SIZE + $LONG_SIZE;
40 $BUCKET_LIST_SIZE = $MAX_BUCKETS * $BUCKET_SIZE;
41}
42
43sub set_pack {
44 ##
45 # Set pack/unpack modes (see file header for more)
46 ##
47 my ($long_s, $long_p, $data_s, $data_p) = @_;
48
49 $LONG_SIZE = $long_s ? $long_s : 4;
50 $LONG_PACK = $long_p ? $long_p : 'N';
51
52 $DATA_LENGTH_SIZE = $data_s ? $data_s : 4;
53 $DATA_LENGTH_PACK = $data_p ? $data_p : 'N';
54
55 _precalc_sizes();
56}
57
58sub set_digest {
59 ##
60 # Set key digest function (default is MD5)
61 ##
62 my ($digest_func, $hash_size) = @_;
63
64 $DIGEST_FUNC = $digest_func ? $digest_func : \&Digest::MD5::md5;
65 $HASH_SIZE = $hash_size ? $hash_size : 16;
66
67 _precalc_sizes();
68}
69
a20d9a3f 70sub open {
20f7b20c 71 ##
72 # Open a fh to the database, create if nonexistent.
73 # Make sure file signature matches DBM::Deep spec.
74 ##
a20d9a3f 75 my $self = shift;
cd59cad8 76 my $obj = shift;
a20d9a3f 77
20f7b20c 78 if (defined($obj->_fh)) { $self->close( $obj ); }
79
a20d9a3f 80 eval {
81 local $SIG{'__DIE__'};
82 # Theoretically, adding O_BINARY should remove the need for the binmode
83 # Of course, testing it is going to be ... interesting.
84 my $flags = O_RDWR | O_CREAT | O_BINARY;
85
86 my $fh;
cd59cad8 87 sysopen( $fh, $obj->_root->{file}, $flags )
a20d9a3f 88 or $fh = undef;
cd59cad8 89 $obj->_root->{fh} = $fh;
90 }; if ($@ ) { $obj->_throw_error( "Received error: $@\n" ); }
20f7b20c 91 if (! defined($obj->_fh)) {
92 return $obj->_throw_error("Cannot sysopen file: " . $obj->_root->{file} . ": $!");
93 }
a20d9a3f 94
cd59cad8 95 my $fh = $obj->_fh;
a20d9a3f 96
97 #XXX Can we remove this by using the right sysopen() flags?
98 # Maybe ... q.v. above
99 binmode $fh; # for win32
100
cd59cad8 101 if ($obj->_root->{autoflush}) {
a20d9a3f 102 my $old = select $fh;
103 $|=1;
104 select $old;
105 }
20f7b20c 106
cd59cad8 107 seek($fh, 0 + $obj->_root->{file_offset}, SEEK_SET);
a20d9a3f 108
109 my $signature;
110 my $bytes_read = read( $fh, $signature, length(DBM::Deep->SIG_FILE));
20f7b20c 111
a20d9a3f 112 ##
113 # File is empty -- write signature and master index
114 ##
115 if (!$bytes_read) {
cd59cad8 116 seek($fh, 0 + $obj->_root->{file_offset}, SEEK_SET);
a20d9a3f 117 print( $fh DBM::Deep->SIG_FILE);
1bf65be7 118 $self->create_tag($obj, $obj->_base_offset, $obj->_type, chr(0) x $INDEX_SIZE);
a20d9a3f 119
120 my $plain_key = "[base]";
1bf65be7 121 print( $fh pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key );
a20d9a3f 122
123 # Flush the filehandle
124 my $old_fh = select $fh;
125 my $old_af = $|; $| = 1; $| = $old_af;
126 select $old_fh;
127
128 my @stats = stat($fh);
cd59cad8 129 $obj->_root->{inode} = $stats[1];
130 $obj->_root->{end} = $stats[7];
a20d9a3f 131
132 return 1;
133 }
20f7b20c 134
a20d9a3f 135 ##
136 # Check signature was valid
137 ##
138 unless ($signature eq DBM::Deep->SIG_FILE) {
cd59cad8 139 $self->close( $obj );
140 return $obj->_throw_error("Signature not found -- file is not a Deep DB");
a20d9a3f 141 }
142
20f7b20c 143 my @stats = stat($fh);
144 $obj->_root->{inode} = $stats[1];
cd59cad8 145 $obj->_root->{end} = $stats[7];
20f7b20c 146
a20d9a3f 147 ##
148 # Get our type from master index signature
149 ##
d4b1166e 150 my $tag = $self->load_tag($obj, $obj->_base_offset);
a20d9a3f 151
152#XXX We probably also want to store the hash algorithm name and not assume anything
153#XXX The cool thing would be to allow a different hashing algorithm at every level
154
155 if (!$tag) {
20f7b20c 156 return $obj->_throw_error("Corrupted file, no master index record");
a20d9a3f 157 }
cd59cad8 158 if ($obj->{type} ne $tag->{signature}) {
20f7b20c 159 return $obj->_throw_error("File type mismatch");
a20d9a3f 160 }
20f7b20c 161
a20d9a3f 162 return 1;
163}
164
cd59cad8 165sub close {
166 my $self = shift;
167 my $obj = shift;
168
169 if ( my $fh = $obj->_root->{fh} ) {
170 close $fh;
171 }
172 $obj->_root->{fh} = undef;
173
174 return 1;
175}
176
d4b1166e 177sub create_tag {
20f7b20c 178 ##
179 # Given offset, signature and content, create tag and write to disk
180 ##
d4b1166e 181 my $self = shift;
20f7b20c 182 my ($obj, $offset, $sig, $content) = @_;
183 my $size = length($content);
184
d4b1166e 185 my $fh = $obj->_fh;
186
20f7b20c 187 seek($fh, $offset + $obj->_root->{file_offset}, SEEK_SET);
1bf65be7 188 print( $fh $sig . pack($DATA_LENGTH_PACK, $size) . $content );
20f7b20c 189
190 if ($offset == $obj->_root->{end}) {
1bf65be7 191 $obj->_root->{end} += DBM::Deep->SIG_SIZE + $DATA_LENGTH_SIZE + $size;
20f7b20c 192 }
193
194 return {
195 signature => $sig,
196 size => $size,
1bf65be7 197 offset => $offset + DBM::Deep->SIG_SIZE + $DATA_LENGTH_SIZE,
20f7b20c 198 content => $content
199 };
d4b1166e 200}
201
202sub load_tag {
20f7b20c 203 ##
204 # Given offset, load single tag and return signature, size and data
205 ##
d4b1166e 206 my $self = shift;
20f7b20c 207 my ($obj, $offset) = @_;
208
d4b1166e 209 my $fh = $obj->_fh;
210
20f7b20c 211 seek($fh, $offset + $obj->_root->{file_offset}, SEEK_SET);
212 if (eof $fh) { return undef; }
213
d4b1166e 214 my $b;
1bf65be7 215 read( $fh, $b, DBM::Deep->SIG_SIZE + $DATA_LENGTH_SIZE );
216 my ($sig, $size) = unpack( "A $DATA_LENGTH_PACK", $b );
20f7b20c 217
218 my $buffer;
219 read( $fh, $buffer, $size);
220
221 return {
222 signature => $sig,
223 size => $size,
1bf65be7 224 offset => $offset + DBM::Deep->SIG_SIZE + $DATA_LENGTH_SIZE,
20f7b20c 225 content => $buffer
226 };
d4b1166e 227}
228
229sub index_lookup {
20f7b20c 230 ##
231 # Given index tag, lookup single entry in index and return .
232 ##
233 my $self = shift;
234 my ($obj, $tag, $index) = @_;
235
1bf65be7 236 my $location = unpack($LONG_PACK, substr($tag->{content}, $index * $LONG_SIZE, $LONG_SIZE) );
20f7b20c 237 if (!$location) { return; }
238
239 return $self->load_tag( $obj, $location );
240}
241
242sub add_bucket {
243 ##
244 # Adds one key/value pair to bucket list, given offset, MD5 digest of key,
245 # plain (undigested) key and value.
246 ##
d4b1166e 247 my $self = shift;
20f7b20c 248 my ($obj, $tag, $md5, $plain_key, $value) = @_;
249 my $keys = $tag->{content};
250 my $location = 0;
251 my $result = 2;
252
253 my $root = $obj->_root;
254
255 my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $value->isa( 'DBM::Deep' ) };
256 my $internal_ref = $is_dbm_deep && ($value->_root eq $root);
257
258 my $fh = $obj->_fh;
259
260 ##
261 # Iterate through buckets, seeing if this is a new entry or a replace.
262 ##
1bf65be7 263 for (my $i=0; $i<$MAX_BUCKETS; $i++) {
264 my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE));
20f7b20c 265 if (!$subloc) {
266 ##
267 # Found empty bucket (end of list). Populate and exit loop.
268 ##
269 $result = 2;
270
271 $location = $internal_ref
272 ? $value->_base_offset
273 : $root->{end};
274
1bf65be7 275 seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $root->{file_offset}, SEEK_SET);
276 print( $fh $md5 . pack($LONG_PACK, $location) );
20f7b20c 277 last;
278 }
279
1bf65be7 280 my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE);
20f7b20c 281 if ($md5 eq $key) {
282 ##
283 # Found existing bucket with same key. Replace with new value.
284 ##
285 $result = 1;
286
287 if ($internal_ref) {
288 $location = $value->_base_offset;
1bf65be7 289 seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $root->{file_offset}, SEEK_SET);
290 print( $fh $md5 . pack($LONG_PACK, $location) );
20f7b20c 291 return $result;
292 }
293
294 seek($fh, $subloc + DBM::Deep->SIG_SIZE + $root->{file_offset}, SEEK_SET);
295 my $size;
1bf65be7 296 read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
20f7b20c 297
298 ##
299 # If value is a hash, array, or raw value with equal or less size, we can
300 # reuse the same content area of the database. Otherwise, we have to create
301 # a new content area at the EOF.
302 ##
303 my $actual_length;
304 my $r = Scalar::Util::reftype( $value ) || '';
305 if ( $r eq 'HASH' || $r eq 'ARRAY' ) {
1bf65be7 306 $actual_length = $INDEX_SIZE;
20f7b20c 307
308 # if autobless is enabled, must also take into consideration
309 # the class name, as it is stored along with key/value.
310 if ( $root->{autobless} ) {
311 my $value_class = Scalar::Util::blessed($value);
312 if ( defined $value_class && !$value->isa('DBM::Deep') ) {
313 $actual_length += length($value_class);
314 }
315 }
316 }
317 else { $actual_length = length($value); }
318
319 if ($actual_length <= $size) {
320 $location = $subloc;
321 }
322 else {
323 $location = $root->{end};
1bf65be7 324 seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $HASH_SIZE + $root->{file_offset}, SEEK_SET);
325 print( $fh pack($LONG_PACK, $location) );
20f7b20c 326 }
327
328 last;
329 }
330 }
331
332 ##
333 # If this is an internal reference, return now.
334 # No need to write value or plain key
335 ##
336 if ($internal_ref) {
337 return $result;
338 }
339
340 ##
341 # If bucket didn't fit into list, split into a new index level
342 ##
343 if (!$location) {
344 seek($fh, $tag->{ref_loc} + $root->{file_offset}, SEEK_SET);
1bf65be7 345 print( $fh pack($LONG_PACK, $root->{end}) );
20f7b20c 346
1bf65be7 347 my $index_tag = $self->create_tag($obj, $root->{end}, DBM::Deep->SIG_INDEX, chr(0) x $INDEX_SIZE);
20f7b20c 348 my @offsets = ();
349
1bf65be7 350 $keys .= $md5 . pack($LONG_PACK, 0);
20f7b20c 351
1bf65be7 352 for (my $i=0; $i<=$MAX_BUCKETS; $i++) {
353 my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE);
20f7b20c 354 if ($key) {
1bf65be7 355 my $old_subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) +
356 $HASH_SIZE, $LONG_SIZE));
20f7b20c 357 my $num = ord(substr($key, $tag->{ch} + 1, 1));
358
359 if ($offsets[$num]) {
1bf65be7 360 my $offset = $offsets[$num] + DBM::Deep->SIG_SIZE + $DATA_LENGTH_SIZE;
20f7b20c 361 seek($fh, $offset + $root->{file_offset}, SEEK_SET);
362 my $subkeys;
1bf65be7 363 read( $fh, $subkeys, $BUCKET_LIST_SIZE);
20f7b20c 364
1bf65be7 365 for (my $k=0; $k<$MAX_BUCKETS; $k++) {
366 my $subloc = unpack($LONG_PACK, substr($subkeys, ($k * $BUCKET_SIZE) +
367 $HASH_SIZE, $LONG_SIZE));
20f7b20c 368 if (!$subloc) {
1bf65be7 369 seek($fh, $offset + ($k * $BUCKET_SIZE) + $root->{file_offset}, SEEK_SET);
370 print( $fh $key . pack($LONG_PACK, $old_subloc || $root->{end}) );
20f7b20c 371 last;
372 }
373 } # k loop
374 }
375 else {
376 $offsets[$num] = $root->{end};
1bf65be7 377 seek($fh, $index_tag->{offset} + ($num * $LONG_SIZE) + $root->{file_offset}, SEEK_SET);
378 print( $fh pack($LONG_PACK, $root->{end}) );
20f7b20c 379
1bf65be7 380 my $blist_tag = $self->create_tag($obj, $root->{end}, DBM::Deep->SIG_BLIST, chr(0) x $BUCKET_LIST_SIZE);
20f7b20c 381
382 seek($fh, $blist_tag->{offset} + $root->{file_offset}, SEEK_SET);
1bf65be7 383 print( $fh $key . pack($LONG_PACK, $old_subloc || $root->{end}) );
20f7b20c 384 }
385 } # key is real
386 } # i loop
387
388 $location ||= $root->{end};
389 } # re-index bucket list
390
391 ##
392 # Seek to content area and store signature, value and plaintext key
393 ##
394 if ($location) {
395 my $content_length;
396 seek($fh, $location + $root->{file_offset}, SEEK_SET);
397
398 ##
399 # Write signature based on content type, set content length and write actual value.
400 ##
401 my $r = Scalar::Util::reftype($value) || '';
402 if ($r eq 'HASH') {
403 print( $fh DBM::Deep->TYPE_HASH );
1bf65be7 404 print( $fh pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE );
405 $content_length = $INDEX_SIZE;
20f7b20c 406 }
407 elsif ($r eq 'ARRAY') {
408 print( $fh DBM::Deep->TYPE_ARRAY );
1bf65be7 409 print( $fh pack($DATA_LENGTH_PACK, $INDEX_SIZE) . chr(0) x $INDEX_SIZE );
410 $content_length = $INDEX_SIZE;
20f7b20c 411 }
412 elsif (!defined($value)) {
413 print( $fh DBM::Deep->SIG_NULL );
1bf65be7 414 print( $fh pack($DATA_LENGTH_PACK, 0) );
20f7b20c 415 $content_length = 0;
416 }
417 else {
418 print( $fh DBM::Deep->SIG_DATA );
1bf65be7 419 print( $fh pack($DATA_LENGTH_PACK, length($value)) . $value );
20f7b20c 420 $content_length = length($value);
421 }
422
423 ##
424 # Plain key is stored AFTER value, as keys are typically fetched less often.
425 ##
1bf65be7 426 print( $fh pack($DATA_LENGTH_PACK, length($plain_key)) . $plain_key );
20f7b20c 427
428 ##
429 # If value is blessed, preserve class name
430 ##
431 if ( $root->{autobless} ) {
432 my $value_class = Scalar::Util::blessed($value);
433 if ( defined $value_class && $value_class ne 'DBM::Deep' ) {
434 ##
435 # Blessed ref -- will restore later
436 ##
437 print( $fh chr(1) );
1bf65be7 438 print( $fh pack($DATA_LENGTH_PACK, length($value_class)) . $value_class );
20f7b20c 439 $content_length += 1;
1bf65be7 440 $content_length += $DATA_LENGTH_SIZE + length($value_class);
20f7b20c 441 }
442 else {
443 print( $fh chr(0) );
444 $content_length += 1;
445 }
446 }
447
448 ##
449 # If this is a new content area, advance EOF counter
450 ##
451 if ($location == $root->{end}) {
452 $root->{end} += DBM::Deep->SIG_SIZE;
1bf65be7 453 $root->{end} += $DATA_LENGTH_SIZE + $content_length;
454 $root->{end} += $DATA_LENGTH_SIZE + length($plain_key);
20f7b20c 455 }
456
457 ##
458 # If content is a hash or array, create new child DBM::Deep object and
459 # pass each key or element to it.
460 ##
461 if ($r eq 'HASH') {
462 my $branch = DBM::Deep->new(
463 type => DBM::Deep->TYPE_HASH,
464 base_offset => $location,
465 root => $root,
466 );
467 foreach my $key (keys %{$value}) {
468 $branch->STORE( $key, $value->{$key} );
469 }
470 }
471 elsif ($r eq 'ARRAY') {
472 my $branch = DBM::Deep->new(
473 type => DBM::Deep->TYPE_ARRAY,
474 base_offset => $location,
475 root => $root,
476 );
477 my $index = 0;
478 foreach my $element (@{$value}) {
479 $branch->STORE( $index, $element );
480 $index++;
481 }
482 }
483
484 return $result;
485 }
d4b1166e 486
20f7b20c 487 return $obj->_throw_error("Fatal error: indexing failed -- possibly due to corruption in file");
d4b1166e 488}
489
9020ee8c 490sub get_bucket_value {
491 ##
492 # Fetch single value given tag and MD5 digested key.
493 ##
494 my $self = shift;
495 my ($obj, $tag, $md5) = @_;
496 my $keys = $tag->{content};
497
498 my $fh = $obj->_fh;
499
500 ##
501 # Iterate through buckets, looking for a key match
502 ##
503 BUCKET:
1bf65be7 504 for (my $i=0; $i<$MAX_BUCKETS; $i++) {
505 my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE);
506 my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE));
9020ee8c 507
508 if (!$subloc) {
509 ##
510 # Hit end of list, no match
511 ##
512 return;
513 }
514
515 if ( $md5 ne $key ) {
516 next BUCKET;
517 }
518
519 ##
520 # Found match -- seek to offset and read signature
521 ##
522 my $signature;
523 seek($fh, $subloc + $obj->_root->{file_offset}, SEEK_SET);
524 read( $fh, $signature, DBM::Deep->SIG_SIZE);
525
526 ##
527 # If value is a hash or array, return new DBM::Deep object with correct offset
528 ##
529 if (($signature eq DBM::Deep->TYPE_HASH) || ($signature eq DBM::Deep->TYPE_ARRAY)) {
530 my $obj = DBM::Deep->new(
531 type => $signature,
532 base_offset => $subloc,
533 root => $obj->_root,
534 );
535
536 if ($obj->_root->{autobless}) {
537 ##
538 # Skip over value and plain key to see if object needs
539 # to be re-blessed
540 ##
1bf65be7 541 seek($fh, $DATA_LENGTH_SIZE + $INDEX_SIZE, SEEK_CUR);
9020ee8c 542
543 my $size;
1bf65be7 544 read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
9020ee8c 545 if ($size) { seek($fh, $size, SEEK_CUR); }
546
547 my $bless_bit;
548 read( $fh, $bless_bit, 1);
549 if (ord($bless_bit)) {
550 ##
551 # Yes, object needs to be re-blessed
552 ##
553 my $class_name;
1bf65be7 554 read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
9020ee8c 555 if ($size) { read( $fh, $class_name, $size); }
556 if ($class_name) { $obj = bless( $obj, $class_name ); }
557 }
558 }
559
560 return $obj;
561 }
562
563 ##
564 # Otherwise return actual value
565 ##
566 elsif ($signature eq DBM::Deep->SIG_DATA) {
567 my $size;
568 my $value = '';
1bf65be7 569 read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
9020ee8c 570 if ($size) { read( $fh, $value, $size); }
571 return $value;
572 }
573
574 ##
575 # Key exists, but content is null
576 ##
577 else { return; }
578 } # i loop
579
580 return;
581}
ab0e4957 582
583sub delete_bucket {
584 ##
585 # Delete single key/value pair given tag and MD5 digested key.
586 ##
587 my $self = shift;
588 my ($obj, $tag, $md5) = @_;
589 my $keys = $tag->{content};
590
591 my $fh = $obj->_fh;
592
593 ##
594 # Iterate through buckets, looking for a key match
595 ##
596 BUCKET:
1bf65be7 597 for (my $i=0; $i<$MAX_BUCKETS; $i++) {
598 my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE);
599 my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE));
ab0e4957 600
601 if (!$subloc) {
602 ##
603 # Hit end of list, no match
604 ##
605 return;
606 }
607
608 if ( $md5 ne $key ) {
609 next BUCKET;
610 }
611
612 ##
613 # Matched key -- delete bucket and return
614 ##
1bf65be7 615 seek($fh, $tag->{offset} + ($i * $BUCKET_SIZE) + $obj->_root->{file_offset}, SEEK_SET);
616 print( $fh substr($keys, ($i+1) * $BUCKET_SIZE ) );
617 print( $fh chr(0) x $BUCKET_SIZE );
ab0e4957 618
619 return 1;
620 } # i loop
621
622 return;
623}
624
912d50b1 625sub bucket_exists {
626 ##
627 # Check existence of single key given tag and MD5 digested key.
628 ##
629 my $self = shift;
630 my ($obj, $tag, $md5) = @_;
631 my $keys = $tag->{content};
632
633 ##
634 # Iterate through buckets, looking for a key match
635 ##
636 BUCKET:
1bf65be7 637 for (my $i=0; $i<$MAX_BUCKETS; $i++) {
638 my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE);
639 my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE));
912d50b1 640
641 if (!$subloc) {
642 ##
643 # Hit end of list, no match
644 ##
645 return;
646 }
647
648 if ( $md5 ne $key ) {
649 next BUCKET;
650 }
651
652 ##
653 # Matched key -- return true
654 ##
655 return 1;
656 } # i loop
657
658 return;
659}
660
6736c116 661sub find_bucket_list {
662 ##
663 # Locate offset for bucket list, given digested key
664 ##
665 my $self = shift;
666 my ($obj, $md5) = @_;
667
668 ##
669 # Locate offset for bucket list using digest index system
670 ##
671 my $ch = 0;
672 my $tag = $self->load_tag($obj, $obj->_base_offset);
673 if (!$tag) { return; }
674
675 while ($tag->{signature} ne DBM::Deep->SIG_BLIST) {
676 $tag = $self->index_lookup($obj, $tag, ord(substr($md5, $ch, 1)));
677 if (!$tag) { return; }
678 $ch++;
679 }
680
681 return $tag;
682}
683
684sub traverse_index {
685 ##
686 # Scan index and recursively step into deeper levels, looking for next key.
687 ##
688 my $self = shift;
689 my ($obj, $offset, $ch, $force_return_next) = @_;
690 $force_return_next = undef unless $force_return_next;
691
692 my $tag = $self->load_tag($obj, $offset );
693
694 my $fh = $obj->_fh;
695
696 if ($tag->{signature} ne DBM::Deep->SIG_BLIST) {
697 my $content = $tag->{content};
698 my $start;
699 if ($obj->{return_next}) { $start = 0; }
700 else { $start = ord(substr($obj->{prev_md5}, $ch, 1)); }
701
702 for (my $index = $start; $index < 256; $index++) {
1bf65be7 703 my $subloc = unpack($LONG_PACK, substr($content, $index * $LONG_SIZE, $LONG_SIZE) );
6736c116 704 if ($subloc) {
705 my $result = $self->traverse_index( $obj, $subloc, $ch + 1, $force_return_next );
706 if (defined($result)) { return $result; }
707 }
708 } # index loop
709
710 $obj->{return_next} = 1;
711 } # tag is an index
712
713 elsif ($tag->{signature} eq DBM::Deep->SIG_BLIST) {
714 my $keys = $tag->{content};
715 if ($force_return_next) { $obj->{return_next} = 1; }
716
717 ##
718 # Iterate through buckets, looking for a key match
719 ##
1bf65be7 720 for (my $i=0; $i<$MAX_BUCKETS; $i++) {
721 my $key = substr($keys, $i * $BUCKET_SIZE, $HASH_SIZE);
722 my $subloc = unpack($LONG_PACK, substr($keys, ($i * $BUCKET_SIZE) + $HASH_SIZE, $LONG_SIZE));
6736c116 723
724 if (!$subloc) {
725 ##
726 # End of bucket list -- return to outer loop
727 ##
728 $obj->{return_next} = 1;
729 last;
730 }
731 elsif ($key eq $obj->{prev_md5}) {
732 ##
733 # Located previous key -- return next one found
734 ##
735 $obj->{return_next} = 1;
736 next;
737 }
738 elsif ($obj->{return_next}) {
739 ##
740 # Seek to bucket location and skip over signature
741 ##
742 seek($fh, $subloc + DBM::Deep->SIG_SIZE + $obj->_root->{file_offset}, SEEK_SET);
743
744 ##
745 # Skip over value to get to plain key
746 ##
747 my $size;
1bf65be7 748 read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
6736c116 749 if ($size) { seek($fh, $size, SEEK_CUR); }
750
751 ##
752 # Read in plain key and return as scalar
753 ##
754 my $plain_key;
1bf65be7 755 read( $fh, $size, $DATA_LENGTH_SIZE); $size = unpack($DATA_LENGTH_PACK, $size);
6736c116 756 if ($size) { read( $fh, $plain_key, $size); }
757
758 return $plain_key;
759 }
760 } # bucket loop
761
762 $obj->{return_next} = 1;
763 } # tag is a bucket list
764
765 return;
766}
767
768sub get_next_key {
769 ##
770 # Locate next key, given digested previous one
771 ##
772 my $self = shift;
773 my ($obj) = @_;
774
775 $obj->{prev_md5} = $_[1] ? $_[1] : undef;
776 $obj->{return_next} = 0;
777
778 ##
779 # If the previous key was not specifed, start at the top and
780 # return the first one found.
781 ##
782 if (!$obj->{prev_md5}) {
1bf65be7 783 $obj->{prev_md5} = chr(0) x $HASH_SIZE;
6736c116 784 $obj->{return_next} = 1;
785 }
786
787 return $self->traverse_index( $obj, $obj->_base_offset, 0 );
788}
789
a20d9a3f 7901;
791__END__