File Coverage

blib/lib/FlatFile/DataStore.pm
Criterion Covered Total %
statement 728 777 93.6
branch 304 378 80.4
condition 23 40 57.5
subroutine 87 88 98.8
pod 36 72 50.0
total 1178 1355 86.9


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package FlatFile::DataStore;
3             #---------------------------------------------------------------------
4              
5             =head1 NAME
6              
7             FlatFile::DataStore - Perl module that implements a flatfile
8             datastore.
9              
10             =head1 SYNOPSYS
11              
12             use FlatFile::DataStore;
13              
14             # new datastore object
15              
16             my $dir = "/my/datastore/directory";
17             my $name = "dsname";
18             my $ds = FlatFile::DataStore->new( { dir => $dir, name => $name } );
19              
20             # create a record
21              
22             my $record_data = "This is a test record.";
23             my $user_data = "Test1";
24             my $record = $ds->create( {
25             data => \$record_data,
26             user => $user_data,
27             } );
28             my $record_number = $record->keynum;
29              
30             # retrieve it
31              
32             $record = $ds->retrieve( $record_number );
33              
34             # update it
35              
36             $record->data( "Updating the test record." );
37             $record = $ds->update( $record );
38              
39             # delete it
40              
41             $record = $ds->delete( $record );
42              
43             # get its history
44              
45             my @records = $ds->history( $record_number );
46              
47             =head1 DESCRIPTION
48              
49             FlatFile::DataStore implements a simple flatfile datastore. When you
50             create (store) a new record, it is appended to the flatfile. When you
51             update an existing record, the existing entry in the flatfile is
52             flagged as updated, and the updated record is appended to the
53             flatfile. When you delete a record, the existing entry is flagged as
54             deleted, and a "delete record" is I to the flatfile.
55              
56             The result is that all versions of a record are retained in the
57             datastore, and running a history will return all of them. Another
58             result is that each record in the datastore represents a transaction:
59             create, update, or delete.
60              
61             Methods support the following actions:
62              
63             - create
64             - retrieve
65             - update
66             - delete
67             - history
68              
69             Additionally, FlatFile::DataStore::Utils provides the
70             methods
71              
72             - validate
73             - migrate
74              
75             and others.
76              
77             See FlatFile::DataStore::Tiehash for a tied interface.
78              
79             =head1 VERSION
80              
81             FlatFile::DataStore version 1.03
82              
83             =cut
84              
85             our $VERSION = '1.03';
86              
87 23     23   1013213 use 5.008003;
  23         110  
  23         1283  
88 23     23   162 use strict;
  23         45  
  23         854  
89 23     23   137 use warnings;
  23         72  
  23         880  
90              
91 23     23   135 use URI::Escape;
  23         55  
  23         1635  
92 23     23   146 use File::Path;
  23         67  
  23         1367  
93 23     23   125 use Fcntl qw(:DEFAULT :flock);
  23         42  
  23         13832  
94 23     23   156 use Digest::MD5 qw(md5_hex);
  23         87  
  23         1576  
95 23     23   135 use Carp;
  23         42  
  23         1427  
96              
97 23     23   26746 use FlatFile::DataStore::Preamble;
  23         139  
  23         1046  
98 23     23   21885 use FlatFile::DataStore::Record;
  23         66  
  23         700  
99 23     23   16381 use FlatFile::DataStore::Toc;
  23         77  
  23         1186  
100 23     23   190 use Math::Int2Base qw( base_chars int2base base2int );
  23         50  
  23         2633  
101              
102 23     23   132 use Data::Omap qw( :ALL );
  23         187  
  23         404969  
