Moved a few things and started on the MySQL schema
[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';
7
8use DBM::Deep::Iterator ();
9
10# File-wide notes:
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
14# been done.
15
64a531e5 16=head1 NAME
17
18DBM::Deep::Engine
19
20=head1 PURPOSE
21
22This is an internal-use-only object for L<DBM::Deep/>. It mediates the low-level
23mapping between the L<DBM::Deep/> objects and the storage medium.
24
25The purpose of this documentation is to provide low-level documentation for
26developers. It is B<not> intended to be used by the general public. This
27documentation and what it documents can and will change without notice.
28
29=head1 OVERVIEW
30
31The engine exposes an API to the DBM::Deep objects (DBM::Deep, DBM::Deep::Array,
32and DBM::Deep::Hash) for their use to access the actual stored values. This API
33is the following:
34
35=over 4
36
37=item * new
38
39=item * read_value
40
41=item * get_classname
42
43=item * make_reference
44
45=item * key_exists
46
47=item * delete_key
48
49=item * write_value
50
51=item * get_next_key
52
f4d0ac97 53=item * setup
64a531e5 54
55=item * begin_work
56
57=item * commit
58
59=item * rollback
60
61=item * lock_exclusive
62
63=item * lock_shared
64
65=item * unlock
66
67=back
68
69They are explained in their own sections below. These methods, in turn, may
70provide some bounds-checking, but primarily act to instantiate objects in the
71Engine::Sector::* hierarchy and dispatch to them.
72
73=head1 TRANSACTIONS
74
75Transactions in DBM::Deep are implemented using a variant of MVCC. This attempts
76to keep the amount of actual work done against the file low while stil providing
77Atomicity, Consistency, and Isolation. Durability, unfortunately, cannot be done
78with only one file.
79
80=head2 STALENESS
81
82If another process uses a transaction slot and writes stuff to it, then
83terminates, the data that process wrote it still within the file. In order to
84address this, there is also a transaction staleness counter associated within
85every write. Each time a transaction is started, that process increments that
86transaction's staleness counter. If, when it reads a value, the staleness
87counters aren't identical, DBM::Deep will consider the value on disk to be stale
88and discard it.
89
90=head2 DURABILITY
91
92The fourth leg of ACID is Durability, the guarantee that when a commit returns,
93the data will be there the next time you read from it. This should be regardless
94of any crashes or powerdowns in between the commit and subsequent read.
95DBM::Deep does provide that guarantee; once the commit returns, all of the data
96has been transferred from the transaction shadow to the HEAD. The issue arises
97with partial commits - a commit that is interrupted in some fashion. In keeping
98with DBM::Deep's "tradition" of very light error-checking and non-existent
99error-handling, there is no way to recover from a partial commit. (This is
100probably a failure in Consistency as well as Durability.)
101
102Other DBMSes use transaction logs (a separate file, generally) to achieve
103Durability. As DBM::Deep is a single-file, we would have to do something
104similar to what SQLite and BDB do in terms of committing using synchonized
105writes. To do this, we would have to use a much higher RAM footprint and some
106serious programming that make my head hurts just to think about it.
107
108=cut
109
f4d0ac97 110=head2 read_value( $obj, $key )
64a531e5 111
f4d0ac97 112This takes an object that provides _base_offset() and a string. It returns the
113value stored in the corresponding Sector::Value's data section.
114
115=cut
116
117sub read_value { die "read_value must be implemented in a child class" }
118
119=head2 get_classname( $obj )
120
121This takes an object that provides _base_offset() and returns the classname (if
122any) associated with it.
123
124It delegates to Sector::Reference::get_classname() for the heavy lifting.
125
126It performs a staleness check.
127
128=cut
129
130sub get_classname { die "get_classname must be implemented in a child class" }
131
132=head2 make_reference( $obj, $old_key, $new_key )
133
134This takes an object that provides _base_offset() and two strings. The
135strings correspond to the old key and new key, respectively. This operation
136is equivalent to (given C<< $db->{foo} = []; >>) C<< $db->{bar} = $db->{foo} >>.
137
138This returns nothing.
139
140=cut
141
142sub make_reference { die "make_reference must be implemented in a child class" }
143
144=head2 key_exists( $obj, $key )
145
146This takes an object that provides _base_offset() and a string for
147the key to be checked. This returns 1 for true and "" for false.
148
149=cut
150
151sub key_exists { die "key_exists must be implemented in a child class" }
152
153=head2 delete_key( $obj, $key )
154
155This takes an object that provides _base_offset() and a string for
156the key to be deleted. This returns the result of the Sector::Reference
157delete_key() method.
158
159=cut
160
161sub delete_key { die "delete_key must be implemented in a child class" }
162
163=head2 write_value( $obj, $key, $value )
164
165This takes an object that provides _base_offset(), a string for the
166key, and a value. This value can be anything storable within L<DBM::Deep/>.
167
168This returns 1 upon success.
169
170=cut
171
172sub write_value { die "write_value must be implemented in a child class" }
173
174=head2 setup( $obj )
175
176This takes an object that provides _base_offset(). It will do everything needed
177in order to properly initialize all values for necessary functioning. If this is
178called upon an already initialized object, this will also reset the inode.
179
180This returns 1.
181
182=cut
183
184sub setup { die "setup must be implemented in a child class" }
185
186=head2 begin_work( $obj )
187
188This takes an object that provides _base_offset(). It will set up all necessary
189bookkeeping in order to run all work within a transaction.
190
191If $obj is already within a transaction, an error wiill be thrown. If there are
192no more available transactions, an error will be thrown.
193
194This returns undef.
195
196=cut
197
198sub begin_work { die "begin_work must be implemented in a child class" }
199
200=head2 rollback( $obj )
201
202This takes an object that provides _base_offset(). It will revert all
203actions taken within the running transaction.
204
205If $obj is not within a transaction, an error will be thrown.
206
207This returns 1.
208
209=cut
210
211sub rollback { die "rollback must be implemented in a child class" }
212
213=head2 commit( $obj )
214
215This takes an object that provides _base_offset(). It will apply all
216actions taken within the transaction to the HEAD.
217
218If $obj is not within a transaction, an error will be thrown.
219
220This returns 1.
221
222=cut
223
224sub commit { die "commit must be implemented in a child class" }
64a531e5 225
bf941eae 226=head2 get_next_key( $obj, $prev_key )
227
228This takes an object that provides _base_offset() and an optional string
229representing the prior key returned via a prior invocation of this method.
230
231This method delegates to C<< DBM::Deep::Iterator->get_next_key() >>.
232
233=cut
234
235# XXX Add staleness here
236sub get_next_key {
237 my $self = shift;
238 my ($obj, $prev_key) = @_;
239
f4d0ac97 240 # XXX Need to add logic about resetting the iterator if any key in the
241 # reference has changed
bf941eae 242 unless ( $prev_key ) {
243 $obj->{iterator} = DBM::Deep::Iterator->new({
244 base_offset => $obj->_base_offset,
245 engine => $self,
246 });
247 }
248
249 return $obj->{iterator}->get_next_key( $obj );
250}
251
f4d0ac97 252=head2 lock_exclusive()
253
254This takes an object that provides _base_offset(). It will guarantee that
255the storage has taken precautions to be safe for a write.
256
257This returns nothing.
258
259=cut
260
261sub lock_exclusive {
262 my $self = shift;
263 my ($obj) = @_;
264 return $self->storage->lock_exclusive( $obj );
265}
266
267=head2 lock_shared()
268
269This takes an object that provides _base_offset(). It will guarantee that
270the storage has taken precautions to be safe for a read.
271
272This returns nothing.
273
274=cut
275
276sub lock_shared {
277 my $self = shift;
278 my ($obj) = @_;
279 return $self->storage->lock_shared( $obj );
280}
281
282=head2 unlock()
283
284This takes an object that provides _base_offset(). It will guarantee that
285the storage has released the most recently-taken lock.
286
287This returns nothing.
288
289=cut
290
291sub unlock {
292 my $self = shift;
293 my ($obj) = @_;
294
295 my $rv = $self->storage->unlock( $obj );
296
297 $self->flush if $rv;
298
299 return $rv;
300}
301
302=head1 INTERNAL METHODS
303
304The following methods are internal-use-only to DBM::Deep::Engine and its
305child classes.
306
307=cut
308
309=head2 flush()
310
311This takes no arguments. It will do everything necessary to flush all things to
312disk. This is usually called during unlock() and setup().
313
314This returns nothing.
315
316=cut
317
318sub flush {
319 my $self = shift;
320
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;
324
325 return;
326}
327
d6ecf579 328=head2 load_sector( $loc )
329
330This takes an id/location/offset and loads the sector based on the engine's
331defined sector type.
332
333=cut
334
335sub load_sector { $_[0]->sector_type->load( @_ ) }
336
337=head2 ACCESSORS
338
339The following are readonly attributes.
340
341=over 4
342
343=item * storage
344
345=back
346
347=cut
348
349sub storage { $_[0]{storage} }
350
351sub sector_type { die "sector_type must be implemented in a child class" }
352
bf941eae 3531;
354__END__