1 package DBM::Deep::Engine;
6 use warnings FATAL => 'all';
8 use DBM::Deep::Iterator ();
11 # * Every method in here assumes that the storage has been appropriately
12 # safeguarded. This can be anything from flock() to some sort of manual
13 # mutex. But, it's the caller's responsability to make sure that this has
22 This is an internal-use-only object for L<DBM::Deep/>. It mediates the low-level
23 mapping between the L<DBM::Deep/> objects and the storage medium.
25 The purpose of this documentation is to provide low-level documentation for
26 developers. It is B<not> intended to be used by the general public. This
27 documentation and what it documents can and will change without notice.
31 The engine exposes an API to the DBM::Deep objects (DBM::Deep, DBM::Deep::Array,
32 and DBM::Deep::Hash) for their use to access the actual stored values. This API
43 =item * make_reference
61 =item * lock_exclusive
69 They are explained in their own sections below. These methods, in turn, may
70 provide some bounds-checking, but primarily act to instantiate objects in the
71 Engine::Sector::* hierarchy and dispatch to them.
75 Transactions in DBM::Deep are implemented using a variant of MVCC. This attempts
76 to keep the amount of actual work done against the file low while stil providing
77 Atomicity, Consistency, and Isolation. Durability, unfortunately, cannot be done
82 If another process uses a transaction slot and writes stuff to it, then
83 terminates, the data that process wrote it still within the file. In order to
84 address this, there is also a transaction staleness counter associated within
85 every write. Each time a transaction is started, that process increments that
86 transaction's staleness counter. If, when it reads a value, the staleness
87 counters aren't identical, DBM::Deep will consider the value on disk to be stale
92 The fourth leg of ACID is Durability, the guarantee that when a commit returns,
93 the data will be there the next time you read from it. This should be regardless
94 of any crashes or powerdowns in between the commit and subsequent read.
95 DBM::Deep does provide that guarantee; once the commit returns, all of the data
96 has been transferred from the transaction shadow to the HEAD. The issue arises
97 with partial commits - a commit that is interrupted in some fashion. In keeping
98 with DBM::Deep's "tradition" of very light error-checking and non-existent
99 error-handling, there is no way to recover from a partial commit. (This is
100 probably a failure in Consistency as well as Durability.)
102 Other DBMSes use transaction logs (a separate file, generally) to achieve
103 Durability. As DBM::Deep is a single-file, we would have to do something
104 similar to what SQLite and BDB do in terms of committing using synchonized
105 writes. To do this, we would have to use a much higher RAM footprint and some
106 serious programming that make my head hurts just to think about it.
110 =head2 read_value( $obj, $key )
112 This takes an object that provides _base_offset() and a string. It returns the
113 value stored in the corresponding Sector::Value's data section.
117 sub read_value { die "read_value must be implemented in a child class" }
119 =head2 get_classname( $obj )
121 This takes an object that provides _base_offset() and returns the classname (if
122 any) associated with it.
124 It delegates to Sector::Reference::get_classname() for the heavy lifting.
126 It performs a staleness check.
130 sub get_classname { die "get_classname must be implemented in a child class" }
132 =head2 make_reference( $obj, $old_key, $new_key )
134 This takes an object that provides _base_offset() and two strings. The
135 strings correspond to the old key and new key, respectively. This operation
136 is equivalent to (given C<< $db->{foo} = []; >>) C<< $db->{bar} = $db->{foo} >>.
138 This returns nothing.
142 sub make_reference { die "make_reference must be implemented in a child class" }
144 =head2 key_exists( $obj, $key )
146 This takes an object that provides _base_offset() and a string for
147 the key to be checked. This returns 1 for true and "" for false.
151 sub key_exists { die "key_exists must be implemented in a child class" }
153 =head2 delete_key( $obj, $key )
155 This takes an object that provides _base_offset() and a string for
156 the key to be deleted. This returns the result of the Sector::Reference
161 sub delete_key { die "delete_key must be implemented in a child class" }
163 =head2 write_value( $obj, $key, $value )
165 This takes an object that provides _base_offset(), a string for the
166 key, and a value. This value can be anything storable within L<DBM::Deep/>.
168 This returns 1 upon success.
172 sub write_value { die "write_value must be implemented in a child class" }
176 This takes an object that provides _base_offset(). It will do everything needed
177 in order to properly initialize all values for necessary functioning. If this is
178 called upon an already initialized object, this will also reset the inode.
184 sub setup { die "setup must be implemented in a child class" }
186 =head2 begin_work( $obj )
188 This takes an object that provides _base_offset(). It will set up all necessary
189 bookkeeping in order to run all work within a transaction.
191 If $obj is already within a transaction, an error wiill be thrown. If there are
192 no more available transactions, an error will be thrown.
198 sub begin_work { die "begin_work must be implemented in a child class" }
200 =head2 rollback( $obj )
202 This takes an object that provides _base_offset(). It will revert all
203 actions taken within the running transaction.
205 If $obj is not within a transaction, an error will be thrown.
211 sub rollback { die "rollback must be implemented in a child class" }
213 =head2 commit( $obj )
215 This takes an object that provides _base_offset(). It will apply all
216 actions taken within the transaction to the HEAD.
218 If $obj is not within a transaction, an error will be thrown.
224 sub commit { die "commit must be implemented in a child class" }
226 =head2 get_next_key( $obj, $prev_key )
228 This takes an object that provides _base_offset() and an optional string
229 representing the prior key returned via a prior invocation of this method.
231 This method delegates to C<< DBM::Deep::Iterator->get_next_key() >>.
235 # XXX Add staleness here
238 my ($obj, $prev_key) = @_;
240 # XXX Need to add logic about resetting the iterator if any key in the
241 # reference has changed
242 unless ( $prev_key ) {
243 $obj->{iterator} = DBM::Deep::Iterator->new({
244 base_offset => $obj->_base_offset,
249 return $obj->{iterator}->get_next_key( $obj );
252 =head2 lock_exclusive()
254 This takes an object that provides _base_offset(). It will guarantee that
255 the storage has taken precautions to be safe for a write.
257 This returns nothing.
264 return $self->storage->lock_exclusive( $obj );
269 This takes an object that provides _base_offset(). It will guarantee that
270 the storage has taken precautions to be safe for a read.
272 This returns nothing.
279 return $self->storage->lock_shared( $obj );
284 This takes an object that provides _base_offset(). It will guarantee that
285 the storage has released the most recently-taken lock.
287 This returns nothing.
295 my $rv = $self->storage->unlock( $obj );
302 =head1 INTERNAL METHODS
304 The following methods are internal-use-only to DBM::Deep::Engine and its
311 This takes no arguments. It will do everything necessary to flush all things to
312 disk. This is usually called during unlock() and setup().
314 This returns nothing.
321 # Why do we need to have the storage flush? Shouldn't autoflush take care of
322 # things? -RobK, 2008-06-26
323 $self->storage->flush;
328 =head2 load_sector( $loc )
330 This takes an id/location/offset and loads the sector based on the engine's
335 sub load_sector { $_[0]->sector_type->load( @_ ) }
339 The following are readonly attributes.
349 sub storage { $_[0]{storage} }
351 sub sector_type { die "sector_type must be implemented in a child class" }