103             sub untaint;
104              
105             #---------------------------------------------------------------------
106             # globals:
107              
108             my %Preamble = qw(
109             indicator 1
110             transind 1
111             date 1
112             transnum 1
113             keynum 1
114             reclen 1
115             thisfnum 1
116             thisseek 1
117             prevfnum 1
118             prevseek 1
119             nextfnum 1
120             nextseek 1
121             user 1
122             );
123              
124             my %Optional = qw(
125             dirmax 1
126             dirlev 1
127             tocmax 1
128             keymax 1
129             prevfnum 1
130             prevseek 1
131             nextfnum 1
132             nextseek 1
133             userdata 1
134             );
135              
136             # attributes that we generate (vs. user-supplied)
137             my %Generated = qw(
138             uri 1
139             crud 1
140             userlen 1
141             dateformat 1
142             specs 1
143             regx 1
144             preamblelen 1
145             fnumlen 1
146             fnumbase 1
147             translen 1
148             transbase 1
149             keylen 1
150             keybase 1
151             toclen 1
152             datamax 1
153             tocs 1
154             );
155              
156             # all attributes, including some more user-supplied ones
157             my %Attrs = ( %Preamble, %Optional, %Generated, qw(
158             name 1
159             dir 1
160             desc 1
161             recsep 1
162             ) );
163              
164             my $Ascii_chars = qr/^[ -~]+$/; # i.e., printables
165              
166             #---------------------------------------------------------------------
167              
168             =head1 CLASS METHODS
169              
170             =head2 FlatFile::DataStore->new();
171              
172             Constructs a new FlatFile::DataStore object.
173              
174             Accepts hash ref giving values for C and C.
175              
176             my $ds = FlatFile::DataStore->new(
177             { dir => $dir,
178             name => $name,
179             } );
180              
181             To initialize a new datastore, edit the "$dir/$name.uri" file
182             and enter a configuration URI (as the only line in the file),
183             or pass the URI as the value of the C parameter, e.g.,
184              
185             my $ds = FlatFile::DataStore->new(
186             { dir => $dir,
187             name => $name,
188             uri => join( ";" =>
189             "http://example.com?name=$name",
190             "desc=My%20Data%20Store",
191             "defaults=medium",
192             "user=8-%20-%7E",
193             "recsep=%0A",
194             ),
195             } );
196              
197             (See URI Configuration below.)
198              
199             Also accepts a C parameter, which sets the default user
200             data for this instance, e.g.,
201              
202             my $ds = FlatFile::DataStore->new(
203             { dir => $dir,
204             name => $name,
205             userdata => ':',
206             } );
207              
208             Returns a reference to the FlatFile::DataStore object.
209              
210             =cut
211              
212             sub new {
213 66     66 1 78233 my( $class, $parms ) = @_;
214              
215 66         314 my $self = bless {}, $class;
216              
217 66 100       483 $self = $self->init( $parms ) if $parms; # $self could change ...
218 45         234 return $self;
219             }
220              
221             #---------------------------------------------------------------------
222             #
223             # =head2 init(), called by new() to initialize a datastore object
224             #
225             # Parms (from hash ref):
226             #
227             # dir ... the directory where the datastore lives
228             # name ... the name of the datastore
229             # uri ... a uri to be used to configure the datastore
230             #
231             # If dir/name.uri exists, init() will load its values.
232             # If uri is passed in, it will be used to initialize the datastore
233             # only if:
234             #
235             # 1) there isn't a .uri file, or
236             # 2) the .uri file is one line long, or
237             # 3) the .uri file has more lines (4) but no data files exist yet
238             #
239             # Private method.
240             #
241             # =cut
242             #
243              
244             sub init {
245 66     66 0 1378 my( $self, $parms ) = @_;
246              
247 66         438 my $dir = $parms->{'dir'};
248 66         146 my $name = $parms->{'name'};
249              
250 66 100 66     1495 croak qq/Need "dir" and "name"/
251             unless defined $dir and defined $name;
252              
253 62 100       1320 croak qq/Directory doesn't exist: $dir/
254             unless -d $dir;
255              
256 60         261 $self->dir( $dir );
257 60         241 $self->name( $name );
258              
259             # uri file may be
260             # - one line: just the uri, or
261             # - four lines: uri, object, uri_md5, object_md5
262             #
263             # if new_uri and uri file has
264             # - one line ... new_uri can replace old one
265             # - four lines (and new_uri is different) ...
266             # new_uri can replace the old uri (and object)
267             # but only if there aren't any data files yet
268              
269 60         217 my $new_uri = $parms->{'uri'};
270              
271 60         377 my $uri_file = "$dir/$name.uri";
272 60         110 my( $uri, $obj, $uri_md5, $obj_md5 );
273              
274 60 100       1685 if( -e $uri_file ) {
275 10         68 my @lines = $self->read_file( $uri_file ); chomp @lines;
  10         171  
276              
277 10 100       43 if( @lines == 4 ) {
    100          
278 8         29 ( $uri, $obj, $uri_md5, $obj_md5 ) = @lines;
279              
280 8 100       245 croak qq/URI MD5 check failed/ unless $uri_md5 eq md5_hex( $uri );
281 7 100       203 croak qq/Object MD5 check failed/ unless $obj_md5 eq md5_hex( $obj );
282              
283             # new uri ok only if no data has been added yet
284 6 50 33     35 if( $new_uri and
      33        
285             $new_uri ne $uri and
286             not -e $self->which_datafile( 1 ) ) {
287 0         0 $uri = $new_uri;
288             }
289             else {
290 6         18 untaint trusted => $obj;
291 6         1634 $self = eval $obj; # note: *new* $self
292              
293 6 100       187 croak qq/Problem with URI file, $uri_file: $@/ if $@;
294              
295 5         19 $self->dir( $dir ); # dir not in object
296             }
297             }
298             elsif( @lines == 1 ) {
299 1   33     9 $uri = $new_uri || shift @lines;
300             }
301             else {
302 1         234 croak qq/Invalid URI file: $uri_file/;
303             }
304             }
305             else {
306 50         134 $uri = $new_uri;
307             }
308              
309             # if there isn't an object, the datastore hasn't been
310             # initialized yet, so if we have a uri (either passed in
311             # or read from the uri file, let's initialize it
312             # (we could have an instance that only contains name and dir)
313              
314 56 100 66     618 if( !$obj and $uri ) {
315              
316 51         306 $self->uri( $uri );
317              
318             # Note: 'require', not 'use'. This isn't
319             # a "true" module -- we're just bringing in
320             # some more FlatFile::DataStore methods.
321              
322 51         20285 require FlatFile::DataStore::Initialize;
323              
324 51         357 my $uri_parms = $self->burst_query( \%Preamble );
325 48         302 for my $attr ( keys %$uri_parms ) {
326              
327 856 100       2936 croak qq/Unrecognized parameter: $attr/ unless $Attrs{ $attr };
328              
329             # (note: using $attr as method name here)
330 855         3351 $self->$attr( $uri_parms->{ $attr } );
331             }
332              
333             # check that all fnums and seeks are the same ...
334             #
335             # (note: prevfnum, prevseek, nextfnum, and nextseek are
336             # optional, but if you have one of them, you must have
337             # all four, so checking for one of them here, i.e.,
338             # prevfnum, is enough)
339              
340 47 100       326 if( $self->prevfnum ) {
341              
342 42 100 66     137 croak qq/fnum parameters differ/
343             unless $self->thisfnum eq $self->prevfnum and
344             $self->thisfnum eq $self->nextfnum;
345              
346 41 100 66     156 croak qq/seek parameters differ/
347             unless $self->thisseek eq $self->prevseek and
348             $self->thisseek eq $self->nextseek;
349              
350             }
351              
352             # now for some generated attributes ...
353 45         105 my( $len, $base );
354              
355             # (we can use thisfnum because all fnums are the same)
356 45         156 ( $len, $base ) = split /-/, $self->thisfnum;
357 45         334 $self->fnumlen( $len );
358 45         149 $self->fnumbase( $base );
359              
360 45         125 ( $len, $base ) = split /-/, $self->transnum;
361 45         220 $self->translen( $len );
362 45         147 $self->transbase( $base );
363              
364 45         146 ( $len, $base ) = split /-/, $self->keynum;
365 45         199 $self->keylen( $len );
366 45         142 $self->keybase( $base );
367              
368 45         151 $self->dateformat( (split /-/, $self->date)[1] );
369 45         236 $self->regx( $self->make_preamble_regx );
370 43         230 $self->crud( $self->make_crud );
371 41         245 $self->tocs( {} );
372 41         151 $self->dir( $dir ); # dir not in uri
373              
374 41         157 $self->toclen( 10 + # blanks between parts
375             3 * $self->fnumlen + # datafnum, tocfnum, keyfnum
376             2 * $self->keylen + # numrecs keynum
377             6 * $self->translen + # transnum and cruds
378             length $self->recsep );
379              
380             # (note: we can use thisseek because all seeks are the same)
381 41         143 ( $len, $base ) = split /-/, $self->thisseek;
382 41         185 my $maxnum = substr( base_chars( $base ), -1) x $len;
383 41         463 my $maxint = base2int $maxnum, $base;
384              
385             # if we give a datamax, it can't be larger than maxint
386 41 100       2033 if( my $max = $self->datamax ) {
387 8         34 $self->datamax( convert_max( $max ) );
388 8 100       26 if( $self->datamax > $maxint ) {
389              
390 1         3 croak join '' =>
391             "datamax too large: (", $self->datamax, ") ",
392             "thisseek is ", $self->thisseek,
393             " so maximum datamax is $maxnum base-$base ",
394             "(decimal: $maxint)";
395             }
396             }
397             else {
398 33         112 $self->datamax( $maxint );
399             }
400              
401 40 100       189 if( my $max = $self->dirmax ) {
402 4         16 $self->dirmax( convert_max( $max ) );
403 4 100       14 $self->dirlev( 1 ) unless $self->dirlev;
404             }
405              
406 40 100       163 if( my $max = $self->keymax ) {
407 5         26 $self->keymax( convert_max( $max ) );
408             }
409              
410 40 100       162 if( my $max = $self->tocmax ) {
411 3         17 $self->tocmax( convert_max( $max ) );
412             }
413              
414 40 100       138 if( my $user = $self->user ) {
415 39         266 $self->userlen( (split /-/, $user)[0] );
416             }
417              
418 40         450 for my $attr ( keys %Attrs ) {
419 1499 100 66     5034 croak qq/Uninitialized attribute: $attr/
420             if not $Optional{ $attr } and not defined $self->$attr;
421             }
422              
423 39         338 $self->initialize;
424             }
425              
426 43         180 for( $parms->{'userdata'} ) {
427 43 100       247 $self->userdata( $_ ) if defined;
428             }
429              
430 43         264 return $self; # this is either the same self or a new self
431             }
432              
433             #---------------------------------------------------------------------
434              
435             =head1 OBJECT METHODS, Record Processing (CRUD)
436              
437             =head2 create( $record )
438              
439             or create( { data => \$record_data, user => $user_data } )
440             or create( { record => $record[, data => \$record_data][, user => $user_data] } )
441              
442             Creates a record. If the parameter is a record object,
443             the record data and user data will be gotten from it.
444             Otherwise, if the parameter is a hash reference, the
445             expected keys are:
446              
447             - record => FlatFile::DataStore::Record object
448             - data => string or scalar reference
449             - user => string
450              
451             If no record is passed, both 'data' and 'user' are required.
452             Otherwise, if a record is passed, the record data and user
453             data will be gotten from it unless one or both are explicitly
454             provided.
455              
456             Returns a Flatfile::DataStore::Record object.
457              
458             Note: the record data (but not the user data) is stored in the
459             FF::DS::Record object as a scalar reference. This is done for
460             efficiency in the cases where the record data may be very large.
461             Likewise, the data parm passed to create() may be a scalar
462             reference.
463              
464             =cut
465              
466             sub create {
467 77     77 1 7672 my $self = shift;
468 77         378 my( $data_ref, $user_data ) = $self->normalize_parms( @_ );
469              
470             # get next keynum
471             # (we don't call nextkeynum(), because we need the
472             # $top_toc object for other things, too)
473              
474 74         369 my $top_toc = $self->new_toc( { int => 0 } );
475 74         318 my $keyint = $top_toc->keynum + 1;
476 74         228 my $keylen = $self->keylen;
477 74         206 my $keybase = $self->keybase;
478 74         235 my $keynum = int2base $keyint, $keybase, $keylen;
479              
480 74 100       1372 croak qq/Database exceeds configured size, keynum too long: $keynum/
481             if length $keynum > $keylen;
482              
483             # get keyfile
484             # need to lock files before getting seek positions
485             # want to lock keyfile before datafile
486              
487 73         288 my( $keyfile, $keyfint ) = $self->keyfile( $keyint );
488 72         270 my $keyfh = $self->locked_for_write( $keyfile );
489 72         1245 my $keyseek = -s $keyfile; # seekpos into keyfile
490              
491             # get datafile ($datafnum may increment)
492 72   100     300 my $datafnum = $top_toc->datafnum || 1; # (||1 only in create)
493 72         393 $datafnum = int2base $datafnum, $self->fnumbase, $self->fnumlen;
494 72         1205 my $reclen = length $$data_ref;
495              
496 72         98 my $datafile;
497 72         280 ( $datafile, $datafnum ) = $self->datafile( $datafnum, $reclen );
498 70         220 my $datafh = $self->locked_for_write( $datafile );
499 70         1108 my $dataseek = -s $datafile; # seekpos into datafile
500              
501             # get next transaction number
502 70         327 my $transint = $self->nexttransnum( $top_toc );
503              
504             # make new record
505 69         285 my $record = $self->new_record( {
506             data => $data_ref,
507             preamble => {
508             indicator => $self->crud->{'create'},
509             transind => $self->crud->{'create'},
510             date => now( $self->dateformat ),
511             transnum => $transint,
512             keynum => $keyint,
513             reclen => $reclen,
514             thisfnum => $datafnum,
515             thisseek => $dataseek,
516             user => $user_data,
517             } } );
518              
519             # write record to datafile
520 69         532 my $preamble = $record->preamble_string;
521 69         684 my $dataline = $preamble . $$data_ref . $self->recsep;
522 69         324 $self->write_bytes( $datafh, $dataseek, \$dataline );
523              
524             # write preamble to keyfile
525 69         221 $self->write_bytes( $keyfh, $keyseek, \($preamble . $self->recsep) );
526            
527             # update table of contents (toc) file
528 69         396 my $toc = $self->new_toc( { num => $datafnum } );
529              
530             # (note: datafnum and tocfnum are set in toc->new)
531 69         324 $toc->keyfnum( $keyfint );
532 69         341 $toc->keynum( $keyint );
533 69         252 $toc->transnum( $transint );
534 69         241 $toc->create( $toc->create + 1 );
535 69         210 $toc->numrecs( $toc->numrecs + 1 );
536 69         244 $toc->write_toc( $toc->datafnum );
537              
538             # update top toc
539 69         329 $top_toc->datafnum( $toc->datafnum );
540 69         237 $top_toc->keyfnum( $toc->keyfnum );
541 69         218 $top_toc->tocfnum( $toc->tocfnum );
542 69         224 $top_toc->keynum( $toc->keynum );
543 69         226 $top_toc->transnum( $toc->transnum );
544 69         226 $top_toc->create( $top_toc->create + 1 );
545 69         209 $top_toc->numrecs( $top_toc->numrecs + 1 );
546              
547 69         234 $top_toc->write_toc( 0 );
548              
549 69 50       973 close $datafh or die "Can't close $datafile: $!";
550 69 50       801 close $keyfh or die "Can't close $keyfile: $!";
551              
552 69         1636 return $record;
553             }
554              
555             #---------------------------------------------------------------------
556              
557             =head2 retrieve( $num[, $pos] )
558              
559             Retrieves a record. The parm C<$num> may be one of
560              
561             - a key number, i.e., record sequence number
562             - a file number
563              
564             The parm C<$pos> is required if C<$num> is a file number.
565              
566             Here's why: When $num is a record key sequence number (key number), a
567             preamble is retrieved from the datastore key file. In that preamble
568             is the file number and seek position where the record data may be
569             gotten. Otherwise, when $num is a file number, the application (you)
570             must supply the seek position into that file. Working from an array
571             of record history is the most likely time you would do this.
572              
573             Returns a Flatfile::DataStore::Record object.
574              
575             =cut
576              
577             sub retrieve {
578 64     64 1 11334 my( $self, $num, $pos ) = @_;
579              
580 64         154 for( $num ) {
581 64 50       923 croak qq/Not a number: '$_'/ unless m{^ [0-9]+ $}x;
582             }
583              
584 64         125 my $fnum;
585             my $seekpos;
586 0         0 my $keystring;
587              
588 64 100       185 if( defined $pos ) {
589 1         1 for( $pos ) {
590 1 50       7 croak qq/Not a number: '$_'/ unless m{^ [0-9]+ $}x;
591             }
592 1         2 $fnum = $num;
593 1         2 $seekpos = $pos;
594             }
595             else {
596 63         118 my $keynum = $num;
597 63         181 my $recsep = $self->recsep;
598 63         275 my $keyseek = $self->keyseek( $keynum );
599              
600 63         206 my $keyfile = $self->keyfile( $keynum );
601 63         233 my $keyfh = $self->locked_for_read( $keyfile );
602              
603 63         327 my $trynum = $self->lastkeynum;
604              
605 63 100       535 croak qq/Record doesn't exist: $keynum/ if $keynum > $trynum;
606              
607 61         248 $keystring = $self->read_preamble( $keyfh, $keyseek );
608 61 50       846 close $keyfh or die "Can't close $keyfile: $!";
609              
610 61         278 my $parms = $self->burst_preamble( $keystring );
611              
612 61         170 $fnum = $parms->{'thisfnum'};
613 61         366 $seekpos = $parms->{'thisseek'};
614             }
615              
616 62         220 my $datafile = $self->which_datafile( $fnum );
617 62         186 my $datafh = $self->locked_for_read( $datafile );
618 62         252 my $record = $self->read_record( $datafh, $seekpos );
619 62 50       991 close $datafh or die "Can't close $datafile: $!";
620              
621             # if we got the record via key file, check that preambles match
622 62 100       185 if( $keystring ) {
623 61         263 my $string = $record->preamble_string;
624              
625 61 50       217 croak qq/Mismatch: "$string" ne "$keystring"/ if $string ne $keystring;
626             }
627              
628 62         506 return $record;
629             }
630              
631             #---------------------------------------------------------------------
632              
633             =head2 retrieve_preamble( $keynum )
634              
635             Retrieves a preamble. The parm C<$keynum> is a key number, i.e.,
636             record sequence number
637              
638             Returns a Flatfile::DataStore::Preamble object.
639              
640             This method allows getting information about the record, e.g., if
641             it's deleted, what's in the user data, etc., without the overhead of
642             retrieving the full record data.
643              
644             =cut
645              
646             sub retrieve_preamble {
647 4     4 1 839 my( $self, $keynum ) = @_;
648              
649 4         12 for( $keynum ) {
650 4 50       65 croak qq/Not a number: '$_'/ unless m{^ [0-9]+ $}x;
651             }
652              
653 4         60 my $keyseek = $self->keyseek( $keynum );
654 4         16 my $keyfile = $self->keyfile( $keynum );
655 4         15 my $keyfh = $self->locked_for_read( $keyfile );
656              
657 4         16 my $trynum = $self->lastkeynum;
658              
659 4 100       106 croak qq/Record doesn't exist: $keynum/ if $keynum > $trynum;
660              
661 3         16 my $keystring = $self->read_preamble( $keyfh, $keyseek );
662 3 50       55 close $keyfh or die "Can't close $keyfile: $!";
663              
664 3         16 my $preamble = $self->new_preamble( { string => $keystring } );
665              
666 3         28 return $preamble;
667             }
668              
669             #---------------------------------------------------------------------
670              
671             =head2 locate_record_data( $num[, $pos] )
672              
673             Rather than retrieving a record, this subroutine positions you at the
674             record data in the data file. This might be handy if, for example,
675             the record data is text, and you just want part of it. You can scan
676             the data and get what you want without having to read the entire
677             record. Or the data might be XML and you could parse it using SAX
678             without reading it all into memory.
679              
680             The parm C<$num> may be one of
681              
682             - a key number, i.e., record sequence number
683             - a file number
684              
685             The parm C<$pos> is required if C<$num> is a file number. See
686             retrieve() above for why.
687              
688             Returns a list containing the file handle (which is already locked
689             for reading in binmode), the seek position, and the record length.
690              
691             You will be positioned at the seek position, so you could begin
692             reading data, e.g., via C<< <$fh> >>:
693              
694             my( $fh, $pos, $len ) = $ds->locate_record_data( $keynum );
695             my $got;
696             while( <$fh> ) {
697             last if ($got += length) > $len; # in case we read the recsep
698             # [do something with $_ ...]
699             last if $got == $len;
700             }
701             close $fh;
702              
703             The above loop assumes you know each line of the data ends in a
704             newline. Also keep in mind that the file is opened in binmode,
705             so you will be reading bytes (octets), not necessarily characters.
706             Decoding these octets is up to you.
707              
708             XXX ("opened in binmode"?) does that make the example wrong
709             wrt non-unix OS's
710              
711             =cut
712              
713             sub locate_record_data {
714 3     3 1 1612 my( $self, $num, $pos ) = @_;
715              
716 3         11 for( $num ) {
717 3 50       24 croak qq/Not a number: '$_'/ unless m{^ [0-9]+ $}x;
718             }
719              
720 3         6 my $fnum;
721             my $seekpos;
722 0         0 my $keystring;
723 0         0 my $reclen;
724              
725 3 50       14 if( defined $pos ) {
726 0         0 for( $pos ) {
727 0 0       0 croak qq/Not a number: '$_'/ unless m{^ [0-9]+ $}x;
728             }
729 0         0 $fnum = $num;
730 0         0 $seekpos = $pos;
731             }
732             else {
733 3         7 my $keynum = $num;
734 3         19 my $recsep = $self->recsep;
735 3         13 my $keyseek = $self->keyseek( $keynum );
736              
737 3         14 my $keyfile = $self->keyfile( $keynum );
738 3         14 my $keyfh = $self->locked_for_read( $keyfile );
739              
740 3         13 my $trynum = $self->lastkeynum;
741              
742 3 100       115 croak qq/Record doesn't exist: $keynum/ if $keynum > $trynum;
743              
744 2         9 $keystring = $self->read_preamble( $keyfh, $keyseek );
745 2 50       32 close $keyfh or die "Can't close $keyfile: $!";
746              
747 2         9 my $parms = $self->burst_preamble( $keystring );
748              
749 2         7 $fnum = $parms->{'thisfnum'};
750 2         4 $seekpos = $parms->{'thisseek'};
751 2         13 $reclen = $parms->{'reclen'};
752             }
753              
754 2         136 my $datafile = $self->which_datafile( $fnum );
755 2         9 my $datafh = $self->locked_for_read( $datafile );
756 2         9 my $preamble = $self->read_preamble( $datafh, $seekpos );
757              
758             # if we got the record via key file, check that preambles match
759 2 50       9 if( $keystring ) {
760              
761 2 50       10 croak qq/Mismatch: "$preamble" ne "$keystring"/
762             if $preamble ne $keystring;
763             }
764              
765             # if not via key file, we still need the record length
766             else {
767 0         0 my $parms = $self->burst_preamble( $preamble );
768 0         0 $reclen = $parms->{'reclen'};
769             }
770              
771 2         8 $seekpos += $self->preamblelen; # skip to record data
772              
773 2 50       13 sysseek $datafh, $seekpos, 0 or
774              
775             croak qq/Can't seek to $seekpos in $datafile: $!/;
776              
777 2         11 return $datafh, $seekpos, $reclen;
778             }
779              
780             #---------------------------------------------------------------------
781              
782             =head2 update( $record )
783              
784             or update( { string => $preamble_string, data => \$record_data, user => $user_data } )
785             or update( { preamble => $preamble_obj, data => \$record_data, user => $user_data } )
786             or update( { record => $record_obj
787             [, preamble => $preamble_obj]
788             [, string => $preamble_string]
789             [, data => \$record_data]
790             [, user => $user_data] } )
791              
792             Updates a record. If the parameter is a record object,
793             the preamble, record data, and user data will be gotten
794             from it. Otherwise, if the parameter is a hash reference,
795             the expected keys are:
796              
797             - record => FlatFile::DataStore::Record object
798             - preamble => FlatFile::DataStore::Preamble object
799             - string => a preamble string (the string attribute of a preamble object)
800             - data => string or scalar reference
801             - user => string
802              
803             If no record is passed, 'preamble' (or 'string'), 'data', and
804             'user' are required. Otherwise, if a record is passed, the
805             preamble, record data and user data will be gotten from it
806             unless any of them are explicitly provided.
807              
808             Returns a Flatfile::DataStore::Record object.
809              
810             =cut
811              
812             sub update {
813 15     15 1 4112 my $self = shift;
814 15         81 my( $data_ref, $user_data, $pr_obj ) = $self->normalize_parms( @_ );
815              
816 12 100       187 croak qq/Must have at least a previous preamble for update/
817             unless $pr_obj;
818              
819 11         43 my $prevnext = $self->prevfnum; # boolean
820              
821 11         58 my $prevpreamble = $pr_obj->string;
822 11         40 my $keyint = $pr_obj->keynum;
823 11         42 my $prevind = $pr_obj->indicator;
824 11         44 my $prevfnum = $pr_obj->thisfnum;
825 11         40 my $prevseek = $pr_obj->thisseek;
826              
827             # update is okay for these:
828 11         202 my $create = $self->crud->{'create'};
829 11         36 my $update = $self->crud->{'update'};
830 11         31 my $delete = $self->crud->{'delete'};
831              
832 11 100       352 croak qq/update not allowed: $prevind/
833             unless $prevind =~ /[\Q$create$update$delete\E]/;
834              
835             # get keyfile
836             # need to lock files before getting seek positions
837             # want to lock keyfile before datafile
838              
839 10         43 my( $keyfile, $keyfint ) = $self->keyfile( $keyint );
840 10         59 my $keyfh = $self->locked_for_write( $keyfile );
841 10         51 my $keyseek = $self->keyseek( $keyint );
842              
843 10         45 my $try = $self->read_preamble( $keyfh, $keyseek );
844              
845 10 50       57 croak qq/Mismatch: "$try" ne "$prevpreamble"/ unless $try eq $prevpreamble;
846              
847             # get datafile ($datafnum may increment)
848 10         65 my $top_toc = $self->new_toc( { int => 0 } );
849 10         51 my $datafnum = int2base $top_toc->datafnum, $self->fnumbase, $self->fnumlen;
850 10         146 my $reclen = length $$data_ref;
851              
852 10         16 my $datafile;
853 10         43 ( $datafile, $datafnum ) = $self->datafile( $datafnum, $reclen );
854 8         35 my $datafh = $self->locked_for_write( $datafile );
855 8         96 my $dataseek = -s $datafile; # seekpos into datafile
856              
857             # get next transaction number
858 8         48 my $transint = $self->nexttransnum( $top_toc );
859              
860             # make new record
861 7         34 my $preamble_hash = {
862             indicator => $update,
863             transind => $update,
864             date => now( $self->dateformat ),
865             transnum => $transint,
866             keynum => $keyint,
867             reclen => $reclen,
868             thisfnum => $datafnum,
869             thisseek => $dataseek,
870             user => $user_data,
871             };
872 7 50       32 if( $prevnext ) {
873 7         20 $preamble_hash->{'prevfnum'} = $prevfnum;
874 7         34 $preamble_hash->{'prevseek'} = $prevseek;
875             }
876 7         46 my $record = $self->new_record( {
877             data => $data_ref,
878             preamble => $preamble_hash,
879             } );
880              
881             # write record to datafile
882 7         39 my $preamble = $record->preamble_string;
883 7         32 my $dataline = $preamble . $$data_ref . $self->recsep;
884 7         35 $self->write_bytes( $datafh, $dataseek, \$dataline );
885              
886             # write preamble to keyfile (recsep there already)
887 7         26 $self->write_bytes( $keyfh, $keyseek, \$preamble );
888              
889             # update the old preamble
890 7 50       28 if( $prevnext ) {
891 7         30 $prevpreamble = $self->update_preamble( $prevpreamble, {
892             indicator => $self->crud->{ 'oldupd' },
893             nextfnum => $datafnum,
894             nextseek => $dataseek,
895             } );
896 7         50 my $prevdatafile = $self->which_datafile( $prevfnum );
897 7 50       69 if( $prevdatafile eq $datafile ) {
898 7         35 $self->write_bytes( $datafh, $prevseek, \$prevpreamble );
899             }
900             else {
901 0         0 my $prevdatafh = $self->locked_for_write( $prevdatafile );
902 0         0 $self->write_bytes( $prevdatafh, $prevseek, \$prevpreamble );
903 0 0       0 close $prevdatafh or die "Can't close $prevdatafile: $!";
904             }
905             }
906              
907             # update table of contents (toc) file
908 7         44 my $toc = $self->new_toc( { num => $datafnum } );
909              
910             # note: datafnum and tocfnum are set in toc->new
911 7         36 $toc->keyfnum( $top_toc->keyfnum ); # keep last nums going
912 7         27 $toc->keynum( $top_toc->keynum );
913 7         27 $toc->transnum( $transint );
914 7         33 $toc->update( $toc->update + 1 );
915 7         28 $toc->numrecs( $toc->numrecs + 1 );
916              
917             # was the previous record in another data file?
918 7 50       32 if( $prevnext ) {
919 7 50       23 if( $prevfnum ne $datafnum ) {
920 0         0 my $prevtoc = $self->new_toc( { num => $prevfnum } );
921 0         0 $prevtoc->oldupd( $prevtoc->oldupd + 1 );
922 0 0       0 $prevtoc->numrecs( $prevtoc->numrecs - 1 ) if $prevind ne $delete;
923 0         0 $prevtoc->write_toc( $prevtoc->datafnum );
924             }
925             else {
926 7         33 $toc->oldupd( $toc->oldupd + 1 );
927 7 100       49 $toc->numrecs( $toc->numrecs - 1 ) if $prevind ne $delete;
928             }
929             }
930             else {
931 0 0       0 $toc->numrecs( $toc->numrecs - 1 ) if $prevind ne $delete;
932             }
933              
934 7         31 $toc->write_toc( $toc->datafnum );
935              
936             # update top toc
937 7         32 $top_toc->datafnum( $toc->datafnum );
938 7         35 $top_toc->tocfnum( $toc->tocfnum );
939 7         29 $top_toc->transnum( $toc->transnum );
940 7         29 $top_toc->update( $top_toc->update + 1 );
941 7 50       43 $top_toc->oldupd( $top_toc->oldupd + 1 ) if $prevnext;
942 7 100       31 $top_toc->numrecs( $top_toc->numrecs + 1 ) if $prevind eq $delete;
943              
944 7         28 $top_toc->write_toc( 0 );
945              
946 7 50       100 close $datafh or die "Can't close $datafile: $!";
947 7 50       84 close $keyfh or die "Can't close $keyfile: $!";
948              
949 7         118 return $record;
950             }
951              
952             #---------------------------------------------------------------------
953              
954             =head2 delete( $record )
955              
956             or delete( { string => $preamble_string, data => \$record_data, user => $user_data } )
957             or delete( { preamble => $preamble_obj, data => \$record_data, user => $user_data } )
958             or delete( { record => $record_obj
959             [, preamble => $preamble_obj]
960             [, string => $preamble_string]
961             [, data => \$record_data]
962             [, user => $user_data] } )
963              
964             Deletes a record. The parameters are the same as for update().
965              
966             Returns a Flatfile::DataStore::Record object.
967              
968             =cut
969              
970             sub delete {
971 16     16 1 6668 my $self = shift;
972 16         67 my( $data_ref, $user_data, $pr_obj ) = $self->normalize_parms( @_ );
973              
974 13 100       160 croak qq/Must have at least a previous preamble for delete/
975             unless $pr_obj;
976              
977 12         46 my $prevnext = $self->prevfnum; # boolean
978              
979 12         55 my $prevpreamble = $pr_obj->string;
980 12         50 my $keyint = $pr_obj->keynum;
981 12         49 my $prevind = $pr_obj->indicator;
982 12         66 my $prevfnum = $pr_obj->thisfnum;
983 12         47 my $prevseek = $pr_obj->thisseek;
984              
985             # delete is okay for these:
986 12         47 my $create = $self->crud->{'create'};
987 12         39 my $update = $self->crud->{'update'};
988              
989 12 100       298 croak qq/delete not allowed: $prevind/
990             unless $prevind =~ /[\Q$create$update\E]/;
991              
992             # get keyfile
993             # need to lock files before getting seek positions
994             # want to lock keyfile before datafile
995              
996 11         79 my( $keyfile, $keyfint ) = $self->keyfile( $keyint );
997 11         46 my $keyfh = $self->locked_for_write( $keyfile );
998 11         51 my $keyseek = $self->keyseek( $keyint );
999              
1000 11         48 my $try = $self->read_preamble( $keyfh, $keyseek );
1001              
1002 11 50       49 croak qq/Mismatch: "$try" ne "$prevpreamble"/ unless $try eq $prevpreamble;
1003              
1004             # get datafile ($datafnum may increment)
1005 11         61 my $top_toc = $self->new_toc( { int => 0 } );
1006 11         62 my $datafnum = int2base $top_toc->datafnum, $self->fnumbase, $self->fnumlen;
1007 11         161 my $reclen = length $$data_ref;
1008              
1009 11         22 my $datafile;
1010 11         48 ( $datafile, $datafnum ) = $self->datafile( $datafnum, $reclen );
1011 9         44 my $datafh = $self->locked_for_write( $datafile );
1012 9         120 my $dataseek = -s $datafile; # seekpos into datafile
1013              
1014             # get next transaction number
1015 9         54 my $transint = $self->nexttransnum( $top_toc );
1016              
1017             # make new record
1018 8         31 my $delete = $self->crud->{'delete'};
1019 8         48 my $preamble_hash = {
1020             indicator => $delete,
1021             transind => $delete,
1022             date => now( $self->dateformat ),
1023             transnum => $transint,
1024             keynum => $keyint,
1025             reclen => $reclen,
1026             thisfnum => $datafnum,
1027             thisseek => $dataseek,
1028             user => $user_data,
1029             };
1030 8 50       46 if( $prevnext ) {
1031 8         24 $preamble_hash->{'prevfnum'} = $prevfnum;
1032 8         19 $preamble_hash->{'prevseek'} = $prevseek;
1033             }
1034 8         48 my $record = $self->new_record( {
1035             data => $data_ref,
1036             preamble => $preamble_hash,
1037             } );
1038              
1039             # write record to datafile
1040 8         209 my $preamble = $record->preamble_string;
1041 8         42 my $dataline = $preamble . $$data_ref . $self->recsep;
1042 8         44 $self->write_bytes( $datafh, $dataseek, \$dataline );
1043              
1044             # write preamble to keyfile (recsep there already)
1045 8         44 $self->write_bytes( $keyfh, $keyseek, \$preamble );
1046              
1047             # update the old preamble
1048 8 50       34 if( $prevnext ) {
1049 8         32 $prevpreamble = $self->update_preamble( $prevpreamble, {
1050             indicator => $self->crud->{ 'olddel' },
1051             nextfnum => $datafnum,
1052             nextseek => $dataseek,
1053             } );
1054 8         48 my $prevdatafile = $self->which_datafile( $prevfnum );
1055 8 50       50 if( $prevdatafile eq $datafile ) {
1056 8         32 $self->write_bytes( $datafh, $prevseek, \$prevpreamble );
1057             }
1058             else {
1059 0         0 my $prevdatafh = $self->locked_for_write( $prevdatafile );
1060 0         0 $self->write_bytes( $prevdatafh, $prevseek, \$prevpreamble );
1061 0 0       0 close $prevdatafh or die "Can't close $prevdatafile: $!";
1062             }
1063             }
1064              
1065             # update table of contents (toc) file
1066 8         52 my $toc = $self->new_toc( { num => $datafnum } );
1067              
1068             # note: datafnum and tocfnum are set in toc->new
1069 8         43 $toc->keyfnum( $top_toc->keyfnum ); # keep last nums going
1070 8         34 $toc->keynum( $top_toc->keynum );
1071 8         28 $toc->transnum( $transint );
1072 8         39 $toc->delete( $toc->delete + 1 );
1073              
1074             # was the previous record in another data file?
1075 8 50       32 if( $prevnext ) {
1076 8 50       28 if( $prevfnum ne $datafnum ) {
1077 0         0 my $prevtoc = $self->new_toc( { num => $prevfnum } );
1078 0         0 $prevtoc->olddel( $prevtoc->olddel + 1 );
1079 0         0 $prevtoc->numrecs( $prevtoc->numrecs - 1 );
1080 0         0 $prevtoc->write_toc( $prevtoc->datafnum );
1081             }
1082             else {
1083 8         32 $toc->olddel( $toc->olddel + 1 );
1084 8         33 $toc->numrecs( $toc->numrecs - 1 );
1085             }
1086             }
1087             else {
1088 0         0 $toc->numrecs( $toc->numrecs - 1 );
1089             }
1090              
1091 8         60 $toc->write_toc( $toc->datafnum );
1092              
1093             # update top toc
1094 8         34 $top_toc->datafnum( $toc->datafnum );
1095 8         30 $top_toc->tocfnum( $toc->tocfnum );
1096 8         53 $top_toc->transnum( $toc->transnum );
1097 8         160 $top_toc->delete( $top_toc->delete + 1 );
1098 8 50       46 $top_toc->olddel( $top_toc->olddel + 1 ) if $prevnext;
1099 8         29 $top_toc->numrecs( $top_toc->numrecs - 1 );
1100              
1101 8         27 $top_toc->write_toc( 0 );
1102              
1103 8 50       104 close $datafh or die "Can't close $datafile: $!";
1104 8 50       96 close $keyfh or die "Can't close $keyfile: $!";
1105              
1106 8         119 return $record;
1107             }
1108              
1109             #---------------------------------------------------------------------
1110             #
1111             # =head2 normalize_parms( $parms )
1112             #
1113             # Parses parameters for create(), update(), and delete()
1114             #
1115             # If the parameter is a record object, then the preamble, record data,
1116             # and user data will be gotten from it.
1117             #
1118             # Otherwise, if the parameter is a hash reference, the expected keys
1119             # are:
1120             #
1121             # - record => FlatFile::DataStore::Record object
1122             # - preamble => FlatFile::DataStore::Preamble object
1123             # - string => a preamble string (the string attribute of a preamble object)
1124             # - data => string or scalar reference
1125             # - user => string
1126             #
1127             # Returns record data (scalar ref), user data, preamble object
1128             #
1129             # Note that create() ignores the returned preamble, but update() and
1130             # delete() do not.
1131             #
1132             # Private method.
1133             #
1134             # =cut
1135             #
1136              
1137             sub normalize_parms {
1138 108     108 0 253 my( $self, $parms ) = @_;
1139              
1140 108 100       1144 croak qq/Bad call/ unless $parms;
1141              
1142 105         165 my( $data_ref, $user_data, $preamble );
1143              
1144 105         491 my $reftype = ref $parms;
1145 105 100       1644 if( $reftype =~ /Record/ ) {
    100          
1146 19         89 $data_ref = $parms->dataref;
1147 19         78 $user_data = $parms->user;
1148 19         70 $preamble = $parms->preamble;
1149             }
1150             elsif( $reftype eq "HASH" ) {
1151 83         245 for( $parms->{'data'} ) {
1152 83 100       291 if( ref ) { $data_ref = $_ }
  1         3  
1153 82 100       627 else { $data_ref = \$_ if defined }
1154             }
1155 83         248 for( $parms->{'user'} ) {
1156 83 100       358 $user_data = $_ if defined;
1157             }
1158 83         227 for( $parms->{'string'} ) {
1159 83 50       292 $preamble = $self->new_preamble( { string => $_ } )
1160             if defined;
1161             }
1162 83         238 for( $parms->{'preamble'} ) {
1163 83 50       255 $preamble = $_ if defined;
1164             }
1165 83         528 for( $parms->{'record'} ) {
1166 83 100       305 last unless defined;
1167 10 100       54 $data_ref = $_->dataref unless $data_ref;
1168 10 100       53 $user_data = $_->user unless defined $user_data;
1169 10 50       65 $preamble = $_->preamble unless $preamble;
1170             }
1171             }
1172             else {
1173 3         278 croak qq/Parameter must be a hashref or a record object/;
1174             }
1175 102 100       562 croak qq/No record data/ unless $data_ref;
1176              
1177 99         357 return $data_ref, $user_data, $preamble;
1178             }
1179              
1180             #---------------------------------------------------------------------
1181              
1182             =head2 exists()
1183              
1184             Tests if a datastore exists. Currently, a datastore "exists" if there
1185             is a .uri file -- whether the file is valid or not.
1186              
1187             May be called on a datastore object, e.g.,
1188              
1189             $ds->exists()
1190              
1191             Or may be called as a class method, e.g.,
1192              
1193             FlatFile::DataStore->exists({
1194             name => 'example',
1195             dir => '/dbs/example',
1196             })
1197              
1198             If called as a class method, you must pass a hashref that provides
1199             values for 'name' and 'dir'.
1200              
1201             =cut
1202              
1203             sub exists {
1204 2     2 1 908 my( $self, $parms ) = @_;
1205              
1206 2         4 my( $dir, $name );
1207              
1208 2 100       9 if( ref $self ) { # object method
1209 1         4 $dir = $self->dir;
1210 1         4 $name = $self->name;
1211              
1212             # empty object, so datastore doesn't exist
1213 1 50 33     10 return unless $dir and $name;
1214             }
1215              
1216             else { # class method
1217              
1218 1 50       5 if( $parms ) {
1219 0         0 $dir = $parms->{'dir'};
1220 0         0 $name = $parms->{'name'};
1221             }
1222              
1223             # required for class method
1224 1 50 33     117 croak qq/Need dir and name/ unless $dir and $name;
1225             }
1226              
1227 1         21 -e "$dir/$name.uri"; # returned
1228             }
1229              
1230             #---------------------------------------------------------------------
1231              
1232             =head2 history( $keynum )
1233              
1234             Retrieves a record's history. The parm C<$keynum> is always a key
1235             number, i.e., a record sequence number.
1236              
1237             Returns an array of FlatFile::DataStore::Record objects.
1238              
1239             The first element of this array is the current record. The last
1240             element is the original record. That is, the array is in reverse
1241             chronological order.
1242              
1243             =cut
1244              
1245             sub history {
1246 1     1 1 3 my( $self, $keynum ) = @_;
1247              
1248 1         3 for( $keynum ) {
1249 1 50       6 croak qq/Not a number: '$_'/ unless m{^ [0-9]+ $}x;
1250             }
1251              
1252 1         3 my @history;
1253              
1254 1         4 my $rec = $self->retrieve( $keynum );
1255 1         2 push @history, $rec;
1256              
1257 1         6 my $prevfnum = $rec->prevfnum;
1258 1         6 my $prevseek = $rec->prevseek;
1259              
1260 1         5 while( $prevfnum ) {
1261              
1262 0         0 my $rec = $self->retrieve( $prevfnum, $prevseek );
1263 0         0 push @history, $rec;
1264              
1265 0         0 $prevfnum = $rec->prevfnum;
1266 0         0 $prevseek = $rec->prevseek;
1267             }
1268              
1269 1         6 return @history;
1270             }
1271              
1272             #---------------------------------------------------------------------
1273              
1274             =head1 OBJECT METHODS, Accessors
1275              
1276             In the specifications below, square braces ([]) denote optional
1277             parameters, not anonymous arrays, e.g., C<[$omap]> indicates that
1278             C<$omap> is optional, instead of implying that you need to pass it
1279             inside an array.
1280              
1281             =head2 $ds->specs( [$omap] )
1282              
1283             Sets and returns the C attribute value if C<$omap> is given,
1284             otherwise just returns the value.
1285              
1286             An 'omap' is an ordered hash as defined in
1287              
1288             http://yaml.org/type/omap.html
1289              
1290             and implemented here using Data::Omap. That is, it's an array of
1291             single-key hashes. This ordered hash contains the specifications for
1292             constructing and parsing a record preamble as defined in the name.uri
1293             file.
1294              
1295             In list context, the value returned is a list of hashrefs. In scalar
1296             context, the value returned is an arrayref containing the list of
1297             hashrefs.
1298              
1299             =cut
1300              
1301             sub specs {
1302 482     482 1 826 my( $self, $omap ) = @_;
1303 482         1131 for( $self->{specs} ) {
1304 482 100       1134 if( $omap ) {
1305              
1306 49 100       280 croak qq/Invalid omap: /.omap_errstr()
1307             unless omap_is_valid( $omap );
1308              
1309 48         4334 $_ = $omap;
1310             }
1311 481 50       1409 return unless defined;
1312 481 100       2418 return @$_ if wantarray;
1313 122         376 return $_;
1314             }
1315             }
1316              
1317             #---------------------------------------------------------------------
1318              
1319             =head2 $ds->dir( [$dir] )
1320              
1321             Sets and returns the C attribute value if C<$dir> is given,
1322             otherwise just returns the value.
1323              
1324             If C<$dir> is given and is a null string, the C object attribute
1325             is removed from the object. If C<$dir> is not null, the directory
1326             must already exist. In other words, this module will not create the
1327             directory where the database is to be stored.
1328              
1329             =cut
1330              
1331             sub dir {
1332 1140     1140 1 3368 my( $self, $dir ) = @_;
1333 1140 100 100     3991 if( defined $dir and $dir eq "" ) { delete $self->{dir} }
  38         173  
1334             else {
1335 1102         2595 for( $self->{dir} ) {
1336 1102 100       2213 if( defined $dir ) {
1337              
1338 145 100       2966 croak qq/Directory doesn't exist: $dir/ unless -d $dir;
1339              
1340 144         291 $_ = $dir
1341             }
1342 1101         3818 return $_;
1343             }
1344             }
1345             }
1346              
1347             #---------------------------------------------------------------------
1348              
1349             =head2 Preamble accessors (from the uri)
1350              
1351             The following methods set and return their respective attribute values
1352             if C<$value> is given. Otherwise, they just return the value.
1353              
1354             $ds->indicator( [$value] ); # length-characters
1355             $ds->transind( [$value] ); # length-characters
1356             $ds->date( [$value] ); # length-format
1357             $ds->transnum( [$value] ); # length-base
1358             $ds->keynum( [$value] ); # length-base
1359             $ds->reclen( [$value] ); # length-base
1360             $ds->thisfnum( [$value] ); # length-base
1361             $ds->thisseek( [$value] ); # length-base
1362             $ds->prevfnum( [$value] ); # length-base
1363             $ds->prevseek( [$value] ); # length-base
1364             $ds->nextfnum( [$value] ); # length-base
1365             $ds->nextseek( [$value] ); # length-base
1366             $ds->user( [$value] ); # length-characters
1367              
1368             =head2 Other accessors
1369              
1370             $ds->name( [$value] ); # from uri, name of datastore
1371             $ds->desc( [$value] ); # from uri, description of datastore
1372             $ds->recsep( [$value] ); # from uri, character(s)
1373             $ds->uri( [$value] ); # full uri as is
1374             $ds->preamblelen( [$value] ); # length of preamble string
1375             $ds->toclen( [$value] ); # length of toc entry
1376             $ds->keylen( [$value] ); # length of stored keynum
1377             $ds->keybase( [$value] ); # base of stored keynum
1378             $ds->translen( [$value] ); # length of stored transaction number
1379             $ds->transbase( [$value] ); # base of stored transaction number
1380             $ds->fnumlen( [$value] ); # length of stored file number
1381             $ds->fnumbase( [$value] ); # base of stored file number
1382             $ds->userlen( [$value] ); # format from uri
1383             $ds->dateformat( [$value] ); # format from uri
1384             $ds->regx( [$value] ); # capturing regx for preamble string
1385             $ds->datamax( [$value] ); # maximum bytes in a data file
1386             $ds->crud( [$value] ); # hash ref, e.g.,
1387              
1388             {
1389             create => '+',
1390             oldupd => '#',
1391             update => '=',
1392             olddel => '*',
1393             delete => '-',
1394             '+' => 'create',
1395             '#' => 'oldupd',
1396             '=' => 'update',
1397             '*' => 'olddel',
1398             '-' => 'delete',
1399             }
1400              
1401             (logical actions <=> symbolic indicators)
1402              
1403             =head2 Accessors for optional attributes
1404              
1405             $ds->dirmax( [$value] ); # maximum files in a directory
1406             $ds->dirlev( [$value] ); # number of directory levels
1407             $ds->tocmax( [$value] ); # maximum toc entries
1408             $ds->keymax( [$value] ); # maximum key entries
1409             $ds->userdata( [$value] ); # default user data
1410              
1411             If no C, directories will keep being added to.
1412              
1413             If no C, toc, key, and data files will reside in top-level
1414             directory. If C is given, C defaults to 1.
1415              
1416             If no C, there will be only one toc file, which will grow
1417             indefinitely.
1418              
1419             If no C, there will be only one key file, which will grow
1420             indefinitely.
1421              
1422             If no C, will default to a null string (padded with spaces)
1423             unless supplied another way.
1424              
1425             =cut
1426              
1427 130 100   130 1 334 sub indicator {for($_[0]->{indicator} ){$_=$_[1]if@_>1;return$_}}
  130         666  
  130         618  
