File Coverage

blib/lib/SimpleCDB.pm
Criterion Covered Total %
statement 196 247 79.3
branch 65 118 55.0
condition 14 32 43.7
subroutine 28 34 82.3
pod 0 4 0.0
total 303 435 69.6


line stmt bran cond sub pod time code
1             package SimpleCDB;
2             #
3             ########################################################################
4             #
5             # Perl-only Constant Database
6             # (c) Benjamin D. Low
7             #
8             # See end of file for pod documentation.
9             # See HISTORY file for commentary on major developments.
10             #
11             ########################################################################
12             #
13              
14 7     7   36 use strict;
  7         8  
  7         234  
15              
16             # prefer 5.004, but can do with 5.003
17             # - all the comments in this file re. 5.003 are with respect to a
18             # Solaris 2.5.1 machine. It may well be the issues are the fault
19             # of the o/s, not perl - YMMV.
20 7     7   186 use 5.003;
  7         22  
  7         241  
21              
22 7     7   36 use Carp;
  7         7  
  7         649  
23              
24 7     7   6226 use Tie::Hash;
  7         7030  
  7         212  
25              
26 7     7   43 use vars qw/@ISA @EXPORT $VERSION $DEBUG/;
  7         10  
  7         548  
27              
28 7     7   30 use Exporter ();
  7         14  
  7         305  
29             @ISA = qw/Exporter Tie::Hash/;
30             @EXPORT = @Fcntl::EXPORT;
31              
32             $VERSION = '1.0';
33              
34 7     7   35 use vars qw/$NFILES $SEP $METAFILE $LOCKRDTIMEOUT $LOCKWRTIMEOUT $ERROR/;
  7         13  
  7         1512  
35             $NFILES = 16;
36             $SEP = "\x00"; # default separator
37             $METAFILE = '_info'; # info about the DB, reqd for reading
38              
39             $LOCKRDTIMEOUT = 5; # how long to block for read access
40             $LOCKWRTIMEOUT = 900; # " write "
41              
42             $ERROR = undef; # error message
43              
44             BEGIN
45             {
46 7     7   21 my @flock = qw/:DEFAULT/;
47 7 50       42 if ($] >= 5.004) # should have a complete Fcntl
48             {
49 7         268 push @flock, ':flock';
50             }
51             else # hope for the best...
52             {
53             sub LOCK_SH () { 1 };
54             sub LOCK_EX () { 2 };
55             sub LOCK_NB () { 4 };
56             sub LOCK_UN () { 8 };
57             }
58              
59 7     7   30 use Fcntl @flock;
  7         8  
  7         29939  
60             }
61              
62             # don't let POSIX's EXPORT list (:flock) clash w/ Fcntl
63 7     7   6555 { package SimpleDB::POSIX; use POSIX; }
  7         139495  
  7         58  
64              
65             BEGIN
66             # what to do if EWOULDBLOCK isn't defined...
67             # - unfortunately different systems have different values for
68             # EWOULDBLOCK (11 on Solaris/Linux, 246 on HP/UX). Oh well.
69             {
70 7     7   60335 no strict 'subs';
  7         21  
  7         366  
71              
72             #print "POSIX::EWOULDBLOCK is " . (eval 'POSIX::EWOULDBLOCK'
73             # eq 'POSIX::EWOULDBLOCK' ? 'not ' : '') . "defined\n";
74              
75             # if EWOULDBLOCK is defined (as a sub, string, or whatever),
76             # the test eval will pick it up, otherwise it'll just return
77             # the string
78 7 50   7   7668 eval 'package POSIX; sub EWOULDBLOCK() { 11 } '
79             if (eval 'POSIX::EWOULDBLOCK' eq 'POSIX::EWOULDBLOCK');
80             }
81              
82 7     7   14045 use FileHandle; # IO::File wasn't around for < 5.004
  7         130980  
  7         51  
83              
84 0 0 0 0 0 0 sub debug ($@) { $^W=0; if ($DEBUG and $_[0]<=$DEBUG) { shift; warn @_,"\n" } }
  0         0  
  0         0  
  0         0  
