Moved a few things and started on the MySQL schema
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine.pm
1 package DBM::Deep::Engine;
2
3 use 5.006_000;
4
5 use strict;
6 use warnings FATAL => 'all';
7
8 use 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
16 =head1 NAME
17
18 DBM::Deep::Engine
19
20 =head1 PURPOSE
21
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.
24
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.
28
29 =head1 OVERVIEW
30
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
33 is 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
53 =item * setup
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
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.
72
73 =head1 TRANSACTIONS
74
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
78 with only one file.
79
80 =head2 STALENESS
81
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
88 and discard it.
89
90 =head2 DURABILITY
91
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.)
101
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.
107
108 =cut
109
110 =head2 read_value( $obj, $key )
111
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.
114
115 =cut
116
117 sub read_value { die "read_value must be implemented in a child class" }
118
119 =head2 get_classname( $obj )
120
121 This takes an object that provides _base_offset() and returns the classname (if
122 any) associated with it.
123
124 It delegates to Sector::Reference::get_classname() for the heavy lifting.
125
126 It performs a staleness check.
127
128 =cut
129
130 sub get_classname { die "get_classname must be implemented in a child class" }
131
132 =head2 make_reference( $obj, $old_key, $new_key )
133
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} >>.
137
138 This returns nothing.
139
140 =cut
141
142 sub make_reference { die "make_reference must be implemented in a child class" }
143
144 =head2 key_exists( $obj, $key )
145
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.
148
149 =cut
150
151 sub key_exists { die "key_exists must be implemented in a child class" }
152
153 =head2 delete_key( $obj, $key )
154
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
157 delete_key() method.
158
159 =cut
160
161 sub delete_key { die "delete_key must be implemented in a child class" }
162
163 =head2 write_value( $obj, $key, $value )
164
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/>.
167
168 This returns 1 upon success.
169
170 =cut
171
172 sub write_value { die "write_value must be implemented in a child class" }
173
174 =head2 setup( $obj )
175
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.
179
180 This returns 1.
181
182 =cut
183
184 sub setup { die "setup must be implemented in a child class" }
185
186 =head2 begin_work( $obj )
187
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.
190
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.
193
194 This returns undef.
195
196 =cut
197
198 sub begin_work { die "begin_work must be implemented in a child class" }
199
200 =head2 rollback( $obj )
201
202 This takes an object that provides _base_offset(). It will revert all
203 actions taken within the running transaction.
204
205 If $obj is not within a transaction, an error will be thrown.
206
207 This returns 1.
208
209 =cut
210
211 sub rollback { die "rollback must be implemented in a child class" }
212
213 =head2 commit( $obj )
214
215 This takes an object that provides _base_offset(). It will apply all
216 actions taken within the transaction to the HEAD.
217
218 If $obj is not within a transaction, an error will be thrown.
219
220 This returns 1.
221
222 =cut
223
224 sub commit { die "commit must be implemented in a child class" }
225
226 =head2 get_next_key( $obj, $prev_key )
227
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.
230
231 This method delegates to C<< DBM::Deep::Iterator->get_next_key() >>.
232
233 =cut
234
235 # XXX Add staleness here
236 sub get_next_key {
237     my $self = shift;
238     my ($obj, $prev_key) = @_;
239
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,
245             engine      => $self,
246         });
247     }
248
249     return $obj->{iterator}->get_next_key( $obj );
250 }
251
252 =head2 lock_exclusive()
253
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.
256
257 This returns nothing.
258
259 =cut
260
261 sub lock_exclusive {
262     my $self = shift;
263     my ($obj) = @_;
264     return $self->storage->lock_exclusive( $obj );
265 }
266
267 =head2 lock_shared()
268
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.
271
272 This returns nothing.
273
274 =cut
275
276 sub lock_shared {
277     my $self = shift;
278     my ($obj) = @_;
279     return $self->storage->lock_shared( $obj );
280 }
281
282 =head2 unlock()
283
284 This takes an object that provides _base_offset(). It will guarantee that
285 the storage has released the most recently-taken lock.
286
287 This returns nothing.
288
289 =cut
290
291 sub 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
304 The following methods are internal-use-only to DBM::Deep::Engine and its
305 child classes.
306
307 =cut
308
309 =head2 flush()
310
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().
313
314 This returns nothing.
315
316 =cut
317
318 sub 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
328 =head2 load_sector( $loc )
329
330 This takes an id/location/offset and loads the sector based on the engine's
331 defined sector type.
332
333 =cut
334
335 sub load_sector { $_[0]->sector_type->load( @_ ) }
336
337 =head2 ACCESSORS
338
339 The following are readonly attributes.
340
341 =over 4
342
343 =item * storage
344
345 =back
346
347 =cut
348
349 sub storage { $_[0]{storage} }
350
351 sub sector_type { die "sector_type must be implemented in a child class" }
352
353 1;
354 __END__