1428 87 100   87 1 247 sub transind {for($_[0]->{transind} ){$_=$_[1]if@_>1;return$_}}
  87         307  
  87         205  
1429 132 100   132 1 315 sub date {for($_[0]->{date} ){$_=$_[1]if@_>1;return$_}}
  132         471  
  132         489  
1430 132 100   132 1 317 sub transnum {for($_[0]->{transnum} ){$_=$_[1]if@_>1;return$_}}
  132         412  
  132         425  
1431 133 100   133 1 408 sub keynum {for($_[0]->{keynum} ){$_=$_[1]if@_>1;return$_}}
  133         379  
  133         432  
1432 88 100   88 1 326 sub reclen {for($_[0]->{reclen} ){$_=$_[1]if@_>1;return$_}}
  88         287  
  88         247  
1433 215 100   215 1 491 sub thisfnum {for($_[0]->{thisfnum} ){$_=$_[1]if@_>1;return$_}}
  215         569  
  215         717  
1434 210 100   210 1 460 sub thisseek {for($_[0]->{thisseek} ){$_=$_[1]if@_>1;return$_}}
  210         532  
  210         1018  
1435              
1436             # prevfnum, prevseek, nextfnum, nextseek are optional attributes;
1437             # prevfnum() is set up to avoid autovivification, because it is
1438             # the accessor used to test if these optional attributes are set
1439              
1440             sub prevfnum {
1441 155     155 1 249 my $self = shift;
1442 155 100       559 return $self->{prevfnum} = $_[0] if @_;
1443 113 100       1215 return $self->{prevfnum} if exists $self->{prevfnum};
1444             }
1445              
1446 84 100   84 1 225 sub prevseek {for($_[0]->{prevseek} ){$_=$_[1]if@_>1;return$_}}
  84         326  
  84         681  
