File Coverage

ddb.pm
Criterion Covered Total %
statement 685 737 92.9
branch 129 188 68.6
condition 20 42 47.6
subroutine 57 69 82.6
pod 0 49 0.0
total 891 1085 82.1


line stmt bran cond sub pod time code
1             # ddb by Dan Brumleve
2             # stupid berkeleydb always corrupts my files
3              
4             package ddb;
5 251     251   873982 use POSIX qw(:sys_wait_h);
  251         2067236  
  251         3514  
6 251     251   416911 use Fcntl qw(:seek :flock O_RDONLY O_RDWR O_TRUNC O_CREAT);
  251         753  
  251         43925  
7 251     251   1757 use Digest::MD5;
  251         2008  
  251         22339  
8              
9             BEGIN {
10 251     251   753 eval { require File::Sync; };
  251         254765  
11 251 50       13054259 $@ and *File::Sync::fsync = sub { 1 };
  0         0  
12             }
13              
14             # usage
15             #
16             # use ddb;
17             # $db = tie %db, ddb, 'file.ddb';
18             #
19             # $db{$key} = $val;
20             # ...
21             #
22             # $db->repair;
23             # $db->defrag;
24             # untie %db;
25              
26             # globals
27             $VERSION = '1.3.1';
28             $hash_size = 16381; # default, or pass to tie after filename
29             $sentinel = 1;
30             $empty_buf_size = 256;
31             $magic = 0xDDB10000;
32             $debug = 0;
33             $max_procs = 10; # for test
34             $show_step = 100;
35             $ptr_pos = undef;
36              
37             # file format
38             #
39             # [magic, int32] [hash_size, int32] [hash_table, hash_size * int32]
40             # ... [record] ... [record] ... [record] ...
41              
42             # record format
43             #
44             # [sentinel, byte] [next_pos, int32]
45             # [key_len int32] [key, key_len * byte]
46             # [padding, 0-3 bytes] [val_hash int32]
47             # [val_len int32] [val, val_len * byte]
48             #
49             # in between each record can be zero or more null-bytes of free space.
50             # the hash table values are absolute file offsets pointing to the
51             # first byte of a record. all int32s are big-endian and aligned.
52             # so every sentinel byte position % 4 == 3.
53              
54             # tie implementation comes first
55              
56             sub EXISTS {
57 694     694   2034 my ($db, $key) = @_;
58              
59 694         2932 $db->lock_sh;
60 694         4419 my ($pos, $next_pos) = $db->find($key);
61 694         2522 $db->lock_un;
62              
63 694         7968 defined($pos)
64             }
65              
66             sub FETCH {
67 33028     33028   80251 my ($db, $key) = @_;
68 33028         38370 my $val;
69              
70 33028         66192 $db->lock_sh;
71 33028         71318 my ($pos, $next_pos) = $db->find($key);
72 33028 50       108524 defined $pos or goto DONE;
73              
74 33028         126911 $val = $db->read_val(length($key));
75              
76 33028         570769 DONE:
77             $db->lock_un;
78 33028         297948 $val
79             }
80              
81             sub STORE {
82 1679     1679   29015 my ($db, $key, $val) = @_;
83              
84 1679 50       15656 unless (defined $val) {
85             # how else to make it undef?
86 0         0 $db->DELETE($key);
87 0         0 return undef;
88             }
89              
90 1679         19754 $db->lock_ex;
91 1679         40522 my ($pos, $next_pos) = $db->find($key);
92              
93 1679 100       5990 if (defined($pos)) {
94 351         8824 my $key_len = length($key);
95 351         2572 my $val_len = length($val);
96 351         16648 $db->align_val($key_len);
97 351         7224 $db->seek(4, SEEK_CUR);
98 351         2457 my $old_val_len = $db->read_int;
99              
100 351 100       3672 if ($old_val_len < $val_len) {
101 74         2065 my $rec = $db->pack_rec($key, $val, $next_pos);
102 74         600822 $db->append_rec($rec);
103 74         2568 my $old_rec_len = $db->rec_len($key_len, $old_val_len);
104 74         2127 $db->erase($pos, $old_rec_len);
105             } else {
106 277         3122 $db->replace_val($key, $val, $pos, $next_pos, $old_val_len);
107             }
108             } else {
109 1328         10985 my $rec = $db->pack_rec($key, $val, 0);
110 1328         7576 $db->append_rec($rec);
111             }
112              
113 1679         18223 $db->lock_un;
114 1679         40624 $val
115             }
116              
117             sub DELETE {
118 3509     3509   11568 my ($db, $key) = @_;
119 3509         6761 my $val;
120              
121 3509         17892 $db->lock_ex;
122 3509         18623 my ($pos, $next_pos) = $db->find($key);
123 3509 100       15130 defined $pos or goto DONE;
124              
125 3457         5558 my $key_len = length($key);
126 3457         11510 $val = $db->read_val($key_len);
127 3457         7427 my $val_len = length($val);
128              
129 3457         21071 $db->seek($ptr_pos, SEEK_SET);
130 3457         14938 $db->write_int($next_pos);
131 3457         11809 $db->sync;
132              
133 3457         77015694 my $rec_len = $db->rec_len($key_len, $val_len);
134 3457         28308 $db->erase($pos, $rec_len);
135 3457         34120 $db->sync;
136              
137 3509         23703231 DONE:
138             $db->lock_un;
139 3509         183801 $val
140             }
141              
142             sub CLEAR {
143 255     255   2520 my $db = shift;
144              
145 255         1521 $db->lock_ex;
146 255         1517 $db->seek(0, SEEK_SET);
147 255         1273 $db->write_int($magic);
148 255         1018 $db->write_int($db->{hash_size});
149 255         1526 $db->write_zero(4 * $db->{hash_size});
150              
151 255         1013 my $pos = $db->tell;
152 255         1013 $db->truncate($pos);
153              
154 255         1017 $db->sync;
155 255         11750798 $db->lock_un;
156             ( )
157 255         1561 }
158              
159             sub NEXTKEY {
160 67380     67380   148855 my $db = shift;
161              
162 67380         374660 $db->lock_sh;
163 67380         185010 my ($pos, $key) = $db->next_pos;
164 67379         242481 $db->lock_un;
165              
166 67379         445060 $key
167             }
168              
169             sub FIRSTKEY {
170 1037     1037   3314 my $db = shift;
171 1037         2951 undef $db->{cur_hash};
172 1037         2279 @{$db->{cur_keys}} = ( );
  1037         8384  
173 1037         4887 $$db{rec_count} = 0;
174 1037         3932 $db->NEXTKEY
175             }
176              
177             sub TIEHASH {
178 251     251   7279 my ($p, $filename, $hash_size) = @_;
179              
180 251         3012 my $db = bless {
181             fh => undef,
182             filename => $filename,
183             hash_size => $hash_size,
184             cur_hash => undef,
185             cur_keys => [ ],
186             rec_count => 0,
187             lock_count => 0,
188             lock_type => undef,
189             }, $p;
190              
191 251         2008 $db->reopen;
192              
193 251         1757 $db->lock_ex;
194 251         1506 my $end_pos = $db->seek(0, SEEK_END);
195              
196 251 50       4267 if ($end_pos == 0) {
197 251   33     1757 my $hash_size = $db->{hash_size} || $ddb::hash_size;
198 251         2008 $db->warn("empty, creating $hash_size hash entries");
199 251         1757 $db->write_int($magic);
200 251         1255 $db->write_int($hash_size);
201 251         1757 $db->write_zero(4 * $hash_size);
202 251         4518 $end_pos = $db->tell;
203             }
204              
205 251         1004 $db->seek(0, SEEK_SET);
206 251         502 local $ptr_pos = 'magic';
207 251         1255 my $check_magic = $db->read_int;
208 251 50       1506 pack('N', $check_magic) eq pack('N', $magic) or
209             $db->die("bad magic $check_magic");
210              
211 251         502 local $ptr_pos = 'hash_size';
212 251         502 $db->{hash_size} = $db->read_int;
213              
214 251         1004 my $min_size = $db->data_section;
215 251 50       1255 $end_pos < $min_size and
216             $db->die("file truncated, $end_pos / $min_size expected bytes");
217 251         1004 $db->sync;
218 251         93133048 $db->lock_un;
219              
220 251         2510 $db
221             }
222              
223             sub UNTIE {
224 1     1   14 my $db = shift;
225              
226 1         3 $db->{lock_count} = 0;
227 1         3 $db->{lock_type} = undef;
228 1         2 @{$db->{cur_keys}} = ( );
  1         4  
229 1         3 $db->{rec_count} = 0;
230 1         1 $db->{cur_hash} = undef;
231              
232 1         22 close $db->{fh};
233 1         9 undef $db->{fh};
234             }
235              
236              
237             # now everything else, bottom-up
238              
239             sub data_section {
240 321     321 0 644 my $db = shift;
241 321         1174 8 + 4 * $db->{hash_size}
242             }
243              
244             sub rec_len {
245 115941     115941 0 228911 my ($db, $key_len, $val_len) = @_;
246 115941         615533 17 + $key_len + (-$key_len % 4) + $val_len
247             }
248              
249             sub key_hash {
250 177535     177535 0 307165 my ($db, $key) = @_;
251 177535         293401 my $hash = 0;
252 177535         1799896 $hash ^= $_ for unpack 'N4', Digest::MD5::md5($key);
253 177535         753328 $hash % $db->{hash_size}
254             }
255              
256             sub val_hash {
257 72945     72945 0 353111 my ($db, $val) = @_;
258 72945         114481 my $hash = 0;
259 72945         716027 $hash ^= $_ for unpack 'N4', Digest::MD5::md5($val);
260             # no modulus
261 72945         360113 unpack 'l', pack 'l', $hash
262             }
263              
264             sub key_hash_pos {
265 129519     129519 0 207846 my ($db, $hash) = @_;
266 129519         627293 8 + 4 * $hash
267             }
268              
269             sub cur_keys {
270 0     0 0 0 my $db = shift;
271 0         0 @{$db->{cur_keys}}
  0         0  
272             }
273              
274             sub die {
275 31     31 0 62 my ($db, $msg) = @_;
276              
277 31   33     60 $msg ||= $! . "\n";
278 31 50       99 unless ($msg =~ /\n$/) {
279 31         67 my $pos = $db->tell;
280 31         67 $msg .= " at $pos";
281 31 50       89 defined($ptr_pos) and $msg .= " from $ptr_pos";
282 31         49 $msg .= "\n";
283             }
284              
285 31 50       1600 $db->{lock_count} > 0 and $db->lock_un;
286 31         301 die "$0: $$db{filename}: $msg";
287             }
288              
289             sub warn {
290 331     331 0 947 my ($db, $msg) = @_;
291              
292 331   33     993 $msg ||= $! . "\n";
293 331 50       1638 unless ($msg =~ /\n$/) {
294 331         628 $msg .= "\n";
295             }
296              
297 331         30638 warn "$0: $$db{filename}: $msg";
298             }
299              
300             sub show_status {
301 0     0 0 0 my $db = shift;
302              
303 0 0       0 defined($$db{cur_hash}) or return;
304 0         0 my $last_complete = int(100 * ($$db{cur_hash} - 1) / $$db{hash_size});
305 0         0 my $complete = int(100 * $$db{cur_hash} / $$db{hash_size});
306 0 0 0     0 $last_complete == $complete && $$db{rec_count} % $show_step and return;
307 0 0       0 my $nl = ($complete == 100) ? " \r\n" : " \r";
308 0         0 print STDERR "$0: $$db{rec_count} records, $complete% complete $nl";
309             }
310              
311              
312             # file operations
313              
314             sub sync {
315 17631     17631 0 34997 my $db = shift;
316 17631 50       146168 File::Sync::fsync($db->{fh}) or $db->warn('fsync failed');
317             }
318              
319             sub tell {
320 1283324     1283324 0 2068132 my $db = shift;
321 1283324         7111501 sysseek $db->{fh}, 0, SEEK_CUR
322             }
323              
324             sub seek {
325 793590     793590 0 1808865 my ($db, $where, $whence) = @_;
326 793590         5112127 sysseek $db->{fh}, $where, $whence
327             }
328              
329             sub truncate {
330 847     847 0 3704 my ($db, $size) = @_;
331 847         91105 truncate($db->{fh}, $size)
332             }
333              
334             sub read {
335 2293013     2293013 0 3438421 my ($db, undef, $len) = @_;
336 2293013         18844329 my $check_len = sysread($db->{fh}, $_[1], $len);
337 2293013 100       7600911 unless ($check_len == $len) {
338 6         14 my $pos = $db->tell - $check_len;
339 6         27 $db->die("cannot read $len bytes");
340             }
341 2293007         5472663 $_[0]
342             }
343              
344             sub read_byte {
345 459856     459856 0 660286 my $db = shift;
346 459856         1007534 $db->read(my $p_byte, 1);
347 459850         1464612 unpack C => $p_byte
348             }
349              
350             sub read_sentinel {
351 459856     459856 0 1617253 my $db = shift;
352 459856         1203445 my $byte = $db->read_byte;
353 459850 100       1914421 $byte eq $sentinel or $db->die("bad sentinel $byte");
354             }
355              
356             sub read_int {
357 1265563     1265563 0 2064750 my $db = shift;
358              
359 1265563 100       3576001 if ($debug) {
360 1265061         5379760 my $pos = $db->tell;
361 1265061 0       3817716 $pos % 4 and $db->warn(
    50          
362             "misaligned read_int at $pos" .
363             (defined($ptr_pos) ? " from $ptr_pos" : "")
364             );
365             }
366              
367 1265563         2946440 $db->read(my $p_int, 4);
368 1265563         4960150 my $int = unpack 'l', pack 'l', unpack 'N', $p_int;
369              
370 1265563         2969383 $int
371             }
372              
373             sub read_empty {
374 3881     3881 0 7483 my $db = shift;
375 3881         8514 my $total = 0;
376              
377 3881         46654 while ((my $buf_size = sysread($db->{fh}, my $buf, $empty_buf_size)) > 0) {
378 4738         55036 $buf =~ /^(\0*)/;
379 4738         20626 my $empty = length($1);
380 4738         12354 $total += $empty;
381              
382 4738 100       25404 if ($empty < $buf_size) {
383 3822         15484 $db->seek($empty - $buf_size, SEEK_CUR);
384 3822         13966 last;
385             }
386             }
387              
388             $total
389 3881         11627 }
390              
391             sub read_key {
392 459856     459856 0 836495 my ($db, $pos, $end_pos) = @_;
393              
394 459856         983135 $db->read_sentinel;
395 459843         1124623 my $next_pos = $db->read_int;
396 459843         1055685 my $key_len = $db->read_int;
397              
398 459843 100       1558002 if (@_ > 1) {
399 71268 50 33     488727 $key_len < 0 || $pos + 9 + $key_len > $end_pos and
400             $db->die("key_len $key_len out of bounds");
401             }
402              
403 459843         1130783 $db->read(my $key, $key_len);
404 459843 50       2480757 wantarray ? ($key, $next_pos, $key_len) : $key
405             }
406              
407             sub read_val {
408 107753     107753 0 267584 my ($db, $key_len, $pos, $end_pos) = @_;
409              
410 107753         492193 $db->align_val($key_len);
411 107753         271464 my $val_hash = $db->read_int;
412 107753         567783 my $val_len = $db->read_int;
413 107753         333667 my $rec_len = $db->rec_len($key_len, $val_len);
414              
415 107753 100       274607 if (@_ > 2) {
416 71268 100 66     608746 $val_len < 0 || $pos + $rec_len > $end_pos and
417             $db->die("val_len $val_len out of bounds");
418             }
419              
420 107751         420041 $db->read(my $val, $val_len);
421 107751 100       495678 wantarray ? ($val, $val_hash, $rec_len) : $val
422             }
423              
424             sub read_rec {
425 3822     3822 0 11353 my ($db, $pos, $end_pos) = @_;
426 3822         19871 my ($key, $next_pos, $key_len) = $db->read_key($pos, $end_pos);
427 3820         20651 my ($val, $val_hash, $rec_len) = $db->read_val($key_len, $pos, $end_pos);
428 3819         21582 ($key, $val, $next_pos, $val_hash, $rec_len)
429             }
430              
431             sub align_val {
432 108104     108104 0 215187 my ($db, $key_len) = @_;
433 108104 50       623766 $db->seek((defined($key_len) ? -$key_len : -$db->tell) % 4, SEEK_CUR);
434             }
435              
436             sub write {
437 27458     27458 0 67145 my ($db, $str) = @_;
438              
439 27458         78373 my $len = length($str);
440 27458         1134698 my $check_len = syswrite($db->{fh}, $str, $len);
441              
442 27458 50       78204 unless ($check_len == $len) {
443 0         0 my $missed = $check_len - $len;
444 0         0 $db->die("cannot write $missed/$check_len bytes");
445             }
446              
447             $len
448 27458         65415 }
449              
450             sub write_byte {
451 0     0 0 0 my ($db, $byte) = @_;
452 0         0 $db->write(pack C => $byte)
453             }
454              
455             sub write_sentinel {
456 0     0 0 0 my $db = shift;
457 0         0 $db->write_byte($sentinel)
458             }
459              
460             sub write_int {
461 13310     13310 0 43728 my ($db, $int) = @_;
462              
463 13310 100       77966 if ($debug) {
464 12808         62264 my $pos = $db->tell;
465 12808 0       66567 $pos % 4 and $db->warn(
    50          
466             "misaligned write_int at $pos" .
467             (defined($ptr_pos) ? " from $ptr_pos" : "")
468             );
469             }
470              
471 13310         134716 $db->write(pack 'N', $int);
472             }
473              
474             sub write_key {
475 0     0 0 0 my ($db, $key) = @_;
476 0         0 my $lkey = pack('N', length($key)) . $key;
477 0         0 $db->write($lkey)
478             }
479              
480             sub write_val {
481 0     0 0 0 my ($db, $val) = @_;
482 0         0 my $val_hash = $db->val_hash($val);
483 0         0 my $lval = pack('NN', $val_hash, length($val)) . $val;
484 0         0 $db->write($lval)
485             }
486              
487             sub write_zero {
488 8953     8953 0 35726 my ($db, $len) = @_;
489 8953         88700 $db->write("\0" x $len)
490             }
491              
492             sub pack_rec {
493 4656     4656 0 14419 my ($db, $key, $val, $next_pos, $val_hash) = @_;
494              
495 4656         22346 my $val_align = "\0" x (-length($key) % 4);
496 4656 100       30562 defined($val_hash) or $val_hash = $db->val_hash($val);
497              
498 4656         121782 my $rec = join '',
499             pack('C', $sentinel),
500             pack('N', $next_pos),
501             pack('N', length($key)), $key,
502             $val_align,
503             pack('N', $val_hash),
504             pack('N', length($val)), $val,
505             ;
506              
507 4656 50       14992 if ($debug) {
508 4656 50       30539 length($rec) == $db->rec_len(length($key), length($val))
509             or $db->warn('record length problem');
510             }
511              
512             $rec
513 4656         20260 }
514              
515             sub write_rec {
516 4912     4912 0 22512 my ($db, $pos, $rec) = @_;
517              
518 4912         14443 $db->seek($pos, SEEK_SET);
519              
520 4912 50       15444 if ($debug) {
521 4912 50       16216 $db->tell % 4 == 3 or $db->warn("writing misaligned record at $pos");
522             }
523              
524 4912         18045 $db->write($rec);
525 4912         52980 $db->sync;
526              
527 4912         108170897 $db->seek($ptr_pos, SEEK_SET);
528 4912         4648925 $db->write_int($pos);
529 4912         24869 $db->sync;
530             }
531              
532             sub append_rec {
533 1935     1935 0 20232 my ($db, $rec) = @_;
534              
535             # prewrite zero for file integrity
536 1935         7463 my $pos = $db->seek(0, SEEK_END);
537 1935         5473 my $align = 3 - $pos % 4;
538 1935         3094 $pos += $align;
539 1935         15178 $db->write_zero($align + length($rec));
540              
541 1935         10478 $db->write_rec($pos, $rec);
542              
543 1935         13629905 $pos
544             }
545              
546             sub move_rec {
547 2977     2977 0 137965 my ($db, $rec, $old_pos, $new_pos) = @_;
548 2977         4839 my $rec_len = length($rec);
549              
550             # always move backwards
551 2977 100       10726 if ($old_pos < $new_pos + $rec_len) {
552             # swap using the end of the file as a buffer
553 256         1486 my $tmp_pos = $db->append_rec($rec);
554 256         4211 $db->erase($old_pos, $rec_len);
555 256         1245 $db->write_rec($new_pos, $rec);
556 256         873823 $db->truncate($tmp_pos);
557             } else {
558 2721         11745 $db->write_rec($new_pos, $rec);
559 2721         27677680 $db->erase($old_pos, $rec_len);
560             }
561            
562 2977         17875 $new_pos
563             }
564              
565             sub replace_val {
566 277     277 0 1639 my ($db, $key, $val, $pos, $next_pos, $old_val_len) = @_;
567              
568 277         1837 my $val_len = length($val);
569 277         2265 my $val_hash = $db->val_hash($val);
570 277         3059 my $rec = $db->pack_rec($key, $val, $next_pos, $val_hash);
571 277         998 my $val_pos = $pos + length($rec) - $val_len - 8;
572              
573 277         1418 my $new_pos = $db->append_rec($rec);
574              
575             # put it back where it was
576 277         2725 $db->seek($val_pos + 8, SEEK_SET);
577 277         4121 $db->write($val . ("\0" x ($old_val_len - $val_len)));
578              
579 277         2790 $db->seek($val_pos, SEEK_SET);
580 277         1250 $db->write_int($val_hash);
581 277         3409 $db->write_int($val_len);
582              
583 277         1559 $db->seek($ptr_pos, SEEK_SET);
584 277         1095 $db->write_int($pos);
585 277         1234 $db->sync;
586              
587 277         1520576 $db->truncate($new_pos);
588              
589 277         1891 $pos
590             }
591              
592             sub lock_ex {
593 9691     9691 0 46864 my $db = shift;
594 9691 100       96204 $$ == $db->{pid} or $db->reopen;
595              
596 9691 100       71231 if ($db->{lock_count} > 0) {
    50          
597             # this is allowed by flock but it releases the LOCK_SH
598             # while waiting for the LOCK_EX to avoid deadlock.
599             # ddb disallows it to avoid any confusion; just
600             # LOCK_UN first if you want the flock behavior.
601 4020 50       105307 $db->{lock_type} == LOCK_EX or $db->die("lock conversion");
602             } elsif ($db->{lock_count} == 0) {
603 5671 50       32418893 RETRY: unless (flock($db->{fh}, LOCK_EX)) {
604 0         0 $db->warn("flock error, retrying: $!");
605 0         0 $db->reopen;
606 0         0 goto RETRY;
607             }
608             } else {
609 0         0 $db->die("negative lock count");
610             }
611              
612 9691         29436 $db->{lock_type} = LOCK_EX;
613 9691         26995 ++$db->{lock_count}
614             }
615              
616             sub lock_sh {
617 101899     101899 0 144844 my $db = shift;
618 101899 50       462447 $$ == $db->{pid} or $db->reopen;
619              
620 101899 100       212621 if ($db->{lock_count} == 0) {
    50          
621 100905 50       95223815 RETRY: unless (flock($db->{fh}, LOCK_SH)) {
622 0         0 $db->warn("flock error, retrying: $!");
623 0         0 $db->reopen;
624 0         0 goto RETRY;
625             }
626 100905         218485 $db->{lock_type} = LOCK_SH;
627             } elsif ($db->{lock_count} < 0) {
628 0         0 $db->die("negative lock count");
629             }
630              
631 101899         198340 ++$db->{lock_count}
632             }
633              
634             sub lock_un {
635 111590     111590 0 190567 my $db = shift;
636              
637 111590 50       491132 if ($db->{lock_count} < 1) {
    100          
638 0         0 $db->warn("no locks held");
639 0         0 flock($db->{fh}, LOCK_UN);
640 0         0 0
641             } elsif ($db->{lock_count} == 1) {
642 106576         1304308 flock($db->{fh}, LOCK_UN);
643 106576         309633 undef $db->{lock_type};
644 106576         264031 --$db->{lock_count}
645             } else {
646 5014         29982 --$db->{lock_count}
647             }
648             }
649              
650 0     0 0 0 sub lock { shift->lock_ex }
651 0     0 0 0 sub unlock { shift->lock_un }
652              
653             # we call this after fork so locks work again
654             sub reopen {
655 502     502 0 1205 my $db = shift;
656              
657 502 100       44561 $db->{fh} and close $db->{fh};
658 502         7754 undef $db->{fh};
659 502 50       36135 if ($db->{lock_count} > 0) {
660 0         0 $db->warn('reopening with held locks');
661 0         0 undef $db->{lock_type};
662 0         0 $db->{lock_count} = 0;
663             }
664              
665 502 50       24564701 sysopen($db->{fh}, $db->{filename}, O_RDWR | O_CREAT) or $db->die;
666 502         4186 binmode $db->{fh};
667              
668 502         10262219 $db->{pid} = $$; # keep track of forks
669              
670 502         4856 $db
671             }
672              
673             sub find {
674 110086     110086 0 211146 my ($db, $key) = @_;
675              
676 110086         272761 my $hash = $db->key_hash($key);
677 110086         309134 $ptr_pos = $db->key_hash_pos($hash);
678              
679 110086         363091 $db->seek($ptr_pos, SEEK_SET);
680 110086         833440 my $pos = $db->read_int;
681 110086         174134 my %loop_test; # debug
682              
683 110086         315804 while ($pos != 0) {
684 388576 50 33     2559991 $pos % 4 == 3 && $pos >= 0 or
685             $db->die("found misaligned record");
686              
687 388576 50       1090589 if ($debug) {
688 388576 50       1682460 $loop_test{$pos}++ and
689             $db->die("loop record");
690             }
691              
692 388576         1016241 $db->seek($pos, SEEK_SET);
693 388576         2873107 my ($check_key, $next_pos) = $db->read_key;
694              
695 388575 100       1874625 $check_key eq $key and
    100          
696             return wantarray ? ($pos, $next_pos) : $pos;
697              
698 280238         471716 $ptr_pos = $pos + 1;
699 280238         793294 $pos = $next_pos
700             }
701              
702             ( )
703 1748         8367 }
704              
705             sub erase {
706 6508     6508 0 21291 my ($db, $pos, $rec_len) = @_;
707 6508         37322 $db->seek($pos, SEEK_SET);
708 6508         158811 $db->write_zero($rec_len);
709 6508         43876 $rec_len
710             }
711              
712             # no rec_len known
713             sub erase_panic {
714 4     4 0 13 my ($db, $pos, $status_cb) = @_;
715 4   50 0   18 $status_cb ||= sub { };
  0         0  
716 4         12 $db->$status_cb(0);
717              
718 4         12 my $end_pos = $db->seek(0, SEEK_END);
719              
720 4         15 local $db->{cur_keys} = [ ];
721 4         27 local $db->{cur_hash} = undef;
722 4         11 local $db->{rec_count} = 0;
723 4         7 my $count = 0;
724            
725 4         7 while (1) {
726 198         569 my ($k_pos, $k) = $db->next_pos;
727 198 100       398 defined($k_pos) or last;
728 194         471 $db->$status_cb(++$count);
729 194 100       422 $k_pos > $pos or next;
730 66 100       148 $k_pos < $end_pos and $end_pos = $k_pos;
731             }
732            
733 4         8 my $rec_len = $end_pos - $pos;
734 4         24 $db->warn("erasing corrupted record at $pos+$rec_len");
735              
736 4         18 $db->seek($pos, SEEK_SET);
737 4         24 $db->write_zero($rec_len);
738              
739 4         24 $rec_len
740             }
741              
742             # during iteration we preload a hash-bucket at a time and
743             # check each key right before returning it.
744              
745             sub next_pos {
746 68378     68378 0 85884 my ($db, $status_cb) = @_;
747 68378   100 20250   1203756 $status_cb ||= sub { };
  20250         30563  
748              
749 68378   50     375951 $db->{cur_keys} ||= [ ];
750 68378         126427 my $cur_keys = $db->{cur_keys};
751 68378         213038 my $end_pos = $db->seek(0, SEEK_END);
752              
753 68378         97845 while (1) {
754 87783         306043 while (defined(my $key = shift @$cur_keys)) {
755 67354         173286 my ($pos, $next_pos) = $db->find($key);
756 67354 100       395035 if (defined($pos)) {
757 67353         93100 ++$db->{rec_count};
758 67353         478100 return ($pos, $key);
759             }
760 1 50       11 $debug and $db->warn("skipping unlinked cached record");
761             }
762              
763 20430 100       57961 $db->{cur_hash} =
764             defined($db->{cur_hash}) ?
765             $db->{cur_hash} + 1 : 0;
766 20430         42705 $db->$status_cb;
767 20430 100       118288 unless ($db->{cur_hash} < $db->{hash_size}) {
768 998         2169 undef $db->{cur_hash};
769 998         4091 return ( );
770             }
771              
772 19432         127953 $ptr_pos = $db->key_hash_pos($db->{cur_hash});
773 19432         45422 $db->seek($ptr_pos, SEEK_SET);
774 19432         53797 my $pos = $db->read_int;
775              
776 19432         27157 my %loop_test; # debug-only
777              
778 19432         48436 while ($pos != 0) {
779 67474 100 100     524679 $pos % 4 == 3 && $pos >= 0 or
780             $db->die("misaligned record");
781              
782 67459 50       153738 if ($debug) {
783 67459 100       317902 $loop_test{$pos}++ and
784             $db->die("loop found");
785             }
786              
787 67458         179045 $db->seek($pos, SEEK_SET);
788 67458         151830 my ($key, $next_pos, $key_len) = $db->read_key($pos, $end_pos);
789              
790 67448 50       204768 if ($debug) {
791 67448 50       562781 $db->{cur_hash} == $db->key_hash($key) or
792             $db->die("key_hash mismatch");
793              
794 67448         189206 my ($val, $val_hash) = $db->read_val($key_len, $pos, $end_pos);
795 67447         169013 my $check_val_hash = $db->val_hash($val);
796 67447 50       191139 $check_val_hash == $val_hash or
797             $db->die("val_hash mismatch");
798             }
799              
800 67447         140755 push @$cur_keys, $key;
801 67447         143260 $ptr_pos = $pos + 1;
802 67447         186645 $pos = $next_pos
803             }
804             }
805             }
806              
807             # scan the data section linearly and remove empty space
808             sub defrag {
809 67     67 0 214 my ($db, $status_cb) = @_;
810 67   50 4153   1346 $status_cb ||= sub { };
  4153         7020  
811              
812 67         307 local $debug = 1;
813              
814 67         389 $db->lock_ex;
815 67         1106 my $end_pos = $db->seek(0, SEEK_END);
816 67         254 $db->$status_cb(0, $end_pos);
817            
818 67         224 my $empty_pos = $db->data_section;
819 67         191 my $empty_len = 0;
820            
821 67         261 while ($empty_pos < $end_pos) {
822 3881         33983 $db->seek($empty_pos + $empty_len, SEEK_SET);
823 3881         17404 $empty_len += $db->read_empty;
824 3881         8973 my $pos = $empty_pos + $empty_len;
825              
826 3881 100       14966 unless ($pos < $end_pos) {
827 59 50       668 $empty_pos < $end_pos and $db->truncate($end_pos = $empty_pos);
828 59         174 last;
829             }
830              
831             sub ep_status_cb
832 4020     4020 0 19533 { shift->$status_cb($empty_pos, $end_pos - $empty_len, @_) }
833 3822         14080 ep_status_cb($db);
834 3822         18376 $ptr_pos = "defrag $pos";
835              
836 3822         18557 $db->lock_ex;
837 3822         9215 my ($key, $val, $next_pos, $val_hash, $rec_len) = eval {
838 3822         19826 $db->read_rec($pos, $end_pos)
839             };
840 3822 100       13532 if ($@) {
841 3         385 warn($@);
842 3         23 $empty_len += $db->erase_panic($pos, \&ep_status_cb);
843 3         17 next;
844             }
845 3819         25178 $db->lock_un;
846              
847 3819         15311 my $check_val_hash = $db->val_hash($val);
848              
849 3819         15737 my $check_pos = $db->find($key);
850 3818 100       11976 unless ($check_pos == $pos) {
851 36 100       112 if ($check_val_hash == $val_hash) {
852 35 50       94 if (defined($check_pos)) {
853             # this can delete indexed data in a pathological case
854             # (a corrupted record with valid hash that overlaps indexed
855             # records, very unlikely by accident). but it's doesn't
856             # have to scan the entire database like erase_panic.
857            
858 0         0 $db->warn("erasing unlinked record at $pos+$rec_len");
859 0         0 $empty_len += $db->erase($pos, $rec_len);
860             } else {
861             # this record is left over from an aborted delete or
862             # part of a chain after an erased corrupted record,
863             # so we relink it.
864              
865 35         259 $db->warn("relinking unlinked record at $pos+$rec_len");
866              
867 35         314 $db->seek($pos + 1, SEEK_SET);
868 35         209 $db->write_int(0);
869              
870 35         94 $db->seek($ptr_pos, SEEK_SET);
871 35         87 $db->write_int($pos);
872 35         121 $db->sync;
873             }
874             } else {
875 1         10 $db->warn("val_hash mismatch at $pos+$rec_len");
876 1         10 $empty_len += $db->erase_panic($pos, \&ep_status_cb);
877             }
878 36         155183 next;
879             }
880              
881 3782 50       9589 $check_val_hash == $val_hash or
882             $db->die("val_hash mismatch");
883              
884 3782         12299 my $align = 3 - $empty_pos % 4;
885 3782         5153 $empty_pos += $align;
886 3782         5418 $empty_len -= $align;
887              
888 3782 100       9669 if ($empty_len > 0) {
889 2977         12901 my $rec = $db->pack_rec($key, $val, $next_pos, $val_hash);
890 2977         10845 $db->move_rec($rec, $pos, $empty_pos);
891             } else {
892             # should never happen
893 805         1116 $empty_len = 0;
894             }
895              
896 3782         29450 $empty_pos += $rec_len;
897             }
898            
899 66         305 $db->sync;
900 66         2625218 $db->$status_cb($end_pos, $end_pos);
901 66         478 $db->lock_un;
902             }
903              
904             # this will null out any pointers to corrupted records
905             sub repair {
906 9     9 0 35 my ($db, $status_cb) = @_;
907 9   50 941   105 $status_cb ||= sub { };
  941         1229  
908              
909 9         21 local $debug = 1;
910 9         36 local $db->{cur_keys} = [ ];
911 9         25 local $db->{cur_hash} = undef;
912 9         23 local $db->{rec_count} = 0;
913              
914 9         39 $db->lock_ex;
915              
916 9         12 while (1) {
917 796         1929 $db->lock_sh;
918 796         890 my $pos = eval { $db->next_pos($status_cb) };
  796         1798  
919              
920 796 100       1832 unless ($@) {
921 770         1626 $db->lock_un;
922 770 100       1782 defined($pos) or last;
923 761         1343 $db->$status_cb;
924 761         998 next;
925             }
926 26         1900 warn $@;
927              
928 26 50       94 unless ($ptr_pos > 0) {
929 0         0 $db->warn("bad ptr $ptr_pos, cannot repair bucket $$db{cur_hash}");
930 0         0 next;
931             }
932              
933             # $db->seek($ptr_pos, SEEK_SET);
934             # my $pos = $db->read_int;
935             # $db->seek($pos, SEEK_SET);
936             # $db->lock_sh;
937             # my ($key, $next_pos) = eval { $db->read_key };
938             # $@ or $db->lock_un;
939             # $next_pos ||= 0;
940             # $next_pos == $ptr_pos - 1 and $next_pos = 0; # loops
941              
942 26         34 my $next_pos = 0;
943 26         143 $db->warn("unlinking from $ptr_pos, to $next_pos (run defrag)");
944 26         93 $db->seek($ptr_pos, SEEK_SET);
945 26         71 $db->write_int($next_pos);
946             }
947              
948 9         28 $db->sync;
949 9         38106 $db->lock_un;
950             }
951              
952             # run a bunch of tests. this will erase your database.
953             sub test {
954 251     251 0 12550 my ($db, $db_hash, $ok_cb) = @_;
955              
956 251 50       1255 ref($db_hash) or $db->die('test requires ref to tied hash');
957 251         1004 local *db = \%$db_hash;
958 251 50       1255 tied(%db) == $db or $db->die('tied hash does not match object');
959              
960 251 0 50 0   1004 $ok_cb ||= sub { $_[2] or $_[0]->die("not ok $_[1]\n") };
  0         0  
961 3072     3072 0 16808 sub ok { $db->$ok_cb(@_) }
962              
963 251     0   16064 local $SIG{PIPE} = sub { };
  0         0  
964 251         1004 local $debug = 1;
965 251         4267 my $procs = 0;
966              
967 251         1004 ok 0, 65;
968              
969             # clear
970 251         8785 $db->{hash_size} = 19;
971 251         1757 %db = ( );
972              
973             # store, fetch, delete, exists
974 251         6275 $db{hello} = 'world';
975 251         3765 ok 1, $db{hello} eq 'world';
976 251         115209 ok 2, 'world' eq delete $db{hello};
977 251         92619 ok 3, !exists $db{hello};
978              
979             # small key and value
980 251         53212 $db{''} = '';
981 251         3263 ok 4, exists $db{''};
982 251         95129 ok 5, defined $db{''};
983 251         39658 ok 6, $db{''} eq '';
984 251         56475 ok 7, '' eq delete $db{''};
985 251         90862 ok 8, keys(%db) == 0;
986              
987             # parallel inserts
988 251         56475 for my $key (1 .. 100) {
989 20150         6076793029 wait, --$procs until $procs < $max_procs;
990 20150 100       167319 ++$procs; fork and next;
  20150         279988199  
991 100         55938 $db{$key} = $key;
992 100         0 exit 0;
993             }
994 151         253262032 --$procs until wait < 0;
995 151         449980 delete $db{50};
996              
997 151         1510 my ($ksum, $vsum);
998 151         2869 $ksum += $_ for keys %db;
999 151         4077 $vsum += $_ for values %db;
1000 151         10117 ok 9, keys(%db) == 99;
1001 151         25217 ok 10, $ksum == 5000;
1002 151         184975 ok 11, $vsum == 5000;
1003              
1004             # swap a bunch of values with recursive locks in parallel
1005 151         1963 for (1 .. 99) {
1006 10098         1891113064 wait, --$procs until $procs < $max_procs;
1007 10098 100       237018 ++$procs; fork and next;
  10098         32821060  
1008 99         26639 my $key1 = 1 + int rand 49;
1009 99         25917 my $key2 = 51 + int rand 49;
1010 99         50413 $db->lock_ex;
1011 99         48784 @db{$key1, $key2} = @db{$key2, $key1};
1012 99         6358 $db->lock_un;
1013 99         0 exit 0;
1014             }
1015 52         93090504 --$procs until wait < 0;
1016              
1017 52         468 my $sum = 0; $sum += $_ for values %db;
  52         2652  
1018 52         6500 ok 12, $sum == 5000;
1019 52         23140 ok 13, scalar grep $_ ne $db{$_}, keys %db; # odd number of swaps
1020              
1021             # remove half the keys, making holes for defragging
1022 52   66     21008 $_ & 1 or delete $db{$_} for 1 .. 100;
1023 52         468 ok 14, keys(%db) == 50;
1024              
1025             # defragging does not change iteration order
1026 52         19552 my $db_str0 = join ":", map "$_-$db{$_}", keys %db;
1027 52         1872 $db->defrag;
1028 52         624 my $db_str1 = join ":", map "$_-$db{$_}", keys %db;
1029 52         1352 ok 15, $db_str0 eq $db_str1;
1030              
1031             # big values
1032 52         17836 my $big = 100000;
1033 52         25012 $db{'x' x $big} = 'y' x $big;
1034 52         5616 ok 16, $db{'x' x $big} eq 'y' x $big;
1035 52         19500 ok 17, $procs == 0;
1036              
1037             # growing values in parallel
1038 52         9828 while (my ($k, $v) = each %db) {
1039 1377         366853050 wait, --$procs until $procs < $max_procs;
1040 1377 100       5218 ++$procs; fork and next;
  1377         24376139  
1041 51         20234 $db{$k} = $v . $v;
1042 51         0 exit 0;
1043             }
1044 1         1566620 --$procs until wait < 0;
1045              
1046 1         23 ok 18, keys(%db) == 51;
1047 1         721 ok 19, $db{'x' x $big} eq 'y' x (2 * $big);
1048 1         348 ok 20, exists $db{51};
1049              
1050             # defrag should shrink after value growth
1051 1         208 my $end0 = $db->seek(0, SEEK_END);
1052 1         13 $db->defrag;
1053 1         8 my $end1 = $db->seek(0, SEEK_END);
1054 1         7 ok 21, $end1 < $end0;
1055              
1056             # but not again
1057 1         387 $db->defrag;
1058 1         5 my $end2 = $db->seek(0, SEEK_END);
1059 1         13 ok 22, $end1 == $end2;
1060              
1061             # clear should truncate
1062 1         394 %db = ('a' .. 'z');
1063 1         18 my $end3 = $db->seek(0, SEEK_END);
1064 1         7 ok 23, $end3 < $end2;
1065 1         348 ok 24, values(%db) == 13;
1066              
1067 1         388 $db->reopen;
1068 1         8 ok 25, join('', map $_ . $db{$_}, sort keys %db) eq join('', 'a' .. 'z');
1069              
1070             # grow a value for a while and add noise in front of it
1071 1         369 %db = ( );
1072 1         21 $db{a} = 'a' x $_ for 1 .. 5;
1073 1         8 my $offset = $db->data_section + 20;
1074 1         3 $offset += 3 - $offset % 4;
1075 1         6 $db->seek($offset, SEEK_SET);
1076 1         9 $db->write(pack('C', $sentinel) . "\x02\x03\x04\x05");
1077              
1078             # defrag should erase the noise and warn
1079 1         8 $db->warn("warnings expected on test 26");
1080 1         8 $db->defrag;
1081 1         9 my $end4 = $db->seek(0, SEEK_END);
1082 1         7 my $check_end4 = $db->data_section;
1083 1         4 $check_end4 += 3 - $check_end4 % 4;
1084 1         7 $check_end4 += $db->rec_len(1, 5);
1085 1         7 ok 26, $end4 == $check_end4;
1086 1         425 ok 27, $db{a} eq 'a' x 5;
1087              
1088 1         206 $db{pack 'C', $_} = $_ for 0 .. 255;
1089 1         14 ok 28, $db{a} == ord 'a';
1090              
1091             # skeet-shooting test
1092 1         50 $db->warn("warnings permitted on test 29");
1093 1         3 my @pid;
1094              
1095 1     0   33 $SIG{ALRM} = sub { };
  0         0  
1096 1         6 for (1 .. $max_procs) {
1097 10 50       12367 if (my $pid = fork) {
1098 10         157 ++$procs;
1099 10         239 push @pid, $pid;
1100 10         136 next;
1101             }
1102 0         0 $db{pack 'C', int rand 256} = 'x';
1103 0         0 exit 0;
1104             }
1105 1         73 undef $SIG{ALRM};
1106              
1107 1         80 while ($procs > 0) {
1108 4         9449 kill ALRM => $_ for @pid;
1109 4         416466 select undef, undef, undef, 0.1;
1110 4         9217 --$procs while waitpid(-1, &WNOHANG) > 0;
1111             }
1112              
1113 1         27 $db->defrag;
1114 1         31 ok 29, join('', sort keys %db) eq pack('C*', 0 .. 255);
1115 1         127 ok 30, $procs == 0;
1116              
1117             # delete future records while iterating
1118 1         16 $db->warn("warnings permitted on test 31");
1119 1         2 my $total = 256;
1120 1         6 while (my ($k, $v) = each %db) {
1121 184         552 my $unp_k2 = 2 * unpack('C', $k);
1122 184         9445 my $k2 = pack('C', $unp_k2);
1123 184 100       1308 if (exists $db{$k2}) {
1124 107         173 --$total;
1125 107         1265 delete $db{$k2};
1126             }
1127             }
1128              
1129 1         14 ok 31, keys(%db) == $total;
1130              
1131 1         462 while (my $k = each %db) { delete $db{$k}; }
  149         3644  
1132 1         8 ok 32, keys(%db) == 0;
1133              
1134 1         350 $db->defrag;
1135 1         9 my $size = $db->seek(0, SEEK_END);
1136 1         7 ok 33, $size == $db->data_section;
1137              
1138 1         455 for (1 .. 100) {
1139 100 100       473 if ($_ & 1) {
1140 50         538 $db{$_} = 'x' x $_;
1141             } else {
1142 50         1074 $db{'x' x $_} = $_;
1143             }
1144             }
1145              
1146 1         13 ok 34, length($db{87}) == 87;
1147 1         358 ok 35, $db{'x' x 50} == 50;
1148              
1149             # link corruption
1150 1         201 %db = (1 .. 200);
1151              
1152 1         58 my ($pos, $next_pos) = $db->find(101);
1153 1         6 ok 36, defined($pos);
1154 1         443 $db->seek($ptr_pos, SEEK_SET);
1155 1         7 $db->write("\xFF" x 4); # oops
1156              
1157 1         3 my ($pos, $next_pos) = $db->find(99);
1158 1         5 ok 37, defined($pos);
1159 1         172 $db->seek($ptr_pos, SEEK_SET);
1160 1         6 $db->write("\xFE" x 4); # oops again
1161              
1162 1         6 $db->warn("warnings expected on test 38");
1163 1         8 $db->repair;
1164 1         7 ok 38, keys(%db) <= 98;
1165 1         564 ok 39, !exists $db{101};
1166 1         213 ok 40, !exists $db{99};
1167              
1168 1         198 $db->warn("warnings expected on test 41");
1169 1         6 $db->defrag;
1170 1         8 ok 41, keys(%db) == 100;
1171              
1172             # no warnings
1173 1         523 my $keys = keys(%db);
1174 1         33 $db->repair;
1175 1         6 $db->defrag;
1176 1         8 ok 42, keys(%db) == $keys;
1177              
1178 1         460 my $end_pos = $db->seek(0, SEEK_END);
1179 1         9 my $key = 'hello';
1180 1         14 $db{$key} = 'world';
1181 1         13 my $keys = keys(%db);
1182 1         140 my ($pos) = $db->find($key);
1183 1         5 ok 43, defined($pos);
1184              
1185             # corrupt a sentinel, expect error
1186 1         354 $db->warn('warnings expected on test 44');
1187 1         5 $db->seek($pos, SEEK_SET);
1188 1         6 $db->write("\x03");
1189 1         2 eval { my @keys = keys %db };
  1         6  
1190 1 50       185 $@ and warn $@;
1191 1         7 ok 44, $@;
1192              
1193             # fix the hash table
1194 1         327 $db->warn('warnings expected on test 45');
1195 1         5 $db->repair;
1196 1         12 ok 45, !exists $db{$key};
1197 1         1936 ok 46, keys(%db) < $keys;
1198              
1199             # fix the data
1200 1         392 $db->warn('warnings expected on test 47');
1201 1         6 my $keys = keys(%db);
1202 1         40 $db->defrag;
1203 1         11 ok 47, keys(%db) == $keys;
1204 1         499 ok 48, $db->seek(0, SEEK_END) == $end_pos;
1205              
1206 1         219 $db->lock_sh;
1207 1         4 my ($k_pos, $k) = $db->next_pos;
1208 1         5 $db->lock_un;
1209 1   33     15 ok 49, defined($k_pos) && defined($k);
1210              
1211             # defrag fails on bad link
1212 1         233 $db->warn('warnings expected on test 50');
1213 1         6 $db->seek($k_pos + 1, SEEK_SET);
1214 1         6 $db->write("\x07" x 4);
1215 1         2 eval { $db->defrag };
  1         6  
1216 1 50       131 $@ and warn $@;
1217 1         7 ok 50, $@;
1218              
1219             # fix it
1220 1         347 $db->warn('warnings expected on test 51');
1221 1         5 $db->repair;
1222 1         7 $db->defrag;
1223 1         10 ok 51, exists $db{$k};
1224              
1225             # write random data
1226 1         362 $db->warn('warnings expected on test 52');
1227 1         5 %db = ( );
1228 1         31 $db{$_} = 'x' x $_ for 1 .. 100;
1229 1         8 $db->seek(1139, SEEK_SET);
1230 1         110 $db->write(pack 'C*', map int(rand(256)), 1 .. 101);
1231              
1232 1         9 $db->repair;
1233 1         8 $db->defrag;
1234              
1235 1         13 my $keys = keys %db;
1236 1         28 ok 52, $keys > 0;
1237 1         408 ok 53, $keys < 100;
1238              
1239             # no warnings
1240 1         168 $db->repair;
1241 1         6 $db->defrag;
1242 1         9 ok 54, keys(%db) == $keys;
1243              
1244             # loop test
1245 1         402 $db{$_} = 'x' x $_ for 1 .. 200;
1246 1         5 my $keys = 200;
1247 1         7 my ($k1_pos, $k1) = $db->next_pos;
1248 1         6 ok 55, defined($k1_pos);
1249 1         359 my ($k2_pos, $k2) = $db->next_pos;
1250 1         5 ok 56, defined($k2_pos);
1251 1         178 my ($k3_pos, $k3) = $db->next_pos;
1252 1         5 ok 57, defined($k3_pos);
1253 1         182 ok 58, $db->{cur_hash} == 0;
1254              
1255 1         175 $db->warn('warnings expected on test 59');
1256 1         6 $db->seek($k2_pos + 1, SEEK_SET);
1257 1         6 $db->write_int($k1_pos);
1258 1         6 $db->repair;
1259 1         8 $db->defrag;
1260              
1261 1         13 ok 59, exists $db{$k1};
1262 1         359 ok 60, exists $db{$k2};
1263 1         312 ok 61, exists $db{$k3};
1264 1         455 ok 62, keys(%db) == $keys;
1265              
1266             # ultimate test
1267 1         443 $db->warn('warnings expected on test 63');
1268 1         6 $db->seek(8, SEEK_SET);
1269 1         10 $db->write_int(int rand(1 << 16)) for 1 .. 3000;
1270              
1271 1         9 $db->seek($db->key_hash_pos($db->key_hash('hello')), SEEK_SET);
1272 1         5 $db->write_int(0);
1273 1         14 $db{hello} = 'world';
1274              
1275 1         12 $db->repair;
1276 1         7 $db->defrag;
1277 1         13 ok 63, $db{hello} eq 'world';
1278              
1279             # no warnings or truncation
1280 1         374 my $size = $db->seek(0, SEEK_END);
1281 1         13 $db->repair;
1282 1         5 $db->defrag;
1283 1         4 ok 64, $db->seek(0, SEEK_END) == $size;
1284 1         259 ok 65, keys(%db) == 1;
1285              
1286 1         237 1
1287             }
1288              
1289             1
1290             # the end