File Coverage

blib/lib/Data/ObjectStore.pm
Criterion Covered Total %
statement 970 970 100.0
branch 303 304 99.6
condition 84 84 100.0
subroutine 137 137 100.0
pod 19 19 100.0
total 1513 1514 99.9


line stmt bran cond sub pod time code
1             package Data::ObjectStore;
2              
3 2     2   1073 use strict;
  2         8  
  2         44  
4 2     2   8 use warnings;
  2         4  
  2         39  
5              
6 2     2   8 no warnings 'numeric';
  2         3  
  2         45  
7 2     2   8 no warnings 'uninitialized';
  2         4  
  2         54  
8 2     2   9 no warnings 'recursion';
  2         4  
  2         62  
9              
10 2     2   9 use File::Path qw( make_path );
  2         3  
  2         164  
11 2     2   10 use Scalar::Util qw(weaken);
  2         2  
  2         226  
12 2     2   877 use Time::HiRes qw(time);
  2         2230  
  2         8  
13 2     2   269 use vars qw($VERSION);
  2         4  
  2         109  
14              
15 2     2   872 use Data::RecordStore;
  2         56499  
  2         52  
16 2     2   672 use Data::ObjectStore::Cache;
  2         3  
  2         112  
17              
18             $VERSION = '2.11';
19              
20             our $DEBUG = 0;
21             our $UPGRADING;
22              
23             use constant {
24 2         6595 DATA_PROVIDER => 0,
25             DIRTY => 1,
26             WEAK => 2,
27             STOREINFO => 3,
28             OPTIONS => 4,
29             CACHE => 5,
30            
31             ID => 0,
32             DATA => 1,
33             METADATA => 3,
34             LEVEL => 4,
35             DIRTY_BIT => 5, #for objs
36 2     2   10 };
  2         4  