1447 84 100   84 1 257 sub nextfnum {for($_[0]->{nextfnum} ){$_=$_[1]if@_>1;return$_}}
  84         262  
  84         298  
1448 83 100   83 1 235 sub nextseek {for($_[0]->{nextseek} ){$_=$_[1]if@_>1;return$_}}
  83         287  
  83         294  
1449 126 100   126 1 472 sub user {for($_[0]->{user} ){$_=$_[1]if@_>1;return$_}}
  126         513  
  126         381  
1450              
1451 1067 100   1067 1 2488 sub name {for($_[0]->{name} ){$_=$_[1]if@_>1;return$_}}
  1067         2505  
  1067         3061  
1452 87 100   87 1 279 sub desc {for($_[0]->{desc} ){$_=$_[1]if@_>1;return$_}}
  87         382  
  87         223  
1453 925 100   925 1 1919 sub recsep {for($_[0]->{recsep} ){$_=$_[1]if@_>1;return$_}}
  925         2180  
  925         3277  
1454 180 100   180 0 700 sub uri {for($_[0]->{uri} ){$_=$_[1]if@_>1;return$_}}
  180         842  
  180         520  
1455 79 100   79 0 207 sub userlen {for($_[0]->{userlen} ){$_=$_[1]if@_>1;return$_}}
  79         257  
  79         582  
