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