85              
86             my $digest; # sub ref to routine to create a hash of a string
87             my %_digest = # randomly sorted mapping of decimal -> hex numbers
88             qw/
89             0 fc 1 81 2 ab 3 c8 4 82 5 ad 6 f2 7 ff 8 c2 9 bd
90             10 dd 11 84 12 dc 13 a2 14 db 15 c9 16 a1 17 b5 18 d9 19 b4
91             20 d7 21 ae 22 ce 23 92 24 cd 25 99 26 87 27 c1 28 a7 29 a5
92             30 bf 31 8e 32 e6 33 e7 34 ea 35 98 36 f5 37 f9 38 fb 39 df
93             40 cb 41 d2 42 8f 43 d5 44 b2 45 da 46 b9 47 0d 48 0e 49 11
94             50 12 51 14 52 17 53 19 54 1a 55 1b 56 1c 57 1e 58 1f 59 20
95             60 21 61 22 62 23 63 24 64 25 65 26 66 27 67 28 68 2a 69 2b
96             70 2c 71 2d 72 2f 73 30 74 31 75 32 76 33 77 34 78 35 79 37
97             80 39 81 3a 82 3b 83 3d 84 3e 85 40 86 41 87 42 88 43 89 45
98             90 46 91 48 92 4d 93 4f 94 51 95 52 96 55 97 56 98 57 99 58
99             100 59 101 5c 102 5d 103 5f 104 60 105 61 106 62 107 64 108 66 109 67
100             110 68 111 6b 112 6c 113 6d 114 6e 115 6f 116 70 117 71 118 72 119 73
101             120 74 121 76 122 79 123 7b 124 7c 125 7d 126 7e 127 7f 128 80 129 83
102             130 85 131 86 132 88 133 89 134 8a 135 8b 136 8c 137 8d 138 90 139 91
103             140 93 141 94 142 95 143 96 144 97 145 9a 146 9b 147 9c 148 9d 149 9e
104             150 9f 151 a0 152 a3 153 a4 154 a6 155 a8 156 a9 157 aa 158 ac 159 af
105             160 b0 161 b1 162 b3 163 b6 164 b7 165 b8 166 ba 167 bb 168 bc 169 be
106             170 c0 171 c3 172 c4 173 c5 174 c6 175 c7 176 ca 177 cc 178 cf 179 d0
107             180 d1 181 d3 182 d4 183 d6 184 d8 185 de 186 e0 187 e1 188 e2 189 e3
108             190 e4 191 e5 192 00 193 01 194 e8 195 e9 196 02 197 eb 198 ec 199 ed
109             200 ee 201 ef 202 f0 203 f1 204 04 205 f3 206 f4 207 05 208 f6 209 f7
110             210 f8 211 07 212 fa 213 09 214 0a 215 fd 216 fe 217 0c 218 16 219 1d
111             220 5e 221 13 222 2e 223 69 224 15 225 0f 226 10 227 08 228 47 229 03
112             230 75 231 44 232 78 233 38 234 50 235 6a 236 4c 237 36 238 7a 239 29
113             240 5b 241 18 242 4b 243 5a 244 4a 245 49 246 63 247 54 248 0b 249 77
114             250 3f 251 65 252 53 253 06 254 3c 255 4e
115             /;
116              
117             BEGIN
118             {
119 7 50   7   44788 if (eval 'use Digest::MD5 (); 1')
  7     7   65  
  7         21  
  7         57  
120             {
121             #debug 1, "using Digest::MD5";
122 7         27303 $digest = \&Digest::MD5::md5_hex;
123             }
124             else
125              
126             # crypt is waaaayyyyy too slow (which is to be expected I suppose, presumably
127             # it's purposely designed to be an expensive operation :-)
128             # else
129             # {
130             # # resort to crypt - both much slower and less rigorous than Digest::MD5
131             # # - fudge crypt's output to be a hex string, skipping the salt and
132             # # omitting the tail fractional-byte
133             # #debug 1, "using crypt";
134             # $digest = sub { unpack('@1H10', pack('H*', crypt ($_[0], 'kylan'))) };
135             # }
136             {
137             $digest = sub
138             {
139             # yeah, well, this works but no guarantees
140 0   0     0 my $d = $_[0] || 'bcc3b7b7b80';
141 0         0 my $cs = unpack('%16C*', $d) . length($d);
142 0         0 join '', map {$_digest{(($_ ^ $cs) + 3) % 256}} unpack('C*', $d);
  0         0  
143 0         0 };
144             }
145             }
146              
147             sub digest
148             # returns a hex-encoded digest of a string, of a given length
149             {
150 181642     181642 0 472941 local $^W = 0;
151             # 5.003 doesn't support the $c->() syntax
152 181642 100       256214 substr (&{$digest}($_[0]), 0, $_[1]) || '0';
  181642         1098912  
153             }
154              
155             sub newFileHandle ($;$$)
156             {
157             # 5.003's FileHandle doesn't support 'perm' field
158 933 50   933 0 11285 return ($] >= 5.004) ? new FileHandle (@_) : new FileHandle ($_[0], $_[1]);
159             }
160              
161             sub _lock
162             {
163 138     138   328 my ($s, $op) = @_;
164 138         240 my $l;
165             eval
166 138         452 {
167 138     0   5177 local $SIG{ALRM} = sub { $! = POSIX::EWOULDBLOCK; die "$!\n" };
  0         0  
  0         0  
168 138 100       2190 alarm(($op & LOCK_EX) ? $s->{wrt} : $s->{rdt});
169 138         5707720 $l = flock($s->{lockfh}, $op);
170 138         3577 alarm(0);
171             };
172 138 50       1053 if ($@) { chomp $@; } elsif (!$l) { $@ = "$!" }
  0 50       0  
  0         0  
173 138         853 return $l;
174             }
175              
176             sub TIEHASH
177             # args compatible w/ DB_File:
178             # dir - where to put files
179             # flags - file open (DB) flags
180             # perms - file creation permissions
181             # plus:
182             # nfiles - number of files to use when creating the DB (rounded to power of 16)
183             # sep - character to use as the internal field separator
184             #
185             # - to avoid problems with system file decriptor limits, files are opened
186             # and closed as-needed in the access routines.
187             {
188 138     138   674 $ERROR = undef;
189              
190 138         1117 my ($class, $dir, $flags, $perms, $nfiles, $sep, $rdt, $wrt) = @_;
191 138   50     1170 $dir ||= '.';
192 138 100       588 $flags |= O_CREAT if ($flags & O_WRONLY);
193 138   50     1899 $perms ||= 0666; # don't restrict the user's umask
194 138   33     1013 $nfiles ||= $NFILES;
195 138   33     990 $sep ||= $SEP;
196              
197 138   33     904 $rdt ||= $LOCKRDTIMEOUT;
198 138   33     814 $wrt ||= $LOCKWRTIMEOUT;
199              
200             #debug 2, sprintf 'TIEHASH (%s, %s, 0x%x, %s, %d, \x%x)',
201             # $class, $dir, $flags, $perms, $nfiles, ord($sep);
202              
203 138 50       1488 $ERROR = 'must specify flags', return undef unless defined $flags;
204 138 50       639 $ERROR = 'invalid flag: O_APPEND', return undef if ($flags & O_APPEND);
205              
206             # check base directory exists, create if necessary
207 138 100       4991 unless (-d $dir)
208 1 50       116 { mkdir($dir, $perms|0700) or $ERROR="mkdir failed: $!", return undef; }
209              
210 138         588 my $s = {}; # object data
211              
212 138         756 $s->{rdt} = $rdt;
213 138         432 $s->{wrt} = $wrt;
214              
215             # acquire lock (held till object is destroyed)
216 138 100       602 if ($flags & (O_WRONLY|O_RDWR))
217             {
218 10 50       76 $s->{lockfh} = newFileHandle ("$dir/$METAFILE",
219             O_WRONLY|O_TRUNC|O_CREAT, $perms)
220             or $ERROR="could not write [$dir/$METAFILE]: $!", return undef;
221              
222             # there may be current readers, wait my turn
223 10 50       3278 _lock($s, LOCK_EX) or $ERROR = "lock_ex failed: $@", return undef;
224              
225             #debug 1, 'LOCK_EX';
226             # nfiles, sep written to info file at end of this sub
227             }
228             else
229             {
230             # well, how about that - yet another broken part of 5.003
231             # - flock won't give you a shared lock (gives errno = "Bad file
232             # number", as if the file mode was wrong (e.g. EX lock on a
233             # readonly file)). Anyhow, using a O_RDWR file partially works -
234             # you get an exclusive lock (even for LOCK_SH).
235             # - want to avoid a reader waiting for a database update (which can
236             # take quite a while), so don't block (for long)
237 128 50       1001 my $m = ($] < 5.004) ? O_RDWR : O_RDONLY;
238 128 50       1913 $s->{lockfh} = newFileHandle ("$dir/$METAFILE", $m, $perms)
239             or $ERROR="could not read [$dir/$METAFILE]: $!", return undef;
240              
241 128 50       27003 _lock($s, LOCK_SH) or $ERROR = "lock_sh failed: $@", return undef;
242              
243             #debug 1, 'LOCK_SH';
244 128         12540 $nfiles = $s->{lockfh}->getline();
245 128         14027 $sep = $s->{lockfh}->getline();
246 128 50 33     4898 $ERROR = "invalid info file [$dir/$METAFILE]", return undef
247             unless (defined $nfiles and defined $sep);
248 128         380 chomp $nfiles;
249 128         639 chomp $sep;
250             }
251              
252 138         415 $s->{perms} = $perms;
253 138         813 $s->{nfiles} = $nfiles;
254 138         707 $s->{sep} = $sep;
255 138         512 $s->{sep_ord} = ord($sep);
256              
257 138 50       1236 $ERROR = "invalid number of files [$nfiles]", return undef
258             unless $nfiles =~ /^[1-9]\d*$/;
259              
260 138         755 $s->{dir} = $dir;
261             # hang on to open flags, for use in _open
262             # - exclude TRUNC and friends (files may be opened multiple times)
263 138         393 $s->{fflags} = $flags & (O_RDONLY | O_WRONLY | O_RDWR | O_CREAT);
264              
265             # create the file/s (round nfiles up to a power of 16)
266             # - a digest length of 0 is fine (nfiles == 1)
267 138         2638 $s->{dlen} = POSIX::ceil(log($nfiles)/log(16)); # filename/digest len
268 138         538 $nfiles = 16 ** $s->{dlen};
269             #debug 6, "digest length = [$s->{dlen}], nfiles = [$nfiles]";
270              
271 138         427 $s->{f} = {}; # digest => filename
272 138         463 $s->{fh} = {}; # digest => filehandle
273 138         323 $s->{fpos} = {}; # digest => fileposition
274 138         380 $s->{dlist} = []; # list of digest values
275              
276 138         263 my $i;
277 138         558 for ($i = 0; $i < $nfiles; $i++)
278             {
279             # 5.003's printf doesn't support '*'
280             #my $d = sprintf ('%0*x', $s->{dlen}, $i);
281 2208   50     11771 my $d = substr(('0' x 16) . sprintf ('%x', $i), -($s->{dlen}||1));
282 2208         3703 my $f = "$dir/$d";
283             #debug 6, "filename [$f]";
284              
285             # hang on to the digest values + filenames for _open
286 2208         2271 push (@{$s->{dlist}}, $d);
  2208         5327  
287 2208         9372 $s->{f}{$d} = $f;
288              
289 2208 100       24394 truncate($f, 0) if ($flags & O_TRUNC); # start afresh if required
290             }
291              
292 138 100       441 if ($flags & (O_WRONLY|O_RDWR))
293             {
294 10         179 $s->{lockfh}->print($nfiles . "\n");
295 10         234 $s->{lockfh}->print($sep . "\n");
296             }
297              
298 138         1335 bless $s, $class;
299             }
300              
301             #my $_opens = 0;
302              
303             sub _open
304             # open a DB file, transparently staying within the system file descriptor
305             # limits
306             {
307 795     795   1553 my ($self, $d) = @_;
308 795         1283 my $fh;
309              
310 795         1006 my $n = int rand @{$self->{dlist}};
  795         2244  
311 795         1977 my $i = 0;
312 795         1548 while ($i < @{$self->{dlist}})
  795         2241  
313             {
314 795         4407 $fh = $self->{fh}{$d} =
315             newFileHandle ($self->{f}{$d}, $self->{fflags}, $self->{perms});
316 795 50       114965 last if defined ($fh); # good, opened the file
317              
318 0 0       0 last unless $! == POSIX::EMFILE; # abort on any other condition
319              
320             # if we're out of descriptors, close a random file to free one up
321             # - would like to just grab the next one off the fh hash, but can't
322             # efficiently use 'each' over the filehandles hash as there's
323             # no simple way to reset the iterator
324             # - use a separate array containing digest values
325             # - remember the file position of closed files, to restore later
326 0         0 my $t = $self->{dlist}[($n + $i) % @{$self->{dlist}}]; # target
  0         0  
327 0 0       0 if (defined $self->{fh}{$t})
328             {
329             #debug 5, "\$! = EMFILE -> closing [$t] (" .
330             # fileno($self->{fh}{$t}) . ")";
331 0         0 $self->{fpos}{$t} = $self->{fh}{$t}->tell();
332 0         0 close($self->{fh}{$t});
333 0         0 $self->{fh}{$t} = undef;
334             }
335 0         0 $i++;
336             }
337              
338 795 50       1902 $ERROR = "could not open [$self->{f}{$d}]: $!", return undef
339             unless defined $fh;
340              
341             # reposition the file pointer
342 795 50       1257 $fh->seek(0, ${$self->{fpos}}{$d}) if defined ${$self->{fpos}}{$d};
  0         0  
  795         3473  
343              
344             #$_opens++;
345              
346             #debug 4, "opened [$d] (" . fileno($fh) . ")";
347              
348 795         3621 return $fh;
349             }
350              
351             sub _escape ($;$$)
352             # 'special' characters (newlines and the field separator) need to be escaped
353             # when they appear within a hash key or value
354             # - these special values are replaced with their 'base64' encoding
355             # - further, special note must be made for undef and empty strings, I use the
356             # _ and - characters to do this, and escape them if present in the 'user'
357             # string
358             {
359 362642 50   362642   1240783 if (not defined $_[0]) { $_[0] = '-' } # undef
  0 50       0  
    50          
    50          
360 0         0 elsif ($_[0] eq '' ) { $_[0] = '_' } # empty string
361 0         0 elsif ($_[0] eq '_') { $_[0] = '%5F' }
362 0         0 elsif ($_[0] eq '-') { $_[0] = '%2D' }
363             else # non-empty, incl "false" (e.g. '0')
364             {
365 362642         781156 $_[0] =~ s/%/%25/sg; # percents
366 362642         736331 $_[0] =~ s/\n/%0a/sg; # newlines
367 362642 100       994071 $_[0] =~ s/\Q$_[1]\E/\%{$_[2]}/sge if $_[1]; # separator
  0         0  
  0         0  
368             }
369             }
370              
371             sub _unescape ($)
372             {
373 242641 50   242641   565279 if ($_[0] eq '_') { $_[0] = '' }
  0 50       0  
374 0         0 elsif ($_[0] eq '-') { $_[0] = undef }
  242641         567200  
375 120018         481051 else {$_[0] =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg }
376             }
377              
378             sub FETCH
379             {
380 121642     121642   140898 $ERROR = undef;
381              
382 121642         156787 my ($self, $key) = @_;
383              
384             # is this a call via NEXTKEY?
385 121642 100       260654 if (exists $self->{nextval})
386             {
387             #debug 6, " record cached via NEXTKEY";
388 121000         169003 my $v = $self->{nextval};
389 121000         184215 delete $self->{nextval}; # make sure stale results don't arise
390 121000         451926 return $v;
391             }
392              
393 642 50       2953 croak("FETCH: DB is WRONLY") if ($self->{fflags} & O_WRONLY);
394              
395 642         3193 my $d = digest($key, $self->{dlen});
396              
397             # escape newlines and separators
398             # - as per STORE - compare apples with apples
399 642         3213 _escape($key, $self->{sep}, $self->{sep_ord});
400              
401             #debug 2, "FETCH ($self, $key [$d])";
402              
403 642 100       4326 my $fh = defined $self->{fh}{$d} ? $self->{fh}{$d} : _open($self, $d);
404 642 50       1785 return undef unless defined $fh;
405             #debug 4, " fileno: " . fileno($fh);
406              
407 642         4573 $fh->seek(0, 0); # rewind
408 642         8910 my $l = $self->{cache}; # last line read is cached, presuming multiple reads
409             #debug 6, " line cached" if defined $l;
410 642 100       9642 $l = $fh->getline() unless defined $l;
411 642         7783 while (defined $l)
412             {
413             #debug 9, " at " . $fh->tell();
414 411336 100       15832413 last if $l =~ /^\Q$key$self->{sep}\E/;
415 410695         11173384 $l = $fh->getline();
416             }
417              
418 641 50       4540 if ($l)
419             {
420 641         2833 $self->{cache} = $l;
421              
422             #debug 3, " found at " . $fh->tell();
423 641         2303 chomp $l;
424 641         4553 my ($k, $v) = split($self->{sep}, $l, 2);
425 641         3222 _unescape($v);
426 641         5621 return $v;
427             }
428             else
429             {
430             #debug 3, "\tkey not found";
431 0         0 return undef;
432             }
433             }
434              
435             sub EXISTS
436             {
437 0     0   0 $ERROR = undef;
438              
439 0         0 my ($self, $key) = @_;
440              
441 0 0       0 croak("EXISTS: DB is WRONLY") if ($self->{fflags} & O_WRONLY);
442              
443 0         0 my $d = digest($key, $self->{dlen});
444              
445             # escape newlines and separators
446             # - as per STORE - compare apples with apples
447 0         0 _escape($key, $self->{sep}, $self->{sep_ord});
448              
449             #debug 2, "EXISTS ($self, $key [$d])";
450              
451 0 0       0 my $fh = defined $self->{fh}{$d} ? $self->{fh}{$d} : _open($self, $d);
452 0 0       0 return undef unless defined $fh;
453             #debug 4, " fileno: " . fileno($fh);
454              
455 0         0 $fh->seek(0, 0); # rewind
456 0         0 my $l;
457 0         0 while (defined ($l = $fh->getline()))
458             {
459 0 0       0 last if $l =~ /^\Q$key$self->{sep}\E/;
460             }
461 0 0       0 $self->{cache} = $l if defined $l; # cache for FETCH, if found
462              
463             #debug 3, $_ ? " found at " . $fh->tell() : " not found";
464              
465             # returning undef seems to cause perl to try to FETCH the key
466             # - presumably this is some kind of fall-back if the exists operator
467             # "fails"
468 0 0       0 return $l ? 1 : 0;
469             }
470              
471             sub _nextfile
472             # find and open the next non-null file
473             # - i.e. skip files which don't exist (recall that files are opened (created)
474             # on-demand)
475             # - note that a file may be used (contain data), but not open
476             {
477 119     119   263 my ($self) = @_;
478 119         182 my $fh;
479              
480 119 100       393 $self->{'next'} = 0 unless defined $self->{'next'};
481              
482 119   100     469 while (not defined $fh and $self->{'next'} < @{$self->{dlist}})
  119         534  
483             {
484             # lookup next fh hash key
485 112         399 my $d = $self->{dlist}->[$self->{'next'}];
486              
487             # if fh=defined, file is already open (which also implies it exists :-)
488             # - otherwise, open the filename, if it exists
489 112 50       437 unless (defined ($fh = $self->{fh}{$d}))
490             {
491 112 50       4614 $fh = _open($self, $d) if -e $self->{f}{$d};
492             }
493 112         449 $self->{'next'}++; # get ready for next time round
494             }
495             #warn "_next = " . ($self->{'next'} - 1) . "\n" if $fh;
496 119         3297 return $fh;
497             }
498              
499             sub FIRSTKEY
500             {
501 7     7   8 my $self = shift;
502              
503 7 50       35 croak("FIRSTKEY: DB is WRONLY") if ($self->{fflags} & O_WRONLY);
504              
505             #debug 2, "FIRSTKEY ($self)";
506              
507             # find the first file
508 7         15 $self->{'next'} = undef; # index into $self->{dlist}
509 7         27 $self->{NEXTKEYfh} = _nextfile($self);
510              
511 7         22 NEXTKEY($self);
512             }
513              
514             sub NEXTKEY
515             # return the 'next' key in an iteration sequence started via FIRSTKEY
516             # - perl will call FETCH on this key to extract the value
517             # - kind of wasteful, would end up doing multiple reads for the same
518             # piece of data, so cache the result (carefully - the value may well
519             # be undef)
520             {
521 121007     121007   139511 $ERROR = undef;
522              
523 121007         131986 my $self = shift; # 'lastkey' is unused
524              
525             #debug 2, "NEXTKEY ($self) [$self->{'next'}]";
526              
527             # read next record, over all files
528 121007         112984 my $l;
529 121007         160007 my $fh = $self->{NEXTKEYfh}; # initialised by FIRSTKEY
530              
531 121007   100     2991933 while (defined $fh and not defined ($l = $fh->getline()))
532             {
533 112         4726 $fh = $self->{NEXTKEYfh} = _nextfile($self);
534             }
535 121007 100       3788555 return undef unless defined $l;
536              
537 121000         144960 chomp $l;
538 121000         410991 my ($k, $v) = split($self->{sep}, $l, 2);
539              
540             # unescape key and value
541             # - value is 'cached' to be returned by the next FETCH
542 121000         229448 _unescape($k);
543 121000         181629 _unescape($v);
544              
545 121000         296377 $self->{nextval} = $v;
546             # undef keys will cause perl to stop iterating, thinking NEXTKEY
547             # has finished... (need an "undef but true" value :-)
548             # - this creates a small discrepancy in that you can directly STORE
549             # and FETCH undef and empty keys, but both return empty strings
550 121000 50       632252 return defined $k ? $k : '';
551             }
552              
553             sub STORE
554             {
555 181000     181000   234311 $ERROR = undef;
556              
557 181000         316398 my ($self, $key, $value) = @_;
558              
559 181000 50       481707 croak("STORE: DB is RDONLY") unless ($self->{fflags} & (O_WRONLY|O_RDWR));
560              
561 181000         391885 my $d = digest($key, $self->{dlen});
562              
563             # escape newlines and separators
564 181000         480155 _escape($key, $self->{sep}, $self->{sep_ord});
565              
566             #debug 2, "STORE ($self, $key [$d])";
567              
568 181000 100       505846 my $fh = defined $self->{fh}{$d} ? $self->{fh}{$d} : _open($self, $d);
569 181000 50       350470 return undef unless defined $fh;
570             #debug 4, " fileno: " . fileno($fh);
571              
572             # only do newlines for value
573 181000         288093 _escape($value);
574              
575 181000         405824 my $s = join($self->{sep}, $key, $value);
576              
577 181000         497209 $fh->seek(0,2);
578 181000         4100823 $fh->print($s . "\n");
579             }
580              
581              
582             sub DESTROY
583             {
584 138     138   412 $ERROR = undef;
585              
586 138         272 my ($self) = @_;
587             #debug 2, join(', ', 'DESTROY', @_);
588             #debug 3, "$_opens opens";
589             #debug 4, "currently opened files = " .
590             # scalar map {defined $self->{fh}{$_}} keys %{$self->{fh}};
591              
592 138 50       357 map {close $self->{fh}{$_} if defined $self->{fh}{$_}} keys %{$self->{fh}};
  795         32306  
  138         1601  
593              
594             #debug 1, 'LOCK_UN';
595              
596 138         60893 flock($self->{lockfh}, LOCK_UN);
597 138         8971 close($self->{lockfh});
598             }
599              
600             sub nop
601             {
602 0     0 0   $ERROR = undef;
603              
604 0           my ($self, $method) = @_;
605              
606 0           croak ref($self) . " does not define the method ${method}";
607             }
608              
609 0     0     sub CLEAR { my $self = shift; $self->nop("CLEAR") }
  0            
610 0     0     sub DELETE { my $self = shift; $self->nop("DELETE") }
  0            
611              
612             1; # return true, as require requires
613              
614             __END__