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   1264 use strict;
  2         9  
  2         52  
4 2     2   10 use warnings;
  2         4  
  2         46  
5              
6 2     2   10 no warnings 'numeric';
  2         3  
  2         52  
7 2     2   9 no warnings 'uninitialized';
  2         2  
  2         60  
8 2     2   9 no warnings 'recursion';
  2         3  
  2         79  
9              
10 2     2   11 use File::Path qw( make_path );
  2         4  
  2         208  
11 2     2   12 use Scalar::Util qw(weaken);
  2         3  
  2         294  
12 2     2   1023 use Time::HiRes qw(time);
  2         2618  
  2         7  
13 2     2   367 use vars qw($VERSION);
  2         5  
  2         125  
14              
15 2     2   1008 use Data::RecordStore;
  2         65604  
  2         71  
16 2     2   856 use Data::ObjectStore::Cache;
  2         5  
  2         129  
17              
18             $VERSION = '2.12';
19              
20             our $DEBUG = 0;
21             our $UPGRADING;
22              
23             use constant {
24 2         7634 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   13 };
  2         5  
37             my( @METAFIELDS ) = qw( created updated );
38              
39             sub open_store {
40 67     67 1 78445 my( $cls, @options ) = @_;
41              
42 67 100       280 die "Data::ObjectStore->open_store requires at least one argument" if 0 == @options;
43            
44 66 100       229 if( 1 == @options ) {
45 34         124 unshift @options, 'DATA_PROVIDER';
46             }
47 66         233 my( %options ) = @options;
48              
49 66         156 my $data_provider = $options{DATA_PROVIDER};
50 66 100       207 if( ! ref( $data_provider ) ) {
51             # the default record store Data::RecordStore
52 64         262 $options{BASE_PATH} = "$data_provider/RECORDSTORE";
53 64         507 $data_provider = Data::RecordStore->open_store( %options );
54             }
55 66 100       274602 my $cache = $options{CACHE} ? ref( $options{CACHE} ) ? $options{CACHE} : Data::ObjectStore::Cache->new( $options{CACHE} ) : undef;
    100          
56 66         456 my $store = bless [
57             $data_provider,
58             {}, #DIRTY CACHE
59             {}, #WEAK CACHE
60             undef,
61             \%options,
62             $cache,
63             ], $cls;
64              
65 66 100       262 if( ! $UPGRADING ) {
66 63         260 $store->[STOREINFO] = $store->_fetch_store_info_node;
67 63         253 $store->load_root_container;
68 63 100       275 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         195 $store->save;
72             }
73 65         416 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 732 my( $self ) = @_;
82 2 100       7 if( $self->[CACHE] ) {
83 1         6 $self->[CACHE]->empty;
84             }
85             }
86              
87             # locks the given lock names
88             sub lock {
89 1     1 1 5 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 5308 my $self = shift;
102 8         19 my( %keep );
103 8         23 my( @working ) = ( 1 );
104              
105 8         18 my $data_provider = $self->[DATA_PROVIDER];
106 8         26 my $highest = $data_provider->entry_count;
107              
108 8         570 while( @working ) {
109 31         67 my $try = shift @working;
110              
111 31         66 $keep{$try}++;
112              
113 31         70 my $obj = $self->_knot( $try );
114 31         59 my $d = $obj->[DATA];
115 31         45 my %placed;
116             push @working, (
117 31 100       176 grep { ! $keep{$_} && 0 == $placed{$_}++ }
118 31         80 map { substr( $_, 1 ) }
119 31 100       109 grep { /^r/ }
  83         207  
120             (ref( $d ) eq 'ARRAY' ? @$d : values( %$d ) ));
121             }
122              
123 8         15 my $pcount;
124 8         29 for( my $i=1; $i<=$highest; $i++ ) {
125 46 100       126 if( ! $keep{$i} ) {
126 17         62 $data_provider->delete_record( $i );
127 17         25403 ++$pcount;
128             }
129             }
130            
131 8         52 return $pcount;
132             } #quick_purge
133              
134              
135             sub upgrade_store {
136 3     3 1 8229 my( $source_path, $dest_path ) = @_;
137              
138 3 100       73 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         19 my $from_recstore = Data::RecordStore->open_store( "$source_path/RECORDSTORE" );
144              
145 2         3053 my $info = $from_recstore->fetch( 1 );
146 2         968 my( $vers ) = ( $info =~ /[ \`]ObjectStore_version\`v([^\`]*)/ );
147              
148 2 100       13 if( $vers >= 2 ) {
149 1         10 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         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   18 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       20 if( $obj ) {
172 3         8 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         15 my $clone_thing = $dest_store->_knot( $clone );
183 5 100       27 if( ref($clone_thing) !~ /^(ARRAY|HASH|Data::ObjectStore::Hash|Data::ObjectStore::Array)$/ ) {
184 3         9 $clone_thing->[DIRTY_BIT] = 1;
185             }
186 5         10 my $odata = $clone_thing->[DATA];
187              
188 5         8 my $meta = $clone_thing->[METADATA];
189 5         16 my $time = time;
190 5         12 $meta->{created} = $time;
191 5         7 $meta->{updated} = $time;
192              
193 5         15 $dest_store->save( $clone );
194              
195 5         5133 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       12 if ( $oid > 0 ) {
200 3         6 $odata->[$_] = "r$oid";
201 3 100       8 if ( $oid != $id) {
202 2         5 push @connections, $oid;
203             }
204             }
205             }
206             }
207             else {
208 4         14 for my $key (keys %$odata) {
209 14 100       37 if ( $odata->{$key} > 0 ) {
210 7         12 my $oid = $odata->{$key};
211 7         12 $odata->{$key} = "r$oid";
212 7 100       16 if ( $oid != $id) {
213 5         12 push @connections, $oid;
214             }
215             }
216             }
217             }
218 5         17 $dest_store->save( $clone );
219              
220 5         3635 for my $oid (@connections) {
221 7         53 my $connect_obj = _transfer_obj( $source_store, $dest_store, $oid, 1 + $i );
222 7         16 my $connect_thing = $dest_store->_knot( $connect_obj );
223 7         16 $dest_store->save( $connect_obj );
224             }
225              
226 5         15 $dest_store->fetch( $id );
227             } #_transfer_obj
228              
229 1         4 my $info_node = _transfer_obj( $source_store, $dest_store, 1, 0 );
230 1         8 $info_node->set_ObjectStore_version( $Data::RecordStore::VERSION );
231 1         3 $dest_store->save( $info_node );
232              
233 1         1833 $UPGRADING = 0;
234             } #upgrade_store
235              
236             sub load_root_container {
237 140     140 1 816 my $self = shift;
238 140         345 my $info_node = $self->_fetch_store_info_node;
239 140         436 my $root = $info_node->get_root;
240 140 100       367 unless( $root ) {
241 29         94 $root = $self->create_container;
242 29         126 $info_node->set_root( $root );
243 29         73 $self->save;
244             }
245 140         604 return $root;
246             } #load_root_container
247              
248              
249             sub info {
250 72     72 1 149 my $node = shift->[STOREINFO];
251             my $info = {
252 72         161 map { $_ => $node->get($_) }
  288         601  
253             qw( db_version ObjectStore_version created_time last_update_time )
254             };
255 72         553 $info;
256             } #info
257              
258              
259             sub get_db_version {
260 2     2 1 14 shift->info->{db_version};
261             }
262              
263              
264             sub get_store_version {
265 64     64 1 223 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 8910 my( $self, $class, $data ) = @_;
279 68 100       186 if( ref( $class ) ) {
280 10         18 $data = $class;
281 10         21 $class = 'Data::ObjectStore::Container';
282             }
283 68   100     264 $class //= 'Data::ObjectStore::Container';
284              
285 68 100       341 if( $class !~ /^Data::ObjectStore::/ ) {
286 6         17 my $clname = $class;
287 6         26 $clname =~ s/::/\//g;
288 6         53 require "$clname.pm";
289             }
290              
291 68         181 my $id = $self->_new_id;
292              
293 68         255 my $time = time;
294 68         535 my $obj = bless [ $id,
295             undef,
296             $self,
297             { created => $time,
298             updated => $time },
299             ], $class;
300 68         231 $self->_store_weak( $id, $obj );
301 68         190 $self->_dirty( $id );
302              
303 68         279 for my $fld (keys %$data) {
304 30         84 $obj->set( $fld, $data->{$fld} );
305             }
306              
307 68         245 $obj->_init(); #called the first time the object is created.
308 68         209 $obj->[DIRTY_BIT] = 1;
309 68         397 $obj;
310             } #create_container
311              
312             sub save {
313 168     168 1 15019 my( $self, $ref, $class_override ) = @_;
314 168 100       431 if( ref( $ref ) ) {
315 24         55 return $self->_save( $ref, $class_override );
316             }
317 144         322 my $node = $self->_fetch_store_info_node;
318 144         431 my $now = time;
319 144         646 $self->[DATA_PROVIDER]->use_transaction;
320              
321 144         154741 my( @dirty ) = keys %{$self->[DIRTY]};
  144         3480  
322            
323 144         482 for my $id ( @dirty ) {
324 7278         14979 my $obj = $self->[DIRTY]{$id};
325             # assings id if none were given
326 7278         10997 $self->_knot( $obj );
327             } #each dirty
328              
329 144         245 ( @dirty ) = keys %{$self->[DIRTY]};
  144         2312  
330 144         367 for my $id ( @dirty ) {
331 7278         531985 my $obj = delete $self->[DIRTY]{$id};
332 7278         17900 $self->_save( $obj );
333             } #each dirty
334              
335 144         75125 $node->set_last_update_time( $now );
336 144         408 $self->_save( $node );
337              
338 144         163433 $self->[DATA_PROVIDER]->commit_transaction;
339 144         2536503 $self->[DIRTY] = {};
340 144         2131 return 1;
341             } #save
342              
343             sub _save {
344 7446     7446   14642 my( $self, $obj, $class_override ) = @_;
345 7446         15163 my $thingy = $self->_knot( $obj );
346 7446 100       40393 if( ref($thingy) !~ /^(ARRAY|HASH|Data::ObjectStore::Hash|Data::ObjectStore::Array)$/ ) {
347 288 100       706 if( !$thingy->[DIRTY_BIT] ) {
348 5         11 return;
349             }
350 283         439 $thingy->[DIRTY_BIT] = 0;
351             }
352 7441         18580 my $id = $thingy->[ID];
353 7441         14505 delete $self->[DIRTY]{$id}; # need the upgrading cas?
354              
355             #
356             # Save to the record store.
357             #
358 7441         17714 my $text_rep = $thingy->_freezedry;
359 7441 100       20446 my( @meta ) = $class_override ? $class_override : ref( $thingy );
360 7441         14248 for my $fld (@METAFIELDS) {
361 14882         34821 my $val = $thingy->[METADATA]{$fld};
362 14882         26081 push @meta, $val;
363             }
364 7441         61871 my $meta_string = join('|', @meta );
365 7441         33147 $self->[DATA_PROVIDER]->stow( "$meta_string $text_rep", $id );
366            
367             } #_save
368              
369             sub existing_id {
370 3     3 1 985 my( $self, $obj ) = @_;
371 3 100       18 return undef unless ref($obj);
372 2         8 my $tied = $self->_knot( $obj );
373 2 100       66 return $tied ? $tied->[ID] : undef;
374             }
375              
376             sub _has_dirty {
377 15     15   4976 my $self = shift;
378 15         26 scalar( keys %{$self->[DIRTY]});
  15         105  
379             }
380              
381             sub _knot {
382 75081     75081   112900 my( $self, $item ) = @_;
383 75081         118446 my $r = ref( $item );
384 75081 100       129675 if( $r ) {
385 74618 100 100     138842 if( $r eq 'ARRAY' ) {
    100 100        
    100          
386 71544         144796 return tied @$item;
387             }
388             elsif( $r eq 'HASH' ) {
389 2093         4073 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         2251 return $item;
395             }
396 2         16 return undef;
397             }
398 463 100       813 if( $item > 0 ) {
399 459         709 my $xout = $self->_xform_out( $item );
400 459         790 my $zout = $self->_knot( $xout );
401 459         694 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   690 my( $self ) = @_;
411 348         791 my $node = $self->fetch( 1 );
412 348 100       782 unless( $node ) {
413 29         130 my $first_id = $self->_new_id;
414 29         134 my $now = time;
415 29         271 $node = bless [ 1, {}, $self, { created => $now, updated => $now } ], 'Data::ObjectStore::Container';
416 29         155 $self->_store_weak( 1, $node );
417 29         103 $self->_dirty( 1 );
418 29         92 $node->[DIRTY_BIT] = 1;
419 29         151 $node->set_db_version( $Data::RecordStore::VERSION );
420 29         114 $node->set_ObjectStore_version( $Data::ObjectStore::VERSION );
421 29         114 $node->set_created_time( $now );
422 29         82 $node->set_last_update_time( $now );
423             }
424 348         742 $node;
425             } #_fetch_store_info_node
426              
427             sub _thaw {
428 318     318   573 my( $dryfroze ) = @_;
429              
430             # so foo` or foo\\` but not foo\\\`
431             # also this will never start with a `
432 318         2380 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       1000 if ( 0 < grep { /\\$/ } @$pieces ) {
  4835         8137  
438              
439 33         66 my $newparts = [];
440              
441 33         51 my $is_hanging = 0;
442 33         52 my $working_part = '';
443              
444 33         61 for my $part (@$pieces) {
445              
446             # if the part ends in a hanging escape
447 398 100       1240 if ( $part =~ /(^|[^\\])((\\\\)+)?[\\]$/ ) {
    100          
448 123 100       214 if ( $is_hanging ) {
449 28         53 $working_part .= "`$part";
450             } else {
451 95         141 $working_part = $part;
452             }
453 123         173 $is_hanging = 1;
454             } elsif ( $is_hanging ) {
455 95         201 my $newpart = "$working_part`$part";
456 95         266 $newpart =~ s/\\`/`/gs;
457 95         182 $newpart =~ s/\\\\/\\/gs;
458 95         177 push @$newparts, $newpart;
459 95         166 $is_hanging = 0;
460             } else {
461             # normal part
462 180         346 push @$newparts, $part;
463             }
464             }
465 33         87 $pieces = $newparts;
466              
467             } #if there were escaped ` characters
468              
469 318         544 $pieces;
470             } #_thaw
471              
472              
473             sub fetch {
474 62972     62972 1 112686 my( $self, $id, $force ) = @_;
475 62972   100     134586 my $ref = $self->[DIRTY]{$id} // $self->[WEAK]{$id};
476            
477 62972 100       132701 return $ref if $ref;
478              
479 362         1126 my $stowed = $self->[DATA_PROVIDER]->fetch( $id );
480 362 100       182751 return undef unless $stowed;
481              
482 318         831 my $pos = index( $stowed, ' ' );
483 318 100       822 die "Data::ObjectStore::_fetch : Malformed record '$stowed'" if $pos == -1;
484              
485 316         628 my $metastr = substr $stowed, 0, $pos;
486 316         1279 my( $class, @meta ) = split /\|/, $metastr;
487              
488 316         647 my $meta = {};
489 316         871 for my $fldi (0..$#METAFIELDS) {
490 632         1031 my $fld = $METAFIELDS[$fldi];
491 632         851 my $val = $meta[$fldi];
492 632         1659 $meta->{$fld} = $val;
493             }
494              
495 316         674 my $dryfroze = substr $stowed, $pos + 1;
496              
497 316 100       1536 if( $class !~ /^Data::ObjectStore::/ ) {
498 8         18 my $clname = $class;
499 8         27 $clname =~ s/::/\//g;
500              
501 8         16 eval {
502 8         393 require "$clname.pm";
503 5 100       69 unless( $class->can( '_reconstitute' ) ) {
504 2 100       8 if( $force ) {
505 1         37 warn "Forcing '$class' to be 'Data::ObjectStore::Container'";
506 1         5 $class = 'Data::ObjectStore::Container';
507             } else {
508 1         20 die "Object in the store was marked as '$class' but that is not a 'Data::ObjectStore::Container'";
509             }
510             }
511             };
512 8 100       1090 if( $@ ) {
513 4 100       14 if( $force ) {
514 1         42 warn "warn '$class' to be 'Data::ObjectStore::Container'";
515 1         8 $class = 'Data::ObjectStore::Container';
516             } else {
517 3         18 die $@;
518             }
519             }
520             }
521              
522 313         675 my $pieces = _thaw( $dryfroze );
523              
524 313         1845 my $ret = $class->_reconstitute( $self, $id, $pieces, $meta );
525 313         879 $self->_store_weak( $id, $ret );
526 313         1623 return $ret;
527             } #_fetch
528              
529             #
530             # Convert from reference, scalar or undef to value marker
531             #
532             sub _xform_in {
533 23446     23446   36646 my( $self, $val ) = @_;
534 23446 100       41060 if( ref( $val ) ) {
535 188         521 my $id = $self->_get_id( $val );
536 185         766 return $id, "r$id";
537             }
538 23258 100       73148 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   13294 my( $self, $val ) = @_;
546              
547 8281 100 100     26660 return undef unless defined( $val ) && $val ne 'u';
548              
549 6263 100       12105 if( index($val,'v') == 0 ) {
550 5419         16687 return substr( $val, 1 );
551             }
552 844 100       2796 if( $val =~ /^r(\d+)/ ) {
553 381         812 return $self->fetch( $1 );
554             }
555 463         777 return $self->fetch( $val );
556             }
557              
558             sub _store_weak {
559 7543     7543   13864 my( $self, $id, $ref ) = @_;
560              
561 7543 100       16229 if( $self->[CACHE] ) {
562 542         1777 $self->[CACHE]->stow( $id, $ref );
563             }
564            
565 7543         29620 $self->[WEAK]{$id} = $ref;
566              
567 7543         24642 weaken( $self->[WEAK]{$id} );
568              
569             } #_store_weak
570              
571             sub _dirty {
572 59397     59397   92287 my( $self, $id ) = @_;
573 59397         96807 my $item = $self->[WEAK]{$id};
574 59397         94636 $self->[DIRTY]{$id} = $item;
575 59397         98317 $item = $self->_knot( $item );
576 59397 100       115555 if( $item ) {
577 59381         133082 $item->[METADATA]{updated} = time();
578             }
579             } #_dirty
580              
581              
582             sub _new_id {
583 7230     7230   11970 my( $self ) = @_;
584 7230         22042 my $newid = $self->[DATA_PROVIDER]->next_id;
585 7230         2484826 $newid;
586             } #_new_id
587              
588             sub _meta {
589 6     6   15 my( $self, $thingy ) = @_;
590             return {
591             created => $thingy->[METADATA]{created},
592             updated => $thingy->[METADATA]{updated},
593 6         41 };
594             } #_meta
595              
596             sub last_updated {
597 4     4 1 22 my( $self, $obj ) = @_;
598 4         10 $obj = $self->_knot( $obj );
599 4 100       13 return undef unless $obj;
600 3         11 $self->_meta( $obj )->{updated};
601             }
602              
603             sub created {
604 4     4 1 11 my( $self, $obj ) = @_;
605 4         9 $obj = $self->_knot( $obj );
606 4 100       14 return undef unless $obj;
607 3         10 $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   369 my( $self, $ref ) = @_;
615              
616 196         320 my $class = ref( $ref );
617 196         270 my $thingy;
618 196 100       530 if ( $class eq 'ARRAY' ) {
    100          
619 62         120 $thingy = tied @$ref;
620 62 100       153 if ( ! $thingy ) {
621 46         117 my $id = $self->_new_id;
622 46         173 my( @items ) = @$ref;
623 46         601 tie @$ref, 'Data::ObjectStore::Array', $self, $id, { created => time, updated => time}, 0, $Data::ObjectStore::Array::MAX_BLOCKS;
624 46         105 my $tied = tied @$ref;
625              
626 46         167 $self->_store_weak( $id, $ref );
627 46         120 $self->_dirty( $id );
628 46         201 push @$ref, @items;
629 46         120 return $id;
630             }
631 16         26 $ref = $thingy;
632 16         31 $class = ref( $ref );
633             }
634             elsif ( $class eq 'HASH' ) {
635 24         51 $thingy = tied %$ref;
636 24 100       69 if ( ! $thingy ) {
637 18         48 my $id = $self->_new_id;
638 18         97 my( %items ) = %$ref;
639 18         559 tie %$ref, 'Data::ObjectStore::Hash', $self, $id, { created => time, updated => time};
640 18         59 my $tied = tied %$ref;
641              
642 18         69 $self->_store_weak( $id, $ref );
643 18         61 $self->_dirty( $id );
644 18         64 for my $key (keys( %items) ) {
645 23         102 $ref->{$key} = $items{$key};
646             }
647 18         70 return $id;
648             }
649 6         10 $ref = $thingy;
650 6         14 $class = ref( $ref );
651             }
652             else {
653 110         154 $thingy = $ref;
654             }
655              
656 132 100 100     896 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         304 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   23 use strict;
  2         5  
  2         71  
675 2     2   13 use warnings;
  2         8  
  2         76  
676 2     2   11 use warnings FATAL => 'all';
  2         4  
  2         90  
677 2     2   11 no warnings 'numeric';
  2         4  
  2         86  
678 2     2   12 no warnings 'recursion';
  2         2  
  2         63  
679              
680 2     2   1100 use Tie::Array;
  2         2287  
  2         98  
681              
682             $Data::ObjectStore::Array::MAX_BLOCKS = 1_000_000;
683              
684              
685             use constant {
686 2         5122 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   14 };
  2         5  
698              
699             sub store {
700 1     1   925 shift->[DSTORE];
701             }
702              
703             sub _freezedry {
704 7115     7115   23936 my $self = shift;
705 7115         10203 my @items;
706 7115 100       21431 my $stuff_count = $self->[BLOCK_COUNT] > $self->[ITEM_COUNT] ? $self->[ITEM_COUNT] : $self->[BLOCK_COUNT];
707 7115 100       14733 if( $stuff_count > 0 ) {
708 7046 100 100     17002 @items = map { if( defined($_) && $_=~ /[\\\`]/ ) { $_ =~ s/[\\]/\\\\/gs; s/`/\\`/gs; } defined($_) ? $_ : 'u' } map { $self->[DATA][$_] } (0..($stuff_count-1));
  28149 100       88699  
  6         19  
  6         24  
  28149         62138  
  28149         70863  
709             }
710              
711 7115   100     52583 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   320 my( $cls, $store, $id, $data, $meta ) = @_;
722 130         231 my $arry = [];
723 130         522 tie @$arry, $cls, $store, $id, $meta, @$data;
724 130         237 return $arry;
725             }
726              
727             sub TIEARRAY {
728 7221     7221   19476 my( $class, $obj_store, $id, $meta, $level, $block_count, $item_count, $underneath, @list ) = @_;
729 7221   100     30996 $item_count //= 0;
730 7221         12419 my $block_size = $block_count ** $level;
731              
732 7221         12862 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         29677 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         19204 return $obj;
751             } #TIEARRAY
752              
753             sub FETCH {
754 49416     49416   90785 my( $self, $idx ) = @_;
755              
756 49416 100       88008 if( $idx >= $self->[ITEM_COUNT] ) {
757 17281         36097 return undef;
758             }
759              
760 32135 100       56663 if( $self->[LEVEL] == 0 ) {
761 3868         7314 return $self->[DSTORE]->_xform_out( $self->[DATA][$idx] );
762             }
763              
764 28267         62176 my $block = $self->_getblock( int( $idx / $self->[BLOCK_SIZE] ) );
765 28267         55542 return $block->FETCH( $idx % $self->[BLOCK_SIZE] );
766              
767             } #FETCH
768              
769             sub FETCHSIZE {
770 5450     5450   174730 shift->[ITEM_COUNT];
771             }
772              
773             sub _embiggen {
774 41     41   96 my( $self, $size ) = @_;
775 41         82 my $store = $self->[DSTORE];
776              
777 41         147 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         113 my $newblock = [];
793 70         148 my $newid = $store->_new_id;
794 70         126 my $meta = { %{$self->[METADATA]} };
  70         331  
795 70         174 $meta->{updated} = time;
796 70         454 tie @$newblock, 'Data::ObjectStore::Array', $store, $newid, $meta, $self->[LEVEL], $self->[BLOCK_COUNT], $self->[ITEM_COUNT], 1;
797 70         204 $store->_store_weak( $newid, $newblock );
798 70         183 $store->_dirty( $newid );
799              
800 70         134 my $tied = tied @$newblock;
801              
802 70         177 $tied->[DATA] = [@{$self->[DATA]}];
  70         197  
803              
804 70         211 $self->[DATA] = [ "r$newid" ];
805              
806 70         140 $self->[BLOCK_SIZE] *= $self->[BLOCK_COUNT];
807 70         105 $self->[LEVEL]++;
808 70         160 $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   99891 my( $self, $block_idx ) = @_;
820              
821 68297   100     142912 my $block_id = $self->[DATA][$block_idx] // 'r0';
822 68297         107656 $block_id = substr( $block_id, 1 );
823 68297         92734 my $store = $self->[DSTORE];
824              
825 68297 100       134297 if( $block_id > 0 ) {
826 61322         99120 my $block = $store->fetch( $block_id );
827 61322         117283 return tied(@$block);
828             }
829              
830 6975         15302 $block_id = $store->_new_id;
831 6975         16350 my $block = [];
832 6975         14255 my $level = $self->[LEVEL] - 1;
833 6975         10416 my $meta = { %{$self->[METADATA]} };
  6975         31850  
834 6975         46877 tie @$block, 'Data::ObjectStore::Array', $store, $block_id, $meta, $level, $self->[BLOCK_COUNT];
835              
836 6975         12972 my $tied = tied( @$block );
837              
838 6975         11018 $tied->[UNDERNEATH] = 1;
839 6975 100       17028 if( $block_idx >= ($self->[BLOCK_COUNT] - 1 ) ) {
840 1736         3042 $tied->[ITEM_COUNT] = $self->[BLOCK_SIZE];
841             }
842              
843 6975         19854 $store->_store_weak( $block_id, $block );
844 6975         16896 $store->_dirty( $block_id );
845 6975         17826 $store->_dirty( $self->[ID] );
846 6975         18569 $self->[DATA][$block_idx] = "r$block_id";
847 6975         14211 return $tied;
848              
849             } #_getblock
850              
851             sub STORE {
852 51006     51006   87930 my( $self, $idx, $val ) = @_;
853              
854 51006 100       100343 if( $idx >= $self->[BLOCK_COUNT]*$self->[BLOCK_SIZE] ) {
855 23         87 $self->_embiggen( $idx + 1 );
856 23         83 $self->STORE( $idx, $val );
857 23         87 return;
858             }
859              
860 50983 100       90451 if( $idx >= $self->[ITEM_COUNT] ) {
861 27410         60746 $self->_storesize( $idx + 1 );
862 27410         40314 my $store = $self->[DSTORE];
863 27410         44389 $store->_dirty( $self->[ID] );
864             }
865              
866 50983 100       93187 if( $self->[LEVEL] == 0 ) {
867 13810         25139 my( $xid, $xin ) = $self->[DSTORE]->_xform_in( $val );
868 13809 100 100     31305 if( $xid > 0 && $xid < 3 ) {
869 2         28 die "cannot store a root node in a list";
870             }
871 13807         20557 my $store = $self->[DSTORE];
872 13807   100     40843 my $xold = $self->[DATA][$idx] // 0;
873 13807 100       27206 if( $xold ne $xin ) {
874 13781         24639 $self->[DATA][$idx] = $xin;
875 13781         27112 $store->_dirty( $self->[ID] );
876             }
877              
878 13807         26597 return;
879             }
880              
881 37173         81414 my $block = $self->_getblock( int( $idx / $self->[BLOCK_SIZE] ) );
882 37173         78258 $block->STORE( $idx % $self->[BLOCK_SIZE], $val );
883              
884             } #STORE
885              
886             sub _storesize {
887 30403     30403   44745 my( $self, $size ) = @_;
888 30403         47378 $self->[ITEM_COUNT] = $size;
889             }
890              
891             sub STORESIZE {
892 10     10   3893 my( $self, $size ) = @_;
893              
894             # fixes the size of the array if the array were to shrink
895 10         33 my $current_oversize = $self->[ITEM_COUNT] - $size;
896 10 100       44 if( $current_oversize > 0 ) {
897 2         7 $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   4977 my( $self, $idx ) = @_;
906 62 100       166 if( $idx >= $self->[ITEM_COUNT] ) {
907 19         106 return 0;
908             }
909 43 100       102 if( $self->[LEVEL] == 0 ) {
910 15         85 return defined($self->[DATA][$idx]);
911             }
912 28         99 return $self->_getblock( int( $idx / $self->[BLOCK_SIZE] ) )->EXISTS( $idx % $self->[BLOCK_SIZE] );
913              
914             } #EXISTS
915              
916             sub DELETE {
917 23     23   15077 my( $self, $idx ) = @_;
918              
919 23         49 my $store = $self->[DSTORE];
920 23         58 my $del = $self->FETCH( $idx );
921 23         83 $self->STORE( $idx, undef );
922 23 100       78 if( $idx == $self->[ITEM_COUNT] - 1 ) {
923 20         48 $self->[ITEM_COUNT]--;
924 20   100     94 while( $self->[ITEM_COUNT] > 0 && ! defined( $self->FETCH( $self->[ITEM_COUNT] - 1 ) ) ) {
925 1152         2450 $self->[ITEM_COUNT]--;
926             }
927              
928             }
929 23         77 $store->_dirty( $self->[ID] );
930              
931 23         105 return $del;
932              
933             } #DELETE
934              
935             sub CLEAR {
936 13     13   7226 my $self = shift;
937 13 100       73 if( $self->[ITEM_COUNT] > 0 ) {
938 12         30 my $store = $self->[DSTORE];
939 12         61 for( 0..$self->[ITEM_COUNT] ) {
940 115         217 my $del = $self->FETCH( $_ );
941             }
942 12         33 $self->[ITEM_COUNT] = 0;
943 12         34 $self->[DATA] = [];
944 12         41 $self->[DSTORE]->_dirty( $self->[ID] );
945             }
946             }
947             sub PUSH {
948 88     88   4666 my( $self, @vals ) = @_;
949 88 100       256 return unless @vals;
950 56         466 $self->SPLICE( $self->[ITEM_COUNT], 0, @vals );
951             }
952             sub POP {
953 23     23   6341 my $self = shift;
954 23         80 my $idx = $self->[ITEM_COUNT] - 1;
955 23 100       89 if( $idx < 0 ) {
956 1         17 return undef;
957             }
958 22         72 my $pop = $self->FETCH( $idx );
959 22         85 $self->STORE( $idx, undef );
960 22         39 $self->[ITEM_COUNT]--;
961 22         90 return $pop;
962             }
963             sub SHIFT {
964 21     21   3597 my( $self ) = @_;
965 21 100       75 return undef unless $self->[ITEM_COUNT];
966 19         66 my( $ret ) = $self->SPLICE( 0, 1 );
967 19         138 $ret;
968             }
969              
970             sub UNSHIFT {
971 19     19   11419 my( $self, @vals ) = @_;
972 19 100       91 return unless @vals;
973 18         67 return $self->SPLICE( 0, 0, @vals );
974             }
975              
976             sub SPLICE {
977 3000     3000   15828 my( $self, $offset, $remove_length, @vals ) = @_;
978              
979             # if no arguments given, clear the array
980 3000 100       6033 if( ! defined( $offset ) ) {
981 1         2 $offset = 0;
982 1         2 $remove_length = $self->[ITEM_COUNT];
983             }
984            
985             # if negative, the offset is from the end
986 3000 100       5773 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       4884 if( $remove_length < 0 ) {
993 2         4 $remove_length = ($self->[ITEM_COUNT] - $offset) + $remove_length;
994             }
995              
996 3000 100 100     9268 return () unless $remove_length || @vals;
997              
998             # check for removal past end
999 2999 100       6804 if( $offset > ($self->[ITEM_COUNT] - 1) ) {
1000 2163         3266 $remove_length = 0;
1001 2163         3537 $offset = $self->[ITEM_COUNT];
1002             }
1003 2999 100       6032 if( $remove_length > ($self->[ITEM_COUNT] - $offset) ) {
1004 13         37 $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         4755 my $new_size = $self->[ITEM_COUNT];
1012 2999         4258 $new_size -= $remove_length;
1013 2999         4436 $new_size += @vals;
1014              
1015 2999 100       7032 if( $new_size > $self->[BLOCK_SIZE] * $self->[BLOCK_COUNT] ) {
1016 18         57 $self->_embiggen( $new_size );
1017             }
1018              
1019 2999         4436 my $store = $self->[DSTORE];
1020 2999         4162 my $BLOCK_COUNT = $self->[BLOCK_COUNT];
1021 2999         4012 my $BLOCK_SIZE = $self->[BLOCK_SIZE]; # embiggen may have changed this, so dont set this before the embiggen call
1022              
1023 2999 100       6037 if( $self->[LEVEL] == 0 ) {
1024             # lowest level, must fit in the size. The end recursion and easy case.
1025 1946         2854 my $blocks = $self->[DATA];
1026 1946         3751 my( @invals ) = ( map { ($store->_xform_in($_))[1] } @vals );
  7645         13098  
1027 1946         4014 for my $inval (@invals) {
1028 7645         12494 my( $inid ) = ( $inval =~ /^r(\d+)/ );
1029              
1030 7645 100 100     14862 if( $inid && $inid < 3 ) {
1031 4         46 die "cannot store a root node in a list";
1032             }
1033             }
1034 1942         7331 my @raw_return = splice @$blocks, $offset, $remove_length, @invals;
1035 1942         2853 my @ret;
1036 1942         3017 for my $rr (@raw_return) {
1037 278         433 push @ret, $store->_xform_out($rr);
1038             }
1039 1942         4746 $self->_storesize( $new_size );
1040 1942         4600 $store->_dirty( $self->[ID] );
1041 1942         4971 return @ret;
1042             } # LEVEL == 0 case
1043              
1044 1053         1511 my( @removed );
1045 1053   100     3923 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         29792 push @removed, $self->FETCH( $offset );
1051 13285         36844 $self->STORE( $offset++, shift @vals );
1052 13285         48973 $remove_length--;
1053             }
1054              
1055 1053 100       2279 if( $remove_length ) {
1056              
1057 59         212 for( my $idx=$offset; $idx<($offset+$remove_length); $idx++ ) {
1058 1163         1824 push @removed, $self->FETCH( $idx );
1059             }
1060              
1061 59         178 my $things_to_move = $self->[ITEM_COUNT] - ($offset+$remove_length);
1062 59         117 my $to_idx = $offset;
1063 59         87 my $from_idx = $to_idx + $remove_length;
1064 59         142 for( 1..$things_to_move ) {
1065 439         703 $self->STORE( $to_idx, $self->FETCH( $from_idx ) );
1066 439         615 $to_idx++;
1067 439         629 $from_idx++;
1068             }
1069             } # has things to remove
1070              
1071 1053 100       2400 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         1544 my $block_idx = int( $offset / $BLOCK_SIZE );
1079 774         1346 my $block_off = $offset % $BLOCK_SIZE;
1080              
1081 774   100     3135 while( @vals && ($self->[ITEM_COUNT] > $block_idx*$BLOCK_SIZE+$block_off) ) {
1082 42         108 my $block = $self->_getblock( $block_idx );
1083 42         108 my $bubble_size = $block->FETCHSIZE - $block_off;
1084 42 100       180 if( $bubble_size > 0 ) {
1085 34         85 my @bubble = $block->SPLICE( $block_off, $bubble_size );
1086 34         95 push @vals, @bubble;
1087             }
1088 42 100       131 my $can_insert = @vals > ($BLOCK_SIZE-$block_off) ? ($BLOCK_SIZE-$block_off) : @vals;
1089 42         138 $block->SPLICE( $block_off, 0, splice( @vals, 0, $can_insert ) );
1090 42         99 $block_idx++;
1091 42         139 $block_off = 0;
1092             }
1093 774         1716 while( @vals ) {
1094 2787         6678 my $block = $self->_getblock( $block_idx );
1095 2787         4625 my $remmy = $BLOCK_SIZE - $block_off;
1096 2787 100       6916 if( $remmy > @vals ) { $remmy = @vals; }
  87         131  
1097              
1098 2787         12206 $block->SPLICE( $block_off, $block->[ITEM_COUNT], splice( @vals, 0, $remmy) );
1099 2775         8066 $block_idx++;
1100 2775         6563 $block_off = 0;
1101             }
1102              
1103             } # has vals
1104              
1105 1041         2631 $self->_storesize( $new_size );
1106              
1107 1041         4721 return @removed;
1108              
1109             } #SPLICE
1110              
1111       1     sub EXTEND {
1112             }
1113              
1114             sub DESTROY {
1115 6773     6773   8414982 my $self = shift;
1116 6773         65189 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   19 use strict;
  2         24  
  2         61  
1131 2     2   18 use warnings;
  2         5  
  2         91  
1132              
1133 2     2   11 no warnings 'uninitialized';
  2         3  
  2         65  
1134 2     2   10 no warnings 'numeric';
  2         5  
  2         70  
1135 2     2   11 no warnings 'recursion';
  2         3  
  2         48  
1136              
1137 2     2   989 use Tie::Hash;
  2         1775  
  2         104  
1138              
1139             $Data::ObjectStore::Hash::BUCKET_SIZE = 29;
1140             $Data::ObjectStore::Hash::MAX_SIZE = 1_062_599;
1141              
1142             use constant {
1143 2         3861 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   13 };
  2         3  
1152              
1153             sub store {
1154 1     1   951 shift->[DSTORE];
1155             }
1156              
1157             sub _freezedry {
1158 45     45   81 my $self = shift;
1159 45         74 my $r = $self->[DATA];
1160             join( "`",
1161             $self->[LEVEL],
1162             $self->[BUCKETS],
1163             $self->[SIZE],
1164 45 100       427 map { if( $_=~ /[\\\`]/ ) { s/[\\]/\\\\/gs; s/`/\\`/gs; } $_ }
  3021 100       5071  
  6         14  
  6         18  
  3021         5449  
1165             $self->[LEVEL] ? @$r : %$r
1166             );
1167             }
1168              
1169             sub _reconstitute {
1170 34     34   105 my( $cls, $store, $id, $data, $meta ) = @_;
1171 34         65 my $hash = {};
1172 34         228 tie %$hash, $cls, $store, $id, $meta, @$data;
1173              
1174 34         87 return $hash;
1175             }
1176              
1177             sub TIEHASH {
1178 76     76   682 my( $class, $obj_store, $id, $meta, $level, $buckets, $size, @fetch_buckets ) = @_;
1179 76   100     242 $level //= 0;
1180 76   100     192 $size ||= 0;
1181 76 100       162 unless( $buckets ) {
1182 42         62 $buckets = $Data::ObjectStore::Hash::BUCKET_SIZE;
1183             }
1184 76 100       1507 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   856 my $self = shift;
1197 4 100       22 if( $self->[SIZE] > 0 ) {
1198 3         7 $self->[SIZE] = 0;
1199 3         6 my $store = $self->[DSTORE];
1200 3         9 $store->_dirty( $self->[ID] );
1201 3 100       12 if( $self->[LEVEL] == 0 ) {
1202 2         4 %{$self->[DATA]} = ();
  2         14  
1203             } else {
1204 1         2 @{$self->[DATA]} = ();
  1         6  
1205             }
1206             }
1207             } #CLEAR
1208              
1209             sub DELETE {
1210 9     9   3031 my( $self, $key ) = @_;
1211              
1212 9 100       26 return undef unless $self->EXISTS( $key );
1213              
1214 7         16 $self->[SIZE]--;
1215              
1216 7         19 my $data = $self->[DATA];
1217 7         12 my $store = $self->[DSTORE];
1218              
1219 7 100       20 if( $self->[LEVEL] == 0 ) {
1220 5         18 $store->_dirty( $self->[ID] );
1221 5         20 my $delin = delete $data->{$key};
1222 5         16 return $store->_xform_out( $delin );
1223             } else {
1224 2         4 my $hval = 0;
1225 2         9 foreach (split //,$key) {
1226 6         11 $hval = $hval*33 - ord($_);
1227             }
1228 2         6 $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   24514 my( $self, $key ) = @_;
1237              
1238 3439 100       5847 if( $self->[LEVEL] == 0 ) {
1239 3197         7904 return exists $self->[DATA]{$key};
1240             } else {
1241 242         316 my $data = $self->[DATA];
1242 242         290 my $hval = 0;
1243 242         483 foreach (split //,$key) {
1244 337         500 $hval = $hval*33 - ord($_);
1245             }
1246 242         370 $hval = $hval % $self->[BUCKETS];
1247 242         386 my $hash_id = substr($data->[$hval],1);
1248 242 100       481 if( $hash_id > 0 ) {
1249 240         414 my $hash = $self->[DSTORE]->fetch( $hash_id );
1250 240         359 my $tied = tied %$hash;
1251 240         479 return $tied->EXISTS( $key );
1252             }
1253              
1254             }
1255 2         6 return 0;
1256             } #EXISTS
1257              
1258             sub FETCH {
1259 2277     2277   13177 my( $self, $key ) = @_;
1260 2277         3088 my $data = $self->[DATA];
1261              
1262 2277 100       3396 if( $self->[LEVEL] == 0 ) {
1263 1853         3608 return $self->[DSTORE]->_xform_out( $data->{$key} );
1264             } else {
1265 424         488 my $hval = 0;
1266 424         809 foreach (split //,$key) {
1267 521         775 $hval = $hval*33 - ord($_);
1268             }
1269 424         620 $hval = $hval % $self->[BUCKETS];
1270 424         902 my $hash = $self->[DSTORE]->_knot(substr($data->[$hval],1));
1271 424 100       807 if( $hash ) {
1272 423         701 return $hash->FETCH( $key );
1273             }
1274             }
1275 1         6 return undef;
1276             } #FETCH
1277              
1278             sub STORE {
1279 1558     1558   22415 my( $self, $key, $val ) = @_;
1280 1558         2308 my $data = $self->[DATA];
1281              
1282 1558         2036 my $store = $self->[DSTORE];
1283 1558         2730 my( $xid, $xin ) = $store->_xform_in( $val );
1284 1557 100       3086 if( $xid > 0 ) {
1285 22 100       86 if( $xid < 3 ) {
1286 11         25 $self->[SIZE]--;
1287 11         160 die "cannot store a root node in a hash";
1288             }
1289             }
1290              
1291             #
1292             # EMBIGGEN TEST
1293             #
1294 1546 100       2853 unless( $self->EXISTS( $key ) ) {
1295 1537         2256 $self->[SIZE]++;
1296             }
1297              
1298            
1299 1546 100       2682 if( $self->[LEVEL] == 0 ) {
1300 1516         2390 my $oldin = $data->{$key};
1301 1516 100       3231 if( $xin ne $oldin ) {
1302 1515         4713 $data->{$key} = $xin;
1303 1515         3386 $store->_dirty( $self->[ID] );
1304              
1305 1515 100       6486 if( $self->[SIZE] > $Data::ObjectStore::Hash::MAX_SIZE ) {
1306              
1307             # do the thing converting this to a deeper level
1308 4         13 $self->[LEVEL] = 1;
1309 4         8 my( @newhash );
1310              
1311 4         151 my( @newids ) = ( 0 ) x $Data::ObjectStore::Hash::BUCKET_SIZE;
1312 4         11 $self->[BUCKETS] = $Data::ObjectStore::Hash::BUCKET_SIZE;
1313 4         24 for my $key (keys %$data) {
1314 52         84 my $hval = 0;
1315 52         124 foreach (split //,$key) {
1316 56         88 $hval = $hval*33 - ord($_);
1317             }
1318 52         80 $hval = $hval % $self->[BUCKETS];
1319 52         71 my $hash = $newhash[$hval];
1320 52 100       75 if( $hash ) {
1321 30         38 my $tied = tied %$hash;
1322 30         74 $tied->STORE( $key, $store->_xform_out($data->{$key}) );
1323             } else {
1324 22         37 $hash = {};
1325 22         44 my $hash_id = $store->_new_id;
1326             tie %$hash, 'Data::ObjectStore::Hash',
1327 22         213 $store, $hash_id, {%{$self->[METADATA]}},
1328 22         86 0, 0, 1, $key, $data->{$key};
1329 22         69 $store->_store_weak( $hash_id, $hash );
1330 22         48 $store->_dirty( $hash_id );
1331 22         57 $newhash[$hval] = $hash;
1332 22         62 $newids[$hval] = "r$hash_id";
1333             }
1334              
1335             }
1336 4         17 $self->[DATA] = \@newids;
1337 4         21 $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         11 $store->_dirty( $self->[ID] );
1344              
1345             } # EMBIGGEN CHECK
1346             }
1347             }
1348             else { #
1349            
1350 30         40 my $store = $self->[DSTORE];
1351 30         41 my $hval = 0;
1352 30         58 foreach (split //,$key) {
1353 49         70 $hval = $hval*33 - ord($_);
1354             }
1355 30         44 $hval = $hval % $self->[BUCKETS];
1356 30         52 my $hash_id = substr($data->[$hval],1);
1357 30         37 my $hash;
1358             # check if there is a subhash created here
1359 30 100       58 if( $hash_id > 0 ) {
1360             # subhash was already created, so store the new val in it
1361 28         49 $hash = $store->fetch( $hash_id );
1362 28         40 my $tied = tied %$hash;
1363 28         56 $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         5 $hash = {};
1368 2         7 $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         27  
1371              
1372 2         8 $store->_store_weak( $hash_id, $hash );
1373 2         5 $store->_dirty( $hash_id );
1374 2         11 $data->[$hval] = "r$hash_id";
1375             }
1376             }
1377              
1378             } #STORE
1379              
1380             sub FIRSTKEY {
1381 213     213   6967 my $self = shift;
1382              
1383 213         314 my $data = $self->[DATA];
1384 213 100       480 if( $self->[LEVEL] == 0 ) {
1385 187         346 my $a = scalar keys %$data; #reset
1386 187         421 my( $k, $val ) = each %$data;
1387 187 100       509 return wantarray ? ( $k => $self->[DSTORE]->_xform_out( $val ) ) : $k;
1388             }
1389 26         79 $self->[NEXT] = [undef,undef];
1390 26         77 return $self->NEXTKEY;
1391             }
1392              
1393             sub NEXTKEY {
1394 6236     6236   11478 my $self = shift;
1395 6236         7969 my $data = $self->[DATA];
1396 6236         8129 my $lvl = $self->[LEVEL];
1397 6236 100       9263 if( $lvl == 0 ) {
1398 3437         5840 my( $k, $val ) = each %$data;
1399 3437 100       9330 return wantarray ? ( $k => $self->[DSTORE]->_xform_out($val) ) : $k;
1400             }
1401             else {
1402 2799         3710 my $store = $self->[DSTORE];
1403              
1404 2799         4443 my $at_start = ! defined( $self->[NEXT][0] );
1405              
1406 2799 100       4043 if( $at_start ) {
1407 26         53 $self->[NEXT][0] = 0;
1408 26         54 $self->[NEXT][1] = undef;
1409             }
1410              
1411 2799         3561 my $hash = $self->[NEXT][1];
1412 2799   100     8421 $at_start ||= ! $hash;
1413 2799 100       4400 unless( $hash ) {
1414 2148         4723 my $hash_id = substr( $data->[$self->[NEXT][0]], 1 );
1415 2148 100       4640 $hash = $store->fetch( $hash_id ) if $hash_id > 1;
1416             }
1417              
1418 2799 100       4068 if( $hash ) {
1419 809         1077 my $tied = tied( %$hash );
1420 809 100       1412 my( $k, $v ) = $at_start ? $tied->FIRSTKEY : $tied->NEXTKEY;
1421 809 100       1350 if( defined( $k ) ) {
1422 651         879 $self->[NEXT][1] = $hash; #to keep the weak reference
1423 651 50       3959 return wantarray ? ( $k => $v ) : $k;
1424             }
1425             }
1426              
1427 2148         2709 $self->[NEXT][1] = undef;
1428 2148         2605 $self->[NEXT][0]++;
1429              
1430 2148 100       3797 if( $self->[NEXT][0] > $#$data ) {
1431 26         46 $self->[NEXT][0] = undef;
1432 26         314 return undef;
1433             }
1434             # recursion case, the next bucket has been incremented
1435 2122         9166 return $self->NEXTKEY;
1436             }
1437              
1438             } #NEXTKEY
1439              
1440             sub DESTROY {
1441 22     22   18196 my $self = shift;
1442              
1443             #remove all WEAK_REFS to the buckets
1444 22         723 undef $self->[DATA];
1445              
1446 22         160 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   16 use strict;
  2         4  
  2         60  
1457 2     2   11 use warnings;
  2         3  
  2         65  
1458 2     2   36 no warnings 'uninitialized';
  2         4  
  2         73  
1459 2     2   11 no warnings 'numeric';
  2         2  
  2         90  
1460              
1461             use constant {
1462 2         546 ID => 0,
1463             DATA => 1,
1464             DSTORE => 2,
1465             METADATA => 3,
1466             VOLATILE => 4,
1467             DIRTY_BIT => 5,
1468 2     2   13 };
  2         3  
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   5909 '""' => sub { my $self = shift; $self->[ID] },
  3201         7191  
1476 8 100   8   1258 eq => sub { ref($_[1]) && $_[1]->[ID] == $_[0]->[ID] },
1477 3 100   3   28 ne => sub { ! ref($_[1]) || $_[1]->[ID] != $_[0]->[ID] },
1478 3 100   3   53 '==' => sub { ref($_[1]) && $_[1]->[ID] == $_[0]->[ID] },
1479 3 100   3   29 '!=' => sub { ! ref($_[1]) || $_[1]->[ID] != $_[0]->[ID] },
1480 2     2   14 fallback => 1;
  2         4  
  2         35  
1481              
1482              
1483             sub set {
1484 412     412   962 my( $self, $fld, $val ) = @_;
1485              
1486              
1487 412         2135 my $store = $self->[DSTORE];
1488 412         953 my( $inid, $inval ) = $store->_xform_in( $val );
1489 411 100 100     1461 if( $self->[ID] > 2 && $inid > 0 && $inid < 3 ) {
      100        
1490 4         40 die "cannot store a root node in a container";
1491             }
1492              
1493 407         946 my $oldval = $self->[DATA]{$fld};
1494              
1495 407 100 100     1622 if( ! defined $self->[DATA]{$fld} || $oldval ne $inval ) {
1496 406         1186 $store->_dirty( $self->[ID] );
1497 406         777 $self->[DIRTY_BIT] = 1;
1498 406 100       941 if( ! defined $val ) {
1499 7         25 $self->[DATA]{$fld} = undef;
1500 7         24 return;
1501             }
1502             }
1503              
1504 400         932 $self->[DATA]{$fld} = $inval;
1505 400         1003 return $store->_xform_out( $self->[DATA]{$fld} );
1506             } #set
1507              
1508             sub remove_field {
1509 2     2   13 my( $self, $fld ) = @_;
1510 2         12 $self->[DSTORE]->_dirty( $self->[ID] );
1511 2         6 $self->[DIRTY_BIT] = 1;
1512 2         7 delete $self->[DATA]{$fld};
1513             }
1514              
1515             sub fields {
1516 4     4   14 my $self = shift;
1517 4         6 return [keys %{$self->[DATA]}];
  4         51  
1518             } #fields
1519              
1520             sub get {
1521 581     581   1110 my( $self, $fld, $default ) = @_;
1522              
1523 581         2282 my $cur = $self->[DATA]{$fld};
1524 581         934 my $store = $self->[DSTORE];
1525 581 100 100     2539 if( ( ! defined( $cur ) || $cur eq 'u' ) && defined( $default ) ) {
      100        
1526 21         69 my( $xid, $xin ) = $store->_xform_in( $default );
1527 21 100 100     138 if( ref( $default ) && $self->[ID] > 2 && $xid < 3 ) {
      100        
1528 2         23 die "cannot store a root node in a container";
1529             }
1530 19         66 $store->_dirty( $self->[ID] );
1531 19         54 $self->[DIRTY_BIT] = 1;
1532 19         57 $self->[DATA]{$fld} = $xin;
1533             }
1534 579         1431 return $store->_xform_out( $self->[DATA]{$fld} );
1535              
1536             } #get
1537              
1538             sub clearvol {
1539 1     1   4 my( $self, $key ) = @_;
1540 1         5 delete $self->[VOLATILE]{$key};
1541             }
1542              
1543             sub clearvols {
1544 2     2   7 my( $self, @keys ) = @_;
1545 2 100       8 unless( @keys ) {
1546 1         3 @keys = @{$self->vol_fields};
  1         4  
1547             }
1548 2         6 for my $key (@keys) {
1549 2         7 delete $self->[VOLATILE]{$key};
1550             }
1551             }
1552              
1553             sub vol {
1554 12     12   35 my( $self, $key, $val ) = @_;
1555 12 100       29 if( defined( $val ) ) {
1556 6         14 $self->[VOLATILE]{$key} = $val;
1557             }
1558 12         42 return $self->[VOLATILE]{$key};
1559             }
1560              
1561             sub vol_fields {
1562 4     4   8 return [keys %{shift->[VOLATILE]}];
  4         33  
1563             }
1564              
1565             sub lock {
1566 1     1   7 shift->store->lock(@_);
1567             }
1568             sub unlock {
1569 1     1   192 shift->store->unlock;
1570             }
1571              
1572             sub store {
1573 3     3   15 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   10197 my( $s, $arg ) = @_;
1582 97         169 my $func = our $AUTOLOAD;
1583 97 100       992 if( $func =~/:add_to_(.*)/ ) {
    100          
    100          
    100          
    100          
    100          
1584 2         6 my( $fld ) = $1;
1585 2     2   1684 no strict 'refs';
  2         4  
  2         209  
1586             *$AUTOLOAD = sub {
1587 12     12   40 my( $self, @vals ) = @_;
1588 12         36 my $get = "get_$fld";
1589 12         49 my $arry = $self->$get([]); # init array if need be
1590 12         63 push( @$arry, @vals );
1591 2         24 };
1592 2     2   13 use strict 'refs';
  2         4  
  2         192  
1593 2         9 goto &$AUTOLOAD;
1594             } #add_to
1595             elsif( $func =~/:add_once_to_(.*)/ ) {
1596 1         4 my( $fld ) = $1;
1597 2     2   21 no strict 'refs';
  2         10  
  2         270  
1598             *$AUTOLOAD = sub {
1599 3     3   12 my( $self, @vals ) = @_;
1600 3         9 my $get = "get_$fld";
1601 3         10 my $arry = $self->$get([]); # init array if need be
1602 3         11 for my $val ( @vals ) {
1603 9 100       23 unless( grep { $val eq $_ } @$arry ) {
  32         76  
1604 2         6 push @$arry, $val;
1605             }
1606             }
1607 1         10 };
1608 2     2   13 use strict 'refs';
  2         3  
  2         157  
1609 1         7 goto &$AUTOLOAD;
1610             } #add_once_to
1611             elsif( $func =~ /:remove_from_(.*)/ ) { #removes the first instance of the target thing from the list
1612 1         5 my $fld = $1;
1613 2     2   19 no strict 'refs';
  2         13  
  2         299  
1614             *$AUTOLOAD = sub {
1615 1     1   4 my( $self, @vals ) = @_;
1616 1         4 my $get = "get_$fld";
1617 1         5 my $arry = $self->$get([]); # init array if need be
1618 1         4 my( @ret );
1619             V:
1620 1         3 for my $val (@vals ) {
1621 3         9 for my $i (0..$#$arry) {
1622 11 100       30 if( $arry->[$i] eq $val ) {
1623 3         9 push @ret, splice @$arry, $i, 1;
1624 3         9 next V;
1625             }
1626             }
1627             }
1628 1         5 return @ret;
1629 1         11 };
1630 2     2   13 use strict 'refs';
  2         4  
  2         191  
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   12 no strict 'refs';
  2         4  
  2         275  
1636             *$AUTOLOAD = sub {
1637 1     1   4 my( $self, @vals ) = @_;
1638 1         5 my $get = "get_$fld";
1639 1         3 my $arry = $self->$get([]); # init array if need be
1640 1         4 my @ret;
1641 1         3 for my $val (@vals) {
1642 3         9 for( my $i=0; $i<=@$arry; $i++ ) {
1643 23 100       49 if( $arry->[$i] eq $val ) {
1644 7         19 push @ret, splice @$arry, $i, 1;
1645 7         21 $i--;
1646             }
1647             }
1648             }
1649 1         5 return @ret;
1650 1         10 };
1651 2     2   13 use strict 'refs';
  2         4  
  2         128  
1652 1         5 goto &$AUTOLOAD;
1653             }
1654             elsif ( $func =~ /:set_(.*)/ ) {
1655 53         157 my $fld = $1;
1656 2     2   11 no strict 'refs';
  2         3  
  2         140  
1657             *$AUTOLOAD = sub {
1658 382     382   21782 my( $self, $val ) = @_;
1659 382         1004 $self->set( $fld, $val );
1660 53         587 };
1661 2     2   12 use strict 'refs';
  2         3  
  2         119  
1662 53         238 goto &$AUTOLOAD;
1663             }
1664             elsif( $func =~ /:get_(.*)/ ) {
1665 38         161 my $fld = $1;
1666 2     2   15 no strict 'refs';
  2         11  
  2         149  
1667             *$AUTOLOAD = sub {
1668 291     291   3155 my( $self, $init_val ) = @_;
1669 291         781 $self->get( $fld, $init_val );
1670 38         428 };
1671 2     2   12 use strict 'refs';
  2         4  
  2         781  
1672 38         187 goto &$AUTOLOAD;
1673             }
1674             else {
1675 1         19 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   513 my $self = shift;
1703 286 100 100     445 join( "`", map { if( defined($_) && $_=~ /[\\\`]/ ) { s/[\\]/\\\\/gs; s/`/\\`/gs; } defined($_) ? $_ : 'u' } %{$self->[DATA]} );
  2062 100       5906  
  25         75  
  25         79  
  2062         4641  
  286         1325  
1704             }
1705              
1706             sub _reconstitute {
1707 154     154   394 my( $cls, $store, $id, $data, $meta ) = @_;
1708 154         729 my $obj = [$id,{@$data},$store, $meta, {}];
1709 154 100       460 if( $cls ne 'Data::ObjectStore::Container' ) {
1710 3         6 my $clname = $cls;
1711 3         10 $clname =~ s/::/\//g;
1712              
1713 3         15 require "$clname.pm";
1714             }
1715              
1716 154         301 bless $obj, $cls;
1717              
1718 154         417 $obj->_load;
1719 154         247 $obj;
1720             }
1721              
1722             sub DESTROY {
1723 147     147   30757 my $self = shift;
1724 147         1625 delete $self->[DSTORE][Data::ObjectStore::WEAK]{$self->[ID]};
1725             }
1726              
1727             # END PACKAGE Data::ObjectStore::Container
1728              
1729             1;
1730              
1731             __END__