File Coverage

blib/lib/DBM/Deep/Engine.pm
Criterion Covered Total %
statement 91 91 100.0
branch 13 14 92.8
condition 4 4 100.0
subroutine 37 37 100.0
pod 23 23 100.0
total 168 169 99.4


line stmt bran cond sub pod time code
1             package DBM::Deep::Engine;
2              
3 59     59   959 use 5.008_004;
  59         206  
4              
5 59     54   394 use strict;
  54         119  
  54         1305  
6 54     54   309 use warnings FATAL => 'all';
  54         156  
  54         1922  
7 54     53   357 no warnings 'recursion';
  53         147  
  53         1864  
8              
9 53     53   23148 use DBM::Deep::Iterator ();
  53         166  
  53         46587  
10              
11             # File-wide notes:
12             # * Every method in here assumes that the storage has been appropriately
13             # safeguarded. This can be anything from flock() to some sort of manual
14             # mutex. But, it's the caller's responsibility to make sure that this has
15             # been done.
16              
17             sub SIG_HASH () { 'H' }
18             sub SIG_ARRAY () { 'A' }
19              
20             =head1 NAME
21              
22             DBM::Deep::Engine - mediate mapping between DBM::Deep objects and storage medium
23              
24             =head1 PURPOSE
25              
26             This is an internal-use-only object for L. It mediates the low-level
27             mapping between the L objects and the storage medium.
28              
29             The purpose of this documentation is to provide low-level documentation for
30             developers. It is B intended to be used by the general public. This
31             documentation and what it documents can and will change without notice.
32              
33             =head1 OVERVIEW
34              
35             The engine exposes an API to the DBM::Deep objects (DBM::Deep, DBM::Deep::Array,
36             and DBM::Deep::Hash) for their use to access the actual stored values. This API
37             is the following:
38              
39             =over 4
40              
41             =item * new
42              
43             =item * read_value
44              
45             =item * get_classname
46              
47             =item * make_reference
48              
49             =item * key_exists
50              
51             =item * delete_key
52              
53             =item * write_value
54              
55             =item * get_next_key
56              
57             =item * setup
58              
59             =item * clear
60              
61             =item * begin_work
62              
63             =item * commit
64              
65             =item * rollback
66              
67             =item * lock_exclusive
68              
69             =item * lock_shared
70              
71             =item * unlock
72              
73             =back
74              
75             They are explained in their own sections below. These methods, in turn, may
76             provide some bounds-checking, but primarily act to instantiate objects in the
77             Engine::Sector::* hierarchy and dispatch to them.
78              
79             =head1 TRANSACTIONS
80              
81             Transactions in DBM::Deep are implemented using a variant of MVCC. This attempts
82             to keep the amount of actual work done against the file low while still providing
83             Atomicity, Consistency, and Isolation. Durability, unfortunately, cannot be done
84             with only one file.
85              
86             =head2 STALENESS
87              
88             If another process uses a transaction slot and writes stuff to it, then
89             terminates, the data that process wrote is still within the file. In order to
90             address this, there is also a transaction staleness counter associated within
91             every write. Each time a transaction is started, that process increments that
92             transaction's staleness counter. If, when it reads a value, the staleness
93             counters aren't identical, DBM::Deep will consider the value on disk to be stale
94             and discard it.
95              
96             =head2 DURABILITY
97              
98             The fourth leg of ACID is Durability, the guarantee that when a commit returns,
99             the data will be there the next time you read from it. This should be regardless
100             of any crashes or powerdowns in between the commit and subsequent read.
101             DBM::Deep does provide that guarantee; once the commit returns, all of the data
102             has been transferred from the transaction shadow to the HEAD. The issue arises
103             with partial commits - a commit that is interrupted in some fashion. In keeping
104             with DBM::Deep's "tradition" of very light error-checking and non-existent
105             error-handling, there is no way to recover from a partial commit. (This is
106             probably a failure in Consistency as well as Durability.)
107              
108             Other DBMSes use transaction logs (a separate file, generally) to achieve
109             Durability. As DBM::Deep is a single-file, we would have to do something
110             similar to what SQLite and BDB do in terms of committing using synchronized
111             writes. To do this, we would have to use a much higher RAM footprint and some
112             serious programming that makes my head hurt just to think about it.
113              
114             =cut
115              
116             =head1 METHODS
117              
118             =head2 read_value( $obj, $key )
119              
120             This takes an object that provides _base_offset() and a string. It returns the
121             value stored in the corresponding Sector::Value's data section.
122              
123             =cut
124              
125 3     3 1 57 sub read_value { die "read_value must be implemented in a child class" }
126              
127             =head2 get_classname( $obj )
128              
129             This takes an object that provides _base_offset() and returns the classname (if
130             any) associated with it.
131              
132             It delegates to Sector::Reference::get_classname() for the heavy lifting.
133              
134             It performs a staleness check.
135              
136             =cut
137              
138 2     2 1 22 sub get_classname { die "get_classname must be implemented in a child class" }
139              
140             =head2 make_reference( $obj, $old_key, $new_key )
141              
142             This takes an object that provides _base_offset() and two strings. The
143             strings correspond to the old key and new key, respectively. This operation
144             is equivalent to (given C<< $db->{foo} = []; >>) C<< $db->{bar} = $db->{foo} >>.
145              
146             This returns nothing.
147              
148             =cut
149              
150 2     2 1 6 sub make_reference { die "make_reference must be implemented in a child class" }
151              
152             =head2 key_exists( $obj, $key )
153              
154             This takes an object that provides _base_offset() and a string for
155             the key to be checked. This returns 1 for true and "" for false.
156              
157             =cut
158              
159 3     3 1 51 sub key_exists { die "key_exists must be implemented in a child class" }
160              
161             =head2 delete_key( $obj, $key )
162              
163             This takes an object that provides _base_offset() and a string for
164             the key to be deleted. This returns the result of the Sector::Reference
165             delete_key() method.
166              
167             =cut
168              
169 3     3 1 40 sub delete_key { die "delete_key must be implemented in a child class" }
170              
171             =head2 write_value( $obj, $key, $value )
172              
173             This takes an object that provides _base_offset(), a string for the
174             key, and a value. This value can be anything storable within L.
175              
176             This returns 1 upon success.
177              
178             =cut
179              
180 3     3 1 15 sub write_value { die "write_value must be implemented in a child class" }
181              
182             =head2 setup( $obj )
183              
184             This takes an object that provides _base_offset(). It will do everything needed
185             in order to properly initialize all values for necessary functioning. If this is
186             called upon an already initialized object, this will also reset the inode.
187              
188             This returns 1.
189              
190             =cut
191              
192 3     3 1 42 sub setup { die "setup must be implemented in a child class" }
193              
194             =head2 begin_work( $obj )
195              
196             This takes an object that provides _base_offset(). It will set up all necessary
197             bookkeeping in order to run all work within a transaction.
198              
199             If $obj is already within a transaction, an error will be thrown. If there are
200             no more available transactions, an error will be thrown.
201              
202             This returns undef.
203              
204             =cut
205              
206 3     3 1 30 sub begin_work { die "begin_work must be implemented in a child class" }
207              
208             =head2 rollback( $obj )
209              
210             This takes an object that provides _base_offset(). It will revert all
211             actions taken within the running transaction.
212              
213             If $obj is not within a transaction, an error will be thrown.
214              
215             This returns 1.
216              
217             =cut
218              
219 3     3 1 15 sub rollback { die "rollback must be implemented in a child class" }
220              
221             =head2 commit( $obj )
222              
223             This takes an object that provides _base_offset(). It will apply all
224             actions taken within the transaction to the HEAD.
225              
226             If $obj is not within a transaction, an error will be thrown.
227              
228             This returns 1.
229              
230             =cut
231              
232 3     3 1 42 sub commit { die "commit must be implemented in a child class" }
233              
234             =head2 get_next_key( $obj, $prev_key )
235              
236             This takes an object that provides _base_offset() and an optional string
237             representing the prior key returned via a prior invocation of this method.
238              
239             This method delegates to C<< DBM::Deep::Iterator->get_next_key() >>.
240              
241             =cut
242              
243             # XXX Add staleness here
244             sub get_next_key {
245 547     547 1 1021 my $self = shift;
246 547         1234 my ($obj, $prev_key) = @_;
247              
248             # XXX Need to add logic about resetting the iterator if any key in the
249             # reference has changed
250 547 100       1545 unless ( defined $prev_key ) {
251 164 50   24   714 eval "use " . $self->iterator_class; die $@ if $@;
  163     20   757  
  26     16   13801  
  26     15   312  
  26     9   557  
  22     6   300  
  22     6   132  
  22     6   386  
  18         231  
  18         89  
  17         277  
  16         124  
  16         54  
  15         244  
  9         68  
  9         25  
  9         187  
  6         52  
  6         15  
  6         103  
  6         46  
  6         16  
  6         119  
  6         55  
  6         16  
  6         114  
252 163         687 $obj->{iterator} = $self->iterator_class->new({
253             base_offset => $obj->_base_offset,
254             engine => $self,
255             });
256             }
257              
258 545         1995 return $obj->{iterator}->get_next_key( $obj );
259             }
260              
261             =head2 lock_exclusive()
262              
263             This takes an object that provides _base_offset(). It will guarantee that
264             the storage has taken precautions to be safe for a write.
265              
266             This returns nothing.
267              
268             =cut
269              
270             sub lock_exclusive {
271 5832     5832 1 9242 my $self = shift;
272 5832         10497 my ($obj) = @_;
273 5832         11357 return $self->storage->lock_exclusive( $obj );
274             }
275              
276             =head2 lock_shared()
277              
278             This takes an object that provides _base_offset(). It will guarantee that
279             the storage has taken precautions to be safe for a read.
280              
281             This returns nothing.
282              
283             =cut
284              
285             sub lock_shared {
286 4628     4628 1 7478 my $self = shift;
287 4628         7903 my ($obj) = @_;
288 4628         8453 return $self->storage->lock_shared( $obj );
289             }
290              
291             =head2 unlock()
292              
293             This takes an object that provides _base_offset(). It will guarantee that
294             the storage has released the most recently-taken lock.
295              
296             This returns nothing.
297              
298             =cut
299              
300             sub unlock {
301 10453     10453 1 16261 my $self = shift;
302 10453         18009 my ($obj) = @_;
303              
304 10453         20064 my $rv = $self->storage->unlock( $obj );
305              
306 10449 100       34338 $self->flush if $rv;
307              
308 10447         28023 return $rv;
309             }
310              
311             =head1 INTERNAL METHODS
312              
313             The following methods are internal-use-only to DBM::Deep::Engine and its
314             child classes.
315              
316             =cut
317              
318             =head2 flush()
319              
320             This takes no arguments. It will do everything necessary to flush all things to
321             disk. This is usually called during unlock() and setup().
322              
323             This returns nothing.
324              
325             =cut
326              
327             sub flush {
328 5520     5520 1 9415 my $self = shift;
329              
330             # Why do we need to have the storage flush? Shouldn't autoflush take care of
331             # things? -RobK, 2008-06-26
332 5520         11942 $self->storage->flush;
333              
334 5518         9208 return;
335             }
336              
337             =head2 load_sector( $loc )
338              
339             This takes an id/location/offset and loads the sector based on the engine's
340             defined sector type.
341              
342             =cut
343              
344 23036     23036 1 60473 sub load_sector { $_[0]->sector_type->load( @_ ) }
345              
346             =head2 clear( $obj )
347              
348             This takes an object that provides _base_offset() and deletes all its
349             elements, returning nothing.
350              
351             =cut
352              
353 3     3 1 28 sub clear { die "clear must be implemented in a child class" }
354              
355             =head2 cache / clear_cache
356              
357             This is the cache of loaded Reference sectors.
358              
359             =cut
360              
361 2385   100 2385 1 8830 sub cache { $_[0]{cache} ||= {} }
362 4     4 1 25 sub clear_cache { %{$_[0]->cache} = () }
  4         15  
