Commit | Line | Data |
696cadb7 |
1 | package DBM::Deep::Engine3; |
2 | |
3 | use 5.6.0; |
4 | |
5 | use strict; |
6 | |
7 | our $VERSION = q(0.99_03); |
8 | |
696cadb7 |
9 | use Scalar::Util (); |
10 | |
11 | # File-wide notes: |
8fbac729 |
12 | # * Every method in here assumes that the storage has been appropriately |
696cadb7 |
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 | |
17 | # Setup file and tag signatures. These should never change. |
18 | sub SIG_FILE () { 'DPDB' } |
19 | sub SIG_HEADER () { 'h' } |
20 | sub SIG_INTERNAL () { 'i' } |
21 | sub SIG_HASH () { 'H' } |
22 | sub SIG_ARRAY () { 'A' } |
23 | sub SIG_NULL () { 'N' } |
24 | sub SIG_DATA () { 'D' } |
25 | sub SIG_INDEX () { 'I' } |
26 | sub SIG_BLIST () { 'B' } |
27 | sub SIG_FREE () { 'F' } |
28 | sub SIG_KEYS () { 'K' } |
29 | sub SIG_SIZE () { 1 } |
30 | |
696cadb7 |
31 | ################################################################################ |
32 | |
8fbac729 |
33 | # Please refer to the pack() documentation for further information |
34 | my %StP = ( |
ad4ae302 |
35 | 1 => 'C', # Unsigned char value |
8fbac729 |
36 | 2 => 'n', # Unsigned short in "network" (big-endian) order |
37 | 4 => 'N', # Unsigned long in "network" (big-endian) order |
38 | 8 => 'Q', # Usigned quad (no order specified, presumably machine-dependent) |
c83524c6 |
39 | ); |
40 | |
696cadb7 |
41 | sub new { |
42 | my $class = shift; |
43 | my ($args) = @_; |
44 | |
45 | my $self = bless { |
c83524c6 |
46 | byte_size => 4, |
696cadb7 |
47 | |
c83524c6 |
48 | digest => undef, |
696cadb7 |
49 | hash_size => 16, # In bytes |
50 | max_buckets => 16, |
3976d8c9 |
51 | num_txns => 16, # HEAD plus 15 running txns |
696cadb7 |
52 | |
53 | storage => undef, |
696cadb7 |
54 | }, $class; |
55 | |
56 | if ( defined $args->{pack_size} ) { |
57 | if ( lc $args->{pack_size} eq 'small' ) { |
c83524c6 |
58 | $args->{byte_size} = 2; |
696cadb7 |
59 | } |
60 | elsif ( lc $args->{pack_size} eq 'medium' ) { |
c83524c6 |
61 | $args->{byte_size} = 4; |
696cadb7 |
62 | } |
63 | elsif ( lc $args->{pack_size} eq 'large' ) { |
c83524c6 |
64 | $args->{byte_size} = 8; |
696cadb7 |
65 | } |
66 | else { |
67 | die "Unknown pack_size value: '$args->{pack_size}'\n"; |
68 | } |
69 | } |
70 | |
71 | # Grab the parameters we want to use |
72 | foreach my $param ( keys %$self ) { |
73 | next unless exists $args->{$param}; |
74 | $self->{$param} = $args->{$param}; |
75 | } |
696cadb7 |
76 | |
8fbac729 |
77 | $self->{byte_pack} = $StP{ $self->byte_size }; |
c83524c6 |
78 | |
696cadb7 |
79 | ## |
80 | # Number of buckets per blist before another level of indexing is |
81 | # done. Increase this value for slightly greater speed, but larger database |
82 | # files. DO NOT decrease this value below 16, due to risk of recursive |
83 | # reindex overrun. |
84 | ## |
85 | if ( $self->{max_buckets} < 16 ) { |
86 | warn "Floor of max_buckets is 16. Setting it to 16 from '$self->{max_buckets}'\n"; |
87 | $self->{max_buckets} = 16; |
88 | } |
89 | |
c83524c6 |
90 | if ( !$self->{digest} ) { |
91 | require Digest::MD5; |
92 | $self->{digest} = \&Digest::MD5::md5; |
93 | } |
94 | |
696cadb7 |
95 | return $self; |
96 | } |
97 | |
98 | ################################################################################ |
99 | |
100 | sub read_value { |
101 | my $self = shift; |
c9f02899 |
102 | my ($obj, $key) = @_; |
3976d8c9 |
103 | |
104 | # This will be a Reference sector |
c9f02899 |
105 | my $sector = $self->_load_sector( $obj->_base_offset ) |
106 | or die "How did read_value fail (no sector for '$obj')?!\n"; |
3976d8c9 |
107 | |
108 | my $key_md5 = $self->_apply_digest( $key ); |
109 | |
110 | # XXX What should happen if this fails? |
111 | my $blist = $sector->get_bucket_list({ |
112 | key_md5 => $key_md5, |
f18dcc13 |
113 | create => 1, |
764e6cb9 |
114 | }) or die "How did read_value fail (no blist)?!\n"; |
3976d8c9 |
115 | |
116 | my $value_sector = $blist->get_data_for( $key_md5 ); |
4056dff7 |
117 | if ( !$value_sector ) { |
118 | # Autovivification |
119 | $value_sector = DBM::Deep::Engine::Sector::Null->new({ |
120 | engine => $self, |
121 | data => undef, |
122 | }); |
123 | |
124 | $blist->write_md5( $key_md5, $key, $value_sector->offset ); |
125 | } |
3976d8c9 |
126 | |
127 | return $value_sector->data; |
696cadb7 |
128 | } |
129 | |
84467b9f |
130 | sub get_classname { |
131 | my $self = shift; |
c9f02899 |
132 | my ($obj) = @_; |
84467b9f |
133 | |
134 | # This will be a Reference sector |
c9f02899 |
135 | my $sector = $self->_load_sector( $obj->_base_offset ) |
136 | or die "How did read_value fail (no sector for '$obj')?!\n"; |
84467b9f |
137 | |
138 | return $sector->get_classname; |
139 | } |
140 | |
696cadb7 |
141 | sub key_exists { |
142 | my $self = shift; |
c9f02899 |
143 | my ($obj, $key) = @_; |
c000ae6e |
144 | |
145 | # This will be a Reference sector |
c9f02899 |
146 | my $sector = $self->_load_sector( $obj->_base_offset ) |
147 | or die "How did key_exists fail (no sector for '$obj')?!\n"; |
c000ae6e |
148 | |
149 | my $key_md5 = $self->_apply_digest( $key ); |
150 | |
151 | # XXX What should happen if this fails? |
152 | my $blist = $sector->get_bucket_list({ |
153 | key_md5 => $key_md5, |
764e6cb9 |
154 | }) or die "How did key_exists fail (no blist)?!\n"; |
c000ae6e |
155 | |
e86cef36 |
156 | # exists() returns 1 or '' for true/false. |
157 | return $blist->has_md5( $key_md5 ) ? 1 : ''; |
696cadb7 |
158 | } |
159 | |
160 | sub delete_key { |
161 | my $self = shift; |
c9f02899 |
162 | my ($obj, $key) = @_; |
e86cef36 |
163 | |
c9f02899 |
164 | my $sector = $self->_load_sector( $obj->_base_offset ) |
165 | or die "How did delete_key fail (no sector for '$obj')?!\n"; |
e86cef36 |
166 | |
167 | my $key_md5 = $self->_apply_digest( $key ); |
168 | |
169 | # XXX What should happen if this fails? |
170 | my $blist = $sector->get_bucket_list({ |
171 | key_md5 => $key_md5, |
764e6cb9 |
172 | }) or die "How did delete_key fail (no blist)?!\n"; |
e86cef36 |
173 | |
174 | return $blist->delete_md5( $key_md5 ); |
696cadb7 |
175 | } |
176 | |
177 | sub write_value { |
178 | my $self = shift; |
c9f02899 |
179 | my ($obj, $key, $value) = @_; |
3976d8c9 |
180 | |
181 | # This will be a Reference sector |
c9f02899 |
182 | my $sector = $self->_load_sector( $obj->_base_offset ) |
183 | or die "How did write_value fail (no sector for '$obj')?!\n"; |
3976d8c9 |
184 | |
185 | my $key_md5 = $self->_apply_digest( $key ); |
186 | |
187 | # XXX What should happen if this fails? |
188 | my $blist = $sector->get_bucket_list({ |
189 | key_md5 => $key_md5, |
190 | create => 1, |
764e6cb9 |
191 | }) or die "How did write_value fail (no blist)?!\n"; |
3976d8c9 |
192 | |
764e6cb9 |
193 | my $r = Scalar::Util::reftype( $value ) || ''; |
d49782fe |
194 | { |
195 | last if $r eq ''; |
196 | last if $r eq 'HASH'; |
197 | last if $r eq 'ARRAY'; |
198 | |
199 | DBM::Deep->_throw_error( |
200 | "Storage of references of type '$r' is not supported." |
201 | ); |
202 | } |
203 | |
764e6cb9 |
204 | my ($class, $type); |
68369f26 |
205 | if ( !defined $value ) { |
4eee718c |
206 | $class = 'DBM::Deep::Engine::Sector::Null'; |
68369f26 |
207 | } |
764e6cb9 |
208 | elsif ( $r eq 'ARRAY' || $r eq 'HASH' ) { |
d49782fe |
209 | if ( $r eq 'ARRAY' && tied(@$value) ) { |
25eb38b8 |
210 | DBM::Deep->_throw_error( "Cannot store something that is tied." ); |
d49782fe |
211 | } |
212 | if ( $r eq 'HASH' && tied(%$value) ) { |
25eb38b8 |
213 | DBM::Deep->_throw_error( "Cannot store something that is tied." ); |
d49782fe |
214 | } |
764e6cb9 |
215 | $class = 'DBM::Deep::Engine::Sector::Reference'; |
ed38e772 |
216 | $type = substr( $r, 0, 1 ); |
764e6cb9 |
217 | } |
68369f26 |
218 | else { |
4eee718c |
219 | $class = 'DBM::Deep::Engine::Sector::Scalar'; |
68369f26 |
220 | } |
3976d8c9 |
221 | |
3ed26433 |
222 | if ( $blist->has_md5( $key_md5 ) ) { |
223 | $blist->get_data_for( $key_md5 )->free; |
224 | } |
ed38e772 |
225 | |
4eee718c |
226 | my $value_sector = $class->new({ |
227 | engine => $self, |
228 | data => $value, |
764e6cb9 |
229 | type => $type, |
4eee718c |
230 | }); |
231 | |
232 | $blist->write_md5( $key_md5, $key, $value_sector->offset ); |
764e6cb9 |
233 | |
234 | # This code is to make sure we write all the values in the $value to the disk |
ed38e772 |
235 | # and to make sure all changes to $value after the assignment are reflected |
236 | # on disk. This may be counter-intuitive at first, but it is correct dwimmery. |
237 | # NOTE - simply tying $value won't perform a STORE on each value. Hence, the |
238 | # copy to a temp value. |
764e6cb9 |
239 | if ( $r eq 'ARRAY' ) { |
ed38e772 |
240 | my @temp = @$value; |
764e6cb9 |
241 | tie @$value, 'DBM::Deep', { |
242 | base_offset => $value_sector->offset, |
243 | storage => $self->storage, |
c9f02899 |
244 | engine => $self, |
764e6cb9 |
245 | }; |
ed38e772 |
246 | @$value = @temp; |
764e6cb9 |
247 | bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value ); |
248 | } |
249 | elsif ( $r eq 'HASH' ) { |
ed38e772 |
250 | my %temp = %$value; |
764e6cb9 |
251 | tie %$value, 'DBM::Deep', { |
252 | base_offset => $value_sector->offset, |
253 | storage => $self->storage, |
c9f02899 |
254 | engine => $self, |
764e6cb9 |
255 | }; |
ed38e772 |
256 | |
257 | %$value = %temp; |
764e6cb9 |
258 | bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value ); |
259 | } |
260 | |
261 | return 1; |
696cadb7 |
262 | } |
263 | |
264 | sub get_next_key { |
265 | my $self = shift; |
c9f02899 |
266 | my ($obj, $prev_key) = @_; |
ed38e772 |
267 | |
268 | # XXX Need to add logic about resetting the iterator if any key in the reference has changed |
269 | unless ( $prev_key ) { |
c9f02899 |
270 | $obj->{iterator} = DBM::Deep::Engine::Iterator->new({ |
271 | base_offset => $obj->_base_offset, |
ed38e772 |
272 | engine => $self, |
273 | }); |
4eee718c |
274 | } |
275 | |
c9f02899 |
276 | return $obj->{iterator}->get_next_key; |
696cadb7 |
277 | } |
278 | |
279 | ################################################################################ |
280 | |
281 | sub setup_fh { |
282 | my $self = shift; |
283 | my ($obj) = @_; |
284 | |
285 | # We're opening the file. |
286 | unless ( $obj->_base_offset ) { |
696cadb7 |
287 | my $bytes_read = $self->_read_file_header; |
696cadb7 |
288 | |
289 | # Creating a new file |
290 | unless ( $bytes_read ) { |
291 | $self->_write_file_header; |
c83524c6 |
292 | |
293 | # 1) Create Array/Hash entry |
8fbac729 |
294 | my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({ |
295 | engine => $self, |
296 | type => $obj->_type, |
297 | }); |
298 | $obj->{base_offset} = $initial_reference->offset; |
c83524c6 |
299 | |
8fbac729 |
300 | $self->storage->flush; |
696cadb7 |
301 | } |
302 | # Reading from an existing file |
303 | else { |
304 | $obj->{base_offset} = $bytes_read; |
764e6cb9 |
305 | my $initial_reference = DBM::Deep::Engine::Sector::Reference->new({ |
306 | engine => $self, |
307 | offset => $obj->_base_offset, |
308 | }); |
309 | unless ( $initial_reference ) { |
696cadb7 |
310 | DBM::Deep->_throw_error("Corrupted file, no master index record"); |
311 | } |
312 | |
764e6cb9 |
313 | unless ($obj->_type eq $initial_reference->type) { |
696cadb7 |
314 | DBM::Deep->_throw_error("File type mismatch"); |
315 | } |
316 | } |
317 | } |
696cadb7 |
318 | |
696cadb7 |
319 | return 1; |
320 | } |
321 | |
c9f02899 |
322 | # begin_work |
323 | sub begin_transaction { |
324 | my $self = shift; |
325 | } |
696cadb7 |
326 | |
c9f02899 |
327 | # rollback |
328 | sub end_transaction { |
696cadb7 |
329 | my $self = shift; |
c9f02899 |
330 | } |
696cadb7 |
331 | |
c9f02899 |
332 | # commit |
333 | sub commit_transaction { |
334 | my $self = shift; |
335 | } |
696cadb7 |
336 | |
c9f02899 |
337 | ################################################################################ |
b9ec359f |
338 | |
c9f02899 |
339 | { |
340 | my $header_fixed = length( SIG_FILE ) + 1 + 4 + 4; |
696cadb7 |
341 | |
c9f02899 |
342 | sub _write_file_header { |
343 | my $self = shift; |
696cadb7 |
344 | |
c9f02899 |
345 | my $header_var = 1 + 1 + 4 + 2 * $self->byte_size; |
696cadb7 |
346 | |
c9f02899 |
347 | my $loc = $self->storage->request_space( $header_fixed + $header_var ); |
c83524c6 |
348 | |
c9f02899 |
349 | $self->storage->print_at( $loc, |
350 | SIG_FILE, |
351 | SIG_HEADER, |
352 | pack('N', 1), # header version - at this point, we're at 9 bytes |
353 | pack('N', $header_var), # header size |
354 | # --- Above is $header_fixed. Below is $header_var |
355 | pack('C', $self->byte_size), |
356 | pack('C', $self->max_buckets), |
357 | pack('N', 0 ), # Running transactions |
358 | pack($StP{$self->byte_size}, 0), # Start of free chain (blist size) |
359 | pack($StP{$self->byte_size}, 0), # Start of free chain (data size) |
360 | ); |
696cadb7 |
361 | |
c9f02899 |
362 | $self->set_trans_loc( $header_fixed + 2 ); |
363 | $self->set_chains_loc( $header_fixed + 6 ); |
696cadb7 |
364 | |
c9f02899 |
365 | return; |
696cadb7 |
366 | } |
367 | |
c9f02899 |
368 | sub _read_file_header { |
369 | my $self = shift; |
696cadb7 |
370 | |
c9f02899 |
371 | my $buffer = $self->storage->read_at( 0, $header_fixed ); |
372 | return unless length($buffer); |
696cadb7 |
373 | |
c9f02899 |
374 | my ($file_signature, $sig_header, $header_version, $size) = unpack( |
375 | 'A4 A N N', $buffer |
376 | ); |
b9ec359f |
377 | |
c9f02899 |
378 | unless ( $file_signature eq SIG_FILE ) { |
379 | $self->storage->close; |
380 | DBM::Deep->_throw_error( "Signature not found -- file is not a Deep DB" ); |
381 | } |
696cadb7 |
382 | |
c9f02899 |
383 | unless ( $sig_header eq SIG_HEADER ) { |
384 | $self->storage->close; |
385 | DBM::Deep->_throw_error( "Old file version found." ); |
386 | } |
696cadb7 |
387 | |
c9f02899 |
388 | my $buffer2 = $self->storage->read_at( undef, $size ); |
389 | my @values = unpack( 'C C', $buffer2 ); |
696cadb7 |
390 | |
c9f02899 |
391 | $self->set_trans_loc( $header_fixed + 2 ); |
392 | $self->set_chains_loc( $header_fixed + 6 ); |
393 | |
394 | if ( @values < 2 || grep { !defined } @values ) { |
395 | $self->storage->close; |
396 | DBM::Deep->_throw_error("Corrupted file - bad header"); |
397 | } |
398 | |
399 | #XXX Add warnings if values weren't set right |
400 | @{$self}{qw(byte_size max_buckets)} = @values; |
b9ec359f |
401 | |
c9f02899 |
402 | my $header_var = 1 + 1 + 4 + 2 * $self->byte_size; |
403 | unless ( $size eq $header_var ) { |
404 | $self->storage->close; |
405 | DBM::Deep->_throw_error( "Unexpected size found ($size <-> $header_var)." ); |
406 | } |
407 | |
408 | return length($buffer) + length($buffer2); |
409 | } |
696cadb7 |
410 | } |
411 | |
3976d8c9 |
412 | sub _load_sector { |
413 | my $self = shift; |
414 | my ($offset) = @_; |
415 | |
416 | my $type = $self->storage->read_at( $offset, 1 ); |
b9ec359f |
417 | return if $type eq chr(0); |
418 | |
3976d8c9 |
419 | if ( $type eq $self->SIG_ARRAY || $type eq $self->SIG_HASH ) { |
420 | return DBM::Deep::Engine::Sector::Reference->new({ |
421 | engine => $self, |
422 | type => $type, |
423 | offset => $offset, |
424 | }); |
425 | } |
426 | elsif ( $type eq $self->SIG_BLIST ) { |
427 | return DBM::Deep::Engine::Sector::BucketList->new({ |
428 | engine => $self, |
429 | type => $type, |
430 | offset => $offset, |
431 | }); |
432 | } |
68369f26 |
433 | elsif ( $type eq $self->SIG_NULL ) { |
434 | return DBM::Deep::Engine::Sector::Null->new({ |
435 | engine => $self, |
436 | type => $type, |
437 | offset => $offset, |
438 | }); |
439 | } |
440 | elsif ( $type eq $self->SIG_DATA ) { |
441 | return DBM::Deep::Engine::Sector::Scalar->new({ |
442 | engine => $self, |
443 | type => $type, |
444 | offset => $offset, |
445 | }); |
446 | } |
b9ec359f |
447 | # This was deleted from under us, so just return and let the caller figure it out. |
448 | elsif ( $type eq $self->SIG_FREE ) { |
449 | return; |
450 | } |
3976d8c9 |
451 | |
ed38e772 |
452 | die "'$offset': Don't know what to do with type '$type'\n"; |
3976d8c9 |
453 | } |
454 | |
455 | sub _apply_digest { |
456 | my $self = shift; |
457 | return $self->{digest}->(@_); |
458 | } |
459 | |
ed38e772 |
460 | sub _add_free_sector { |
461 | my $self = shift; |
462 | my ($offset, $size) = @_; |
b9ec359f |
463 | |
464 | my $chains_offset; |
465 | # Data sector |
466 | if ( $size == 256 ) { |
467 | $chains_offset = $self->byte_size; |
468 | } |
469 | # Blist sector |
470 | else { |
471 | $chains_offset = 0; |
472 | } |
473 | |
474 | my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size ); |
475 | |
3ed26433 |
476 | $self->storage->print_at( $self->chains_loc + $chains_offset, |
b9ec359f |
477 | pack( $StP{$self->byte_size}, $offset ), |
478 | ); |
479 | |
480 | # Record the old head in the new sector after the signature |
481 | $self->storage->print_at( $offset + 1, $old_head ); |
482 | } |
483 | |
484 | sub _request_sector { |
485 | my $self = shift; |
486 | my ($size) = @_; |
487 | |
488 | my $chains_offset; |
489 | # Data sector |
490 | if ( $size == 256 ) { |
491 | $chains_offset = $self->byte_size; |
492 | } |
493 | # Blist sector |
494 | else { |
495 | $chains_offset = 0; |
496 | } |
497 | |
498 | my $old_head = $self->storage->read_at( $self->chains_loc + $chains_offset, $self->byte_size ); |
499 | my $loc = unpack( $StP{$self->byte_size}, $old_head ); |
500 | |
501 | # We don't have any free sectors of the right size, so allocate a new one. |
502 | unless ( $loc ) { |
503 | return $self->storage->request_space( $size ); |
504 | } |
505 | |
506 | my $new_head = $self->storage->read_at( $loc + 1, $self->byte_size ); |
507 | $self->storage->print_at( $self->chains_loc + $chains_offset, $new_head ); |
508 | |
509 | return $loc; |
ed38e772 |
510 | } |
511 | |
696cadb7 |
512 | ################################################################################ |
513 | |
3976d8c9 |
514 | sub storage { $_[0]{storage} } |
515 | sub byte_size { $_[0]{byte_size} } |
516 | sub hash_size { $_[0]{hash_size} } |
517 | sub num_txns { $_[0]{num_txns} } |
518 | sub max_buckets { $_[0]{max_buckets} } |
c000ae6e |
519 | sub blank_md5 { chr(0) x $_[0]->hash_size } |
8fbac729 |
520 | |
c9f02899 |
521 | sub trans_loc { $_[0]{trans_loc} } |
522 | sub set_trans_loc { $_[0]{trans_loc} = $_[1] } |
523 | |
b9ec359f |
524 | sub chains_loc { $_[0]{chains_loc} } |
525 | sub set_chains_loc { $_[0]{chains_loc} = $_[1] } |
526 | |
8fbac729 |
527 | ################################################################################ |
528 | |
ed38e772 |
529 | package DBM::Deep::Engine::Iterator; |
530 | |
531 | sub new { |
532 | my $class = shift; |
533 | my ($args) = @_; |
534 | |
535 | my $self = bless { |
536 | breadcrumbs => [], |
537 | engine => $args->{engine}, |
538 | base_offset => $args->{base_offset}, |
539 | trans_id => $args->{trans_id}, |
540 | }, $class; |
541 | |
542 | Scalar::Util::weaken( $self->{engine} ); |
543 | |
544 | return $self; |
545 | } |
546 | |
547 | sub reset { |
548 | my $self = shift; |
549 | $self->{breadcrumbs} = []; |
550 | } |
551 | |
552 | sub get_next_key { |
553 | my $self = shift; |
554 | |
555 | my $crumbs = $self->{breadcrumbs}; |
556 | |
557 | unless ( @$crumbs ) { |
558 | # This will be a Reference sector |
559 | my $sector = $self->{engine}->_load_sector( $self->{base_offset} ) |
b9ec359f |
560 | # or die "Iterator: How did this fail (no ref sector for '$self->{base_offset}')?!\n"; |
561 | # If no sector is found, thist must have been deleted from under us. |
562 | or return; |
ed38e772 |
563 | push @$crumbs, [ $sector->get_blist_loc, 0 ]; |
564 | } |
565 | |
566 | my $key; |
567 | while ( 1 ) { |
568 | my ($offset, $idx) = @{ $crumbs->[-1] }; |
569 | unless ( $offset ) { |
570 | $self->reset; |
571 | last; |
572 | } |
573 | |
574 | my $sector = $self->{engine}->_load_sector( $offset ) |
b9ec359f |
575 | or die "Iterator: How did this fail (no blist sector for '$offset')?!\n"; |
ed38e772 |
576 | |
577 | my $key_sector = $sector->get_key_for( $idx ); |
578 | unless ( $key_sector ) { |
579 | $self->reset; |
580 | last; |
581 | } |
582 | |
583 | $crumbs->[-1][1]++; |
584 | $key = $key_sector->data; |
585 | last; |
586 | } |
587 | |
588 | return $key; |
589 | } |
590 | |
3976d8c9 |
591 | package DBM::Deep::Engine::Sector; |
592 | |
593 | sub new { |
594 | my $self = bless $_[1], $_[0]; |
595 | Scalar::Util::weaken( $self->{engine} ); |
596 | $self->_init; |
597 | return $self; |
598 | } |
599 | sub _init {} |
600 | |
601 | sub engine { $_[0]{engine} } |
602 | sub offset { $_[0]{offset} } |
603 | sub type { $_[0]{type} } |
604 | |
ed38e772 |
605 | sub free { |
606 | my $self = shift; |
607 | |
b9ec359f |
608 | $self->engine->storage->print_at( $self->offset, |
609 | $self->engine->SIG_FREE, |
610 | chr(0) x ($self->size - 1), |
611 | ); |
612 | |
ed38e772 |
613 | $self->engine->_add_free_sector( |
614 | $self->offset, $self->size, |
615 | ); |
616 | |
b9ec359f |
617 | return; |
ed38e772 |
618 | } |
3976d8c9 |
619 | |
620 | package DBM::Deep::Engine::Sector::Data; |
8fbac729 |
621 | |
622 | our @ISA = qw( DBM::Deep::Engine::Sector ); |
623 | |
3976d8c9 |
624 | # This is in bytes |
625 | sub size { return 256 } |
626 | |
627 | package DBM::Deep::Engine::Sector::Scalar; |
628 | |
629 | our @ISA = qw( DBM::Deep::Engine::Sector::Data ); |
630 | |
ad4ae302 |
631 | sub free { |
632 | my $self = shift; |
633 | |
634 | my $chain_loc = $self->chain_loc; |
635 | |
636 | $self->SUPER::free(); |
637 | |
638 | if ( $chain_loc ) { |
639 | $self->engine->_load_sector( $chain_loc )->free; |
640 | } |
641 | |
642 | return; |
643 | } |
644 | |
3976d8c9 |
645 | sub type { $_[0]{engine}->SIG_DATA } |
8fbac729 |
646 | sub _init { |
647 | my $self = shift; |
648 | |
649 | my $engine = $self->engine; |
650 | |
3976d8c9 |
651 | unless ( $self->offset ) { |
ad4ae302 |
652 | my $data_section = $self->size - 3 - 1 * $engine->byte_size; |
3976d8c9 |
653 | |
654 | my $data = delete $self->{data}; |
655 | |
b9ec359f |
656 | $self->{offset} = $engine->_request_sector( $self->size ); |
ad4ae302 |
657 | |
658 | my $dlen = length $data; |
659 | my $continue = 1; |
660 | my $curr_offset = $self->offset; |
661 | while ( $continue ) { |
662 | |
663 | my $next_offset = 0; |
664 | |
665 | my ($leftover, $this_len, $chunk); |
666 | if ( $dlen > $data_section ) { |
667 | $leftover = 0; |
668 | $this_len = $data_section; |
669 | $chunk = substr( $data, 0, $this_len ); |
670 | |
671 | $dlen -= $data_section; |
672 | $next_offset = $engine->_request_sector( $self->size ); |
673 | $data = substr( $data, $this_len ); |
674 | } |
675 | else { |
676 | $leftover = $data_section - $dlen; |
677 | $this_len = $dlen; |
678 | $chunk = $data; |
679 | |
680 | $continue = 0; |
681 | } |
682 | |
683 | $engine->storage->print_at( $curr_offset, |
684 | $self->type, # Sector type |
685 | pack( $StP{1}, 0 ), # Recycled counter |
686 | pack( $StP{$engine->byte_size}, $next_offset ), # Chain loc |
687 | pack( $StP{1}, $this_len ), # Data length |
688 | $chunk, # Data to be stored in this sector |
689 | chr(0) x $leftover, # Zero-fill the rest |
690 | ); |
691 | |
692 | $curr_offset = $next_offset; |
693 | } |
3976d8c9 |
694 | |
695 | return; |
696 | } |
697 | } |
698 | |
699 | sub data_length { |
700 | my $self = shift; |
701 | |
ad4ae302 |
702 | my $buffer = $self->engine->storage->read_at( |
3976d8c9 |
703 | $self->offset + 2 + $self->engine->byte_size, 1 |
8fbac729 |
704 | ); |
ad4ae302 |
705 | |
706 | return unpack( $StP{1}, $buffer ); |
707 | } |
708 | |
709 | sub chain_loc { |
710 | my $self = shift; |
711 | my $chain_loc = $self->engine->storage->read_at( |
712 | $self->offset + 2, $self->engine->byte_size, |
713 | ); |
714 | return unpack( $StP{$self->engine->byte_size}, $chain_loc ); |
3976d8c9 |
715 | } |
716 | |
717 | sub data { |
718 | my $self = shift; |
8fbac729 |
719 | |
378b4748 |
720 | my $data; |
721 | while ( 1 ) { |
722 | my $chain_loc = $self->chain_loc; |
ad4ae302 |
723 | |
378b4748 |
724 | $data .= $self->engine->storage->read_at( |
725 | $self->offset + 2 + $self->engine->byte_size + 1, $self->data_length, |
726 | ); |
ad4ae302 |
727 | |
378b4748 |
728 | last unless $chain_loc; |
729 | |
730 | $self = $self->engine->_load_sector( $chain_loc ); |
ad4ae302 |
731 | } |
732 | |
733 | return $data; |
8fbac729 |
734 | } |
735 | |
68369f26 |
736 | package DBM::Deep::Engine::Sector::Null; |
737 | |
738 | our @ISA = qw( DBM::Deep::Engine::Sector::Data ); |
739 | |
740 | sub type { $_[0]{engine}->SIG_NULL } |
741 | sub data_length { 0 } |
742 | sub data { return } |
743 | |
744 | sub _init { |
745 | my $self = shift; |
746 | |
747 | my $engine = $self->engine; |
748 | |
749 | unless ( $self->offset ) { |
750 | my $leftover = $self->size - 3 - 1 * $engine->byte_size; |
751 | |
b9ec359f |
752 | $self->{offset} = $engine->_request_sector( $self->size ); |
68369f26 |
753 | $engine->storage->print_at( $self->offset, |
754 | $self->type, # Sector type |
755 | pack( $StP{1}, 0 ), # Recycled counter |
756 | pack( $StP{$engine->byte_size}, 0 ), # Chain loc |
757 | pack( $StP{1}, $self->data_length ), # Data length |
758 | chr(0) x $leftover, # Zero-fill the rest |
759 | ); |
760 | |
761 | return; |
762 | } |
763 | } |
764 | |
3976d8c9 |
765 | package DBM::Deep::Engine::Sector::Reference; |
8fbac729 |
766 | |
3976d8c9 |
767 | our @ISA = qw( DBM::Deep::Engine::Sector::Data ); |
768 | |
769 | sub _init { |
770 | my $self = shift; |
771 | |
772 | my $engine = $self->engine; |
773 | |
774 | unless ( $self->offset ) { |
ba075714 |
775 | my $classname = Scalar::Util::blessed( delete $self->{data} ); |
d4f34951 |
776 | my $leftover = $self->size - 4 - 2 * $engine->byte_size; |
777 | |
778 | my $class_offset = 0; |
779 | if ( defined $classname ) { |
780 | my $class_sector = DBM::Deep::Engine::Sector::Scalar->new({ |
781 | engine => $self->engine, |
782 | data => $classname, |
783 | }); |
784 | $class_offset = $class_sector->offset; |
785 | } |
3976d8c9 |
786 | |
b9ec359f |
787 | $self->{offset} = $engine->_request_sector( $self->size ); |
3976d8c9 |
788 | $engine->storage->print_at( $self->offset, |
d4f34951 |
789 | $self->type, # Sector type |
790 | pack( $StP{1}, 0 ), # Recycled counter |
791 | pack( $StP{$engine->byte_size}, 0 ), # Index/BList loc |
792 | pack( $StP{$engine->byte_size}, $class_offset ), # Classname loc |
793 | chr(0) x $leftover, # Zero-fill the rest |
3976d8c9 |
794 | ); |
795 | |
796 | return; |
797 | } |
764e6cb9 |
798 | |
799 | $self->{type} = $engine->storage->read_at( $self->offset, 1 ); |
800 | |
801 | return; |
3976d8c9 |
802 | } |
803 | |
804 | sub get_blist_loc { |
805 | my $self = shift; |
806 | |
807 | my $engine = $self->engine; |
d4f34951 |
808 | my $blist_loc = $engine->storage->read_at( $self->offset + 2, $engine->byte_size ); |
3976d8c9 |
809 | return unpack( $StP{$engine->byte_size}, $blist_loc ); |
810 | } |
811 | |
812 | sub get_bucket_list { |
813 | my $self = shift; |
814 | my ($args) = @_; |
4eee718c |
815 | $args ||= {}; |
3976d8c9 |
816 | |
817 | # XXX Add in check here for recycling? |
818 | |
819 | my $engine = $self->engine; |
820 | |
821 | my $blist_loc = $self->get_blist_loc; |
822 | |
823 | # There's no index or blist yet |
824 | unless ( $blist_loc ) { |
825 | return unless $args->{create}; |
826 | |
827 | my $blist = DBM::Deep::Engine::Sector::BucketList->new({ |
828 | engine => $engine, |
829 | }); |
d4f34951 |
830 | $engine->storage->print_at( $self->offset + 2, |
3976d8c9 |
831 | pack( $StP{$engine->byte_size}, $blist->offset ), |
832 | ); |
833 | return $blist; |
834 | } |
835 | |
836 | return DBM::Deep::Engine::Sector::BucketList->new({ |
837 | engine => $engine, |
838 | offset => $blist_loc, |
839 | }); |
840 | } |
841 | |
ba075714 |
842 | sub get_classname { |
843 | my $self = shift; |
844 | |
d4f34951 |
845 | my $class_offset = $self->engine->storage->read_at( |
846 | $self->offset + 2 + 1 * $self->engine->byte_size, $self->engine->byte_size, |
ba075714 |
847 | ); |
d4f34951 |
848 | $class_offset = unpack ( $StP{$self->engine->byte_size}, $class_offset ); |
ba075714 |
849 | |
d4f34951 |
850 | return unless $class_offset; |
ba075714 |
851 | |
d4f34951 |
852 | return $self->engine->_load_sector( $class_offset )->data; |
ba075714 |
853 | } |
854 | |
764e6cb9 |
855 | sub data { |
856 | my $self = shift; |
857 | |
858 | my $new_obj = DBM::Deep->new({ |
859 | type => $self->type, |
860 | base_offset => $self->offset, |
861 | storage => $self->engine->storage, |
c9f02899 |
862 | engine => $self->engine, |
764e6cb9 |
863 | }); |
864 | |
ba075714 |
865 | if ( $self->engine->storage->{autobless} ) { |
866 | my $classname = $self->get_classname; |
867 | if ( defined $classname ) { |
868 | bless $new_obj, $classname; |
869 | } |
870 | } |
871 | |
764e6cb9 |
872 | return $new_obj; |
873 | } |
874 | |
3976d8c9 |
875 | package DBM::Deep::Engine::Sector::BucketList; |
876 | |
877 | our @ISA = qw( DBM::Deep::Engine::Sector ); |
878 | |
4eee718c |
879 | sub idx_for_txn { return $_[1] + 1 } |
880 | |
3976d8c9 |
881 | sub _init { |
882 | my $self = shift; |
883 | |
884 | my $engine = $self->engine; |
885 | |
886 | unless ( $self->offset ) { |
c000ae6e |
887 | my $leftover = $self->size - $self->base_size; |
3976d8c9 |
888 | |
b9ec359f |
889 | $self->{offset} = $engine->_request_sector( $self->size ); |
3976d8c9 |
890 | $engine->storage->print_at( $self->offset, |
891 | $engine->SIG_BLIST, # Sector type |
892 | pack( $StP{1}, 0 ), # Recycled counter |
893 | chr(0) x $leftover, # Zero-fill the data |
894 | ); |
895 | } |
8fbac729 |
896 | |
8fbac729 |
897 | return $self; |
898 | } |
8fbac729 |
899 | |
c000ae6e |
900 | sub base_size { 2 } # Sig + recycled counter |
901 | |
3976d8c9 |
902 | sub size { |
903 | my $self = shift; |
4eee718c |
904 | my $e = $self->engine; |
905 | return $self->base_size + $e->max_buckets * $self->bucket_size; # Base + numbuckets * bucketsize |
c000ae6e |
906 | } |
907 | |
908 | sub bucket_size { |
909 | my $self = shift; |
910 | my $e = $self->engine; |
4eee718c |
911 | # Key + transactions |
912 | my $locs_size = (1 + $e->num_txns ) * $e->byte_size; |
913 | return $e->hash_size + $locs_size; |
3976d8c9 |
914 | } |
8fbac729 |
915 | |
3976d8c9 |
916 | sub has_md5 { |
c000ae6e |
917 | my $self = shift; |
918 | my ($found, $idx) = $self->find_md5( @_ ); |
919 | return $found; |
920 | } |
921 | |
922 | sub find_md5 { |
923 | my $self = shift; |
924 | my ($md5) = @_; |
925 | |
c000ae6e |
926 | foreach my $idx ( 0 .. $self->engine->max_buckets - 1 ) { |
927 | my $potential = $self->engine->storage->read_at( |
928 | $self->offset + $self->base_size + $idx * $self->bucket_size, $self->engine->hash_size, |
929 | ); |
930 | |
931 | return (undef, $idx) if $potential eq $self->engine->blank_md5; |
932 | return (1, $idx) if $md5 eq $potential; |
933 | } |
934 | |
935 | return; |
3976d8c9 |
936 | } |
937 | |
938 | sub write_md5 { |
939 | my $self = shift; |
4eee718c |
940 | my ($md5, $key, $value_loc) = @_; |
3976d8c9 |
941 | |
942 | my $engine = $self->engine; |
c000ae6e |
943 | my ($found, $idx) = $self->find_md5( $md5 ); |
4eee718c |
944 | my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size; |
945 | |
946 | unless ($found) { |
947 | my $key_sector = DBM::Deep::Engine::Sector::Scalar->new({ |
948 | engine => $self->engine, |
949 | data => $key, |
950 | }); |
951 | |
952 | $engine->storage->print_at( $spot, |
953 | $md5, |
ed38e772 |
954 | pack( $StP{$self->engine->byte_size}, $key_sector->offset ), |
4eee718c |
955 | ); |
956 | } |
957 | |
958 | $engine->storage->print_at( $spot + $self->engine->hash_size + $self->engine->byte_size, |
3976d8c9 |
959 | pack( $StP{$engine->byte_size}, $value_loc ), # The pointer to the data in the HEAD |
960 | ); |
961 | } |
962 | |
e86cef36 |
963 | sub delete_md5 { |
3976d8c9 |
964 | my $self = shift; |
965 | my ($md5) = @_; |
966 | |
e86cef36 |
967 | my $engine = $self->engine; |
c000ae6e |
968 | my ($found, $idx) = $self->find_md5( $md5 ); |
5c0f86e1 |
969 | return undef unless $found; |
4eee718c |
970 | |
971 | # Save the location so that we can free the data |
ed38e772 |
972 | my $location = $self->get_data_location_for( $idx ); |
3ed26433 |
973 | my $key_sector = $self->get_key_for( $idx ); |
4eee718c |
974 | |
975 | my $spot = $self->offset + $self->base_size + $idx * $self->bucket_size; |
976 | $engine->storage->print_at( $spot, |
977 | $engine->storage->read_at( |
978 | $spot + $self->bucket_size, |
979 | $self->bucket_size * ( $engine->num_txns - $idx - 1 ), |
980 | ), |
981 | chr(0) x $self->bucket_size, |
e86cef36 |
982 | ); |
983 | |
3ed26433 |
984 | $key_sector->free; |
985 | |
ed38e772 |
986 | my $data_sector = $self->engine->_load_sector( $location ); |
987 | my $data = $data_sector->data; |
ed38e772 |
988 | $data_sector->free; |
5c0f86e1 |
989 | |
990 | return $data; |
e86cef36 |
991 | } |
992 | |
ed38e772 |
993 | sub get_data_location_for { |
e86cef36 |
994 | my $self = shift; |
995 | my ($idx) = @_; |
996 | |
3976d8c9 |
997 | my $location = $self->engine->storage->read_at( |
4eee718c |
998 | $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size + $self->engine->byte_size, |
999 | $self->engine->byte_size, |
3976d8c9 |
1000 | ); |
e86cef36 |
1001 | return unpack( $StP{$self->engine->byte_size}, $location ); |
1002 | } |
1003 | |
1004 | sub get_data_for { |
1005 | my $self = shift; |
1006 | my ($md5) = @_; |
1007 | |
1008 | my ($found, $idx) = $self->find_md5( $md5 ); |
1009 | return unless $found; |
ed38e772 |
1010 | my $location = $self->get_data_location_for( $idx ); |
1011 | return $self->engine->_load_sector( $location ); |
1012 | } |
1013 | |
1014 | sub get_key_for { |
1015 | my $self = shift; |
1016 | my ($idx) = @_; |
1017 | |
1018 | my $location = $self->engine->storage->read_at( |
1019 | $self->offset + $self->base_size + $idx * $self->bucket_size + $self->engine->hash_size, |
1020 | $self->engine->byte_size, |
1021 | ); |
1022 | $location = unpack( $StP{$self->engine->byte_size}, $location ); |
1023 | return unless $location; |
68369f26 |
1024 | return $self->engine->_load_sector( $location ); |
3976d8c9 |
1025 | } |
696cadb7 |
1026 | |
1027 | 1; |
1028 | __END__ |