1456 169 100   169 0 716 sub dateformat {for($_[0]->{dateformat} ){$_=$_[1]if@_>1;return$_}}
  169         542  
  169         545  
1457 382 100   382 0 1710 sub regx {for($_[0]->{regx} ){$_=$_[1]if@_>1;return$_}}
  382         951  
  382         3994  
1458 486 100   486 0 1465 sub crud {for($_[0]->{crud} ){$_=$_[1]if@_>1;return$_}}
  486         1213  
  486         1717  
1459 640 100   640 0 1497 sub tocs {for($_[0]->{tocs} ){$_=$_[1]if@_>1;return$_}}
  640         1414  
  640         3930  
1460 238 100   238 1 709 sub datamax {for($_[0]->{datamax} ){$_=$_[1]if@_>1;return$_}}
  238         608  
  238         755  
1461              
1462 426 100   426 0 1059 sub preamblelen {for($_[0]->{preamblelen} ){$_=0+$_[1]if@_>1;return$_}}
  426         1024  
  426         1155  
1463 250 100   250 0 718 sub toclen {for($_[0]->{toclen} ){$_=0+$_[1]if@_>1;return$_}}
  250         785  
  250         623  
1464 374 100   374 0 1044 sub keylen {for($_[0]->{keylen} ){$_=0+$_[1]if@_>1;return$_}}
  374         886  
  374         964  
