File Coverage

blib/lib/FlatFile/DataStore/Tiehash.pm
Criterion Covered Total %
statement 42 55 76.3
branch 22 36 61.1
condition 7 9 77.7
subroutine 8 13 61.5
pod n/a
total 79 113 69.9


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package FlatFile::DataStore; # not FlatFile::DataStore::Tiehash
3             #---------------------------------------------------------------------
4              
5             =head1 NAME
6              
7             FlatFile::DataStore::Tiehash - Provides routines that are used
8             only when tie'ing a hash to a datastore.
9              
10             =head1 SYNOPSYS
11              
12             require FlatFile::DataStore::Tiehash;
13              
14             (But this is done only in FlatFile/DataStore.pm)
15              
16             =head1 DESCRIPTION
17              
18             FlatFile::DataStore::Tiehash provides the routines that are used only
19             when tie'ing a hash to a datastore. It isn't a "true" module; it's
20             intended for loading more methods into the FlatFile::DataStore class.
21              
22             =head1 SYNOPSYS
23              
24             use FlatFile::DataStore; # not FlatFile::DataStore::Tiehash
25              
26             tie my %dshash, 'FlatFile::DataStore', {
27             name => "dsname",
28             dir => "/my/datastore/directory",
29             };
30              
31             # create a record (null string key says, "new record")
32              
33             my $record = $dshash{''} = { data => "Test record", user => "Test user data" };
34             my $record_number = $record->keynum;
35              
36             # update it (have to "have" a record to update it)
37              
38             $record->data( "Updating the test record." );
39             $dshash{ $record_number } = $record;
40              
41             # retrieve it
42              
43             $record = $dshash{ $record_number };
44              
45             # delete it
46              
47             delete $dshash{ $record_number };
48              
49             # -or-
50              
51             tied(%dshash)->delete( $record );
52              
53             # test it ... exists is true after a delete
54              
55             if( $preamble = exists $dshash{ $record_number } ) {
56             print "Deleted." if $preamble->is_deleted;
57             }
58              
59             =head1 DESCRIPTION
60              
61             This module provides the methods that allow you to tie a hash to a data
62             store. The hash keys are integers that range from 0 to
63             $datastore_object->lastkeynum.
64              
65             In the case of delete, you're limited in the tied interface -- you
66             can't supply a "delete record" (one that has information about the
67             delete operation). Instead, it will simply retrieve the existing
68             record and store that as the "delete record". If you want the "delete
69             record" to contain different information (such as who is deleting it),
70             you must call the non-tied delete() method with the datastore object.
71              
72             Note that record data may be created or updated (i.e., STORE'd) three
73             ways:
74              
75             As data string (or scalar reference), e.g.,
76              
77             $record = $dshash{''} = $record_data;
78              
79             As a hash reference (so you can supply some user data), e.g.
80              
81             $record = $dshash{''} = { data => $record_data, user => $user_data };
82              
83             As a record object (record data and user data gotten from object),
84             e.g.,
85              
86             $record->data( $record_data );
87             $record->user( $user_data );
88             $record = $dshash{''} = $record;
89              
90             Note that in the last example, the object fetched is not the same as
91             the one given to be stored (it has a different preamble).
92              
93             The above examples use a "null key" convention. When you assign to the
94             null key entry, it creates a new record in the datastore, which adds a
95             new record key sequence number. When you read the null key entry, it
96             retrieves the last record. Thus when you do this:
97              
98             $record = $dshash{''} = $record_data;
99              
100             You are creating a new record (C<$dshash{''} = $record_data>), and you
101             are then retrieving the last record (C<$record = $dshash{''}>), which
102             happens to be the record you just created. This null key convention
103             saves you from having to do something like this equivalent code:
104              
105             my $ds = tied %dshash;
106             $dshash{ $ds->nextkeynum } = $record_data;
107             $record = $dshash{ $ds->lastkeynum };
108            
109             =head1 VERSION
110              
111             FlatFile::DataStore::Tiehash version 1.03
112              
113             =cut
114              
115             our $VERSION = '1.03';
116              
117 2     2   57 use 5.008003;
  2         8  
  2         114  
118 2     2   14 use strict;
  2         2  
  2         95  
119 2     2   88 use warnings;
  2         5  
  2         74  
120 2     2   12 use Carp;
  2         4  
  2         1914  
121              
122             #---------------------------------------------------------------------
123             # NOTE: TIEHASH() is defined in FlatFile/DataStore.pm
124              
125             #---------------------------------------------------------------------
126             # FETCH() supports tied hash access
127             # Returns a record object.
128              
129             sub FETCH {
130 17     17   69 my( $self, $key ) = @_;
131              
132 17         62 my $lastkeynum = $self->lastkeynum;
133 17 100       56 $key = $lastkeynum if $key eq '';
134              
135 17 50       74 return if $key !~ /^[0-9]+$/;
136 17 50       50 return if $key > $lastkeynum;
137 17         85 $self->retrieve( $key );
138             }
139              
140             #---------------------------------------------------------------------
141             # STORE() supports tied hash access
142             # Keys are limited to 0 .. lastkeynum (integers)
143             # If $key is new, it has to be nextkeynum, i.e., you can't leave
144             # gaps in the sequence of keys
145             # e.g., $h{ keys %h } = { data => "New", user => "record" };
146             # or $h{ tied( %h )->nextkeynum } = { data => "New", user => "record" };
147             # or $h{ '' } = { data => "New", user => "record" };
148             # or $h{ undef } = { data => "New", user => "record" };
149             # ('keys %h' is fairly light-weight, but nextkeynum is more so
150             # and $h{''} (or $h{undef}) is shorthand for nextkeynum)
151              
152             sub STORE {
153 13     13   3913 my( $self, $key, $parms ) = @_;
154              
155 13         58 my $nextkeynum = $self->nextkeynum;
156 13 100       37 $key = $nextkeynum if $key eq '';
157 13 100 100     380 croak qq/Unsupported key format: $key/
158             unless $key =~ /^[0-9]+$/ and $key <= $nextkeynum;
159              
160 11         24 my $reftype = ref $parms; # record, hash, sref, string
161              
162             # for updates, $parms must be a record object
163 11 100       28 if( $key < $nextkeynum ) {
164 4 100 66     125 croak qq/Not a record object: $parms/
165             unless $reftype and $reftype =~ /Record/;
166 3         23 my $keynum = $parms->keynum;
167 3 100       202 croak qq/Record key number, $keynum, doesn't match key: $key/
168             unless $key == $keynum;
169 2         11 return $self->update( $parms );
170             }
171              
172             # for creates, $parms may be record, href, sref, or string
173             else {
174 7 100 66     48 if( !$reftype or $reftype eq "SCALAR" ) { # string
175 1         8 return $self->create({ data => $parms });
176             }
177 6 100       19 if( $reftype =~ /Record/ ) {
178 1         6 return $self->create( $parms );
179             }
180 5 100       15 if( $reftype eq 'HASH' ) { # e.g., {data=>'recdata',user=>'userdata'}
181 4         21 return $self->create( $parms );
182             }
183             else {
184 1         132 croak qq/Unsupported ref type: $reftype/;
185             }
186             }
187             }
188              
189             #---------------------------------------------------------------------
190             # DELETE() supports tied hash access
191             # If you want the "delete record" to contain anything more than
192             # the record being deleted, you have to call tied( %h )->delete()
193             # instead.
194             #
195             # Otherwise, we have to have a record to delete one, so we fetch
196             # it first.
197              
198             sub DELETE {
199 1     1   5 my( $self, $key ) = @_;
200 1 50       8 return if $key !~ /^[0-9]+$/;
201 1 50       5 return if $key > $self->lastkeynum;
202 1         6 my $record = $self->retrieve( $key );
203 1         7 $self->delete( $record );
204             }
205              
206             #---------------------------------------------------------------------
207             # CLEAR() supports tied hash access
208             # except we don't support CLEAR, because it would be very
209             # destructive and it would be a pain to recover from an
210             # accidental %h = ();
211              
212             sub CLEAR {
213 1     1   14 my $self = shift;
214 1         197 croak qq/Clearing the entire datastore is not supported/;
215             }
216              
217             #---------------------------------------------------------------------
218             # FIRSTKEY() supports tied hash access
219             # The keys in a datastore are always 0 .. lastkeynum (integers).
220             # Before the first record is added, nextkeynum() returns 0.
221             # In that case, the sub below would return undef.
222              
223             sub FIRSTKEY {
224 0     0     my $self = shift;
225 0 0         return 0 if $self->nextkeynum > 0;
226             }
227              
228             #---------------------------------------------------------------------
229             # NEXTKEY() supports tied hash access
230             # Because FIRSTKEY/NEXTKEY are functions of integers and require
231             # reading only a single line from a file (lastkeynum() reads the
232             # first line of the first toc file), the 'keys %h' operation is
233             # comparatively light-weight ('values %h' is a different story.)
234              
235             sub NEXTKEY {
236 0     0     my( $self, $prevkey ) = @_;
237 0 0         return if $prevkey >= $self->lastkeynum;
238 0           $prevkey + 1;
239             }
240              
241             #---------------------------------------------------------------------
242             # SCALAR() supports tied hash access
243             # nextkeynum() returns 0 before any records are added. A non-zero
244             # value indicates there are records -- created, updated, and/or
245             # deleted. Note that exists() returns true for a deleted record.
246              
247             sub SCALAR {
248 0     0     my $self = shift;
249 0           $self->nextkeynum;
250             }
251              
252             #---------------------------------------------------------------------
253             # EXISTS() supports tied hash access
254             # This routine will return a true value for created, updated,
255             # *and* deleted records. This true value is in fact a preamble
256             # object, so if needed, you can check the status of the record
257             # (deleted or not), e.g.,
258             #
259             # if( my $preamble = exists( $key ) ) {
260             # print "Deleted." if $preamble->is_deleted();
261             # print "Created." if $preamble->is_created();
262             # print "Updated." if $preamble->is_updated();
263             # }
264              
265             sub EXISTS {
266 0     0     my( $self, $key ) = @_;
267 0 0         return if $key !~ /^[0-9]+$/;
268 0 0         return if $key > $self->lastkeynum;
269 0           $self->retrieve_preamble( $key );
270             }
271              
272             #---------------------------------------------------------------------
273             # UNTIE() supports tied hash access
274             # (see perldoc perltie, The "untie" Gotcha)
275              
276             sub UNTIE {
277 0     0     my( $self, $count ) = @_;
278 0 0         carp "untie attempted while $count inner references still exist" if $count;
279             }
280              
281              
282             1; # returned
283              
284             __END__