363              
364             =head2 supports( $option )
365              
366             This returns a boolean depending on if this instance of DBM::Dep supports
367             that feature. C<$option> can be one of:
368              
369             =over 4
370              
371             =item * transactions
372              
373             =item * singletons
374              
375             =back
376              
377             Any other value will return false.
378              
379             =cut
380              
381 3     3 1 60 sub supports { die "supports must be implemented in a child class" }
382              
383             =head1 ACCESSORS
384              
385             The following are readonly attributes.
386              
387             =over 4
388              
389             =item * storage
390              
391             =item * sector_type
392              
393             =item * iterator_class
394              
395             =back
396              
397             =cut
398              
399 186131     186130 1 505311 sub storage { $_[0]{storage} }
400              
401 3     1 1 16 sub sector_type { die "sector_type must be implemented in a child class" }
402 3     1 1 51 sub iterator_class { die "iterator_class must be implemented in a child class" }
403              
404             # This code is to make sure we write all the values in the $value to the
405             # disk and to make sure all changes to $value after the assignment are
406             # reflected on disk. This may be counter-intuitive at first, but it is
407             # correct dwimmery.
408             # NOTE - simply tying $value won't perform a STORE on each value. Hence,
409             # the copy to a temp value.
410             sub _descend {
411 773     771   1444 my $self = shift;
412 773         1759 my ($value, $value_sector) = @_;
413 773   100     3831 my $r = Scalar::Util::reftype( $value ) || '';
414              
415 773 100       2717 if ( $r eq 'ARRAY' ) {
    100          
416 44         136 my @temp = @$value;
417 44         177 tie @$value, 'DBM::Deep', {
418             base_offset => $value_sector->offset,
419             staleness => $value_sector->staleness,
420             storage => $self->storage,
421             engine => $self,
422             };
423 44         301 @$value = @temp;
424 44 100       261 bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
425             }
426             elsif ( $r eq 'HASH' ) {
427 166         807 my %temp = %$value;
428 166         618 tie %$value, 'DBM::Deep', {
429             base_offset => $value_sector->offset,
430             staleness => $value_sector->staleness,
431             storage => $self->storage,
432             engine => $self,
433             };
434 166         1236 %$value = %temp;
435 166 100       937 bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
436             }
437              
438 773         1729 return;
439             }
440              
441             1;
442             __END__