37             my( @METAFIELDS ) = qw( created updated );
38              
39             sub open_store {
40 67     67 1 69220 my( $cls, @options ) = @_;
41              
42 67 100       250 die "Data::ObjectStore->open_store requires at least one argument" if 0 == @options;
43            
44 66 100       215 if( 1 == @options ) {
45 34         90 unshift @options, 'DATA_PROVIDER';
46             }
47 66         196 my( %options ) = @options;
48              
49 66         131 my $data_provider = $options{DATA_PROVIDER};
50 66 100       196 if( ! ref( $data_provider ) ) {
51             # the default record store Data::RecordStore
52 64         225 $options{BASE_PATH} = "$data_provider/RECORDSTORE";
53 64         376 $data_provider = Data::RecordStore->open_store( %options );
54             }
55 66 100       241838 my $cache = $options{CACHE} ? ref( $options{CACHE} ) ? $options{CACHE} : Data::ObjectStore::Cache->new( $options{CACHE} ) : undef;
    100          
56 66         342 my $store = bless [
57             $data_provider,
58             {}, #DIRTY CACHE
59             {}, #WEAK CACHE
60             undef,
61             \%options,
62             $cache,
63             ], $cls;
64              
65 66 100       213 if( ! $UPGRADING ) {
66 63         185 $store->[STOREINFO] = $store->_fetch_store_info_node;
67 63         193 $store->load_root_container;
68 63 100       198 if( $store->get_store_version < 1.2 ) {
69 1         4 die "Unable to open store of version ".$store->get_store_version.". Please run upgrade_store.";
70             }
71 62         148 $store->save;
72             }
73 65         323 return $store;
74             } #open_store
75              
76             sub data_store {
77 1     1 1 8 return shift->[DATA_PROVIDER];
78             }
79              
80             sub empty_cache {
81 2     2 1 483 my( $self ) = @_;
82 2 100       6 if( $self->[CACHE] ) {
83 1         4 $self->[CACHE]->empty;
84             }
85             }
86              
87             # locks the given lock names
88             sub lock {
89 1     1 1 3 my( $self, @locknames ) = @_;
90 1         5 $self->[DATA_PROVIDER]->lock( @locknames );
91             }
92              
93             # unlocks all locks
94             sub unlock {
95 1     1 1 3 my $self = shift;
96 1         3 $self->[DATA_PROVIDER]->unlock;
97             }
98              
99             # quick purge is not careful with memory.
100             sub quick_purge {
101 8     8 1 4525 my $self = shift;
102 8         12 my( %keep );
103 8         19 my( @working ) = ( 1 );
104              
105 8         16 my $data_provider = $self->[DATA_PROVIDER];
106 8         31 my $highest = $data_provider->entry_count;
107              
108 8         505 while( @working ) {
109 31         47 my $try = shift @working;
110              
111 31         53 $keep{$try}++;
112              
113 31         58 my $obj = $self->_knot( $try );
114 31         46 my $d = $obj->[DATA];
115 31         37 my %placed;
116             push @working, (
117 31 100       145 grep { ! $keep{$_} && 0 == $placed{$_}++ }
118 31         68 map { substr( $_, 1 ) }
119 31 100       104 grep { /^r/ }
  83         174  
120             (ref( $d ) eq 'ARRAY' ? @$d : values( %$d ) ));
121             }
122              
123 8         13 my $pcount;
124 8         26 for( my $i=1; $i<=$highest; $i++ ) {
125 46 100       107 if( ! $keep{$i} ) {
126 17         54 $data_provider->delete_record( $i );
127 17         21542 ++$pcount;
128             }
129             }
130            
131 8         43 return $pcount;
132             } #quick_purge
133              
134              
135             sub upgrade_store {
136 3     3 1 7762 my( $source_path, $dest_path ) = @_;
137              
138 3 100       66 die "upgrade_store destination '$dest_path' already has a store" if -e "$dest_path/RECORDSTORE";
139              
140             #
141             # Fetch the info directly from the record store and examine it manually.
142             #
143 2         18 my $from_recstore = Data::RecordStore->open_store( "$source_path/RECORDSTORE" );
144              
145 2         2890 my $info = $from_recstore->fetch( 1 );
146 2         912 my( $vers ) = ( $info =~ /[ \`]ObjectStore_version\`v([^\`]*)/ );
147              
148 2 100       14 if( $vers >= 2 ) {
149 1         7 die "Store already at version 2 or above. No upgrade needed\n";
150             }
151              
152              
153             # allows store to open old versions
154             # prevents store from creating root objects when created
155 1         2 $UPGRADING = 1;
156              
157 1         7 my $dest_store = Data::ObjectStore->open_store( "$dest_path" );
158 1         6 my $source_store = Data::ObjectStore->open_store( "$source_path" );
159              
160             #
161             # Clones all objects (that connect to the root) from the source
162             # store to the destination store. For this upgrade, the only thing
163             # that is missing is the object META data.
164             #
165             sub _transfer_obj {
166 8     8   19 my( $source_store, $dest_store, $id, $i ) = @_;
167 8         18 my $ind = ' 'x$i;
168              
169 8         20 my $obj = $dest_store->fetch( $id );
170              
171 8 100       19 if( $obj ) {
172 3         8 return $obj;
173             } # obj
174              
175 5         13 my $source_thing = $source_store->_knot( $id );
176              
177 5         16 my $clone = ref( $source_thing )->_reconstitute( $dest_store,
178             $id,
179             _thaw( $source_thing->_freezedry ),
180             {} );
181            
182 5         14 my $clone_thing = $dest_store->_knot( $clone );
183 5 100       26 if( ref($clone_thing) !~ /^(ARRAY|HASH|Data::ObjectStore::Hash|Data::ObjectStore::Array)$/ ) {
184 3         8 $clone_thing->[DIRTY_BIT] = 1;
185             }
186 5         7 my $odata = $clone_thing->[DATA];
187              
188 5         10 my $meta = $clone_thing->[METADATA];
189 5         13 my $time = time;
190 5         11 $meta->{created} = $time;
191 5         7 $meta->{updated} = $time;
192              
193 5         16 $dest_store->save( $clone );
194              
195 5         5238 my( @connections );
196 5 100       23 if ( ref($odata) eq 'ARRAY' ) {
197 1         5 for (0..$#$odata) {
198 4         8 my $oid = $odata->[$_];
199 4 100       9 if ( $oid > 0 ) {
200 3         6 $odata->[$_] = "r$oid";
201 3 100       7 if ( $oid != $id) {
202 2         4 push @connections, $oid;
203             }
204             }
205             }
206             }
207             else {
208 4         15 for my $key (keys %$odata) {
209 14 100       38 if ( $odata->{$key} > 0 ) {
210 7         11 my $oid = $odata->{$key};
211 7         13 $odata->{$key} = "r$oid";
212 7 100       16 if ( $oid != $id) {
213 5         10 push @connections, $oid;
214             }
215             }
216             }
217             }
218 5         16 $dest_store->save( $clone );
219              
220 5         3428 for my $oid (@connections) {
221 7         52 my $connect_obj = _transfer_obj( $source_store, $dest_store, $oid, 1 + $i );
222 7         15 my $connect_thing = $dest_store->_knot( $connect_obj );
223 7         15 $dest_store->save( $connect_obj );
224             }
225              
226 5         14 $dest_store->fetch( $id );
227             } #_transfer_obj
228              
229 1         5 my $info_node = _transfer_obj( $source_store, $dest_store, 1, 0 );
230 1         6 $info_node->set_ObjectStore_version( $Data::RecordStore::VERSION );
231 1         4 $dest_store->save( $info_node );
232              
233 1         1686 $UPGRADING = 0;
234             } #upgrade_store
235              
236             sub load_root_container {
237 140     140 1 633 my $self = shift;
238 140         273 my $info_node = $self->_fetch_store_info_node;
239 140         314 my $root = $info_node->get_root;
240 140 100       292 unless( $root ) {
241 29         78 $root = $self->create_container;
242 29         104 $info_node->set_root( $root );
243 29         75 $self->save;
244             }
245 140         465 return $root;
246             } #load_root_container
247              
248              
249             sub info {
250 72     72 1 129 my $node = shift->[STOREINFO];
251             my $info = {
252 72         141 map { $_ => $node->get($_) }
  288         488  
253             qw( db_version ObjectStore_version created_time last_update_time )
254             };
255 72         482 $info;
256             } #info
257              
258              
259             sub get_db_version {
260 2     2 1 9 shift->info->{db_version};
261             }
262              
263              
264             sub get_store_version {
265 64     64 1 157 shift->info->{ObjectStore_version};
266             }
267              
268             sub get_created_time {
269 4     4 1 10 shift->info->{created_time};
270             }
271              
272             sub get_last_update_time {
273 2     2 1 7 shift->info->{last_update_time};
274             }
275              
276             sub create_container {
277             # works with create_container( { my data } ) or create_container( 'myclass', { my data } )
278 68     68 1 7403 my( $self, $class, $data ) = @_;
279 68 100       147 if( ref( $class ) ) {
280 10         27 $data = $class;
281 10         14 $class = 'Data::ObjectStore::Container';
282             }
283 68   100     219 $class //= 'Data::ObjectStore::Container';
284              
285 68 100       256 if( $class !~ /^Data::ObjectStore::/ ) {
286 6         12 my $clname = $class;
287 6         19 $clname =~ s/::/\//g;
288 6         40 require "$clname.pm";
289             }
290              
291 68         163 my $id = $self->_new_id;
292              
293 68         185 my $time = time;
294 68         436 my $obj = bless [ $id,
295             undef,
296             $self,
297             { created => $time,
298             updated => $time },
299             ], $class;
300 68         205 $self->_store_weak( $id, $obj );
301 68         163 $self->_dirty( $id );
302              
303 68         221 for my $fld (keys %$data) {
304 30         62 $obj->set( $fld, $data->{$fld} );
305             }
306              
307 68         224 $obj->_init(); #called the first time the object is created.
308 68         143 $obj->[DIRTY_BIT] = 1;
309 68         264 $obj;
310             } #create_container
311              
312             sub save {
313 168     168 1 12109 my( $self, $ref, $class_override ) = @_;
314 168 100       339 if( ref( $ref ) ) {
315 24         51 return $self->_save( $ref, $class_override );
316             }
317 144         272 my $node = $self->_fetch_store_info_node;
318 144         355 my $now = time;
319 144         515 $self->[DATA_PROVIDER]->use_transaction;
320              
321 144         129071 my( @dirty ) = keys %{$self->[DIRTY]};
  144         2789  
322            
323 144         343 for my $id ( @dirty ) {
324 7278         11005 my $obj = $self->[DIRTY]{$id};
325             # assings id if none were given
326 7278         8545 $self->_knot( $obj );
327             } #each dirty
328              
329 144         200 ( @dirty ) = keys %{$self->[DIRTY]};
  144         1890  
330 144         258 for my $id ( @dirty ) {
331 7278         490456 my $obj = delete $self->[DIRTY]{$id};
332 7278         13815 $self->_save( $obj );
333             } #each dirty
334              
335 144         68898 $node->set_last_update_time( $now );
336 144         296 $self->_save( $node );
337              
338 144         144603 $self->[DATA_PROVIDER]->commit_transaction;
339 144         2122701 $self->[DIRTY] = {};
340 144         1490 return 1;
341             } #save
342              
343             sub _save {
344 7446     7446   11601 my( $self, $obj, $class_override ) = @_;
345 7446         12775 my $thingy = $self->_knot( $obj );
346 7446 100       31614 if( ref($thingy) !~ /^(ARRAY|HASH|Data::ObjectStore::Hash|Data::ObjectStore::Array)$/ ) {
347 288 100       614 if( !$thingy->[DIRTY_BIT] ) {
348 5         11 return;
349             }
350 283         397 $thingy->[DIRTY_BIT] = 0;
351             }
352 7441         13471 my $id = $thingy->[ID];
353 7441         10948 delete $self->[DIRTY]{$id}; # need the upgrading cas?
354              
355             #
356             # Save to the record store.
357             #
358 7441         14544 my $text_rep = $thingy->_freezedry;
359 7441 100       15732 my( @meta ) = $class_override ? $class_override : ref( $thingy );
360 7441         10929 for my $fld (@METAFIELDS) {
361 14882         24361 my $val = $thingy->[METADATA]{$fld};
362 14882         21347 push @meta, $val;
363             }
364 7441         49177 my $meta_string = join('|', @meta );
365 7441         25413 $self->[DATA_PROVIDER]->stow( "$meta_string $text_rep", $id );
366            
367             } #_save
368              
369             sub existing_id {
370 3     3 1 896 my( $self, $obj ) = @_;
371 3 100       15 return undef unless ref($obj);
372 2         6 my $tied = $self->_knot( $obj );
373 2 100       9 return $tied ? $tied->[ID] : undef;
374             }
375              
376             sub _has_dirty {
377 15     15   723 my $self = shift;
378 15         22 scalar( keys %{$self->[DIRTY]});
  15         80  
379             }
380              
381             sub _knot {
382 75081     75081   91486 my( $self, $item ) = @_;
383 75081         94981 my $r = ref( $item );
384 75081 100       103213 if( $r ) {
385 74618 100 100     107669 if( $r eq 'ARRAY' ) {
    100 100        
    100          
386 71544         116822 return tied @$item;
387             }
388             elsif( $r eq 'HASH' ) {
389 2093         3299 return tied %$item;
390             }
391             elsif( $r eq 'Data::ObjectStore::Array' ||
392             $r eq 'Data::ObjectStore::Hash' ||
393             $r->isa( 'Data::ObjectStore::Container' ) ) {
394 979         1793 return $item;
395             }
396 2         15 return undef;
397             }
398 463 100       753 if( $item > 0 ) {
399 459         661 my $xout = $self->_xform_out( $item );
400 459         673 my $zout = $self->_knot( $xout );
401 459         664 return $zout;
402             }
403 4         9 return undef;
404             }
405              
406             #
407             # The store info node records useful information about the store
408             #
409             sub _fetch_store_info_node {
410 348     348   535 my( $self ) = @_;
411 348         641 my $node = $self->fetch( 1 );
412 348 100       597 unless( $node ) {
413 29         83 my $first_id = $self->_new_id;
414 29         111 my $now = time;
415 29         256 $node = bless [ 1, {}, $self, { created => $now, updated => $now } ], 'Data::ObjectStore::Container';
416 29         124 $self->_store_weak( 1, $node );
417 29         73 $self->_dirty( 1 );
418 29         78 $node->[DIRTY_BIT] = 1;
419 29         112 $node->set_db_version( $Data::RecordStore::VERSION );
420 29         93 $node->set_ObjectStore_version( $Data::ObjectStore::VERSION );
421 29         95 $node->set_created_time( $now );
422 29         67 $node->set_last_update_time( $now );
423             }
424 348         586 $node;
425             } #_fetch_store_info_node
426              
427             sub _thaw {
428 318     318   505 my( $dryfroze ) = @_;
429              
430             # so foo` or foo\\` but not foo\\\`
431             # also this will never start with a `
432 318         2100 my $pieces = [ split /\`/, $dryfroze, -1 ];
433              
434              
435             # check to see if any of the parts were split on escapes
436             # like mypart`foo`oo (should be translated to mypart\`foo\`oo
437 318 100       833 if ( 0 < grep { /\\$/ } @$pieces ) {
  4835         6772  
438              
439 33         45 my $newparts = [];
440              
441 33         42 my $is_hanging = 0;
442 33         46 my $working_part = '';
443              
444 33         45 for my $part (@$pieces) {
445              
446             # if the part ends in a hanging escape
447 398 100       978 if ( $part =~ /(^|[^\\])((\\\\)+)?[\\]$/ ) {
    100          
448 123 100       162 if ( $is_hanging ) {
449 28         46 $working_part .= "`$part";
450             } else {
451 95         116 $working_part = $part;
452             }
453 123         149 $is_hanging = 1;
454             } elsif ( $is_hanging ) {
455 95         163 my $newpart = "$working_part`$part";
456 95         225 $newpart =~ s/\\`/`/gs;
457 95         154 $newpart =~ s/\\\\/\\/gs;
458 95         139 push @$newparts, $newpart;
459 95         123 $is_hanging = 0;
460             } else {
461             # normal part
462 180         260 push @$newparts, $part;
463             }
464             }
465 33         74 $pieces = $newparts;
466              
467             } #if there were escaped ` characters
468              
469 318         486 $pieces;
470             } #_thaw
471              
472              
473             sub fetch {
474 62972     62972 1 89041 my( $self, $id, $force ) = @_;
475 62972   100     109781 my $ref = $self->[DIRTY]{$id} // $self->[WEAK]{$id};
476            
477 62972 100       110851 return $ref if $ref;
478              
479 362         982 my $stowed = $self->[DATA_PROVIDER]->fetch( $id );
480 362 100       160242 return undef unless $stowed;
481              
482 318         658 my $pos = index( $stowed, ' ' );
483 318 100       675 die "Data::ObjectStore::_fetch : Malformed record '$stowed'" if $pos == -1;
484              
485 316         519 my $metastr = substr $stowed, 0, $pos;
486 316         1035 my( $class, @meta ) = split /\|/, $metastr;
487              
488 316         586 my $meta = {};
489 316         820 for my $fldi (0..$#METAFIELDS) {
490 632         902 my $fld = $METAFIELDS[$fldi];
491 632         750 my $val = $meta[$fldi];
492 632         1463 $meta->{$fld} = $val;
493             }
494              
495 316         611 my $dryfroze = substr $stowed, $pos + 1;
496              
497 316 100       1251 if( $class !~ /^Data::ObjectStore::/ ) {
498 8         13 my $clname = $class;
499 8         20 $clname =~ s/::/\//g;
500              
501 8         14 eval {
502 8         330 require "$clname.pm";
503 5 100       52 unless( $class->can( '_reconstitute' ) ) {
504 2 100       6 if( $force ) {
505 1         35 warn "Forcing '$class' to be 'Data::ObjectStore::Container'";
506 1         6 $class = 'Data::ObjectStore::Container';
507             } else {
508 1         11 die "Object in the store was marked as '$class' but that is not a 'Data::ObjectStore::Container'";
509             }
510             }
511             };
512 8 100       841 if( $@ ) {
513 4 100       11 if( $force ) {
514 1         37 warn "warn '$class' to be 'Data::ObjectStore::Container'";
515 1         4 $class = 'Data::ObjectStore::Container';
516             } else {
517 3         19 die $@;
518             }
519             }
520             }
521              
522 313         625 my $pieces = _thaw( $dryfroze );
523              
524 313         1524 my $ret = $class->_reconstitute( $self, $id, $pieces, $meta );
525 313         775 $self->_store_weak( $id, $ret );
526 313         1254 return $ret;
527             } #_fetch
528              
529             #
530             # Convert from reference, scalar or undef to value marker
531             #
532             sub _xform_in {
533 23446     23446   30801 my( $self, $val ) = @_;
534 23446 100       34374 if( ref( $val ) ) {
535 188         418 my $id = $self->_get_id( $val );
536 185         637 return $id, "r$id";
537             }
538 23258 100       56660 return 0, (defined $val ? "v$val" : 'u');
539             }
540              
541             #
542             # Convert from value marker to reference, scalar or undef
543             #
544             sub _xform_out {
545 8281     8281   11776 my( $self, $val ) = @_;
546              
547 8281 100 100     22208 return undef unless defined( $val ) && $val ne 'u';
548              
549 6263 100       10564 if( index($val,'v') == 0 ) {
550 5419         14414 return substr( $val, 1 );
551             }
552 844 100       2357 if( $val =~ /^r(\d+)/ ) {
553 381         684 return $self->fetch( $1 );
554             }
555 463         708 return $self->fetch( $val );
556             }
557              
558             sub _store_weak {
559 7543     7543   11253 my( $self, $id, $ref ) = @_;
560              
561 7543 100       13264 if( $self->[CACHE] ) {
562 542         1463 $self->[CACHE]->stow( $id, $ref );
563             }
564            
565 7543         19537 $self->[WEAK]{$id} = $ref;
566              
567 7543         20011 weaken( $self->[WEAK]{$id} );
568              
569             } #_store_weak
570              
571             sub _dirty {
572 59397     59397   76590 my( $self, $id ) = @_;
573 59397         79087 my $item = $self->[WEAK]{$id};
574 59397         75902 $self->[DIRTY]{$id} = $item;
575 59397         82121 $item = $self->_knot( $item );
576 59397 100       96783 if( $item ) {
577 59381         107173 $item->[METADATA]{updated} = time();
578             }
579             } #_dirty
580              
581              
582             sub _new_id {
583 7230     7230   9415 my( $self ) = @_;
584 7230         18022 my $newid = $self->[DATA_PROVIDER]->next_id;
585 7230         2023404 $newid;
586             } #_new_id
587              
588             sub _meta {
589 6     6   13 my( $self, $thingy ) = @_;
590             return {
591             created => $thingy->[METADATA]{created},
592             updated => $thingy->[METADATA]{updated},
593 6         39 };
594             } #_meta
595              
596             sub last_updated {
597 4     4 1 17 my( $self, $obj ) = @_;
598 4         9 $obj = $self->_knot( $obj );
599 4 100       12 return undef unless $obj;
600 3         10 $self->_meta( $obj )->{updated};
601             }
602              
603             sub created {
604 4     4 1 9 my( $self, $obj ) = @_;
605 4         9 $obj = $self->_knot( $obj );
606 4 100       13 return undef unless $obj;
607 3         8 $self->_meta( $obj )->{created};
608             }
609              
610             # returns the id of the refernce, injesting it if
611             # necessary.
612             # used by tests
613             sub _get_id {
614 196     196   327 my( $self, $ref ) = @_;
615              
616 196         283 my $class = ref( $ref );
617 196         240 my $thingy;
618 196 100       441 if ( $class eq 'ARRAY' ) {
    100          
619 62         117 $thingy = tied @$ref;
620 62 100       126 if ( ! $thingy ) {
621 46         85 my $id = $self->_new_id;
622 46         163 my( @items ) = @$ref;
623 46         504 tie @$ref, 'Data::ObjectStore::Array', $self, $id, { created => time, updated => time}, 0, $Data::ObjectStore::Array::MAX_BLOCKS;
624 46         94 my $tied = tied @$ref;
625              
626 46         138 $self->_store_weak( $id, $ref );
627 46         104 $self->_dirty( $id );
628 46         133 push @$ref, @items;
629 46         110 return $id;
630             }
631 16         23 $ref = $thingy;
632 16         27 $class = ref( $ref );
633             }
634             elsif ( $class eq 'HASH' ) {
635 24         50 $thingy = tied %$ref;
636 24 100       51 if ( ! $thingy ) {
637 18         38 my $id = $self->_new_id;
638 18         81 my( %items ) = %$ref;
639 18         223 tie %$ref, 'Data::ObjectStore::Hash', $self, $id, { created => time, updated => time};
640 18         44 my $tied = tied %$ref;
641              
642 18         57 $self->_store_weak( $id, $ref );
643 18         44 $self->_dirty( $id );
644 18         53 for my $key (keys( %items) ) {
645 23         87 $ref->{$key} = $items{$key};
646             }
647 18         55 return $id;
648             }
649 6         12 $ref = $thingy;
650 6         12 $class = ref( $ref );
651             }
652             else {
653 110         140 $thingy = $ref;
654             }
655              
656 132 100 100     743 die "Data::ObjectStore::_get_id : Cannot ingest object that is not a hash, array or objectstore obj" unless ( $class eq 'Data::ObjectStore::Hash' || $class eq 'Data::ObjectStore::Array' || $ref->isa( 'Data::ObjectStore::Container' ) ); # new id is created upon create container for all Data::ObjectStore::Container instances.
      100        
657              
658 129         266 return $ref->[ID];
659              
660             } #_get_id
661              
662             # END PACKAGE Data::ObjectStore
663              
664             # --------------------------------------------------------------------------------
665              
666             package Data::ObjectStore::Array;
667              
668              
669             ##################################################################################
670             # This module is used transparently by ObjectStore to link arrays into its graph #
671             # structure. This is not meant to be called explicitly or modified. #
672             ##################################################################################
673              
674 2     2   14 use strict;
  2         4  
  2         50  
