File Coverage

blib/lib/Data/ObjectStore.pm
Criterion Covered Total %
statement 972 972 100.0
branch 305 308 99.0
condition 84 84 100.0
subroutine 137 137 100.0
pod 19 19 100.0
total 1517 1520 99.8


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