File Coverage

blib/lib/KiokuDB/Collapser.pm
Criterion Covered Total %
statement 160 174 91.9
branch 49 64 76.5
condition 21 29 72.4
subroutine 32 35 91.4
pod 5 20 25.0
total 267 322 82.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package KiokuDB::Collapser;
4 20     20   13563 use Moose;
  20         38  
  20         218  
5              
6 20     20   108060 no warnings 'recursion';
  20         33  
  20         839  
7              
8 20     20   9768 use Scope::Guard;
  20         7582  
  20         856  
9 20     20   122 use Carp qw(croak);
  20         30  
  20         873  
10 20     20   90 use Scalar::Util qw(isweak refaddr reftype);
  20         28  
  20         904  
11              
12 20     20   6691 use KiokuDB::Entry;
  20         55  
  20         889  
13 20     20   9996 use KiokuDB::Entry::Skip;
  20         55  
  20         779  
14 20     20   8857 use KiokuDB::Reference;
  20         61  
  20         769  
15 20     20   9511 use KiokuDB::Collapser::Buffer;
  20         63  
  20         803  
16 20     20   9797 use KiokuDB::Error::UnknownObjects;
  20         58  
  20         833  
17              
18 20     20   11299 use Data::Visitor 0.18;
  20         408608  
  20         799  
19              
20 20     20   146 use Set::Object qw(set);
  20         31  
  20         1166  
21              
22 20     20   103 use namespace::clean -except => 'meta';
  20         28  
  20         130  
