File Coverage

blib/lib/Yote.pm
Criterion Covered Total %
statement 565 799 70.7
branch 134 240 55.8
condition 28 68 41.1
subroutine 118 151 78.1
pod 1 1 100.0
total 846 1259 67.2


line stmt bran cond sub pod time code
1             package Yote;
2              
3 1     1   521 use strict;
  1         3  
  1         32  
4 1     1   7 use warnings;
  1         2  
  1         34  
5 1     1   6 no warnings 'uninitialized';
  1         5  
  1         41  
6              
7 1     1   6 use vars qw($VERSION);
  1         2  
  1         147  
8              
9             $VERSION = '2.01';
10              
11             =head1 NAME
12              
13             Yote - Persistant Perl container objects in a directed graph of lazilly loaded nodes.
14              
15             =head1 DESCRIPTION
16              
17             This is for anyone who wants to store arbitrary structured state data and doesn't have
18             the time or inclination to write a schema or configure some framework. This can be used
19             orthagonally to any other storage system.
20              
21             Yote only loads data as it needs too. It does not load all stored containers at once.
22             Data is stored in a data directory and is stored using the Data::RecordStore module. A Yote
23             container is a key/value store where the values can be strings, numbers, arrays, hashes
24             or other Yote containers.
25              
26             The entry point for all Yote data stores is the root node. All objects in the store are
27             unreachable if they cannot trace a reference path back to this node. If they cannot, running
28             compress_store will remove them.
29              
30             There are lots of potential uses for Yote, and a few come to mind :
31              
32             * configuration data
33             * data modeling
34             * user preference data
35             * user account data
36             * game data
37             * shopping carts
38             * product information
39              
40             =head1 SYNOPSIS
41              
42             use Yote;
43              
44             my $store = Yote::open_store( '/path/to/data-directory' );
45              
46             my $root_node = $store->fetch_root;
47              
48             $root_node->add_to_myList( $store->newobj( {
49             someval => 123.53,
50             somehash => { A => 1 },
51             someobj => $store->newobj( { foo => "Bar" },
52             'Optional-Yote-Subclass-Package' );
53             } );
54              
55             # the root node now has a list 'myList' attached to it with the single
56             # value of a yote object that yote object has two fields,
57             # one of which is an other yote object.
58              
59             $root_node->add_to_myList( 42 );
60              
61             #
62             # New Yote container objects are created with $store->newobj. Note that
63             # they must find a reference path to the root to be protected from
64             # being deleted from the record store upon compression.
65             #
66             my $newObj = $store->newobj;
67              
68             $root_node->set_field( "Value" );
69              
70             my $val = $root_node->get_value( "default" );
71             # $val eq 'default'
72              
73             $val = $root_node->get_value( "Somethign Else" );
74             # $val eq 'default' (old value not overridden by a new default value)
75              
76              
77             my $otherval = $root_node->get( 'ot3rv@l', 'other default' );
78             # $otherval eq 'other default'
79              
80             $root_node->set( 'ot3rv@l', 'newy valuye' );
81             $otherval2 = $root_node->get( 'ot3rv@l', 'yet other default' );
82             # $otherval2 eq 'newy valuye'
83              
84             $root_node->set_value( "Something Else" );
85              
86             my $val = $root_node->get_value( "default" );
87             # $val eq 'Something Else'
88              
89             my $myList = $root_node->get_myList;
90              
91             for my $example (@$myList) {
92             print ">$example\n";
93             }
94              
95             #
96             # Each object gets a unique ID which can be used to fetch that
97             # object directly from the store.
98             #
99             my $someid = $root_node->get_someobj->{ID};
100              
101             my $someref = $store->fetch( $someid );
102              
103             #
104             # Even hashes and array have unique yote IDS. These can be
105             # determined by calling the _get_id method of the store.
106             #
107             my $hash = $root_node->set_ahash( { zoo => "Zar" } );
108             my $hash_id = $store->_get_id( $hash );
109             my $other_ref_to_hash = $store->fetch( $hash_id );
110              
111             #
112             # Anything that cannot trace a reference path to the root
113             # is eligable for being removed upon compression.
114             #
115              
116             =head1 PUBLIC METHODS
117              
118             =cut
119              
120              
121             =head2 open_store( '/path/to/directory' )
122              
123             Starts up a persistance engine and returns it.
124              
125             =cut
126              
127             sub open_store {
128 2     2 1 995 my $path = pop;
129 2         10 my $store = Yote::ObjStore->_new( { store => $path } );
130 2         5 $store->_init;
131 2         4 $store;
132             }
133              
134             # ---------------------------------------------------------------------------------------------------------------------
135              
136             package Yote::ObjStore;
137              
138 1     1   11 use strict;
  1         3  
  1         24  
139 1     1   6 use warnings;
  1         3  
  1         33  
140 1     1   6 no warnings 'numeric';
  1         3  
  1         42  
141 1     1   7 no warnings 'uninitialized';
  1         2  
  1         36  
142 1     1   7 no warnings 'recursion';
  1         3  
  1         34  
143              
144 1     1   362 use File::Copy;
  1         4990  
  1         78  
145 1     1   8 use File::Path qw(make_path remove_tree);
  1         3  
  1         76  
146 1     1   9 use Scalar::Util qw(weaken);
  1         2  
  1         105  
147              
148 1     1   372 use Module::Loaded;
  1         772  
  1         3981  
149              
150             =head1 NAME
151              
152             Yote::ObjStore - manages Yote::Obj objects in a graph.
153              
154             =head1 DESCRIPTION
155              
156             The Yote::ObjStore does the following things :
157              
158             * fetches the root object
159             * creates new objects
160             * fetches existing objects by id
161             * saves all new or changed objects
162             * finds objects that cannot connect to the root node and removes them
163              
164             =cut
165              
166             # ------------------------------------------------------------------------------------------
167             # * PUBLIC CLASS METHODS *
168             # ------------------------------------------------------------------------------------------
169              
170             =head2 fetch_root
171              
172             Returns the root node of the graph. All things that can be
173             trace a reference path back to the root node are considered active
174             and are not removed when the object store is compressed.
175              
176             =cut
177             sub fetch_root {
178 4     4   527 my $self = shift;
179 4 50       9 die "fetch_root must be called on Yote store object" unless ref( $self );
180 4         10 my $root = $self->fetch( $self->_first_id );
181 4 100       7 unless( $root ) {
182 1         2 $root = $self->_newroot;
183 1         4 $root->{ID} = $self->_first_id;
184 1         2 $self->_stow( $root );
185             }
186 4         11 $root;
187             } #fetch_root
188              
189             =head2 newobj( { ... data .... }, optionalClass )
190              
191             Creates a container object initialized with the
192             incoming hash ref data. The class of the object must be either
193             Yote::Obj or a subclass of it. Yote::Obj is the default.
194              
195             Once created, the object will be saved in the data store when
196             $store->stow_all has been called. If the object is not attached
197             to the root or an object that can be reached by the root, it will be
198             remove when Yote::ObjStore::Compress is called.
199              
200             =cut
201             sub newobj {
202 3     3   16 my( $self, $data, $class ) = @_;
203 3   50     13 $class ||= 'Yote::Obj';
204 3         9 $class->_new( $self, $data );
205             }
206              
207             sub _newroot {
208 1     1   2 my $self = shift;
209 1         3 Yote::Obj->_new( $self, {}, $self->_first_id );
210             }
211              
212             =head2 copy_from_remote_store( $obj )
213              
214             This takes an object that belongs to a seperate store and makes
215             a deep copy of it.
216              
217             =cut
218             sub copy_from_remote_store {
219 0     0   0 my( $self, $obj ) = @_;
220 0         0 my $r = ref( $obj );
221 0 0       0 return $obj unless $r;
222 0 0       0 if( $r eq 'ARRAY' ) {
    0          
223 0         0 return [ map { $self->copy_from_remote_store($_) } @$obj ];
  0         0  
224             } elsif( $r eq 'HASH' ) {
225 0         0 return { map { $_ => $self->copy_from_remote_store($obj->{$_}) } keys %$obj };
  0         0  
226             } else {
227 0         0 my $data = { map { $_ => $self->copy_from_remote_store($obj->{DATA}{$_}) } keys %{$obj->{DATA}} };
  0         0  
  0         0  
228 0         0 return $self->newobj( $data, $r );
229             }
230             }
231              
232             =head2 cache_all()
233              
234             This turns on caching for the store. Any objects loaded will
235             remain cached until clear_cache is called. Normally, they
236             would be DESTROYed once their last reference was removed unless
237             they are in a state that needs stowing.
238              
239             =cut
240             sub cache_all {
241 0     0   0 my $self = shift;
242 0         0 $self->{CACHE_ALL} = 1;
243             }
244              
245             =head2 uncache( obj )
246              
247             This removes the object from the cache if it was in the cache
248              
249             =cut
250             sub uncache {
251 0     0   0 my( $self, $obj ) = @_;
252 0 0       0 if( ref( $obj ) ) {
253 0         0 delete $self->{CACHE}{$self->_get_id( $obj )};
254             }
255             }
256              
257              
258              
259             =head2 pause_cache()
260              
261             When called, no new objects will be added to the cache until
262             cache_all is called.
263              
264             =cut
265             sub pause_cache {
266 0     0   0 my $self = shift;
267 0         0 $self->{CACHE_ALL} = 0;
268             }
269              
270             =head2 clear_cache()
271              
272             When called, this dumps the object cache. Objects that
273             references or have changes that need to be stowed will
274             not be cleared.
275              
276             =cut
277             sub clear_cache {
278 0     0   0 my $self = shift;
279 0         0 $self->{_CACHE} = {};
280             }
281              
282              
283              
284             =head2 fetch( $id )
285              
286             Returns the object with the given id.
287              
288             =cut
289             sub fetch {
290 48     48   70 my( $self, $id ) = @_;
291 48 50       75 return undef unless $id;
292             #
293             # Return the object if we have a reference to its dirty state.
294             #
295 48         67 my $ref = $self->{_DIRTY}{$id};
296 48 100       70 if( defined $ref ) {
297 5         21 return $ref;
298             } else {
299 43         76 $ref = $self->{_WEAK_REFS}{$id};
300 43 100       65 if( $ref ) {
301 1         3 return $ref;
302             }
303 42         53 undef $ref;
304             }
305 42         74 my $obj_arry = $self->{_DATASTORE}->_fetch( $id );
306              
307 42 100       66 if( $obj_arry ) {
308 37         65 my( $id, $class, $data ) = @$obj_arry;
309 37 100       66 if( $class eq 'ARRAY' ) {
    100          
310 11         15 my( @arry );
311 11         48 tie @arry, 'Yote::Array', $self, $id, @$data;
312 11         29 $self->_store_weak( $id, \@arry );
313 11         47 return \@arry;
314             }
315             elsif( $class eq 'HASH' ) {
316 9         12 my( %hash );
317 9         22 tie %hash, 'Yote::Hash', $self, $id, map { $_ => $data->{$_} } keys %$data;
  9         50  
318 9         24 $self->_store_weak( $id, \%hash );
319 9         49 return \%hash;
320             }
321             else {
322 17         19 my $obj;
323 17         20 eval {
324 17         21 my $path = $class;
325 17 50       31 unless( $INC{ $class } ) {
326 1     1   49 eval("use $class");
  0     1   0  
  0     1   0  
  1     1   60  
  0     1   0  
  0     1   0  
  1     1   47  
  0     1   0  
  0     1   0  
  1     1   49  
  0     1   0  
  0     1   0  
  1     1   53  
  0     1   0  
  0     1   0  
  1     1   51  
  0     1   0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         48  
  0         0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         48  
  0         0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         48  
  0         0  
  0         0  
  1         48  
  0         0  
  0         0  
  1         65  
  0         0  
  0         0  
  17         836  
327             }
328 17   33     9115 $obj = $self->{_WEAK_REFS}{$id} || $class->_instantiate( $id, $self );
329             };
330 17 50       36 die $@ if $@;
331 17         28 $obj->{DATA} = $data;
332 17         23 $obj->{ID} = $id;
333 17         38 $self->_store_weak( $id, $obj );
334 17         33 $obj->_load();
335 17         87 return $obj;
336             }
337             }
338 5         17 return undef;
339             } #fetch
340              
341             =head2 run_purger
342              
343             =cut
344             sub run_purger {
345 5     5   21 my( $self, $make_tally, $copy_only ) = @_;
346 5         10 $self->stow_all();
347              
348 5         10 my $keep_db = $self->{_DATASTORE}->_generate_keep_db();
349              
350             # analyze to see what percentage would be kept
351 5         12 my $total = $keep_db->entry_count;
352 5         131 my $keep = 0;
353 5         11 for my $tid (1..$total) {
354 31         55 my( $has_keep ) = $keep_db->get_record( $tid )->[0];
355 31 100       857 $keep++ if $has_keep;
356             }
357              
358             #
359             # If there are more things to keep than not, do a db purge,
360             # otherwise, rebuild the db.
361             #
362 5   66     19 my $do_purge = $keep > ( $total/2 ) && ! $copy_only;
363 5         5 my $purged;
364 5 100       9 if( $do_purge ) {
365 3         7 $purged = $self->{_DATASTORE}->_purge_objects( $keep_db, $make_tally );
366             } else {
367 2         6 $purged = $self->_copy_active_ids( $keep_db, $make_tally );
368             }
369              
370 5         15 $self->{_DATASTORE}->_update_recycle_ids( $keep_db );
371              
372             # commenting out for a test
373 5         63 $keep_db->unlink_store;
374              
375 5         199 $purged;
376             } #run_purger
377              
378             sub _copy_active_ids {
379 2     2   4 my( $self, $copy_db ) = @_;
380 2         4 $self->stow_all();
381              
382 2         4 my $original_dir = $self->{args}{store};
383 2         4 my $backdir = $original_dir . '_COMPRESS_BACK_RECENT';
384 2         4 my $newdir = $original_dir . '_NEW_RECYC';
385              
386 2 100       51 if( -e $backdir ) {
387 1         3 my $oldback = $original_dir . '_COMPRESS_BACK_OLD';
388 1 50       21 if( -d $oldback ) {
389 0         0 warn "Removing old compression backup directory";
390 0         0 remove_tree( $oldback );
391             }
392 1 50       3 move( $backdir, $oldback ) or die $!;
393             }
394              
395 2 50       65 if( -x $newdir ) {
396 0         0 die "Unable to run compress store, temp directory '$newdir' already exists.";
397             }
398 2         12 my $newstore = Yote::ObjStore->_new( { store => $newdir } );
399              
400 2         5 my( @purges );
401 2         5 for my $keep_id ( 1..$copy_db->entry_count ) {
402              
403 12         350 my( $has_keep ) = $copy_db->get_record( $keep_id )->[0];
404 12 100       349 if( $has_keep ) {
    100          
405 4         8 my $obj = $self->fetch( $keep_id );
406              
407 4         13 $newstore->{_DATASTORE}{DATA_STORE}->ensure_entry_count( $keep_id - 1 );
408 4         197 $newstore->_dirty( $obj, $keep_id );
409 4         9 $newstore->_stow( $obj, $keep_id );
410             } elsif( $self->{_DATASTORE}{DATA_STORE}->has_id( $keep_id ) ) {
411 3         176 push @purges, $keep_id;
412             }
413             } #each entry id
414              
415 2 50       9 move( $original_dir, $backdir ) or die $!;
416 2 50       106 move( $newdir, $original_dir ) or die $!;
417              
418 2         73 \@purges;
419              
420             } #_copy_active_ids
421              
422             =head2 has_id
423              
424             Returns true if there is a valid reference linked to the id
425              
426             =cut
427             sub has_id {
428 0     0   0 my( $self, $id ) = @_;
429 0         0 return $self->{_DATASTORE}{DATA_STORE}->has_id( $id );
430             }
431              
432             =head2 stow_all
433              
434             Saves all newly created or dirty objects.
435              
436             =cut
437             sub stow_all {
438 13     13   24 my $self = shift;
439 13         14 my @odata;
440 13         15 for my $obj (values %{$self->{_DIRTY}} ) {
  13         35  
441 10         12 my $cls;
442 10         14 my $ref = ref( $obj );
443 10 100 66     37 if( $ref eq 'ARRAY' || $ref eq 'Yote::Array' ) {
    100 66        
444 4         5 $cls = 'ARRAY';
445             } elsif( $ref eq 'HASH' || $ref eq 'Yote::Hash' ) {
446 1         2 $cls = 'HASH';
447             } else {
448 5         6 $cls = $ref;
449             }
450 10         19 my( $text_rep ) = $self->_raw_data( $obj );
451 10         19 push( @odata, [ $self->_get_id( $obj ), $cls, $text_rep ] );
452             }
453 13         36 $self->{_DATASTORE}->_stow_all( \@odata );
454 13         34 $self->{_DIRTY} = {};
455             } #stow_all
456              
457              
458             =head2 stow( $obj )
459              
460             Saves that object to the database
461              
462             =cut
463             sub stow {
464 0     0   0 my( $self, $obj ) = @_;
465 0         0 my $cls;
466 0         0 my $ref = ref( $obj );
467 0 0 0     0 if( $ref eq 'ARRAY' || $ref eq 'Yote::Array' ) {
    0 0        
468 0         0 $cls = 'ARRAY';
469             } elsif( $ref eq 'HASH' || $ref eq 'Yote::Hash' ) {
470 0         0 $cls = 'HASH';
471             } else {
472 0         0 $cls = $ref;
473             }
474 0         0 my $id = $self->_get_id( $obj );
475 0         0 my( $text_rep ) = $self->_raw_data( $obj );
476 0         0 $self->{_DATASTORE}->_stow( $id, $cls, $text_rep );
477 0         0 delete $self->{_DIRTY}{$id};
478             } #stow
479              
480              
481              
482             # -------------------------------
483             # * PRIVATE METHODS *
484             # -------------------------------
485             sub _new { #Yote::ObjStore
486 4     4   8 my( $pkg, $args ) = @_;
487 4         14 my $self = bless {
488             _DIRTY => {},
489             _WEAK_REFS => {},
490             args => $args,
491             }, $pkg;
492 4         16 $self->{_DATASTORE} = Yote::YoteDB->open( $self, $args );
493 4         7 $self;
494             } #_new
495              
496             sub _init {
497 2     2   4 my $self = shift;
498 2         5 for my $pkg ( qw( Yote::Obj Yote::Array Yote::Hash ) ) {
499 1 50   1   130 $INC{ $pkg } or eval("use $pkg");
  0     1   0  
  0     1   0  
  1     1   48  
  0     1   0  
  0     1   0  
  1         46  
  0         0  
  0         0  
  1         49  
  0         0  
  0         0  
  1         47  
  0         0  
  0         0  
  1         46  
  0         0  
  0         0  
  6         1897  
500             }
501 2         753 $self->fetch_root;
502 2         6 $self->stow_all;
503 2         3 $self;
504             }
505              
506              
507             sub dirty_count {
508 0     0   0 my $self = shift;
509 0         0 return scalar( keys %{$self->{_DIRTY}} );
  0         0  
510             }
511              
512             #
513             # Markes given object as dirty.
514             #
515             sub _dirty {
516             # ( $self, $ref, $id
517 25     25   52 $_[0]->{_DIRTY}->{$_[2]} = $_[1];
518             } #_dirty
519              
520             #
521             # Returns the first ID that is associated with the root Root object
522             #
523             sub _first_id {
524 6     6   21 shift->{_DATASTORE}->_first_id();
525             } #_first_id
526              
527             sub _get_id {
528             # for debugging I think?
529 40     40   93 shift->__get_id( shift );
530             }
531              
532             sub __get_id {
533 40     40   52 my( $self, $ref ) = @_;
534              
535 40         48 my $class = ref( $ref );
536 40 50       59 die "__get_id requires reference. got '$ref'" unless $class;
537              
538 40 50       87 if( $class eq 'Yote::Array') {
    100          
    50          
    100          
539 0         0 return $ref->[0];
540             }
541             elsif( $class eq 'ARRAY' ) {
542 14         18 my $tied = tied @$ref;
543 14 100       22 if( $tied ) {
544 12   33     19 $tied->[0] ||= $self->{_DATASTORE}->_get_id( "ARRAY" );
545 12         30 return $tied->[0];
546             }
547 2         3 my( @data ) = @$ref;
548 2         6 my $id = $self->{_DATASTORE}->_get_id( $class );
549 2         281 tie @$ref, 'Yote::Array', $self, $id;
550 2         6 push( @$ref, @data );
551 2         6 $self->_dirty( $ref, $id );
552 2         4 $self->_store_weak( $id, $ref );
553 2         4 return $id;
554             }
555             elsif( $class eq 'Yote::Hash' ) {
556 0         0 my $wref = $ref;
557 0         0 return $ref->[0];
558             }
559             elsif( $class eq 'HASH' ) {
560 4         9 my $tied = tied %$ref;
561 4 100       7 if( $tied ) {
562 3   33     7 $tied->[0] ||= $self->{_DATASTORE}->_get_id( "HASH" );
563 3         8 return $tied->[0];
564             }
565 1         3 my $id = $self->{_DATASTORE}->_get_id( $class );
566              
567 1         127 my( %vals ) = %$ref;
568              
569 1         6 tie %$ref, 'Yote::Hash', $self, $id;
570 1         3 for my $key (keys %vals) {
571 1         6 $ref->{$key} = $vals{$key};
572             }
573 1         3 $self->_dirty( $ref, $id );
574 1         3 $self->_store_weak( $id, $ref );
575 1         6 return $id;
576             }
577             else {
578 22 100       72 return $ref->{ID} if $ref->{ID};
579 3 50       6 if( $class eq 'Yote::Root' ) {
580 0         0 $ref->{ID} = $self->{_DATASTORE}->_first_id( $class );
581             } else {
582 3   33     10 $ref->{ID} ||= $self->{_DATASTORE}->_get_id( $class );
583             }
584              
585 3         424 return $ref->{ID};
586             }
587              
588             } #_get_id
589              
590             sub _stow {
591 5     5   10 my( $self, $obj, $id ) = @_;
592              
593 5         8 my $class = ref( $obj );
594 5 50       11 return unless $class;
595 5   66     12 $id //= $self->_get_id( $obj );
596 5 50       8 die unless $id;
597              
598 5         20 my( $text_rep, $data ) = $self->_raw_data( $obj );
599              
600 5 100       16 if( $class eq 'ARRAY' ) {
    50          
    50          
    50          
601 2         6 $self->{_DATASTORE}->_stow( $id,'ARRAY', $text_rep );
602 2         677 $self->_clean( $id );
603             }
604             elsif( $class eq 'HASH' ) {
605 0         0 $self->{_DATASTORE}->_stow( $id,'HASH',$text_rep );
606 0         0 $self->_clean( $id );
607             }
608             elsif( $class eq 'Yote::Array' ) {
609 0 0       0 if( $self->_is_dirty( $id ) ) {
610 0         0 $self->{_DATASTORE}->_stow( $id,'ARRAY',$text_rep );
611 0         0 $self->_clean( $id );
612             }
613 0         0 for my $child (@$data) {
614 0 0 0     0 if( $child =~ /^[0-9]/ && $self->{_DIRTY}->{$child} ) {
615 0         0 $self->_stow( $child, $self->{_DIRTY}->{$child} );
616             }
617             }
618             }
619             elsif( $class eq 'Yote::Hash' ) {
620 0 0       0 if( $self->_is_dirty( $id ) ) {
621 0         0 $self->{_DATASTORE}->_stow( $id, 'HASH', $text_rep );
622             }
623 0         0 $self->_clean( $id );
624 0         0 for my $child (values %$data) {
625 0 0 0     0 if( $child =~ /^[0-9]/ && $self->{_DIRTY}->{$child} ) {
626 0         0 $self->_stow( $child, $self->{_DIRTY}->{$child} );
627             }
628             }
629             }
630             else {
631 3 50       6 if( $self->_is_dirty( $id ) ) {
632 3         9 $self->{_DATASTORE}->_stow( $id, $class, $text_rep );
633 3         1193 $self->_clean( $id );
634             }
635 3         8 for my $val (values %$data) {
636 2 50 33     16 if( $val =~ /^[0-9]/ && $self->{_DIRTY}->{$val} ) {
637 0         0 $self->_stow( $val, $self->{_DIRTY}->{$val} );
638             }
639             }
640             }
641 5         14 $id;
642             } #_stow
643              
644             sub _xform_in {
645 16     16   22 my( $self, $val ) = @_;
646 16 100       28 if( ref( $val ) ) {
647 6         9 return $self->_get_id( $val );
648             }
649 10 50       38 return defined $val ? "v$val" : undef;
650             }
651              
652             sub _xform_out {
653 44     44   63 my( $self, $val ) = @_;
654 44 50       74 return undef unless defined( $val );
655 44 100       91 if( index($val,'v') == 0 ) {
656 8         39 return substr( $val, 1 );
657             }
658 36         50 return $self->fetch( $val );
659             }
660              
661             sub _clean {
662 5     5   11 my( $self, $id ) = @_;
663 5         11 delete $self->{_DIRTY}{$id};
664             } #_clean
665              
666             sub _is_dirty {
667 3     3   6 my( $self, $obj ) = @_;
668 3 50       8 my $id = ref($obj) ? _get_id($obj) : $obj;
669 3         4 my $ans = $self->{_DIRTY}{$id};
670 3         9 $ans;
671             } #_is_dirty
672              
673             #
674             # Returns data structure representing object. References are integers. Values start with 'v'.
675             #
676             sub _raw_data {
677 15     15   34 my( $self, $obj ) = @_;
678 15         20 my $class = ref( $obj );
679 15 50       19 return unless $class;
680 15         26 my $id = $self->_get_id( $obj );
681 15 50       30 die unless $id;
682 15         19 my( $r, $is_array );
683 15 100       37 if( $class eq 'ARRAY' ) {
    100          
    50          
    50          
684 6         6 my $tied = tied @$obj;
685 6 50       8 if( $tied ) {
686 6         9 $r = $tied->[1];
687 6         8 $is_array = 1;
688             } else {
689 0         0 die;
690             }
691             }
692             elsif( $class eq 'HASH' ) {
693 1         2 my $tied = tied %$obj;
694 1 50       3 if( $tied ) {
695 1         1 $r = $tied->[1];
696             } else {
697 0         0 die;
698             }
699             }
700             elsif( $class eq 'Yote::Array' ) {
701 0         0 $r = $obj->[1];
702 0         0 $is_array = 1;
703             }
704             elsif( $class eq 'Yote::Hash' ) {
705 0         0 $r = $obj->[1];
706             }
707             else {
708 8         8 $r = $obj->{DATA};
709             }
710              
711 15 100       25 if( $is_array ) {
712 6 100       13 return join( "`", map { if( defined($_) ) { s/[\\]/\\\\/gs; s/`/\\`/gs; } $_ } @$r ), $r;
  100         112  
  16         18  
  16         17  
  100         115  
713             }
714 9 50       20 return join( "`", map { if( defined($_) ) { s/[\\]/\\\\/gs; s/`/\\`/gs; } $_ } %$r ), $r;
  26         41  
  26         33  
  26         29  
  26         53  
715              
716             } #_raw_data
717              
718              
719             sub _store_weak {
720 40     40   70 my( $self, $id, $ref ) = @_;
721 40 50       61 die unless $ref;
722 40         69 $self->{_WEAK_REFS}{$id} = $ref;
723              
724 40         109 weaken( $self->{_WEAK_REFS}{$id} );
725             } #_store_weak
726              
727             # ---------------------------------------------------------------------------------------------------------------------
728              
729             =head1 NAME
730              
731             Yote::Obj - Generic container object for graph.
732              
733             =head1 DESCRIPTION
734              
735             A Yote::Obj is a container class that as a specific idiom for getters
736             and setters. This idiom is set up to avoid confusion and collision
737             with any method names.
738              
739             # sets the 'foo' field to the given value.
740             $obj->set_foo( { value => $store->newobj } );
741              
742             # returns the value for bar, and if none, sets it to 'default'
743             my $bar = $obj->get_bar( "default" );
744              
745             $obj->add_to_somelist( "Freddish" );
746             my $list = $obj->get_somelist;
747             $list->[ 0 ] == "Freddish";
748              
749              
750             $obj->remove_from_somelist( "Freddish" );
751              
752             =cut
753             package Yote::Obj;
754              
755 1     1   16 use strict;
  1         4  
  1         35  
756 1     1   8 use warnings;
  1         2  
  1         58  
757 1     1   9 no warnings 'uninitialized';
  1         2  
  1         271  
758              
759             #
760             # The string version of the yote object is simply its id. This allows
761             # objet ids to easily be stored as hash keys.
762             #
763             use overload
764 65     65   141 '""' => sub { shift->{ID} }, # for hash keys
765 0 0   0   0 eq => sub { ref($_[1]) && $_[1]->{ID} == $_[0]->{ID} },
766 0 0   0   0 ne => sub { ! ref($_[1]) || $_[1]->{ID} != $_[0]->{ID} },
767 0 0   0   0 '==' => sub { ref($_[1]) && $_[1]->{ID} == $_[0]->{ID} },
768 0 0   0   0 '!=' => sub { ! ref($_[1]) || $_[1]->{ID} != $_[0]->{ID} },
769 1     1   9 fallback => 1;
  1         2  
  1         13  
770              
771             =head2 absorb( hashref )
772              
773             pulls the hash data into this object.
774              
775             =cut
776             sub absorb {
777 4     4   6 my( $self, $data ) = @_;
778 4         6 my $obj_store = $self->{STORE};
779 4         18 for my $key ( sort keys %$data ) {
780 8         13 my $item = $data->{ $key };
781 8         14 $self->{DATA}{$key} = $obj_store->_xform_in( $item );
782             }
783 4         9 $obj_store->_dirty( $self, $self->{ID} );
784              
785             } #absorb
786              
787             sub id {
788 0     0   0 shift->{ID};
789             }
790              
791             =head2 set( $field, $value )
792              
793             Assigns the given value to the field in this object and returns the
794             assigned value.
795              
796             =cut
797             sub set {
798 0     0   0 my( $self, $fld, $val ) = @_;
799              
800 0         0 my $inval = $self->{STORE}->_xform_in( $val );
801 0 0       0 if( $self->{DATA}{$fld} ne $inval ) {
802 0         0 $self->{STORE}->_dirty( $self, $self->{ID} );
803             }
804              
805 0 0       0 unless( defined $inval ) {
806 0         0 delete $self->{DATA}{$fld};
807 0         0 return;
808             }
809 0         0 $self->{DATA}{$fld} = $inval;
810 0         0 return $self->{STORE}->_xform_out( $self->{DATA}{$fld} );
811             } #set
812              
813              
814             =head2 get( $field, $default-value )
815              
816             Returns the value assigned to the field, assinging the default
817             value to it if the value is currently not defined.
818              
819             =cut
820             sub get {
821 0     0   0 my( $self, $fld, $default ) = @_;
822 0         0 my $cur = $self->{DATA}{$fld};
823 0 0 0     0 if( ! defined( $cur ) && defined( $default ) ) {
824 0 0       0 if( ref( $default ) ) {
825             # this must be done to make sure the reference is saved for cases where the reference has not yet made it to the store of things to save
826 0         0 $self->{STORE}->_dirty( $default->{STORE}->_get_id( $default ) );
827             }
828 0         0 $self->{STORE}->_dirty( $self, $self->{ID} );
829 0         0 $self->{DATA}{$fld} = $self->{STORE}->_xform_in( $default );
830             }
831 0         0 return $self->{STORE}->_xform_out( $self->{DATA}{$fld} );
832             } #get
833              
834              
835             # -----------------------
836             #
837             # Public Methods
838             # -----------------------
839             #
840             # Defines get_foo, set_foo, add_to_list, remove_from_list
841             #
842             sub AUTOLOAD {
843 10     10   24 my( $s, $arg ) = @_;
844 10         13 my $func = our $AUTOLOAD;
845              
846 10 100       73 if( $func =~/:add_to_(.*)/ ) {
    50          
    50          
    50          
    100          
    50          
847 1         3 my( $fld ) = $1;
848 1     1   731 no strict 'refs';
  1         2  
  1         118  
849             *$AUTOLOAD = sub {
850 1     1   3 my( $self, @vals ) = @_;
851 1         2 my $get = "get_$fld";
852 1         5 my $arry = $self->$get([]); # init array if need be
853 1         2 push( @$arry, @vals );
854 1         7 };
855 1     1   8 use strict 'refs';
  1         3  
  1         99  
856 1         4 goto &$AUTOLOAD;
857             } #add_to
858             elsif( $func =~/:add_once_to_(.*)/ ) {
859 0         0 my( $fld ) = $1;
860 1     1   8 no strict 'refs';
  1         3  
  1         156  
861             *$AUTOLOAD = sub {
862 0     0   0 my( $self, @vals ) = @_;
863 0         0 my $get = "get_$fld";
864 0         0 my $arry = $self->$get([]); # init array if need be
865 0         0 for my $val ( @vals ) {
866 0 0       0 unless( grep { $val eq $_ } @$arry ) {
  0         0  
867 0         0 push @$arry, $val;
868             }
869             }
870 0         0 };
871 1     1   8 use strict 'refs';
  1         2  
  1         96  
872 0         0 goto &$AUTOLOAD;
873             } #add_once_to
874             elsif( $func =~ /:remove_from_(.*)/ ) { #removes the first instance of the target thing from the list
875 0         0 my $fld = $1;
876 1     1   7 no strict 'refs';
  1         3  
  1         174  
877             *$AUTOLOAD = sub {
878 0     0   0 my( $self, @vals ) = @_;
879 0         0 my $get = "get_$fld";
880 0         0 my $arry = $self->$get([]); # init array if need be
881 0         0 for my $val (@vals ) {
882 0         0 for my $i (0..$#$arry) {
883 0 0       0 if( $arry->[$i] eq $val ) {
884 0         0 splice @$arry, $i, 1;
885 0         0 last;
886             }
887             }
888             }
889 0         0 };
890 1     1   9 use strict 'refs';
  1         3  
  1         106  
891 0         0 goto &$AUTOLOAD;
892             }
893             elsif( $func =~ /:remove_all_from_(.*)/ ) { #removes the first instance of the target thing from the list
894 0         0 my $fld = $1;
895 1     1   8 no strict 'refs';
  1         3  
  1         224  
896             *$AUTOLOAD = sub {
897 0     0   0 my( $self, @vals ) = @_;
898 0         0 my $get = "get_$fld";
899 0         0 my $arry = $self->$get([]); # init array if need be
900 0         0 for my $val (@vals) {
901 0         0 my $count = grep { $_ eq $val } @$arry;
  0         0  
902 0         0 while( $count ) {
903 0         0 for my $i (0..$#$arry) {
904 0 0       0 if( $arry->[$i] eq $val ) {
905 0         0 --$count;
906 0         0 splice @$arry, $i, 1;
907 0 0       0 last unless $count;
908             }
909             }
910             }
911             }
912 0         0 };
913 1     1   8 use strict 'refs';
  1         3  
  1         92  
914 0         0 goto &$AUTOLOAD;
915             }
916             elsif ( $func =~ /:set_(.*)/ ) {
917 1         3 my $fld = $1;
918 1     1   7 no strict 'refs';
  1         3  
  1         236  
919             *$AUTOLOAD = sub {
920 1     1   55 my( $self, $val ) = @_;
921 1         25 my $inval = $self->{STORE}->_xform_in( $val );
922 1 50       7 $self->{STORE}->_dirty( $self, $self->{ID} ) if $self->{DATA}{$fld} ne $inval;
923 1 50       2 unless( defined $inval ) {
924 0         0 delete $self->{DATA}{$fld};
925 0         0 return;
926             }
927 1         2 $self->{DATA}{$fld} = $inval;
928 1         3 return $self->{STORE}->_xform_out( $self->{DATA}{$fld} );
929 1         33 };
930 1         4 goto &$AUTOLOAD;
931             }
932             elsif( $func =~ /:get_(.*)/ ) {
933 8         17 my $fld = $1;
934 1     1   8 no strict 'refs';
  1         3  
  1         219  
935             *$AUTOLOAD = sub {
936 24     24   1382 my( $self, $init_val ) = @_;
937 24 100 66     209 if( ! defined( $self->{DATA}{$fld} ) && defined($init_val) ) {
938 1 50       8 if( ref( $init_val ) ) {
939             # this must be done to make sure the reference is saved for cases where the reference has not yet made it to the store of things to save
940 1         3 $self->{STORE}->_dirty( $init_val, $self->{STORE}->_get_id( $init_val ) );
941             }
942 1         3 $self->{STORE}->_dirty( $self, $self->{ID} );
943 1         2 $self->{DATA}{$fld} = $self->{STORE}->_xform_in( $init_val );
944             }
945 24         47 return $self->{STORE}->_xform_out( $self->{DATA}{$fld} );
946 8         41 };
947 1     1   9 use strict 'refs';
  1         8  
  1         453  
948 8         26 goto &$AUTOLOAD;
949             }
950             else {
951 0         0 die "Unknown Yote::Obj function '$func'";
952             }
953              
954             } #AUTOLOAD
955              
956             # -----------------------
957             #
958             # Overridable Methods
959             # -----------------------
960              
961             =head2 _init
962              
963             This is called the first time an object is created. It is not
964             called when the object is loaded from storage. This can be used
965             to set up defaults. This is meant to be overridden.
966              
967             =cut
968       4     sub _init {}
969              
970             =head2 _init
971              
972             This is called each time the object is loaded from the data store.
973             This is meant to be overridden.
974              
975             =cut
976       17     sub _load {}
977              
978              
979              
980             # -----------------------
981             #
982             # Private Methods
983             #
984             # -----------------------
985              
986              
987             sub _new { #new Yote::Obj
988 4     4   9 my( $pkg, $obj_store, $data, $_id ) = @_;
989              
990 4   33     14 my $class = ref($pkg) || $pkg;
991 4         10 my $obj = bless {
992             DATA => {},
993             STORE => $obj_store,
994             }, $class;
995 4   66     32 $obj->{ID} = $_id || $obj_store->_get_id( $obj );
996 4         12 $obj_store->_dirty( $obj, $obj->{ID} );
997 4         8 $obj->_init(); #called the first time the object is created.
998              
999 4 50       10 if( ref( $data ) eq 'HASH' ) {
    0          
1000 4         8 $obj->absorb( $data );
1001             } elsif( $data ) {
1002 0         0 die "Yote::Obj::new must be called with hash or undef. Was called with '". ref( $data ) . "'";
1003             }
1004 4         17 return $obj;
1005             } #_new
1006              
1007             sub _store {
1008 0     0   0 return shift->{STORE};
1009             }
1010              
1011             #
1012             # Called by the object provider; returns a Yote::Obj the object
1013             # provider will stuff data into. Takes the class and id as arguments.
1014             #
1015             sub _instantiate {
1016 17     17   84 bless { ID => $_[1], DATA => {}, STORE => $_[2] }, $_[0];
1017             } #_instantiate
1018              
1019             sub DESTROY {
1020 21     21   33 my $self = shift;
1021 21         93 delete $self->{STORE}{_WEAK_REFS}{$self->{ID}};
1022             }
1023              
1024              
1025             # ---------------------------------------------------------------------------------------------------------------------
1026              
1027             package Yote::Array;
1028              
1029             ############################################################################################################
1030             # This module is used transparently by Yote to link arrays into its graph structure. This is not meant to #
1031             # be called explicitly or modified. #
1032             ############################################################################################################
1033              
1034 1     1   10 use strict;
  1         2  
  1         29  
1035 1     1   12 use warnings;
  1         3  
  1         42  
1036              
1037 1     1   7 no warnings 'uninitialized';
  1         2  
  1         42  
1038 1     1   388 use Tie::Array;
  1         1509  
  1         1024  
1039              
1040             sub TIEARRAY {
1041 13     13   29 my( $class, $obj_store, $id, @list ) = @_;
1042 13         17 my $storage = [];
1043              
1044             # once the array is tied, an additional data field will be added
1045             # so obj will be [ $id, $storage, $obj_store ]
1046 13         25 my $obj = bless [$id,$storage,$obj_store], $class;
1047 13         24 for my $item (@list) {
1048 18         31 push( @$storage, $item );
1049             }
1050 13         33 return $obj;
1051             }
1052              
1053             sub FETCH {
1054 10     10   18 my( $self, $idx ) = @_;
1055 10         18 return $self->[2]->_xform_out ( $self->[1][$idx] );
1056             }
1057              
1058             sub FETCHSIZE {
1059 1     1   3 my $self = shift;
1060 1         1 return scalar(@{$self->[1]});
  1         4  
1061             }
1062              
1063             sub STORE {
1064 2     2   12 my( $self, $idx, $val ) = @_;
1065 2         9 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1066 2         4 $self->[1][$idx] = $self->[2]->_xform_in( $val );
1067             }
1068       0     sub STORESIZE {} #stub for array
1069              
1070             sub EXISTS {
1071 0     0   0 my( $self, $idx ) = @_;
1072 0         0 return defined( $self->[1][$idx] );
1073             }
1074             sub DELETE {
1075 0     0   0 my( $self, $idx ) = @_;
1076 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1077 0         0 delete $self->[1][$idx];
1078             }
1079              
1080             sub CLEAR {
1081 0     0   0 my $self = shift;
1082 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1083 0         0 @{$self->[1]} = ();
  0         0  
1084             }
1085             sub PUSH {
1086 4     4   14 my( $self, @vals ) = @_;
1087 4         17 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1088 4         6 push( @{$self->[1]}, map { $self->[2]->_xform_in($_) } @vals );
  4         10  
  3         11  
1089             }
1090             sub POP {
1091 0     0   0 my $self = shift;
1092 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1093 0         0 return $self->[2]->_xform_out( pop @{$self->[1]} );
  0         0  
1094             }
1095             sub SHIFT {
1096 0     0   0 my( $self ) = @_;
1097 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1098 0         0 my $val = splice @{$self->[1]}, 0, 1;
  0         0  
1099 0         0 return $self->[2]->_xform_out( $val );
1100             }
1101             sub UNSHIFT {
1102 0     0   0 my( $self, @vals ) = @_;
1103 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1104 0         0 unshift @{$self->[1]}, map {$self->[2]->_xform_in($_)} @vals;
  0         0  
  0         0  
1105             }
1106             sub SPLICE {
1107 0     0   0 my( $self, $offset, $length, @vals ) = @_;
1108 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1109 0         0 return map { $self->[2]->_xform_out($_) } splice @{$self->[1]}, $offset, $length, map {$self->[2]->_xform_in($_)} @vals;
  0         0  
  0         0  
  0         0  
1110             }
1111       0     sub EXTEND {}
1112              
1113             sub DESTROY {
1114 13     13   23 my $self = shift;
1115 13         54 delete $self->[2]->{_WEAK_REFS}{$self->[0]};
1116             }
1117              
1118             # ---------------------------------------------------------------------------------------
1119              
1120             package Yote::Hash;
1121              
1122             ######################################################################################
1123             # This module is used transparently by Yote to link hashes into its graph structure. #
1124             # This is not meant to be called explicitly or modified. #
1125             ######################################################################################
1126              
1127 1     1   9 use strict;
  1         2  
  1         34  
1128 1     1   6 use warnings;
  1         3  
  1         37  
1129              
1130 1     1   8 no warnings 'uninitialized';
  1         18  
  1         43  
1131              
1132 1     1   394 use Tie::Hash;
  1         1109  
  1         681  
1133              
1134             sub TIEHASH {
1135 10     10   22 my( $class, $obj_store, $id, %hash ) = @_;
1136 10         15 my $storage = {};
1137             # after $obj_store is a list reference of
1138             # id, data, store
1139 10         19 my $obj = bless [ $id, $storage,$obj_store ], $class;
1140 10         32 for my $key (keys %hash) {
1141 9         20 $storage->{$key} = $hash{$key};
1142             }
1143 10         25 return $obj;
1144             }
1145              
1146             sub STORE {
1147 1     1   3 my( $self, $key, $val ) = @_;
1148 1         7 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1149 1         8 $self->[1]{$key} = $self->[2]->_xform_in( $val );
1150             }
1151              
1152             sub FIRSTKEY {
1153 0     0   0 my $self = shift;
1154 0         0 my $a = scalar keys %{$self->[1]};
  0         0  
1155 0         0 my( $k, $val ) = each %{$self->[1]};
  0         0  
1156 0 0       0 return wantarray ? ( $k => $val ) : $k;
1157             }
1158             sub NEXTKEY {
1159 0     0   0 my $self = shift;
1160 0         0 my( $k, $val ) = each %{$self->[1]};
  0         0  
1161 0 0       0 return wantarray ? ( $k => $val ) : $k;
1162             }
1163              
1164             sub FETCH {
1165 9     9   20 my( $self, $key ) = @_;
1166 9         16 return $self->[2]->_xform_out( $self->[1]{$key} );
1167             }
1168              
1169             sub EXISTS {
1170 0     0   0 my( $self, $key ) = @_;
1171 0         0 return defined( $self->[1]{$key} );
1172             }
1173             sub DELETE {
1174 0     0   0 my( $self, $key ) = @_;
1175 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1176 0         0 return delete $self->[1]{$key};
1177             }
1178             sub CLEAR {
1179 0     0   0 my $self = shift;
1180 0         0 $self->[2]->_dirty( $self->[2]{_WEAK_REFS}{$self->[0]}, $self->[0] );
1181 0         0 %{$self->[1]} = ();
  0         0  
1182             }
1183              
1184             sub DESTROY {
1185 10     10   16 my $self = shift;
1186 10         39 delete $self->[2]->{_WEAK_REFS}{$self->[0]};
1187             }
1188              
1189             # ---------------------------------------------------------------------------------------
1190              
1191             package Yote::YoteDB;
1192              
1193 1     1   9 use strict;
  1         7  
  1         29  
1194 1     1   7 use warnings;
  1         3  
  1         40  
1195              
1196 1     1   7 no warnings 'uninitialized';
  1         2  
  1         38  
1197              
1198 1     1   411 use Data::RecordStore;
  1         13825  
  1         43  
1199              
1200 1     1   10 use File::Path qw(make_path);
  1         3  
  1         62  
1201              
1202             use constant {
1203 1         2203 DATA => 2,
1204 1     1   8 };
  1         3  
1205              
1206             #
1207             # This the main index and stores in which table and position
1208             # in that table that this object lives.
1209             #
1210             sub open {
1211 4     4   7 my( $pkg, $obj_store, $args ) = @_;
1212 4   33     15 my $class = ref( $pkg ) || $pkg;
1213              
1214 4         5 my $DATA_STORE;
1215 4         6 eval {
1216 4         19 $DATA_STORE = Data::RecordStore->open( $args->{ store } );
1217            
1218             };
1219 4 50       1262 if( $@ ) {
1220 0 0       0 if( $@ =~ /old format/ ) {
1221 0         0 die "This yote store is of an older format. It can be converted using the yote_explorer";
1222             }
1223 0         0 die $@;
1224             }
1225 4         16 my $self = bless {
1226             args => $args,
1227             OBJ_STORE => $obj_store,
1228             DATA_STORE => $DATA_STORE,
1229             }, $class;
1230 4         21 $self->{DATA_STORE}->ensure_entry_count( 1 );
1231 4         233 $self;
1232             } #open
1233              
1234             #
1235             # Return a list reference containing [ id, class, data ] that
1236             # corresponds to the $id argument. This is used by Yote::ObjStore
1237             # to build the yote object.
1238             #
1239             sub _fetch {
1240 74     74   115 my( $self, $id ) = @_;
1241 74         160 my $data = $self->{DATA_STORE}->fetch( $id );
1242              
1243 74 100       5843 return undef unless $data;
1244              
1245 69         116 my $pos = index( $data, ' ' ); #there is a always a space after the class.
1246 69 50       108 $pos = ( length( $data ) ) if $pos == -1;
1247 69 50       96 die "Malformed record '$data'" if $pos == -1;
1248 69         113 my $class = substr $data, 0, $pos;
1249 69         103 my $val = substr $data, $pos + 1;
1250 69         141 my $ret = [$id,$class,$val];
1251              
1252             # so foo` or foo\\` but not foo\\\`
1253             # also this will never start with a `
1254 69         195 my $parts = [ split /\`/, $val, -1 ];
1255              
1256             # check to see if any of the parts were split on escapes
1257             # like mypart`foo`oo (should be translated to mypart\`foo\`oo
1258 69 100       127 if( 0 < grep { /\\$/ } @$parts ) {
  292         519  
1259 9         13 my $newparts = [];
1260              
1261 9         13 my $is_hanging = 0;
1262 9         11 my $working_part = '';
1263            
1264 9         14 for my $part (@$parts) {
1265              
1266             # if the part ends in a hanging escape
1267 126 100       332 if( $part =~ /(^|[^\\])((\\\\)+)?[\\]$/ ) {
    100          
1268 54 100       65 if( $is_hanging ) {
1269 18         27 $working_part .= "`$part";
1270             } else {
1271 36         65 $working_part = $part;
1272             }
1273 54         62 $is_hanging = 1;
1274             } elsif( $is_hanging ) {
1275 36         59 my $newpart = "$working_part`$part";
1276 36         77 $newpart =~ s/\\`/`/gs;
1277 36         64 $newpart =~ s/\\\\/\\/gs;
1278 36         49 push @$newparts, $newpart;
1279 36         43 $is_hanging = 0;
1280             } else {
1281             # normal part
1282 36         51 push @$newparts, $part;
1283             }
1284             }
1285 9 50       16 if( $is_hanging ) {
1286 0         0 die "Error in parsing parts\n";
1287             }
1288 9         20 $parts = $newparts;
1289             }
1290              
1291 69 100       121 if( $class eq 'ARRAY' ) {
1292 19         29 $ret->[DATA] = $parts;
1293             } else {
1294 50         131 $ret->[DATA] = { @$parts };
1295             }
1296              
1297 69         135 $ret;
1298             } #_fetch
1299              
1300             #
1301             # The first object in a yote data store can trace a reference to
1302             # all active objects.
1303             #
1304             sub _first_id {
1305 6     6   17 return 1;
1306             } #_first_id
1307              
1308             #
1309             # Create a new object id and return it.
1310             #
1311             sub _get_id {
1312 6     6   9 my $self = shift;
1313 6         17 $self->{DATA_STORE}->next_id;
1314             } #_get_id
1315              
1316              
1317             # used for debugging and testing
1318             sub _max_id {
1319 3     3   20 shift->{DATA_STORE}->entry_count;
1320             }
1321              
1322             sub _generate_keep_db {
1323 6     6   11 my $self = shift;
1324 6         34 my $mark_to_keep_store = Data::RecordStore::FixedStore->open( "I", $self->{args}{store} . '/PURGE_KEEP' );
1325              
1326 6         408 $mark_to_keep_store->empty();
1327 6         210 $mark_to_keep_store->ensure_entry_count( $self->{DATA_STORE}->entry_count );
1328              
1329 6         450 my $check_store = Data::RecordStore::FixedStore->open( "L", $self->{args}{store} . '/CHECK' );
1330 6         345 $check_store->empty();
1331              
1332 6         176 $mark_to_keep_store->put_record( 1, [ 1 ] );
1333              
1334 6         384 my( %seen );
1335 6         11 my( @checks ) = ( 1 );
1336              
1337 6         6 for my $referenced_id ( grep { defined($self->{OBJ_STORE}{_WEAK_REFS}{$_}) } keys %{ $self->{OBJ_STORE}{_WEAK_REFS} } ) {
  10         25  
  6         19  
1338 10         16 push @checks, $referenced_id;
1339             }
1340              
1341              
1342             #
1343             # While there are items to check, check them.
1344             #
1345 6   66     16 while( @checks || $check_store->entry_count > 0 ) {
1346 32   33     60 my $check_id = shift( @checks ) || $check_store->pop->[0];
1347 32         81 $mark_to_keep_store->put_record( $check_id, [ 1 ] );
1348              
1349 32         1739 my $item = $self->_fetch( $check_id );
1350 32         45 $seen{$check_id} = 1;
1351 32         33 my( @additions );
1352 32 100       59 if ( ref( $item->[DATA] ) eq 'ARRAY' ) {
1353 8 100       9 ( @additions ) = grep { /^[^v]/ && ! $seen{$_}++ } @{$item->[DATA]};
  20         48  
  8         15  
1354             } else {
1355 24 100       24 ( @additions ) = grep { /^[^v]/ && ! $seen{$_}++ } values %{$item->[DATA]};
  44         167  
  24         52  
1356             }
1357 32 50       57 if( @checks > 1_000_000 ) {
1358 0         0 for my $cid (@checks) {
1359 0         0 my( $has_keep ) = $mark_to_keep_store->get_record( $cid )->[0];
1360 0 0       0 unless( $has_keep ) {
1361 0         0 $check_store->push( [ $cid ] );
1362             }
1363             }
1364 0         0 splice @checks;
1365             }
1366 32 50       56 if( scalar( keys(%seen) ) > 1_000_000 ) {
1367 0         0 %seen = ();
1368             }
1369 32         98 push @checks, @additions;
1370             }
1371 6         191 $check_store->unlink_store;
1372              
1373 6         251 $mark_to_keep_store;
1374              
1375             } #_generate_keep_db
1376              
1377             #
1378             # Checks to see if the last entries of the stores can be popped off, making the purging quicker
1379             #
1380             sub _truncate_dbs {
1381 5     5   23 my( $self, $mark_to_keep_store, $keep_tally ) = @_;
1382             #loop through each database
1383 5         12 my $stores = $self->{DATA_STORE}->all_stores;
1384 5         421 my( @purged );
1385 5         8 for my $store (@$stores) {
1386 18         156 my $fn = $store->{FILENAME}; $fn =~ s!/[^/]+$!!;
  18         86  
1387 18         24 my $keep;
1388 18   100     46 while( ! $keep && $store->entry_count ) {
1389 17         578 my( $check_id ) = @{ $store->get_record($store->entry_count) };
  17         29  
1390 17         878 ( $keep ) = $mark_to_keep_store->get_record( $check_id )->[0];
1391 17 100       479 if( ! $keep ) {
1392 2 50       7 if( $self->{DATA_STORE}->delete( $check_id ) ) {
1393 2 50       301 if( $keep_tally ) {
1394 2         4 push @purged, $check_id;
1395             }
1396 2         7 $mark_to_keep_store->put_record( $check_id, [ 2 ] ); #mark as already removed by truncate
1397             }
1398             }
1399             }
1400             }
1401 5         18 \@purged;
1402             }
1403              
1404              
1405             sub _update_recycle_ids {
1406 5     5   8 my( $self, $mark_to_keep_store ) = @_;
1407              
1408 5 100       11 return unless $mark_to_keep_store->entry_count > 0;
1409              
1410 3         77 my $store = $self->{DATA_STORE};
1411              
1412              
1413             # find the higest still existing ID and cap the index to this
1414 3         3 my $highest_keep_id;
1415 3         7 for my $cand (reverse ( 1..$mark_to_keep_store->entry_count )) {
1416 4         73 my( $keep ) = $mark_to_keep_store->get_record( $cand )->[0];
1417 4 100       105 if( $keep ) {
1418 3         9 $store->set_entry_count( $cand );
1419 3         125 $highest_keep_id = $cand;
1420 3         4 last;
1421             }
1422             }
1423              
1424 3         11 $store->empty_recycler;
1425              
1426             # iterate each id in the entire object store and add those
1427             # not marked for keeping into the recycling
1428 3         94 for my $cand (reverse( 1.. $highest_keep_id) ) {
1429 18         134 my( $keep ) = $mark_to_keep_store->get_record( $cand )->[0];
1430 18 100       494 unless( $keep ) {
1431 1         3 $store->recycle( $cand );
1432             }
1433             }
1434             } #_update_recycle_ids
1435              
1436              
1437             sub _purge_objects {
1438 4     4   9 my( $self, $mark_to_keep_store, $keep_tally ) = @_;
1439              
1440 4         9 my $purged = $self->_truncate_dbs( $mark_to_keep_store );
1441              
1442 4         10 for my $cand ( 1..$mark_to_keep_store->entry_count) { #iterate each id in the entire object store
1443 26         160 my( $keep ) = $mark_to_keep_store->get_record( $cand )->[0];
1444              
1445 26 50 66     696 die "Tried to purge root entry" if $cand == 1 && ! $keep;
1446 26 100       47 if ( ! $keep ) {
1447 2 50       7 if( $self->{DATA_STORE}->delete( $cand ) ) {
1448 0         0 $mark_to_keep_store->put_record( $cand, [ 3 ] ); #mark as already removed by purge
1449 0 0       0 if( $keep_tally ) {
1450 0         0 push @$purged, $cand;
1451             }
1452             }
1453             }
1454             }
1455              
1456 4         39 $purged;
1457              
1458             } #_purge_objects
1459              
1460              
1461             #
1462             # Saves the object data for object $id to the data store.
1463             #
1464             sub _stow { #Yote::YoteDB::_stow
1465 15     15   30 my( $self, $id, $class, $data ) = @_;
1466 15         30 my $save_data = "$class $data";
1467 15         36 $self->{DATA_STORE}->stow( $save_data, $id );
1468             } #_stow
1469              
1470             #
1471             # Takes a list of object data references and stows them all in the datastore.
1472             # returns how many are stowed.
1473             #
1474             sub _stow_all {
1475 13     13   18 my( $self, $objs ) = @_;
1476 13         13 my $count = 0;
1477 13         23 for my $o ( @$objs ) {
1478 10         1735 $count += $self->_stow( @$o );
1479             }
1480 13         1371 return $count;
1481             } #_stow_all
1482              
1483             1;
1484              
1485             __END__