675 2     2   10 use warnings;
  2         2  
  2         56  
676 2     2   9 use warnings FATAL => 'all';
  2         3  
  2         75  
677 2     2   8 no warnings 'numeric';
  2         4  
  2         70  
678 2     2   8 no warnings 'recursion';
  2         4  
  2         48  
679              
680 2     2   790 use Tie::Array;
  2         1836  
  2         81  
681              
682             $Data::ObjectStore::Array::MAX_BLOCKS = 1_000_000;
683              
684              
685             use constant {
686 2         4167 ID => 0,
687             DATA => 1,
688             DSTORE => 2,
689             METADATA => 3,
690             LEVEL => 4,
691             BLOCK_COUNT => 5,
692             BLOCK_SIZE => 6,
693             ITEM_COUNT => 7,
694             UNDERNEATH => 8,
695              
696             WEAK => 2,
697 2     2   11 };
  2         2  
698              
699             sub store {
700 1     1   872 shift->[DSTORE];
701             }
702              
703             sub _freezedry {
704 7115     7115   9300 my $self = shift;
705 7115         8388 my @items;
706 7115 100       15787 my $stuff_count = $self->[BLOCK_COUNT] > $self->[ITEM_COUNT] ? $self->[ITEM_COUNT] : $self->[BLOCK_COUNT];
707 7115 100       11262 if( $stuff_count > 0 ) {
708 7046 100 100     13367 @items = map { if( defined($_) && $_=~ /[\\\`]/ ) { $_ =~ s/[\\]/\\\\/gs; s/`/\\`/gs; } defined($_) ? $_ : 'u' } map { $self->[DATA][$_] } (0..($stuff_count-1));
  28149 100       71548  
  6         17  
  6         19  
  28149         47717  
  28149         51069  
709             }
710              
711 7115   100     39957 join( "`",
      100        
      100        
712             $self->[LEVEL] || 0,
713             $self->[BLOCK_COUNT],
714             $self->[ITEM_COUNT] || 0,
715             $self->[UNDERNEATH] || 0,
716             @items,
717             );
718             }
719              
720             sub _reconstitute {
721 130     130   309 my( $cls, $store, $id, $data, $meta ) = @_;
722 130         182 my $arry = [];
723 130         455 tie @$arry, $cls, $store, $id, $meta, @$data;
724 130         237 return $arry;
725             }
726              
727             sub TIEARRAY {
728 7221     7221   15371 my( $class, $obj_store, $id, $meta, $level, $block_count, $item_count, $underneath, @list ) = @_;
729 7221   100     25043 $item_count //= 0;
730 7221         10229 my $block_size = $block_count ** $level;
731              
732 7221         9590 my $blocks = [@list];
733             # $#$blocks = $block_count - 1;
734              
735             # once the array is tied, an additional data field will be added
736             # so obj will be [ $id, $storage, $obj_store ]
737             # die if $id == 1;
738 7221         21776 my $obj = bless [
739             $id,
740             $blocks,
741             $obj_store,
742             $meta,
743             $level,
744             $block_count,
745             $block_size,
746             $item_count,
747             $underneath,
748             ], $class;
749              
750 7221         14677 return $obj;
751             } #TIEARRAY
752              
753             sub FETCH {
754 49416     49416   78139 my( $self, $idx ) = @_;
755              
756 49416 100       76168 if( $idx >= $self->[ITEM_COUNT] ) {
757 17281         30317 return undef;
758             }
759              
760 32135 100       47689 if( $self->[LEVEL] == 0 ) {
761 3868         6471 return $self->[DSTORE]->_xform_out( $self->[DATA][$idx] );
762             }
763              
764 28267         53988 my $block = $self->_getblock( int( $idx / $self->[BLOCK_SIZE] ) );
765 28267         49800 return $block->FETCH( $idx % $self->[BLOCK_SIZE] );
766              
767             } #FETCH
768              
769             sub FETCHSIZE {
770 5450     5450   157312 shift->[ITEM_COUNT];
771             }
772              
773             sub _embiggen {
774 41     41   82 my( $self, $size ) = @_;
775 41         72 my $store = $self->[DSTORE];
776              
777 41         129 while( $size > $self->[BLOCK_SIZE] * $self->[BLOCK_COUNT] ) {
778              
779             #
780             # before embiggen ...
781             # DATA = [ 1,2,3,4,5,6 ]
782             # after embiggen
783             # newblock = []
784             # newblockid = 7
785             # DATA = [ 7 ]
786             #
787              
788             #
789             # need to tie a new block, not use _getblock
790             # becaues we do squirrely things with its tied guts
791             #
792 70         108 my $newblock = [];
793 70         132 my $newid = $store->_new_id;
794 70         127 my $meta = { %{$self->[METADATA]} };
  70         325  
795 70         160 $meta->{updated} = time;
796 70         387 tie @$newblock, 'Data::ObjectStore::Array', $store, $newid, $meta, $self->[LEVEL], $self->[BLOCK_COUNT], $self->[ITEM_COUNT], 1;
797 70         189 $store->_store_weak( $newid, $newblock );
798 70         189 $store->_dirty( $newid );
799              
800 70         138 my $tied = tied @$newblock;
801              
802 70         88 $tied->[DATA] = [@{$self->[DATA]}];
  70         170  
803              
804 70         186 $self->[DATA] = [ "r$newid" ];
805              
806 70         118 $self->[BLOCK_SIZE] *= $self->[BLOCK_COUNT];
807 70         98 $self->[LEVEL]++;
808 70         130 $store->_dirty( $self->[ID] );
809             }
810              
811             } #_embiggen
812              
813             #
814             # get a block at the given block index. Returns undef
815             # if there isn't one ther, or creates and returns
816             # one if passed do create
817             #
818             sub _getblock {
819 68297     68297   86071 my( $self, $block_idx ) = @_;
820              
821 68297   100     120251 my $block_id = $self->[DATA][$block_idx] // 'r0';
822 68297         87912 $block_id = substr( $block_id, 1 );
823 68297         77058 my $store = $self->[DSTORE];
824              
825 68297 100       111176 if( $block_id > 0 ) {
826 61322         79213 my $block = $store->fetch( $block_id );
827 61322         97052 return tied(@$block);
828             }
829              
830 6975         10594 $block_id = $store->_new_id;
831 6975         12703 my $block = [];
832 6975         11219 my $level = $self->[LEVEL] - 1;
833 6975         8989 my $meta = { %{$self->[METADATA]} };
  6975         26786  
834 6975         34356 tie @$block, 'Data::ObjectStore::Array', $store, $block_id, $meta, $level, $self->[BLOCK_COUNT];
835              
836 6975         10196 my $tied = tied( @$block );
837              
838 6975         10115 $tied->[UNDERNEATH] = 1;
839 6975 100       13392 if( $block_idx >= ($self->[BLOCK_COUNT] - 1 ) ) {
840 1736         2476 $tied->[ITEM_COUNT] = $self->[BLOCK_SIZE];
841             }
842              
843 6975         15414 $store->_store_weak( $block_id, $block );
844 6975         13038 $store->_dirty( $block_id );
845 6975         13240 $store->_dirty( $self->[ID] );
846 6975         14565 $self->[DATA][$block_idx] = "r$block_id";
847 6975         11251 return $tied;
848              
849             } #_getblock
850              
851             sub STORE {
852 51006     51006   68186 my( $self, $idx, $val ) = @_;
853              
854 51006 100       82627 if( $idx >= $self->[BLOCK_COUNT]*$self->[BLOCK_SIZE] ) {
855 23         67 $self->_embiggen( $idx + 1 );
856 23         75 $self->STORE( $idx, $val );
857 23         81 return;
858             }
859              
860 50983 100       76722 if( $idx >= $self->[ITEM_COUNT] ) {
861 27410         50185 $self->_storesize( $idx + 1 );
862 27410         34591 my $store = $self->[DSTORE];
863 27410         41363 $store->_dirty( $self->[ID] );
864             }
865              
866 50983 100       76009 if( $self->[LEVEL] == 0 ) {
867 13810         21587 my( $xid, $xin ) = $self->[DSTORE]->_xform_in( $val );
868 13809 100 100     25230 if( $xid > 0 && $xid < 3 ) {
869 2         25 die "cannot store a root node in a list";
870             }
871 13807         17136 my $store = $self->[DSTORE];
872 13807   100     29691 my $xold = $self->[DATA][$idx] // 0;
873 13807 100       20793 if( $xold ne $xin ) {
874 13781         19299 $self->[DATA][$idx] = $xin;
875 13781         20955 $store->_dirty( $self->[ID] );
876             }
877              
878 13807         21158 return;
879             }
880              
881 37173         67861 my $block = $self->_getblock( int( $idx / $self->[BLOCK_SIZE] ) );
882 37173         64651 $block->STORE( $idx % $self->[BLOCK_SIZE], $val );
883              
884             } #STORE
885              
886             sub _storesize {
887 30403     30403   36800 my( $self, $size ) = @_;
888 30403         38977 $self->[ITEM_COUNT] = $size;
889             }
890              
891             sub STORESIZE {
892 10     10   3685 my( $self, $size ) = @_;
893              
894             # fixes the size of the array if the array were to shrink
895 10         32 my $current_oversize = $self->[ITEM_COUNT] - $size;
896 10 100       37 if( $current_oversize > 0 ) {
897 2         6 $self->SPLICE( $size, $current_oversize );
898             } #if the array shrinks
899              
900 10         28 $self->_storesize( $size );
901              
902             } #STORESIZE
903              
904             sub EXISTS {
905 62     62   4570 my( $self, $idx ) = @_;
906 62 100       149 if( $idx >= $self->[ITEM_COUNT] ) {
907 19         88 return 0;
908             }
909 43 100       93 if( $self->[LEVEL] == 0 ) {
910 15         74 return defined($self->[DATA][$idx]);
911             }
912 28         82 return $self->_getblock( int( $idx / $self->[BLOCK_SIZE] ) )->EXISTS( $idx % $self->[BLOCK_SIZE] );
913              
914             } #EXISTS
915              
916             sub DELETE {
917 23     23   14093 my( $self, $idx ) = @_;
918              
919 23         71 my $store = $self->[DSTORE];
920 23         64 my $del = $self->FETCH( $idx );
921 23         75 $self->STORE( $idx, undef );
922 23 100       70 if( $idx == $self->[ITEM_COUNT] - 1 ) {
923 20         43 $self->[ITEM_COUNT]--;
924 20   100     92 while( $self->[ITEM_COUNT] > 0 && ! defined( $self->FETCH( $self->[ITEM_COUNT] - 1 ) ) ) {
925 1152         2395 $self->[ITEM_COUNT]--;
926             }
927              
928             }
929 23         79 $store->_dirty( $self->[ID] );
930              
931 23         92 return $del;
932              
933             } #DELETE
934              
935             sub CLEAR {
936 13     13   6870 my $self = shift;
937 13 100       54 if( $self->[ITEM_COUNT] > 0 ) {
938 12         30 my $store = $self->[DSTORE];
939 12         36 for( 0..$self->[ITEM_COUNT] ) {
940 115         193 my $del = $self->FETCH( $_ );
941             }
942 12         24 $self->[ITEM_COUNT] = 0;
943 12         35 $self->[DATA] = [];
944 12         33 $self->[DSTORE]->_dirty( $self->[ID] );
945             }
946             }
947             sub PUSH {
948 88     88   3979 my( $self, @vals ) = @_;
949 88 100       249 return unless @vals;
950 56         347 $self->SPLICE( $self->[ITEM_COUNT], 0, @vals );
951             }
952             sub POP {
953 23     23   6131 my $self = shift;
954 23         54 my $idx = $self->[ITEM_COUNT] - 1;
955 23 100       63 if( $idx < 0 ) {
956 1         6 return undef;
957             }
958 22         58 my $pop = $self->FETCH( $idx );
959 22         69 $self->STORE( $idx, undef );
960 22         41 $self->[ITEM_COUNT]--;
961 22         73 return $pop;
962             }
963             sub SHIFT {
964 21     21   3506 my( $self ) = @_;
965 21 100       78 return undef unless $self->[ITEM_COUNT];
966 19         56 my( $ret ) = $self->SPLICE( 0, 1 );
967 19         80 $ret;
968             }
969              
970             sub UNSHIFT {
971 19     19   10656 my( $self, @vals ) = @_;
972 19 100       346 return unless @vals;
973 18         60 return $self->SPLICE( 0, 0, @vals );
974             }
975              
976             sub SPLICE {
977 3000     3000   13026 my( $self, $offset, $remove_length, @vals ) = @_;
978              
979             # if no arguments given, clear the array
980 3000 100       5098 if( ! defined( $offset ) ) {
981 1         3 $offset = 0;
982 1         3 $remove_length = $self->[ITEM_COUNT];
983             }
984            
985             # if negative, the offset is from the end
986 3000 100       4812 if( $offset < 0 ) {
987 1         3 $offset = $self->[ITEM_COUNT] + $offset;
988             }
989              
990             # if negative, remove everything except the abs($remove_length) at
991             # the end of the list
992 3000 100       4429 if( $remove_length < 0 ) {
993 2         4 $remove_length = ($self->[ITEM_COUNT] - $offset) + $remove_length;
994             }
995              
996 3000 100 100     7052 return () unless $remove_length || @vals;
997              
998             # check for removal past end
999 2999 100       6150 if( $offset > ($self->[ITEM_COUNT] - 1) ) {
1000 2163         2506 $remove_length = 0;
1001 2163         2880 $offset = $self->[ITEM_COUNT];
1002             }
1003 2999 100       5016 if( $remove_length > ($self->[ITEM_COUNT] - $offset) ) {
1004 13         20 $remove_length = $self->[ITEM_COUNT] - $offset;
1005             }
1006              
1007             #
1008             # embiggen to delta size if this would grow. Also use the
1009             # calculated size as a check for correctness.
1010             #
1011 2999         3802 my $new_size = $self->[ITEM_COUNT];
1012 2999         3360 $new_size -= $remove_length;
1013 2999         3556 $new_size += @vals;
1014              
1015 2999 100       5797 if( $new_size > $self->[BLOCK_SIZE] * $self->[BLOCK_COUNT] ) {
1016 18         56 $self->_embiggen( $new_size );
1017             }
1018              
1019 2999         4344 my $store = $self->[DSTORE];
1020 2999         3560 my $BLOCK_COUNT = $self->[BLOCK_COUNT];
1021 2999         3925 my $BLOCK_SIZE = $self->[BLOCK_SIZE]; # embiggen may have changed this, so dont set this before the embiggen call
1022              
1023 2999 100       4852 if( $self->[LEVEL] == 0 ) {
1024             # lowest level, must fit in the size. The end recursion and easy case.
1025 1946         2345 my $blocks = $self->[DATA];
1026 1946         2832 my( @invals ) = ( map { ($store->_xform_in($_))[1] } @vals );
  7645         10621  
1027 1946         3224 for my $inval (@invals) {
1028 7645         9857 my( $inid ) = ( $inval =~ /^r(\d+)/ );
1029              
1030 7645 100 100     12006 if( $inid && $inid < 3 ) {
1031 4         44 die "cannot store a root node in a list";
1032             }
1033             }
1034 1942         5480 my @raw_return = splice @$blocks, $offset, $remove_length, @invals;
1035 1942         2244 my @ret;
1036 1942         2440 for my $rr (@raw_return) {
1037 278         377 push @ret, $store->_xform_out($rr);
1038             }
1039 1942         3782 $self->_storesize( $new_size );
1040 1942         3551 $store->_dirty( $self->[ID] );
1041 1942         4235 return @ret;
1042             } # LEVEL == 0 case
1043              
1044 1053         1253 my( @removed );
1045 1053   100     3025 while( @vals && $remove_length ) {
1046             #
1047             # harmony case. doesn't change the size. eats up vals and remove length
1048             # until one is zero
1049             #
1050 13285         22397 push @removed, $self->FETCH( $offset );
1051 13285         28761 $self->STORE( $offset++, shift @vals );
1052 13285         36881 $remove_length--;
1053             }
1054              
1055 1053 100       1901 if( $remove_length ) {
1056              
1057 59         158 for( my $idx=$offset; $idx<($offset+$remove_length); $idx++ ) {
1058 1163         1578 push @removed, $self->FETCH( $idx );
1059             }
1060              
1061 59         107 my $things_to_move = $self->[ITEM_COUNT] - ($offset+$remove_length);
1062 59         82 my $to_idx = $offset;
1063 59         84 my $from_idx = $to_idx + $remove_length;
1064 59         137 for( 1..$things_to_move ) {
1065 439         649 $self->STORE( $to_idx, $self->FETCH( $from_idx ) );
1066 439         606 $to_idx++;
1067 439         583 $from_idx++;
1068             }
1069             } # has things to remove
1070              
1071 1053 100       1770 if( @vals ) {
1072             #
1073             # while there are any in the insert list, grab all the items in the next block if any
1074             # and append to the insert list, then splice in the insert list to the beginning of
1075             # the block. There still may be items in the insert list, so repeat until it is done
1076             #
1077              
1078 774         1301 my $block_idx = int( $offset / $BLOCK_SIZE );
1079 774         1146 my $block_off = $offset % $BLOCK_SIZE;
1080              
1081 774   100     2482 while( @vals && ($self->[ITEM_COUNT] > $block_idx*$BLOCK_SIZE+$block_off) ) {
1082 42         95 my $block = $self->_getblock( $block_idx );
1083 42         79 my $bubble_size = $block->FETCHSIZE - $block_off;
1084 42 100       88 if( $bubble_size > 0 ) {
1085 34         71 my @bubble = $block->SPLICE( $block_off, $bubble_size );
1086 34         92 push @vals, @bubble;
1087             }
1088 42 100       117 my $can_insert = @vals > ($BLOCK_SIZE-$block_off) ? ($BLOCK_SIZE-$block_off) : @vals;
1089 42         127 $block->SPLICE( $block_off, 0, splice( @vals, 0, $can_insert ) );
1090 42         94 $block_idx++;
1091 42         126 $block_off = 0;
1092             }
1093 774         1306 while( @vals ) {
1094 2787         4920 my $block = $self->_getblock( $block_idx );
1095 2787         3913 my $remmy = $BLOCK_SIZE - $block_off;
1096 2787 100       4915 if( $remmy > @vals ) { $remmy = @vals; }
  87         120  
1097              
1098 2787         9952 $block->SPLICE( $block_off, $block->[ITEM_COUNT], splice( @vals, 0, $remmy) );
1099 2775         6710 $block_idx++;
1100 2775         5421 $block_off = 0;
1101             }
1102              
1103             } # has vals
1104              
1105 1041         2115 $self->_storesize( $new_size );
1106              
1107 1041         3884 return @removed;
1108              
1109             } #SPLICE
1110              
1111       1     sub EXTEND {
1112             }
1113              
1114             sub DESTROY {
1115 6773     6773   6550808 my $self = shift;
1116 6773         38362 delete $self->[DSTORE]->[WEAK]{$self->[ID]};
1117             }
1118              
1119             # END PACKAGE Data::ObjectStore::Array
1120              
1121             # --------------------------------------------------------------------------------
1122              
1123             package Data::ObjectStore::Hash;
1124              
1125             ##################################################################################
1126             # This module is used transparently by ObjectStore to link hashes into its #
1127             # graph structure. This is not meant to be called explicitly or modified. #
1128             ##################################################################################
1129              
1130 2     2   14 use strict;
  2         16  
  2         53  
1131 2     2   9 use warnings;
  2         3  
  2         73  
1132              
1133 2     2   10 no warnings 'uninitialized';
  2         3  
  2         55  
1134 2     2   9 no warnings 'numeric';
  2         2  
  2         54  
1135 2     2   14 no warnings 'recursion';
  2         4  
  2         42  
1136              
1137 2     2   862 use Tie::Hash;
  2         1449  
  2         89  
1138              
1139             $Data::ObjectStore::Hash::BUCKET_SIZE = 29;
1140             $Data::ObjectStore::Hash::MAX_SIZE = 1_062_599;
1141              
1142             use constant {
1143 2         3322 ID => 0,
1144             DATA => 1,
1145             DSTORE => 2,
1146             METADATA => 3,
1147             LEVEL => 4,
1148             BUCKETS => 5,
1149             SIZE => 6,
1150             NEXT => 7,
1151 2     2   10 };
  2         3  
1152              
1153             sub store {
1154 1     1   976 shift->[DSTORE];
1155             }
1156              
1157             sub _freezedry {
1158 45     45   81 my $self = shift;
1159 45         81 my $r = $self->[DATA];
1160             join( "`",
1161             $self->[LEVEL],
1162             $self->[BUCKETS],
1163             $self->[SIZE],
1164 45 100       491 map { if( $_=~ /[\\\`]/ ) { s/[\\]/\\\\/gs; s/`/\\`/gs; } $_ }
  3021 100       4461  
  6         12  
  6         16  
  3021         4319  
1165             $self->[LEVEL] ? @$r : %$r
1166             );
1167             }
1168              
1169             sub _reconstitute {
1170 34     34   90 my( $cls, $store, $id, $data, $meta ) = @_;
1171 34         64 my $hash = {};
1172 34         164 tie %$hash, $cls, $store, $id, $meta, @$data;
1173              
1174 34         71 return $hash;
1175             }
1176              
1177             sub TIEHASH {
1178 76     76   533 my( $class, $obj_store, $id, $meta, $level, $buckets, $size, @fetch_buckets ) = @_;
1179 76   100     213 $level //= 0;
1180 76   100     185 $size ||= 0;
1181 76 100       151 unless( $buckets ) {
1182 42         62 $buckets = $Data::ObjectStore::Hash::BUCKET_SIZE;
1183             }
1184 76 100       1223 bless [ $id,
1185             $level ? [@fetch_buckets] : {@fetch_buckets},
1186             $obj_store,
1187             $meta,
1188             $level,
1189             $buckets,
1190             $size,
1191             [undef,undef],
1192             ], $class;
1193             } #TIEHASH
1194              
1195             sub CLEAR {
1196 4     4   841 my $self = shift;
1197 4 100       19 if( $self->[SIZE] > 0 ) {
1198 3         6 $self->[SIZE] = 0;
1199 3         7 my $store = $self->[DSTORE];
1200 3         8 $store->_dirty( $self->[ID] );
1201 3 100       10 if( $self->[LEVEL] == 0 ) {
1202 2         3 %{$self->[DATA]} = ();
  2         13  
1203             } else {
1204 1         2 @{$self->[DATA]} = ();
  1         5  
1205             }
1206             }
1207             } #CLEAR
1208              
1209             sub DELETE {
1210 9     9   2830 my( $self, $key ) = @_;
1211              
1212 9 100       47 return undef unless $self->EXISTS( $key );
1213              
1214 7         19 $self->[SIZE]--;
1215              
1216 7         14 my $data = $self->[DATA];
1217 7         17 my $store = $self->[DSTORE];
1218              
1219 7 100       17 if( $self->[LEVEL] == 0 ) {
1220 5         33 $store->_dirty( $self->[ID] );
1221 5         14 my $delin = delete $data->{$key};
1222 5         17 return $store->_xform_out( $delin );
1223             } else {
1224 2         6 my $hval = 0;
1225 2         7 foreach (split //,$key) {
1226 6         11 $hval = $hval*33 - ord($_);
1227             }
1228 2         5 $hval = $hval % $self->[BUCKETS];
1229 2         6 my $store = $self->[DSTORE];
1230 2         8 return $store->_knot( $store->fetch( substr($data->[$hval], 1 ) ))->DELETE( $key );
1231             }
1232             } #DELETE
1233              
1234              
1235             sub EXISTS {
1236 3439     3439   20256 my( $self, $key ) = @_;
1237              
1238 3439 100       4934 if( $self->[LEVEL] == 0 ) {
1239 3197         6385 return exists $self->[DATA]{$key};
1240             } else {
1241 242         334 my $data = $self->[DATA];
1242 242         274 my $hval = 0;
1243 242         500 foreach (split //,$key) {
1244 337         514 $hval = $hval*33 - ord($_);
1245             }
1246 242         424 $hval = $hval % $self->[BUCKETS];
1247 242         404 my $hash_id = substr($data->[$hval],1);
1248 242 100       457 if( $hash_id > 0 ) {
1249 240         414 my $hash = $self->[DSTORE]->fetch( $hash_id );
1250 240         376 my $tied = tied %$hash;
1251 240         412 return $tied->EXISTS( $key );
1252             }
1253              
1254             }
1255 2         6 return 0;
1256             } #EXISTS
1257              
1258             sub FETCH {
1259 2277     2277   10558 my( $self, $key ) = @_;
1260 2277         2800 my $data = $self->[DATA];
1261              
1262 2277 100       3001 if( $self->[LEVEL] == 0 ) {
1263 1853         2815 return $self->[DSTORE]->_xform_out( $data->{$key} );
1264             } else {
1265 424         475 my $hval = 0;
1266 424         748 foreach (split //,$key) {
1267 521         711 $hval = $hval*33 - ord($_);
1268             }
1269 424         634 $hval = $hval % $self->[BUCKETS];
1270 424         838 my $hash = $self->[DSTORE]->_knot(substr($data->[$hval],1));
1271 424 100       821 if( $hash ) {
1272 423         670 return $hash->FETCH( $key );
1273             }
1274             }
1275 1         5 return undef;
1276             } #FETCH
1277              
1278             sub STORE {
1279 1558     1558   17148 my( $self, $key, $val ) = @_;
1280 1558         1982 my $data = $self->[DATA];
1281              
1282 1558         1750 my $store = $self->[DSTORE];
1283 1558         2072 my( $xid, $xin ) = $store->_xform_in( $val );
1284 1557 100       2427 if( $xid > 0 ) {
1285 22 100       44 if( $xid < 3 ) {
1286 11         23 $self->[SIZE]--;
1287 11         131 die "cannot store a root node in a hash";
1288             }
1289             }
1290              
1291             #
1292             # EMBIGGEN TEST
1293             #
1294 1546 100       2182 unless( $self->EXISTS( $key ) ) {
1295 1537         1970 $self->[SIZE]++;
1296             }
1297              
1298            
1299 1546 100       2243 if( $self->[LEVEL] == 0 ) {
1300 1516         1950 my $oldin = $data->{$key};
1301 1516 100       2415 if( $xin ne $oldin ) {
1302 1515         2822 $data->{$key} = $xin;
1303 1515         2736 $store->_dirty( $self->[ID] );
1304              
1305 1515 100       4953 if( $self->[SIZE] > $Data::ObjectStore::Hash::MAX_SIZE ) {
1306              
1307             # do the thing converting this to a deeper level
1308 4         11 $self->[LEVEL] = 1;
1309 4         7 my( @newhash );
1310              
1311 4         139 my( @newids ) = ( 0 ) x $Data::ObjectStore::Hash::BUCKET_SIZE;
1312 4         11 $self->[BUCKETS] = $Data::ObjectStore::Hash::BUCKET_SIZE;
1313 4         25 for my $key (keys %$data) {
1314 52         64 my $hval = 0;
1315 52         108 foreach (split //,$key) {
1316 56         93 $hval = $hval*33 - ord($_);
1317             }
1318 52         80 $hval = $hval % $self->[BUCKETS];
1319 52         72 my $hash = $newhash[$hval];
1320 52 100       76 if( $hash ) {
1321 30         38 my $tied = tied %$hash;
1322 30         55 $tied->STORE( $key, $store->_xform_out($data->{$key}) );
1323             } else {
1324 22         35 $hash = {};
1325 22         41 my $hash_id = $store->_new_id;
1326             tie %$hash, 'Data::ObjectStore::Hash',
1327 22         190 $store, $hash_id, {%{$self->[METADATA]}},
1328 22         47 0, 0, 1, $key, $data->{$key};
1329 22         59 $store->_store_weak( $hash_id, $hash );
1330 22         49 $store->_dirty( $hash_id );
1331 22         55 $newhash[$hval] = $hash;
1332 22         67 $newids[$hval] = "r$hash_id";
1333             }
1334              
1335             }
1336 4         15 $self->[DATA] = \@newids;
1337 4         17 $data = $self->[DATA];
1338             # here is the problem. this isnt in weak yet!
1339             # this is a weak reference problem and the problem is at NEXTKEY with
1340             # LEVEL 0 hashes that are loaded from LEVEL 1 hashes that are loaded from
1341             # LEVEL 2 hashes. The level 1 hash is loaded and dumped as needed, not keeping
1342             # the ephermal info (or is that sort of chained..hmm)
1343 4         12 $store->_dirty( $self->[ID] );
1344              
1345             } # EMBIGGEN CHECK
1346             }
1347             }
1348             else { #
1349            
1350 30         41 my $store = $self->[DSTORE];
1351 30         39 my $hval = 0;
1352 30         56 foreach (split //,$key) {
1353 49         73 $hval = $hval*33 - ord($_);
1354             }
1355 30         46 $hval = $hval % $self->[BUCKETS];
1356 30         48 my $hash_id = substr($data->[$hval],1);
1357 30         34 my $hash;
1358             # check if there is a subhash created here
1359 30 100       54 if( $hash_id > 0 ) {
1360             # subhash was already created, so store the new val in it
1361 28         40 $hash = $store->fetch( $hash_id );
1362 28         46 my $tied = tied %$hash;
1363 28         48 $tied->STORE( $key, $val );
1364             } else {
1365             # subhash not already created, so create then store the new val in it
1366             # really improbable case.
1367 2         3 $hash = {};
1368 2         6 $hash_id = $store->_new_id;
1369              
1370 2         6 tie %$hash, 'Data::ObjectStore::Hash', $store, $hash_id, {%{$self->[METADATA]}}, 0, 0, 1, $key, $xin;
  2         16  
1371              
1372 2         8 $store->_store_weak( $hash_id, $hash );
1373 2         6 $store->_dirty( $hash_id );
1374 2         11 $data->[$hval] = "r$hash_id";
1375             }
1376             }
1377              
1378             } #STORE
1379              
1380             sub FIRSTKEY {
1381 213     213   6279 my $self = shift;
1382              
1383 213         300 my $data = $self->[DATA];
1384 213 100       382 if( $self->[LEVEL] == 0 ) {
1385 187         296 my $a = scalar keys %$data; #reset
1386 187         378 my( $k, $val ) = each %$data;
1387 187 100       456 return wantarray ? ( $k => $self->[DSTORE]->_xform_out( $val ) ) : $k;
1388             }
1389 26         79 $self->[NEXT] = [undef,undef];
1390 26         65 return $self->NEXTKEY;
1391             }
1392              
1393             sub NEXTKEY {
1394 6236     6236   9504 my $self = shift;
1395 6236         7127 my $data = $self->[DATA];
1396 6236         7144 my $lvl = $self->[LEVEL];
1397 6236 100       8331 if( $lvl == 0 ) {
1398 3437         4882 my( $k, $val ) = each %$data;
1399 3437 100       7569 return wantarray ? ( $k => $self->[DSTORE]->_xform_out($val) ) : $k;
1400             }
1401             else {
1402 2799         3318 my $store = $self->[DSTORE];
1403              
1404 2799         3824 my $at_start = ! defined( $self->[NEXT][0] );
1405              
1406 2799 100       3821 if( $at_start ) {
1407 26         55 $self->[NEXT][0] = 0;
1408 26         40 $self->[NEXT][1] = undef;
1409             }
1410              
1411 2799         3269 my $hash = $self->[NEXT][1];
1412 2799   100     7269 $at_start ||= ! $hash;
1413 2799 100       3802 unless( $hash ) {
1414 2148         4293 my $hash_id = substr( $data->[$self->[NEXT][0]], 1 );
1415 2148 100       4059 $hash = $store->fetch( $hash_id ) if $hash_id > 1;
1416             }
1417              
1418 2799 100       3766 if( $hash ) {
1419 809         1272 my $tied = tied( %$hash );
1420 809 100       1370 my( $k, $v ) = $at_start ? $tied->FIRSTKEY : $tied->NEXTKEY;
1421 809 100       1335 if( defined( $k ) ) {
1422 651         838 $self->[NEXT][1] = $hash; #to keep the weak reference
1423 651 50       3000 return wantarray ? ( $k => $v ) : $k;
1424             }
1425             }
1426              
1427 2148         2553 $self->[NEXT][1] = undef;
1428 2148         2494 $self->[NEXT][0]++;
1429              
1430 2148 100       3332 if( $self->[NEXT][0] > $#$data ) {
1431 26         49 $self->[NEXT][0] = undef;
1432 26         258 return undef;
1433             }
1434             # recursion case, the next bucket has been incremented
1435 2122         7456 return $self->NEXTKEY;
1436             }
1437              
1438             } #NEXTKEY
1439              
1440             sub DESTROY {
1441 22     22   15411 my $self = shift;
1442              
1443             #remove all WEAK_REFS to the buckets
1444 22         795 undef $self->[DATA];
1445              
1446 22         179 delete $self->[DSTORE]->[Data::ObjectStore::WEAK]{$self->[ID]};
1447             }
1448              
1449             # END PACKAGE Data::ObjectStore::Hash
1450              
1451             # --------------------------------------------------------------------------------
1452              
1453              
1454             package Data::ObjectStore::Container;
1455              
1456 2     2   13 use strict;
  2         4  
  2         47  
1457 2     2   9 use warnings;
  2         4  
  2         43  
1458 2     2   23 no warnings 'uninitialized';
  2         4  
  2         62  
1459 2     2   10 no warnings 'numeric';
  2         2  
  2         77  
1460              
1461             use constant {
1462 2         460 ID => 0,
1463             DATA => 1,
1464             DSTORE => 2,
1465             METADATA => 3,
1466             VOLATILE => 4,
1467             DIRTY_BIT => 5,
1468 2     2   10 };
  2         2  
1469              
1470             #
1471             # The string version of the objectstore object is simply its id. This allows
1472             # object ids to easily be stored as hash keys.
1473             #
1474             use overload
1475 3201     3201   5121 '""' => sub { my $self = shift; $self->[ID] },
  3201         6407  
1476 8 100   8   997 eq => sub { ref($_[1]) && $_[1]->[ID] == $_[0]->[ID] },
1477 3 100   3   21 ne => sub { ! ref($_[1]) || $_[1]->[ID] != $_[0]->[ID] },
1478 3 100   3   95 '==' => sub { ref($_[1]) && $_[1]->[ID] == $_[0]->[ID] },
1479 3 100   3   22 '!=' => sub { ! ref($_[1]) || $_[1]->[ID] != $_[0]->[ID] },
1480 2     2   11 fallback => 1;
  2         2  
  2         19  
1481              
1482              
1483             sub set {
1484 412     412   804 my( $self, $fld, $val ) = @_;
1485              
1486              
1487 412         1710 my $store = $self->[DSTORE];
1488 412         733 my( $inid, $inval ) = $store->_xform_in( $val );
1489 411 100 100     1229 if( $self->[ID] > 2 && $inid > 0 && $inid < 3 ) {
      100        
1490 4         36 die "cannot store a root node in a container";
1491             }
1492              
1493 407         744 my $oldval = $self->[DATA]{$fld};
1494              
1495 407 100 100     1249 if( ! defined $self->[DATA]{$fld} || $oldval ne $inval ) {
1496 406         947 $store->_dirty( $self->[ID] );
1497 406         732 $self->[DIRTY_BIT] = 1;
1498 406 100       707 if( ! defined $val ) {
1499 7         21 $self->[DATA]{$fld} = undef;
1500 7         20 return;
1501             }
1502             }
1503              
1504 400         742 $self->[DATA]{$fld} = $inval;
1505 400         739 return $store->_xform_out( $self->[DATA]{$fld} );
1506             } #set
1507              
1508             sub remove_field {
1509 2     2   11 my( $self, $fld ) = @_;
1510 2         9 $self->[DSTORE]->_dirty( $self->[ID] );
1511 2         5 $self->[DIRTY_BIT] = 1;
1512 2         9 delete $self->[DATA]{$fld};
1513             }
1514              
1515             sub fields {
1516 4     4   11 my $self = shift;
1517 4         6 return [keys %{$self->[DATA]}];
  4         36  
1518             } #fields
1519              
1520             sub get {
1521 581     581   968 my( $self, $fld, $default ) = @_;
1522              
1523 581         1958 my $cur = $self->[DATA]{$fld};
1524 581         725 my $store = $self->[DSTORE];
1525 581 100 100     2028 if( ( ! defined( $cur ) || $cur eq 'u' ) && defined( $default ) ) {
      100        
1526 21         50 my( $xid, $xin ) = $store->_xform_in( $default );
1527 21 100 100     97 if( ref( $default ) && $self->[ID] > 2 && $xid < 3 ) {
      100        
1528 2         19 die "cannot store a root node in a container";
1529             }
1530 19         53 $store->_dirty( $self->[ID] );
1531 19         36 $self->[DIRTY_BIT] = 1;
1532 19         51 $self->[DATA]{$fld} = $xin;
1533             }
1534 579         1097 return $store->_xform_out( $self->[DATA]{$fld} );
1535              
1536             } #get
1537              
1538             sub clearvol {
1539 1     1   4 my( $self, $key ) = @_;
1540 1         3 delete $self->[VOLATILE]{$key};
1541             }
1542              
1543             sub clearvols {
1544 2     2   5 my( $self, @keys ) = @_;
1545 2 100       7 unless( @keys ) {
1546 1         1 @keys = @{$self->vol_fields};
  1         3  
1547             }
1548 2         5 for my $key (@keys) {
1549 2         6 delete $self->[VOLATILE]{$key};
1550             }
1551             }
1552              
1553             sub vol {
1554 12     12   26 my( $self, $key, $val ) = @_;
1555 12 100       24 if( defined( $val ) ) {
1556 6         12 $self->[VOLATILE]{$key} = $val;
1557             }
1558 12         35 return $self->[VOLATILE]{$key};
1559             }
1560              
1561             sub vol_fields {
1562 4     4   5 return [keys %{shift->[VOLATILE]}];
  4         24  
1563             }
1564              
1565             sub lock {
1566 1     1   7 shift->store->lock(@_);
1567             }
1568             sub unlock {
1569 1     1   141 shift->store->unlock;
1570             }
1571              
1572             sub store {
1573 3     3   12 return shift->[DSTORE];
1574             }
1575              
1576             #
1577             # Defines get_foo, set_foo, add_to_foolist, remove_from_foolist where foo
1578             # is any arbitrarily named field.
1579             #
1580             sub AUTOLOAD {
1581 97     97   8469 my( $s, $arg ) = @_;
1582 97         146 my $func = our $AUTOLOAD;
1583 97 100       882 if( $func =~/:add_to_(.*)/ ) {
    100          
    100          
    100          
    100          
    100          
1584 2         5 my( $fld ) = $1;
1585 2     2   1443 no strict 'refs';
  2         4  
  2         167  
1586             *$AUTOLOAD = sub {
1587 12     12   35 my( $self, @vals ) = @_;
1588 12         23 my $get = "get_$fld";
1589 12         44 my $arry = $self->$get([]); # init array if need be
1590 12         32 push( @$arry, @vals );
1591 2         21 };
1592 2     2   10 use strict 'refs';
  2         9  
  2         138  
1593 2         10 goto &$AUTOLOAD;
1594             } #add_to
1595             elsif( $func =~/:add_once_to_(.*)/ ) {
1596 1         4 my( $fld ) = $1;
1597 2     2   12 no strict 'refs';
  2         7  
  2         251  
1598             *$AUTOLOAD = sub {
1599 3     3   7 my( $self, @vals ) = @_;
1600 3         6 my $get = "get_$fld";
1601 3         9 my $arry = $self->$get([]); # init array if need be
1602 3         7 for my $val ( @vals ) {
1603 9 100       22 unless( grep { $val eq $_ } @$arry ) {
  32         88  
1604 2         5 push @$arry, $val;
1605             }
1606             }
1607 1         8 };
1608 2     2   11 use strict 'refs';
  2         4  
  2         117  
1609 1         4 goto &$AUTOLOAD;
1610             } #add_once_to
1611             elsif( $func =~ /:remove_from_(.*)/ ) { #removes the first instance of the target thing from the list
1612 1         3 my $fld = $1;
1613 2     2   18 no strict 'refs';
  2         12  
  2         247  
1614             *$AUTOLOAD = sub {
1615 1     1   4 my( $self, @vals ) = @_;
1616 1         3 my $get = "get_$fld";
1617 1         4 my $arry = $self->$get([]); # init array if need be
1618 1         2 my( @ret );
1619             V:
1620 1         3 for my $val (@vals ) {
1621 3         8 for my $i (0..$#$arry) {
1622 11 100       20 if( $arry->[$i] eq $val ) {
1623 3         8 push @ret, splice @$arry, $i, 1;
1624 3         8 next V;
1625             }
1626             }
1627             }
1628 1         3 return @ret;
1629 1         8 };
1630 2     2   11 use strict 'refs';
  2         9  
  2         152  
1631 1         5 goto &$AUTOLOAD;
1632             }
1633             elsif( $func =~ /:remove_all_from_(.*)/ ) { #removes the first instance of the target thing from the list
1634 1         4 my $fld = $1;
1635 2     2   9 no strict 'refs';
  2         4  
  2         250  
1636             *$AUTOLOAD = sub {
1637 1     1   4 my( $self, @vals ) = @_;
1638 1         3 my $get = "get_$fld";
1639 1         3 my $arry = $self->$get([]); # init array if need be
1640 1         3 my @ret;
1641 1         3 for my $val (@vals) {
1642 3         8 for( my $i=0; $i<=@$arry; $i++ ) {
1643 23 100       44 if( $arry->[$i] eq $val ) {
1644 7         13 push @ret, splice @$arry, $i, 1;
1645 7         15 $i--;
1646             }
1647             }
1648             }
1649 1         4 return @ret;
1650 1         8 };
1651 2     2   11 use strict 'refs';
  2         3  
  2         118  
1652 1         4 goto &$AUTOLOAD;
1653             }
1654             elsif ( $func =~ /:set_(.*)/ ) {
1655 53         142 my $fld = $1;
1656 2     2   11 no strict 'refs';
  2         3  
  2         116  
1657             *$AUTOLOAD = sub {
1658 382     382   19586 my( $self, $val ) = @_;
1659 382         866 $self->set( $fld, $val );
1660 53         442 };
1661 2     2   10 use strict 'refs';
  2         3  
  2         108  
1662 53         199 goto &$AUTOLOAD;
1663             }
1664             elsif( $func =~ /:get_(.*)/ ) {
1665 38         101 my $fld = $1;
1666 2     2   10 no strict 'refs';
  2         3  
  2         118  
1667             *$AUTOLOAD = sub {
1668 291     291   2516 my( $self, $init_val ) = @_;
1669 291         605 $self->get( $fld, $init_val );
1670 38         317 };
1671 2     2   12 use strict 'refs';
  2         2  
  2         671  
1672 38         137 goto &$AUTOLOAD;
1673             }
1674             else {
1675 1         17 die "Data::ObjectStore::Container::$func : unknown function '$func'.";
1676             }
1677              
1678             } #AUTOLOAD
1679              
1680             # -----------------------
1681             #
1682             # Overridable Methods
1683             #
1684             # -----------------------
1685              
1686              
1687              
1688       68     sub _init {}
1689              
1690              
1691       154     sub _load {}
1692              
1693              
1694              
1695             # -----------------------
1696             #
1697             # Private Methods
1698             #
1699             # -----------------------
1700              
1701             sub _freezedry {
1702 286     286   396 my $self = shift;
1703 286 100 100     352 join( "`", map { if( defined($_) && $_=~ /[\\\`]/ ) { s/[\\]/\\\\/gs; s/`/\\`/gs; } defined($_) ? $_ : 'u' } %{$self->[DATA]} );
  2062 100       5145  
  25         47  
  25         55  
  2062         3917  
  286         1048  
1704             }
1705              
1706             sub _reconstitute {
1707 154     154   365 my( $cls, $store, $id, $data, $meta ) = @_;
1708 154         631 my $obj = [$id,{@$data},$store, $meta, {}];
1709 154 100       341 if( $cls ne 'Data::ObjectStore::Container' ) {
1710 3         8 my $clname = $cls;
1711 3         8 $clname =~ s/::/\//g;
1712              
1713 3         13 require "$clname.pm";
1714             }
1715              
1716 154         231 bless $obj, $cls;
1717              
1718 154         408 $obj->_load;
1719 154         252 $obj;
1720             }
1721              
1722             sub DESTROY {
1723 147     147   17597 my $self = shift;
1724 147         1246 delete $self->[DSTORE][Data::ObjectStore::WEAK]{$self->[ID]};
1725             }
1726              
1727             # END PACKAGE Data::ObjectStore::Container
1728              
1729             1;
1730              
1731             __END__