23              
24             extends qw(Data::Visitor);
25              
26             with qw(KiokuDB::Role::UUIDs);
27              
28             has '+tied_as_objects' => ( default => 1 );
29              
30             has live_objects => (
31             isa => "KiokuDB::LiveObjects",
32             is => "ro",
33             required => 1,
34             );
35              
36             has backend => (
37             does => "KiokuDB::Backend",
38             is => "ro",
39             required => 1,
40             );
41              
42             has typemap_resolver => (
43             isa => "KiokuDB::TypeMap::Resolver",
44             is => "ro",
45             handles => [qw(collapse_method id_method)],
46             required => 1,
47             );
48              
49             has compact => (
50             isa => "Bool",
51             is => "rw",
52             default => 1,
53             );
54              
55             has '+weaken' => (
56             default => 0,
57             );
58              
59             has '_buffer' => (
60             isa => "KiokuDB::Collapser::Buffer",
61             is => "ro",
62             clearer => "_clear_buffer",
63             writer => "_set_buffer",
64             );
65              
66             sub collapse {
67 1568     1568 0 8679 my ( $self, %args ) = @_;
68              
69 1568         3842 my $objects = delete $args{objects};
70              
71 1568         3275 my $r;
72              
73 1568 100       4803 if ( $args{shallow} ) {
74 529         3340 $args{only} = set(@$objects);
75             }
76              
77 1568         58090 my $buf = KiokuDB::Collapser::Buffer->new(
78             live_objects => $self->live_objects,
79             options => \%args,
80             );
81              
82 1568     1568   13659 my $g = Scope::Guard->new(sub { $self->_clear_buffer });
  1568         70558  
83 1568         71178 $self->_set_buffer($buf);
84              
85             # recurse through the object, accumilating entries
86 1568         13153 $self->visit(@$objects);
87              
88 1546         15725 my @ids = $buf->merged_objects_to_ids(@$objects);
89              
90 1546         47254 $buf->first_class->insert(@ids);
91              
92             # compact UUID space by merging simple non shared structures into a single
93             # deep entry
94 1546 100       39814 $buf->compact_entries if $self->compact;
95              
96 1546         9836 return ( $buf, @ids );
97             }
98              
99             sub may_compact {
100 68     68 0 130 my ( $self, $ref_or_id ) = @_;
101              
102 68 50       1803 my $id = ref($ref_or_id) ? $ref_or_id->id : $ref_or_id;
103              
104 68         1588 not $self->_buffer->first_class->includes($id);
105             }
106              
107             sub make_entry {
108 9591     9591 0 31674 my ( $self, %args ) = @_;
109              
110 9591   50     39237 my $meta = delete $args{meta} || {};
111              
112 9591         15224 my $object = $args{object};
113              
114 9591 100       17300 if ( my $id = $args{id} ) {
115 9487         257071 my $l = $self->live_objects;
116              
117 9487         30259 my $prev = $l->object_to_entry($object);
118              
119 9487 100 100     32592 if ( !$prev and $l->id_in_storage($id) ) {
120             # FIXME Backend->store( insert => [ ... ], update => [ ... ] )
121             # this happens when keep_entries is false
122 137         562 $prev = KiokuDB::Entry->new( root => $l->id_in_root_set($id) ); # force the operation to be an update
123             }
124              
125 9487 100       250531 my $entry = KiokuDB::Entry->new(
126             ( $prev ? ( prev => $prev ) : () ),
127             %args,
128             );
129              
130 9487         268244 $self->_buffer->insert_entry( $id => $entry, $object, %$meta );
131              
132 9487         31645 return $entry;
133             } else {
134             # intrinsic
135 104         2953 my $entry = KiokuDB::Entry->new(%args);
136              
137 104         3075 $self->_buffer->insert_intrinsic( $object => $entry, %$meta );
138              
139 104         883 return $entry;
140             }
141             }
142              
143             sub make_skip_entry {
144 167     167 0 585 my ( $self, %args ) = @_;
145              
146 167         302 my $object = $args{object};
147              
148 167   66     4318 my $prev = $args{prev} || $self->live_objects->object_to_entry($object);
149              
150 167         376 my $id = $args{id};
151              
152 167 50       420 unless ( $id ) {
153 0 0       0 croak "skip entries must have an ID" unless $prev;
154 0         0 $id = $prev->id;
155             }
156              
157 167         494 return undef;
158             }
159              
160             sub make_ref {
161 12994     12994 0 19156 my ( $self, $id, $value ) = @_;
162              
163 12994         26646 my $weak = isweak($_[2]);
164              
165 12994 100       62542 $self->_buffer->first_class->insert($id) if $weak;
166              
167 12994 100       345519 return KiokuDB::Reference->new(
168             id => $id,
169             $weak ? ( is_weak => 1 ) : ()
170             );
171             }
172              
173             sub visit_seen {
174 2462     2462 1 13850 my ( $self, $seen, $prev ) = @_;
175              
176 2462         58879 my $b = $self->_buffer;
177              
178 2462 50 100     6265 if ( my $entry = $b->intrinsic_entry($seen) ) {
    100          
179 0         0 return $entry->clone;
180             } elsif ( my $id = $self->_buffer->object_to_id($seen) || $self->live_objects->object_to_id($seen) ) {
181 2461 100       7084 $self->_buffer->first_class->insert($id) unless blessed($seen);
182              
183             # return a uuid ref
184 2461         5359 return $self->make_ref( $id => $_[1] );
185             } else {
186 1         7 KiokuDB::Error::UnknownObjects->throw( objects => [ $seen ] );
187             }
188             }
189              
190             sub visit_ref_fallback {
191 8658     8658 0 10552 my ( $self, $ref ) = @_;
192              
193 8658         207164 my $o = $self->_buffer->options;
194              
195 8658 50 66     36337 if ( my $entry = $o->{only_in_storage} && $self->live_objects->object_to_entry($ref) ) {
196 0         0 return $self->make_ref( $entry->id => $_[1] );
197             }
198              
199 8658 100 33     20030 if ( my $id = $self->_ref_id($ref) ) {
    50          
200 6286 50 66     154929 if ( !$self->compact and my $only = $o->{only} ) {
201 0 0       0 unless ( $only->contains($ref) ) {
202 0         0 return $self->make_ref( $id => $_[1] );
203             }
204             }
205              
206 6286         15111 my $collapsed = $self->visit_ref_data($_[1]);
207              
208 6286 100 100     20272 if ( ref($collapsed) eq 'KiokuDB::Reference' and $collapsed->id eq $id ) {
209 33         887 return $collapsed; # tied
210             } else {
211 6253         6459 push @{ $self->_buffer->simple_entries }, $id;
  6253         169370  
212              
213 6253         15722 $self->make_entry(
214             id => $id,
215             object => $ref,
216             data => $collapsed,
217             );
218              
219 6253         14818 return $self->make_ref( $id => $_[1] );
220             }
221             } elsif ( $self->compact and not isweak($_[1]) ) {
222             # for now we assume this data just won't be shared, instead of
223             # compacting it later.
224 2372         8889 return $self->SUPER::visit_ref($_[1]);
225             } else {
226 0         0 KiokuDB::Error::UnknownObjects->throw( objects => [ $ref ] );
227             }
228             }
229              
230             sub visit_ref_data {
231 6342     6342 0 7817 my ( $self, $ref ) = @_;
232 6342         22846 $self->SUPER::visit_ref($_[1]);
233             }
234              
235             sub _ref_id {
236 8695     8695   11859 my ( $self, $ref ) = @_;
237              
238 8695         211112 my $l = $self->live_objects;
239              
240 8695 100       27059 if ( my $id = $l->object_to_id($ref) ) {
241 223         624 return $id;
242             } else {
243 8472         197539 my $b = $self->_buffer;
244              
245 8472 100       214114 if ( $b->options->{only_known} ) {
246 2372 50       55496 if ( $self->compact ) {
247             # if we're compacting this is not an error, we just compact in place
248             # and we generate an error if we encounter this data again in visit_seen
249 2372         56614 return;
250             } else {
251 0         0 KiokuDB::Error::UnknownObjects->throw( objects => [ $ref ] );
252             }
253             } else {
254 6100         15326 my $id = $self->generate_uuid;
255 6100         14761 $b->insert( $id => $ref );
256 6100         16431 return $id;
257             }
258             }
259             }
260              
261             # avoid retying, we want to get back Reference or Entry objects
262 37     37 0 310 sub visit_tied_hash { shift->visit_tied(@_) }
263 0     0 0 0 sub visit_tied_array { shift->visit_tied(@_) }
264 0     0 0 0 sub visit_tied_scalar { shift->visit_tied(@_) }
265 0     0 0 0 sub visit_tied_glob { shift->visit_tied(@_) }
266              
267             sub visit_tied {
268 37     37 1 84 my ( $self, $tied, $ref ) = @_;
269              
270 37         108 my $tie = $self->visit($tied);
271              
272 37 50       331 if ( my $id = $self->_ref_id($ref) ) {
273 37 50 33     953 if ( !$self->compact and my $only = $self->_buffer->options->{only} ) {
274 0 0       0 unless ( $only->contains($ref) ) {
275 0         0 return $self->make_ref( $id => $_[1] );
276             }
277             }
278              
279 37         76 push @{ $self->_buffer->simple_entries }, $id;
  37         964  
280              
281 37         269 $self->make_entry(
282             id => $id,
283             object => $ref,
284             data => $tie,
285             tied => substr(reftype($ref), 0, 1),
286             );
287              
288 37         145 return $self->make_ref( $id => $_[2] );
289             } else {
290 0         0 return $self->make_entry(
291             object => $ref,
292             data => $tie,
293             tied => substr(reftype($ref), 0, 1),
294             );
295             }
296             }
297              
298 4371     4371 1 60527 sub visit_object { shift->visit_with_typemap(@_) }
299 8738     8738 1 96775 sub visit_ref { shift->visit_with_typemap(@_) }
300              
301             sub visit_with_typemap {
302 13109     13109 0 14075 my ( $self, $ref ) = @_;
303              
304 13109         40176 my $collapse = $self->collapse_method(ref $ref);
305              
306 13093         41599 shift->$collapse(@_);
307             }
308              
309             sub collapse_first_class {
310 4253     4253 0 6498 my ( $self, $collapse, $object, @entry_args ) = @_;
311              
312             # Data::Visitor stuff for circular refs
313 4253         14303 $self->_register_mapping( $object, $object );
314              
315 4253         137758 my ( $l, $b ) = ( $self->live_objects, $self->_buffer );
316              
317 4253         14881 my $id = $l->object_to_id($object);
318 4253         12453 my $in_storage = $l->id_in_storage($id);
319              
320 4253         116955 my $o = $b->options;
321              
322 4253 100 100     13443 if ( $o->{only_in_storage} && $in_storage ) {
323 3         10 return $self->make_ref( $id => $_[2] );
324             }
325              
326 4250 100       46739 if ( my $only = $o->{only} ) {
327 1679 100       9165 unless ( $only->contains($object) ) {
328 878 100       4350 if ( $in_storage ) {
329 876         2454 return $self->make_ref( $id => $_[2] );
330             } else {
331 2         18 KiokuDB::Error::UnknownObjects->throw( objects => [ $object ] );
332             }
333             }
334             }
335              
336 3372 100       10989 unless ( $id ) {
337 2184 100       6050 if ( $o->{only_known} ) {
338 3         18 KiokuDB::Error::UnknownObjects->throw( objects => [ $object ] );
339             } else {
340 2181         8966 my $id_method = $self->id_method(ref $object);
341              
342 2181         8667 $id = $self->$id_method($object);
343              
344 2181 100       8371 if ( defined( my $conflict = $l->id_to_object($id) ) ) {
345 66         310 return $self->id_conflict( $id, $_[2], $conflict );
346             } else {
347 2115         6457 $b->insert( $id => $object );
348             }
349             }
350             }
351              
352 3303         12945 my @args = (
353             object => $object,
354             id => $id,
355             class => ref($object),
356             @entry_args,
357             );
358              
359 3303         13166 $self->$collapse(@args);
360              
361             # we pass $_[1], an alias, so that isweak works
362 3298         10695 return $self->make_ref( $id => $_[2] );
363             }
364              
365             sub id_conflict {
366 66     66 0 127 my ( $self, $id, $object, $other ) = @_;
367              
368 66         242 $self->make_skip_entry( id => $id, object => $object );
369              
370 66         1686 $self->_buffer->insert( $id => $object );
371              
372 66         304 return $self->make_ref( $id => $_[2] );
373             }
374              
375              
376             sub collapse_intrinsic {
377 104     104 0 182 my ( $self, $collapse, $object, @entry_args ) = @_;
378              
379 104         201 my $class = ref $object;
380              
381 104         301 my @args = (
382             object => $object,
383             class => $class,
384             @entry_args,
385             );
386              
387 104         291 return $self->$collapse(@args);
388             }
389              
390             # we don't reblass in collapse_naive
391             sub retain_magic {
392 8676     8676 1 226690 my ( $self, $proto, $clone ) = @_;
393 8676         27192 return $clone;
394             }
395              
396             __PACKAGE__->meta->make_immutable;
397              
398             __PACKAGE__
399              
400             __END__
401              
402             =pod
403              
404             =head1 NAME
405              
406             KiokuDB::Collapser - Collapse object hierarchies to entry
407             data
408              
409             =head1 SYNOPSIS
410              
411             # mostly internal
412              
413             =head1 DESCRIPTION
414              
415             The collapser simplifies real objects into L<KiokuDB::Entry> objects to pass to
416             the backend.
417              
418             Non object data is collapsed by walking it with L<Data::Visitor> (which
419             L<KiokuDB::Collapser> inherits from).
420              
421             Object collapsing is detailed in L</"COLLAPSING STRATEGIES">.
422              
423             The object's data will be copied into the L<KiokuDB::Entry> with references to
424             other data structures translated into L<KiokuDB::Reference> objects.
425              
426             Reference addresses are mapped to unique identifiers, which are generated as
427             necessary.
428              
429             =head2 Compacting
430              
431             If C<compact> is disabled then every reference is symbolic, and every data
432             structure has an entry.
433              
434             If compacting is enabled (the default) the minimum number of entry objects
435             required for consistency is created.
436              
437             Every blessed, shared or tied data structure requires an entry object, as does
438             every target of a weak reference. "Simple" structures, such as plain
439             hashes/arrays will be left inline as data intrinsic to the object it was found in.
440              
441             Compacting is usually desirable, but sometimes isn't (for instance with an RDF
442             like store).
443              
444             =head1 COLLAPSING STRATEGIES
445              
446             Collapsing strategies are chosen based on the type of the object being
447             collapsed, using L<KiokuDB::TypeMap::Resolver>.
448              
449             The resolver consults the typemap (L<KiokuDB::TypeMap>), and caches the results
450             as keyed by C<ref $object>.
451              
452             The typemap contains normal entries (keyed by C<ref $object eq $class>) or isa
453             entries (filtered by C<< $object->isa($class) >>). The rationale is that a typemap
454             entry for a superclass might not support all subclasses as well.
455              
456             Any strategy may be collapsed as a first class object, or intrinsicly, inside
457             its parent (in which case it isn't assigned a UUID). This is determined based
458             on the C<intrinsic> attribute to the entry. For instance, if L<Path::Class>
459             related objects should be collapsed as if they are values, the following
460             typemap entry can be used:
461              
462             isa_entries => {
463             'Path::Class::Entity' => KiokuDB::TypeMap::Entry::Callback->new(
464             intrinsic => 1,
465             collapse => "stringify",
466             expand => "new",
467             ),
468             },
469              
470             If no typemap entry exists, L<KiokuDB::TypeMap::Entry::MOP> is used by default.
471             See L<KiokuDB::TypeMap::Resolver> for more details.
472              
473             These are the strategies in brief:
474              
475             =head2 MOP
476              
477             When the object has a L<Class::MOP> registered metaclass (any L<Moose> object,
478             but not only), the MOP is used to walk the object's attributes and construct
479             the simplified version without breaking encapsulation.
480              
481             See L<KiokuDB::TypeMap::Entry::MOP>.
482              
483             =head2 Naive
484              
485             This collapsing strategy simply walks the object's data using L<Data::Visitor>.
486              
487             This allows collapsing of L<Class::Accessor> based objects, for instance, but
488             should be used with care.
489              
490             See L<KiokuDB::TypeMap::Entry::Naive>
491              
492             =head2 Callback
493              
494             This collapsing strategy allows callbacks to be used to map the types.
495              
496             It is more limited than the other strategies, but very convenient for simple
497             values.
498              
499             See L<KiokuDB::TypeMap::Entry::Callback> for more details.
500              
501             =head2 Passthrough
502              
503             This delegates collapsing to the backend serialization. This is convenient for
504             when a backend uses e.g. L<Storable> to serialize entries, and the object in
505             question already has a C<STORABLE_freeze> and C<STORABLE_thaw> method.
506              
507             =cut
508