1465 553 100   553 0 1166 sub keybase {for($_[0]->{keybase} ){$_=0+$_[1]if@_>1;return$_}}
  553         1262  
  553         1269  
1466 387 100   387 0 830 sub translen {for($_[0]->{translen} ){$_=0+$_[1]if@_>1;return$_}}
  387         881  
  387         928  
1467 566 100   566 0 1182 sub transbase {for($_[0]->{transbase} ){$_=0+$_[1]if@_>1;return$_}}
  566         1326  
  566         1247  
1468 1208 100   1208 0 2621 sub fnumlen {for($_[0]->{fnumlen} ){$_=0+$_[1]if@_>1;return$_}}
  1208         2498  
  1208         3230  
1469 1473 100   1473 0 3032 sub fnumbase {for($_[0]->{fnumbase} ){$_=0+$_[1]if@_>1;return$_}}
  1473         3134  
  1473         3951  
1470              
1471             # optional (set up to avoid autovivification):
1472              
1473             sub dirmax {
1474 148     148 1 222 my $self = shift;
1475 148 100       398 return $self->{dirmax} = $_[0] if @_;
1476 140 100       719 return $self->{dirmax} if exists $self->{dirmax};
1477             }
1478             sub dirlev {
1479 839     839 1 1095 my $self = shift;
1480 839 100       1880 return $self->{dirlev} = 0+$_[0] if @_;
1481 835 100       4035 return $self->{dirlev} if exists $self->{dirlev};
1482             }
1483             sub tocmax {
1484 1201     1201 1 1629 my $self = shift;
1485 1201 100       2461 return $self->{tocmax} = $_[0] if @_;
1486 1195 100       6938 return $self->{tocmax} if exists $self->{tocmax};
1487             }
1488             sub keymax {
1489 305     305 1 470 my $self = shift;
1490 305 100       780 return $self->{keymax} = $_[0] if @_;
1491 295 100       1663 return $self->{keymax} if exists $self->{keymax};
1492             }
1493              
1494             # default to null string (will be space-padded)
1495             sub userdata {
1496 68     68 1 4782 my $self = shift;
1497 68 100       231 return $self->{userdata} = $_[0] if @_;
1498 56 100       241 return '' unless exists $self->{userdata};
1499 32         141 return $self->{userdata};
1500             }
1501              
1502             #---------------------------------------------------------------------
1503             #
1504             # =head2 new_toc( \%parms )
1505             #
1506             # This method is a wrapper for FlatFile::DataStore::Toc->new().
1507             #
1508             # Private method.
1509             #
1510             # =cut
1511             #
1512              
1513             sub new_toc {
1514 283     283 0 475 my( $self, $parms ) = @_;
1515 283         549 $parms->{'datastore'} = $self;
1516 283         2117 FlatFile::DataStore::Toc->new( $parms );
1517             }
1518              
1519             #---------------------------------------------------------------------
1520             #
1521             # =head2 new_preamble( \%parms )
1522             #
1523             # This method is a wrapper for FlatFile::DataStore::Preamble->new().
1524             #
1525             # Private method.
1526             #
1527             # =cut
1528             #
1529              
1530             sub new_preamble {
1531 149     149 0 263 my( $self, $parms ) = @_;
1532 149         357 $parms->{'datastore'} = $self;
1533 149         1154 FlatFile::DataStore::Preamble->new( $parms );
1534             }
1535              
1536             #---------------------------------------------------------------------
1537             #
1538             # =head2 new_record( \%parms )
1539             #
1540             # This method is a wrapper for FlatFile::DataStore::Record->new().
1541             #
1542             # Private method.
1543             #
1544             # =cut
1545             #
1546              
1547             sub new_record {
1548 146     146 0 266 my( $self, $parms ) = @_;
1549 146         281 my $preamble = $parms->{'preamble'};
1550 146 100       641 if( ref $preamble eq 'HASH' ) { # not an object
1551 84         255 $parms->{'preamble'} = $self->new_preamble( $preamble );
1552             }
1553 146         1449 FlatFile::DataStore::Record->new( $parms );
1554             }
1555              
1556             #---------------------------------------------------------------------
1557             #
1558             # =head2 keyfile( $keyint )
1559             #
1560             # Takes an integer that is the record sequence number and returns the
1561             # path to the keyfile where that record's preamble is.
1562             #
1563             # Private method.
1564             #
1565             # =cut
1566             #
1567              
1568             sub keyfile {
1569 164     164 0 292 my( $self, $keyint ) = @_;
1570              
1571 164         382 my $name = $self->name;
1572 164         455 my $fnumlen = $self->fnumlen;
1573 164         571 my $fnumbase = $self->fnumbase;
1574              
1575 164         237 my $keyfint = 1;
1576 164         282 my $keyfile = $name;
1577              
1578             # get key file number (if any) based on keymax and keyint
1579 164 100       397 if( my $keymax = $self->keymax ) {
1580 23         50 $keyfint = int( $keyint / $keymax ) + 1;
1581 23         72 my $keyfnum = int2base $keyfint, $fnumbase, $fnumlen;
1582              
1583 23 100       544 croak qq/Database exceeds configured size, keyfnum too long: $keyfnum/
1584             if length $keyfnum > $fnumlen;
1585              
1586 22         53 $keyfile .= ".$keyfnum";
1587             }
1588              
1589 163         383 $keyfile .= ".key";
1590              
1591             # get path based on dirlev (if any), dirmax, and key file number
1592 163 100       396 if( my $dirlev = $self->dirlev ) {
1593 20         55 my $dirmax = $self->dirmax;
1594 20         35 my $path = "";
1595 20         28 my $this = $keyfint;
1596 20         45 for( 1 .. $dirlev ) {
1597 30 50       244 my $dirint = $dirmax? (int( ( $this - 1 ) / $dirmax ) + 1): 1;
1598 30         84 my $dirnum = int2base $dirint, $fnumbase, $fnumlen;
1599 30 100       411 $path = $path? "$dirnum/$path": $dirnum;
1600 30         74 $this = $dirint;
1601             }
1602 20         60 $path = $self->dir . "/$name/key$path";
1603 20 100       1130 mkpath( $path ) unless -d $path;
1604 20         62 $keyfile = "$path/$keyfile";
1605             }
1606             else {
1607 143         362 $keyfile = $self->dir . "/$keyfile";
1608             }
1609              
1610 163 100       676 return ( $keyfile, $keyfint ) if wantarray;
1611 70         219 return $keyfile;
1612              
1613             }
1614              
1615             #---------------------------------------------------------------------
1616             #
1617             # =head2 datafile(), called by create(), update(), and delete()
1618             #
1619             # Similar to which_datafile(), this method takes a file number
1620             # and returns the path to that datafile. Unlike which_datafile(),
1621             # this method also takes a record length to check for overflow.
1622             #
1623             # That is, if the record about to be written would make a datafile
1624             # become too large (> datamax), the file number is incremented,
1625             # and the path to that new datafile is returned -- along with the
1626             # new file number. Calls to datafile() should always take this
1627             # new file number into account.
1628             #
1629             # Will croak if the record is way too big (> datamax) or if the new
1630             # file number is longer than the max length for file numbers. In
1631             # either case, a new datastore must be configured to handle the
1632             # extra data, and the old datastore must be migrated to it.
1633             #
1634             # Private method.
1635             #
1636             # =cut
1637             #
1638              
1639             sub datafile {
1640 93     93 0 204 my( $self, $fnum, $reclen ) = @_;
1641              
1642 93         308 my $datafile = $self->which_datafile( $fnum );
1643              
1644             # check if we're about to overfill the data file
1645             # and if so, increment fnum for a new data file
1646              
1647 93         326 my $datamax = $self->datamax;
1648 93         281 my $checksize = $self->preamblelen + $reclen + length $self->recsep;
1649 93   100     1545 my $datasize = -s $datafile || 0;
1650              
1651 93 100       300 if( $datasize + $checksize > $datamax ) {
1652              
1653 6 100       562 croak qq/Record too long: $checksize > $datamax/
1654             if $checksize > $datamax;
1655              
1656 3         8 my $fnumlen = $self->fnumlen;
1657 3         8 my $fnumbase = $self->fnumbase;
1658 3         14 $fnum = int2base( 1 + base2int( $fnum, $fnumbase ), $fnumbase, $fnumlen );
1659              
1660 3 50       585 croak qq/Database exceeds configured size, fnum too long: $fnum/
1661             if length $fnum > $fnumlen;
1662              
1663 0         0 $datafile = $self->which_datafile( $fnum );
1664             }
1665              
1666 87         447 return $datafile, $fnum;
1667             }
1668              
1669             #---------------------------------------------------------------------
1670             #
1671             # =head2 which_datafile()
1672             #
1673             # Takes a file number and returns the path to that datafile.
1674             #
1675             # Takes into account dirlev and dirmax, if set, and will create
1676             # new directories as needed.
1677             #
1678             # Private method.
1679             #
1680             # =cut
1681             #
1682              
1683             sub which_datafile {
1684 211     211 0 446 my( $self, $datafnum ) = @_;
1685              
1686 211         571 my $name = $self->name;
1687 211         576 my $datafile = "$name.$datafnum.data";
1688              
1689             # get path based on dirlev, dirmax, and data file number
1690 211 100       564 if( my $dirlev = $self->dirlev ) {
1691 24         50 my $fnumlen = $self->fnumlen;
1692 24         52 my $fnumbase = $self->fnumbase;
1693 24         53 my $dirmax = $self->dirmax;
1694 24         51 my $path = "";
1695 24         82 my $this = base2int $datafnum, $fnumbase;
1696 24         902 for( 1 .. $dirlev ) {
1697 36 50       110 my $dirint = $dirmax? (int( ( $this - 1 ) / $dirmax ) + 1): 1;
1698 36         120 my $dirnum = int2base $dirint, $fnumbase, $fnumlen;
1699 36 100       486 $path = $path? "$dirnum/$path": $dirnum;
1700 36         90 $this = $dirint;
1701             }
1702 24         62 $path = $self->dir . "/$name/data$path";
1703 24 100       2192 mkpath( $path ) unless -d $path;
1704 24         71 $datafile = "$path/$datafile";
1705             }
1706             else {
1707 187         443 $datafile = $self->dir . "/$datafile";
1708             }
1709              
1710 211         671 return $datafile;
1711             }
1712              
1713             #---------------------------------------------------------------------
1714             #
1715             # =head2 sub all_datafiles(), called by validate utility
1716             #
1717             # Returns an array of paths for all of the data files in the
1718             # datastore.
1719             #
1720             # Private method.
1721             #
1722             # =cut
1723             #
1724              
1725             sub all_datafiles {
1726 0     0 0 0 my( $self ) = @_;
1727              
1728 0         0 my $fnumlen = $self->fnumlen;
1729 0         0 my $fnumbase = $self->fnumbase;
1730 0         0 my $top_toc = $self->new_toc( { int => 0 } );
1731 0         0 my $datafint = $top_toc->datafnum;
1732 0         0 my @files;
1733 0         0 for( 1 .. $datafint ) {
1734 0         0 my $datafnum = int2base $_, $fnumbase, $fnumlen;
1735 0         0 push @files, $self->which_datafile( $datafnum );
1736             }
1737 0         0 return @files;
1738             }
1739              
1740             #---------------------------------------------------------------------
1741              
1742             =head1 OBJECT METHODS, Other
1743              
1744             =head2 howmany( [$regx] )
1745              
1746             Returns count of records whose indicators match regx, e.g.,
1747              
1748             $self->howmany( qr/create|update/ );
1749             $self->howmany( qr/delete/ );
1750             $self->howmany( qr/oldupd|olddel/ );
1751              
1752             If no regx, howmany() returns numrecs from the toc file, which
1753             should give the same number as qr/create|update/.
1754              
1755             =cut
1756              
1757             sub howmany {
1758 1     1 1 2 my( $self, $regx ) = @_;
1759              
1760 1         7 my $top_toc = $self->new_toc( { int => 0 } );
1761              
1762 1 50       8 return $top_toc->numrecs unless $regx;
1763              
1764 0         0 my $howmany = 0;
1765 0         0 for( qw( create update delete oldupd olddel ) ) {
1766 0 0       0 $howmany += $top_toc->$_() if /$regx/ }
1767 0         0 return $howmany;
1768             }
1769              
1770             #---------------------------------------------------------------------
1771              
1772             =head2 lastkeynum()
1773              
1774             Returns the last key number used, i.e., the sequence number of the
1775             last record added to the datastore, as an integer.
1776              
1777             =cut
1778              
1779             sub lastkeynum {
1780 103     103 1 174 my( $self ) = @_;
1781              
1782 103         463 my $top_toc = $self->new_toc( { int => 0 } );
1783 103         472 my $keyint = $top_toc->keynum;
1784              
1785 103         463 return $keyint;
1786             }
1787              
1788             #---------------------------------------------------------------------
1789              
1790             =head2 nextkeynum()
1791              
1792             Returns lastkeynum()+1 (a convenience method). This could be useful
1793             for adding a new record to a hash tied to a datastore, e.g.,
1794              
1795             $h{ $ds->nextkeynum } = "New record data.";
1796              
1797             (but also note that there is a "null key" convention for this -- see
1798             FlatFile::DataStore::Tiehash)
1799              
1800             =cut
1801              
1802             sub nextkeynum {
1803 14     14 1 54 for( $_[0]->lastkeynum ) {
1804 14 50       41 return 0 unless defined;
1805 14         42 return $_ + 1;
1806             }
1807             }
1808              
1809             #---------------------------------------------------------------------
1810             #
1811             # =head2 keyseek( $keyint )
1812             #
1813             # Gets seekpos of a particular line in the key file.
1814             #
1815             # Takes the record sequence number as an integer and returns
1816             # the seek position needed to retrieve the record's preamble from
1817             # the pertinent keyfile.
1818             #
1819             # Interestingly, this seek position is only a function of the keyint
1820             # and keymax values, so this routine doesn't need to know (and doesn't
1821             # return) which keyfile we're seeking into.
1822             #
1823             # Private method.
1824             #
1825             # =cut
1826             #
1827            
1828             sub keyseek {
1829 91     91 0 181 my( $self, $keyint ) = @_;
1830              
1831 91         6508 my $keylen = $self->preamblelen + length( $self->recsep );
1832              
1833 91         149 my $keyseek;
1834 91 100       281 if( my $keymax = $self->keymax ) {
1835 8         24 my $skip = int( $keyint / $keymax );
1836 8         22 $keyseek = $keylen * ( $keyint - ( $skip * $keymax ) ); }
1837             else {
1838 83         160 $keyseek = $keylen * $keyint; }
1839              
1840 91         219 return $keyseek;
1841             }
1842              
1843             #---------------------------------------------------------------------
1844             #
1845             # =head2 nexttransnum(), get next transaction number
1846             #
1847             # Takes a FF::DS::Toc (table of contents) object, which should be
1848             # the "top" Toc that has many of the key values for the datastore.
1849             #
1850             # Returns the next transaction number as an integer.
1851             # Note: transaction numbers begin with 1 (not 0).
1852             #
1853             # Will croak if this number is longer than allowed by the current
1854             # configuration. In that case, a new datastore that allows for
1855             # more transactions must be configured and the old datastore
1856             # migrated to it.
1857             #
1858             # Private method.
1859             #
1860             # =cut
1861             #
1862              
1863             sub nexttransnum {
1864 87     87 0 170 my( $self, $top_toc ) = @_;
1865              
1866 87   33     260 $top_toc ||= $self->new_toc( { int => 0 } );
1867              
1868 87         351 my $transint = $top_toc->transnum + 1;
1869 87         248 my $translen = $self->translen;
1870 87         326 my $transbase = $self->transbase;
1871 87         290 my $transnum = int2base $transint, $transbase, $translen;
1872              
1873 87 100       1855 croak qq/Database exceeds configured size, transnum too long: $transnum/
1874             if length $transnum > $translen;
1875              
1876 84         218 return $transint;
1877             }
1878              
1879             #---------------------------------------------------------------------
1880             #
1881             # =head2 burst_preamble()
1882             #
1883             # Takes a preamble string (as stored on disk) and parses out all
1884             # of the values, based on regx and specs.
1885             #
1886             # Returns a hash ref of these values.
1887             #
1888             # Called by FF::DS::Preamble->new() to create an object from a string,
1889             # and by retrieve() and locate_record_data() to get the file number
1890             # and seek pos for reading a record.
1891             #
1892             # Private method.
1893             #
1894             # =cut
1895             #
1896              
1897             sub burst_preamble {
1898 130     130 0 541 my( $self, $string ) = @_;
1899              
1900 130 50       296 croak qq/No preamble to burst/ unless $string;
1901              
1902 130         381 my @fields = $string =~ $self->regx;
1903              
1904 130 100       613 croak qq/Something is wrong with preamble: $string/ unless @fields;
1905              
1906 129         178 my %parms;
1907             my $i;
1908 129         343 for( $self->specs ) { # specs() returns an array of hashrefs
1909 1597         18276 my( $key, $aref ) = %$_;
1910 1597         3222 my( $pos, $len, $parm ) = @$aref;
1911 1597         2625 my $field = $fields[ $i++ ];
1912 1597         2829 for( $key ) {
1913 1597 100       7961 if( /indicator|transind|date/ ) {
    100          
    100          
1914 387         2253 $parms{ $key } = $field;
1915             }
1916             elsif( /user/ ) {
1917 129         187 my $try = $field;
1918 129         329 $try =~ s/\s+$//;
1919 129         545 $parms{ $key } = $try;
1920             }
1921             elsif( /fnum/ ) {
1922 347 100       1572 next if $field =~ /^-+$/;
1923 153         719 $parms{ $key } = $field;
1924             }
1925             else {
1926 734 100       2066 next if $field =~ /^-+$/;
1927 540         2316 $parms{ $key } = base2int( $field, $parm );
1928             }
1929             }
1930             }
1931 129         732 return \%parms;
1932             }
1933              
1934             #---------------------------------------------------------------------
1935             #
1936             # =head2 update_preamble()
1937             #
1938             # Called by update() and delete() to flag old recs.
1939             #
1940             # Takes a preamble string and a hash ref of values to change, and
1941             # returns a new preamble string with those values changed.
1942             #
1943             # Will croak if the new preamble does not match the regx attribute.
1944             #
1945             # Private method.
1946             #
1947             # =cut
1948             #
1949              
1950             sub update_preamble {
1951 34     34 0 11018 my( $self, $preamble, $parms ) = @_;
1952              
1953 34         95 my $omap = $self->specs;
1954              
1955 34         113 for( keys %$parms ) {
1956              
1957 63         305 my $value = $parms->{ $_ };
1958              
1959 63         389 my $specs = omap_get_values( $omap, $_ );
1960 63 100       1930 croak qq/Unrecognized field: $_/ unless $specs;
1961              
1962 62         75 my( $pos, $len, $parm ) = @{$specs};
  62         130  
1963              
1964 62         73 my $try;
1965 62 100       358 if( /indicator|transind|date|user/ ) {
    100          
1966 23         93 $try = sprintf "%-${len}s", $value;
1967              
1968 23 100       598 croak qq/Invalid value for $_: $try/
1969             unless $try =~ $Ascii_chars;
1970             }
1971             # the fnums should be in their base form already
1972             elsif( /fnum/ ) {
1973 18         74 $try = sprintf "%0${len}s", $value;
1974             }
1975             else {
1976 21         85 $try = int2base $value, $parm, $len;
1977             }
1978              
1979 58 100       1678 croak qq/Value of $_ too long: $try/ if length $try > $len;
1980              
1981 45         126 substr $preamble, $pos, $len, $try; # update the field
1982             }
1983              
1984 16 100       60 croak qq/Something is wrong with preamble: $preamble/
1985             unless $preamble =~ $self->regx;
1986              
1987 15         43 return $preamble;
1988             }
1989              
1990             #---------------------------------------------------------------------
1991             # file read/write:
1992             #---------------------------------------------------------------------
1993              
1994             #---------------------------------------------------------------------
1995             #
1996             # =head2 locked_for_read()
1997             #
1998             # Takes a file name, opens it for input, locks it, sets binmode, and
1999             # returns the open file handle.
2000             #
2001             # Private method.
2002             #
2003             # =cut
2004             #
2005              
2006             sub locked_for_read {
2007 136     136 0 267 my( $self, $file ) = @_;
2008 136         346 untaint path => $file;
2009              
2010 136         231 my $fh;
2011 136 50       6127 sysopen( $fh, $file, O_RDONLY|O_CREAT )
2012             or croak qq/Can't open $file for read: $!/;
2013 136 50       1073 flock $fh, LOCK_SH or croak qq/Can't lock $file shared: $!/;
2014 136         282 binmode $fh;
2015              
2016 136         334 return $fh;
2017             }
2018              
2019             #---------------------------------------------------------------------
2020             #
2021             # =head2 locked_for_write()
2022             #
2023             # Takes a file name, opens it for read/write, locks it, sets binmode,
2024             # and returns the open file handle.
2025             #
2026             # Private method.
2027             #
2028             # =cut
2029             #
2030              
2031             sub locked_for_write {
2032 386     386 0 685 my( $self, $file ) = @_;
2033 386         980 untaint path => $file;
2034              
2035 386         620 my $fh;
2036 386 50       38664 sysopen( $fh, $file, O_RDWR|O_CREAT ) or croak qq/Can't open $file for read-write: $!/;
2037 386         1674 my $ofh = select( $fh ); $| = 1; select ( $ofh ); # flush buffers
  386         1068  
  386         1365  
2038 386 50       3440 flock $fh, LOCK_EX or croak qq/Can't lock $file exclusive: $!/;
2039 386         814 binmode $fh;
2040              
2041 386         1504 return $fh;
2042             }
2043              
2044             #---------------------------------------------------------------------
2045             #
2046             # =head2 read_record()
2047             #
2048             # Takes an open file handle and a seek position and
2049             #
2050             # - seeks there to read the preamble
2051             # - seeks to the record data and reads that
2052             # - returns a record object created from the preamble and data
2053             #
2054             # Private method.
2055             #
2056             # =cut
2057             #
2058              
2059             sub read_record {
2060 62     62 0 132 my( $self, $fh, $seekpos ) = @_;
2061              
2062             # we don't call read_preamble() because we need len anyway
2063 62         172 my $len = $self->preamblelen;
2064 62         181 my $sref = $self->read_bytes( $fh, $seekpos, $len );
2065 62         458 my $preamble = $self->new_preamble( { string => $$sref } );
2066              
2067 62         158 $seekpos += $len;
2068 62         211 $len = $preamble->reclen;
2069 62         192 my $recdata = $self->read_bytes( $fh, $seekpos, $len );
2070              
2071 62         344 my $record = $self->new_record( {
2072             preamble => $preamble,
2073             data => $recdata, # scalar ref
2074             } );
2075              
2076 62         240 return $record;
2077             }
2078              
2079             #---------------------------------------------------------------------
2080             #
2081             # =head2 read_preamble()
2082             #
2083             # Takes an open file handle (probably the key file) and a seek
2084             # position and
2085             #
2086             # - seeks there to read the preamble
2087             # - returns the preamble string (not an object)
2088             #
2089             # Private method.
2090             #
2091             # =cut
2092             #
2093              
2094             sub read_preamble {
2095 89     89 0 193 my( $self, $fh, $seekpos ) = @_;
2096              
2097 89         214 my $len = $self->preamblelen;
2098 89         283 my $sref = $self->read_bytes( $fh, $seekpos, $len );
2099              
2100 89         301 return $$sref; # want the string, not the ref
2101             }
2102              
2103             #---------------------------------------------------------------------
2104             #
2105             # =head2 read_bytes()
2106             #
2107             # Takes an open file handle, a seek position and a length, reads
2108             # that many bytes from that position, and returns a scalar
2109             # reference to that data. It is expected that the file is set
2110             # to binmode.
2111             #
2112             # Private method.
2113             #
2114             # =cut
2115             #
2116              
2117             sub read_bytes {
2118 215     215 0 359 my( $self, $fh, $seekpos, $len ) = @_;
2119              
2120 215         257 my $string;
2121 215 50       1344 sysseek $fh, $seekpos, 0 or croak qq/Can't seek: $!/;
2122 215         1742 my $rc = sysread $fh, $string, $len;
2123 215 50       541 croak qq/Can't read: $!/ unless defined $rc;
2124              
2125 215         493 return \$string;
2126             }
2127              
2128             #---------------------------------------------------------------------
2129             #
2130             # =head2 write_bytes()
2131             #
2132             # Takes an open file handle, a seek position, and a scalar
2133             # reference and writes that data to the file at that position.
2134             # It is expected that the file is set to binmode.
2135             #
2136             # Private method.
2137             #
2138             # =cut
2139             #
2140              
2141             sub write_bytes {
2142 351     351 0 640 my( $self, $fh, $seekpos, $sref ) = @_;
2143              
2144 351 50       2307 sysseek $fh, $seekpos, 0 or croak qq/Can't seek: $!/;
2145 351 50       9011 syswrite $fh, $$sref or croak qq/Can't write: $!/;
2146              
2147             }
2148              
2149             #---------------------------------------------------------------------
2150             #
2151             # =head2 read_file(), used by init() to read the .uri file
2152             #
2153             # Takes a file name, locks it for reading, and returns the
2154             # contents as an array of lines
2155             #
2156             # Private method.
2157             #
2158             # =cut
2159             #
2160              
2161             sub read_file {
2162 10     10 0 25 my( $self, $file ) = @_;
2163 10         36 untaint path => $file;
2164              
2165 10         16 my $fh;
2166 10 50       565 open $fh, '<', $file or croak qq/Can't open $file for read: $!/;
2167 10 50       101 flock $fh, LOCK_SH or croak qq/Can't lock $file shared: $!/;
2168             # binmode $fh; # NO binmode here, please
2169              
2170 10         373 return <$fh>;
2171             }
2172              
2173              
2174             #---------------------------------------------------------------------
2175             #
2176             # =head2 now(), expects a string that contains
2177             #
2178             # 'yyyy', 'mm', 'da', 'tttttt' (hhmmss) in some order, or
2179             # 'yy', 'm', 'd', 'ttt' (hms) in some order
2180             #
2181             # ('yyyy' is a magic string that denotes decimal vs. base62)
2182             #
2183             # Returns current date formatted as requested.
2184             #
2185             # Private method.
2186             #
2187             # =cut
2188             #
2189              
2190             sub now {
2191 84     84 0 161 my( $format ) = @_;
2192 84     84   614 my( $yr, $mo, $da, $hr, $mn, $sc ) =
2193 84         6034 sub{($_[5]+1900,$_[4]+1,$_[3],$_[2],$_[1],$_[0])}->(localtime);
2194 84         480 for( $format ) {
2195 84 100       356 if( /yyyy/ ) { # decimal
2196 22         149 s/ yyyy / sprintf "%04d", $yr /ex; # Y10K bug
  22         123  
2197 22         78 s/ mm / sprintf "%02d", $mo /ex;
  22         50  
2198 22         62 s/ dd / sprintf "%02d", $da /ex;
  22         48  
2199 22         72 s/ tttttt / sprintf "%02d%02d%02d", $hr, $mn, $sc /ex;
  0         0  
2200             }
2201             else { # base62
2202 62         320 s/ yy / int2base( $yr, 62 ) /ex; # Y3844 bug
  62         208  
2203 62         1197 s/ m / int2base( $mo, 62 ) /ex;
  62         193  
2204 62         950 s/ d / int2base( $da, 62 ) /ex;
  62         176  
2205 62         924 s/ ttt / int2base( $hr, 62 ).
  61         205  
2206             int2base( $mn, 62 ).
2207             int2base( $sc, 62 ) /ex;
2208             }
2209             }
2210 84         3045 return $format;
2211             }
2212              
2213             #---------------------------------------------------------------------
2214             #
2215             # =head2 TIEHASH() supports tied hash access
2216             #
2217             # Returns datastore object.
2218             #
2219             # Note: because of how new_toc and new_record are implemented, I
2220             # couldn't make Tiehash a subclass, so I'm requiring it into this
2221             # class. This may change in the future -- or not.
2222             #
2223             # Somewhat private method.
2224             #
2225             # =cut
2226             #
2227              
2228             sub TIEHASH {
2229              
2230             # Note: 'require', not 'use'. This isn't
2231             # a "true" module -- we're just bringing in
2232             # some more FlatFile::DataStore methods.
2233              
2234 4     4   5442 require FlatFile::DataStore::Tiehash;
2235              
2236 4         14 my $class = shift;
2237 4         27 $class->new( @_ );
2238             }
2239              
2240             #---------------------------------------------------------------------
2241             BEGIN {
2242 23     23   1521 my %allow = (
2243             trusted => qr{^ (.*) $}x, # i.e., anything
2244             path => qr{^ ([-.\w/]*) $}x, # e.g., /tmp/sess/s.1.data
2245             );
2246              
2247             sub untaint {
2248 538     538 0 896 my( $key, $var ) = @_;
2249 538         923 for( $var ) {
2250 538 50       1188 return unless defined;
2251 538 50       2895 return if /^$/;
2252             }
2253 538         964 for( $key ) {
2254 538 50       1519 die "Not defined: $_" unless $allow{ $_ }; # programmer error
2255 538 50       4347 if( $var =~ /$allow{ $_ }/ ) { $_[1] = $1 } # must set the alias
  538         2343  
2256 0           else { die "Invalid $_.\n" } # intentionally coy
2257             }
2258             }}
2259              
2260             1; # returned
2261              
2262             __END__