File Coverage

blib/lib/KiokuDB/TypeMap/Entry/MOP.pm
Criterion Covered Total %
statement 196 211 92.8
branch 66 88 75.0
condition 12 15 80.0
subroutine 31 31 100.0
pod 0 14 0.0
total 305 359 84.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package KiokuDB::TypeMap::Entry::MOP;
4 22     22   126926 use Moose;
  22         241328  
  22         122  
5              
6 22     22   107046 use Scalar::Util qw(refaddr);
  22         79  
  22         1129  
7 22     22   100 use Carp qw(croak);
  22         34  
  22         872  
8              
9 22     22   7916 use KiokuDB::Thunk;
  22         67  
  22         874  
10              
11 22     22   163 no warnings 'recursion';
  22         39  
  22         1886  
12              
13             sub does_role {
14             my ($meta, $role) = @_;
15             return unless my $does = $meta->can('does_role');
16             return $meta->$does($role);
17             }
18              
19 22     22   114 use namespace::clean -except => 'meta';
  22         33  
  22         157  
20              
21             with (
22             'KiokuDB::TypeMap::Entry::Std',
23             'KiokuDB::TypeMap::Entry::Std::Expand' => {
24             alias => { compile_expand => 'compile_expand_body' },
25             }
26             );
27              
28             has check_class_versions => (
29             isa => "Bool",
30             is => "ro",
31             default => 1,
32             );
33              
34             has version_table => (
35             isa => "HashRef[Str|CodeRef|HashRef]",
36             is => "ro",
37             default => sub { return {} },
38             );
39              
40             has class_version_table => (
41             isa => "HashRef[HashRef[Str|CodeRef|HashRef]]",
42             is => "ro",
43             default => sub { return {} },
44             );
45              
46             has write_upgrades => (
47             isa => "Bool",
48             is => "ro",
49             default => 0,
50             );
51              
52             # FIXME collapser and expaner should both be methods in Class::MOP::Class,
53             # apart from the visit call
54              
55             sub compile_collapse_body {
56 278     278 0 506 my ( $self, $class, @args ) = @_;
57              
58 278         714 my $meta = Class::MOP::get_metaclass_by_name($class);
59              
60 1421   66     21210 my @attrs = grep {
61 278         1892 !does_role($_->meta, 'KiokuDB::Meta::Attribute::DoNotSerialize')
62             and
63             !does_role($_->meta, 'MooseX::Storage::Meta::Attribute::Trait::DoNotSerialize')
64             } $meta->get_all_attributes;
65              
66 278         938 my %lazy;
67 278         535 foreach my $attr ( @attrs ) {
68 1419         10885 $lazy{$attr->name} = does_role($attr->meta, "KiokuDB::Meta::Attribute::Lazy");
69             }
70              
71 278         1632 my $meta_instance = $meta->get_meta_instance;
72              
73 278         2455 my %attrs;
74              
75 278 100       880 if ( $meta->is_anon_class ) {
76              
77             # FIXME ancestral roles all the way up to first non anon ancestor,
78             # at least check for additional attributes or other metadata which we
79             # should probably error on anything we can't store
80              
81             # theoretically this can do multiple inheritence too
82              
83 35         561 my $ancestor = $meta;
84 35         70 my @anon;
85              
86 35         80 search: {
87 35         38 push @anon, $ancestor;
88              
89 35         116 my @super = $ancestor->superclasses;
90              
91 35 50       1358 if ( @super == 1 ) {
    0          
92 35         114 $ancestor = Class::MOP::get_metaclass_by_name($super[0]);
93 35 50       186 if ( $ancestor->is_anon_class ) {
94 0         0 redo search;
95             }
96             } elsif ( @super > 1 ) {
97 0         0 croak "Cannot resolve anonymous class with multiple inheritence: " . $meta->name;
98             } else {
99 0         0 croak "no super, ancestor: $ancestor (" . $ancestor->name . ")";
100             }
101             }
102              
103 35         468 my $class_meta = $ancestor->name;
104              
105 35         81 foreach my $anon ( reverse @anon ) {
106 35         299 $class_meta = {
107             roles => [
108 0         0 map { $_->name } map {
109 35         1112 $_->isa("Moose::Meta::Role::Composite")
110 35 50       782 ? @{$_->get_roles}
111             : $_
112 35         56 } @{ $anon->roles }
113             ],
114             superclasses => [ $class_meta ],
115             };
116             }
117              
118 35 50       204 if ( $class_meta->{superclasses}[0] eq $ancestor->name ) {
119             # no need for redundancy, expansion will provide this as the default
120 35         96 delete $class_meta->{superclasses};
121             }
122              
123             %attrs = (
124 35         200 class => $ancestor->name,
125             class_meta => $class_meta,
126             );
127             }
128              
129 278         3546 my $immutable = does_role($meta, "KiokuDB::Role::Immutable");
130 278         32931 my $content_id = does_role($meta, "KiokuDB::Role::ID::Content");
131              
132 278         26637 my @extra_args;
133              
134 278 100       2117 if ( defined( my $version = $meta->version ) ) {
135 36         551 push @extra_args, class_version => "$version"; # force stringification for version objects
136             }
137              
138             return (
139             sub {
140 3177     3177   11295 my ( $self, %args ) = @_;
141              
142 3177         6244 my $object = $args{object};
143              
144 3177 100       6772 if ( $immutable ) {
145             # FIXME this doesn't handle unset_root
146 202 100       5394 if ( $self->live_objects->object_in_storage($object) ) {
    100          
147 35         962 return $self->make_skip_entry( %args, prev => $self->live_objects->object_to_entry($object) );
148             } elsif ( $content_id ) {
149 165 100       4508 if ( ($self->backend->exists($args{id}))[0] ) { # exists works in list context
150 66         404 return $self->make_skip_entry(%args);
151             }
152             }
153             }
154              
155 3076         4460 my %collapsed;
156              
157 3076         5584 attr: foreach my $attr ( @attrs ) {
158 22827         298306 my $name = $attr->name;
159 22827 100       50947 if ( $attr->has_value($object) ) {
160 13393 50       319333 if ( $lazy{$name} ) {
161 0         0 my $value = $meta_instance->Class::MOP::Instance::get_slot_value($object, $name); # FIXME fix KiokuDB::Meta::Instance to allow fetching thunk
162              
163 0 0       0 if ( ref $value eq 'KiokuDB::Thunk' ) {
164 0         0 $collapsed{$name} = $value->collapsed;
165 0         0 next attr;
166             }
167             }
168              
169 13393         31854 my $value = $attr->get_raw_value($object);
170 13393 100       286503 $collapsed{$name} = ref($value) ? $self->visit($value) : $value;
171             }
172             }
173              
174 3071         35773 return $self->make_entry(
175             @extra_args,
176             %args,
177             data => \%collapsed,
178             );
179             },
180 278         7909 %attrs,
181             );
182             }
183              
184             sub compile_expand {
185 278     278 0 560 my ( $self, $class, $resolver, @args ) = @_;
186              
187 278         733 my $meta = Class::MOP::get_metaclass_by_name($class);
188              
189 278         885 my $typemap_entry = $self;
190              
191 278         726 my $anon = $meta->is_anon_class;
192              
193 278         3814 my $inner = $self->compile_expand_body($class, $resolver, @args);
194              
195 278         1026 my $version = $meta->version;
196              
197             return sub {
198 5237     5237   9195 my ( $linker, $entry, @args ) = @_;
199              
200 5237 100 100     145893 if ( $entry->has_class_meta and !$anon ) {
201             # the entry is for an anonymous subclass of this class, we need to
202             # compile that entry and short circuit to it. if $anon is true then
203             # we're already compiled, and the class_meta is already handled
204 35         182 my $anon_meta = $self->reconstruct_anon_class($entry);
205              
206 35         5063 my $anon_class = $anon_meta->name;
207              
208 35 50       180 unless ( $resolver->resolved($anon_class) ) {
209 0         0 $resolver->compile_entry($anon_class, $typemap_entry);
210             }
211              
212 35         117 my $method = $resolver->expand_method($anon_class);
213 35         135 return $linker->$method($entry, @args);
214             }
215              
216 5202 100 100     152707 if ( !$self->check_class_versions or $self->is_version_up_to_date($meta, $version, $entry->class_version) ) {
217 5187         16701 $linker->$inner($entry, @args);
218             } else {
219 15         61 my $upgraded = $self->upgrade_entry( linker => $linker, meta => $meta, entry => $entry, expand_args => \@args);
220              
221 11 100       2628 if ( $self->write_upgrades ) {
222 5 50       28 croak "Upgraded entry can't be updated (mismatch in 'prev' chain)"
223             unless refaddr($entry) == refaddr($upgraded->root_prev);
224              
225 5         106 $linker->backend->insert($upgraded);
226             }
227              
228 11         93 $linker->$inner($upgraded, @args);
229             }
230             }
231 278         5783 }
232              
233             { my %cache;
234             sub is_version_up_to_date {
235 139     139 0 292 my ( $self, $meta, $version, $entry_version ) = @_;
236              
237             # no clever stuff, only if they are the same string they are the same version
238              
239 22     22   26139 no warnings 'uninitialized'; # undef $VERSION is allowed
  22         42  
  22         3207  
240 139 100       606 return 1 if $version eq $entry_version;
241              
242 23         116 my $key = join(":", $meta->name, $entry_version); # $VERSION isn't supposed to change at runtime
243              
244 23 100       73 return $cache{$key} if exists $cache{$key};
245              
246             # check the version table for equivalent versions (recursively)
247             # ref handlers are upgrade hooks
248 19         61 foreach my $handler ( $self->find_version_handlers($meta, $entry_version) ) {
249 14 100       60 return $cache{$key} = $self->is_version_up_to_date( $meta, $version, $handler ) if not ref $handler;
250             }
251              
252 12         82 return $cache{$key} = undef;
253             }
254              
255 28     28 0 280 sub clear_version_cache { %cache = () }
256             }
257              
258             sub find_version_handlers {
259 37     37 0 56 my ( $self, $meta, $version ) = @_;
260              
261 22     22   116 no warnings 'uninitialized'; # undef $VERSION is allowed
  22         40  
  22         4637  
262              
263 37 100       78 if ( does_role($meta, "KiokuDB::Role::Upgrade::Handlers") ) {
264 26         4387 return $meta->name->kiokudb_upgrade_handler($version);
265             } else {
266 11         1760 return grep { defined } map { $_->{$version} } $self->class_version_table->{$meta->name}, $self->version_table;
  22         43  
  22         40  
267             }
268             }
269              
270             sub upgrade_entry {
271 15     15 0 59 my ( $self, %args ) = @_;
272              
273 15         36 my ( $meta, $entry ) = @args{qw(meta entry)};
274              
275 15 100       37 if ( does_role($meta, "KiokuDB::Role::Upgrade::Data") ) {
276 1         210 return $meta->name->kiokudb_upgrade_data(%args);
277             } else {
278 14         3248 return $self->upgrade_entry_from_version( %args, from_version => $entry->class_version );
279             }
280             }
281              
282             sub upgrade_entry_from_version {
283 18     18 0 68 my ( $self, %args ) = @_;
284              
285 18         51 my ( $meta, $from_version, $entry ) = @args{qw(meta from_version entry)};
286              
287 22     22   104 no warnings 'uninitialized'; # undef $VERSION is allowed
  22         39  
  22         24206  
288              
289 18         40 foreach my $handler ( $self->find_version_handlers($meta, $from_version) ) {
290 14 100       33 if ( ref $handler ) {
291              
292 10         31 my $cb = $self->_process_upgrade_handler($handler);
293              
294             # apply handler
295 10         43 my $converted = $self->$cb(%args);
296              
297 10 50       27348 if ( $self->is_version_up_to_date( $meta, $meta->version, $converted->class_version ) ) {
    0          
298 10         64 return $converted;
299             } elsif ( $entry->class_version eq $converted->class_version ) {
300 0         0 croak "Upgrade from " . $entry->class_version . " did change 'class_version' field";
301             } else {
302             # more error context
303             return try {
304 0         0 $self->upgrade_entry_from_version(%args, entry => $converted, from_version => $converted->class_version);
305 0         0 } catch {
306 0         0 croak "$_\n... when upgrading from $from_version";
307             };
308             }
309             } else {
310             # nonref is equivalent version, recursively search for handlers for that version
311 4         25 return $self->upgrade_entry_from_version( %args, from_version => $handler );
312             }
313             }
314              
315 4 50       123 croak "No handler found for " . $meta->name . " version $from_version" . ( $entry->class_version ne $from_version ? "(entry version is " . $entry->class_version . ")" : "" );
316             }
317              
318             sub _process_upgrade_handler {
319 10     10   17 my ( $self, $handler ) = @_;
320              
321 10 100       31 if ( ref $handler eq 'HASH' ) {
322 4 50 66     20 croak "Data provided in upgrade handler must be a hash"
323             if ref $handler->{data} and ref $handler->{data} ne 'HASH';
324              
325 4 50       14 croak "No class_version provided in upgrade handler"
326             unless defined $handler->{class_version};
327              
328             return sub {
329 4     4   17 my ( $self, %args ) = @_;
330              
331 4         7 my $entry = $args{entry};
332              
333 4 50       95 croak "Entry data not a hash reference"
334             unless ref $entry->data eq 'HASH';
335              
336 4         85 $entry->derive(
337             %$handler,
338             data => {
339 4 100       32 %{ $entry->data },
340 4         12 %{ $handler->{data} || {} },
341             },
342             );
343 4         23 };
344             }
345              
346 6         8 return $handler;
347             }
348              
349             sub compile_create {
350 278     278 0 535 my ( $self, $class ) = @_;
351              
352 278         653 my $meta = Class::MOP::get_metaclass_by_name($class);
353              
354 278         1163 my $meta_instance = $meta->get_meta_instance;
355              
356 278         1923 my $cache = does_role($meta, "KiokuDB::Role::Cacheable");
357              
358 278 100       33640 my @register_args = (
359             ( $cache ? ( cache => 1 ) : () ),
360             );
361              
362             return sub {
363 5198     5198   26934 return ( $meta_instance->create_instance(), @register_args );
364 278         1532 };
365             }
366              
367             sub compile_clear {
368 278     278 0 383 my ( $self, $class ) = @_;
369              
370             return sub {
371 35     35   76 my ( $linker, $obj ) = @_;
372 35         163 %$obj = (); # FIXME
373             }
374 278         1667 }
375              
376             sub compile_expand_data {
377 556     556 0 859 my ( $self, $class, @args ) = @_;
378              
379 556         1409 my $meta = Class::MOP::get_metaclass_by_name($class);
380              
381 556         2290 my $meta_instance = $meta->get_meta_instance;
382              
383 556         2734 my ( %attrs, %lazy );
384              
385 2842   66     32338 my @attrs = grep {
386 556         1544 !does_role($_->meta, 'KiokuDB::Meta::Attribute::DoNotSerialize')
387             and
388             !does_role($_->meta, 'MooseX::Storage::Meta::Attribute::Trait::DoNotSerialize')
389             } $meta->get_all_attributes;
390              
391 556         1896 foreach my $attr ( @attrs ) {
392 2838         21693 $attrs{$attr->name} = $attr;
393 2838         5082 $lazy{$attr->name} = does_role($attr->meta, "KiokuDB::Meta::Attribute::Lazy");
394             }
395              
396             return sub {
397 5233     5233   8916 my ( $linker, $instance, $entry, @args ) = @_;
398              
399 5233         113335 my $data = $entry->data;
400              
401 5233         6124 my @values;
402              
403 5233         17744 foreach my $name ( keys %$data ) {
404 22652 50       219438 my $attr = $attrs{$name} or croak "Unknown attribute: $name";
405 22652         25094 my $value = $data->{$name};
406              
407 22652 100       31674 if ( ref $value ) {
408 15104 50       20565 if ( $lazy{$name} ) {
409 0         0 my $thunk = KiokuDB::Thunk->new( collapsed => $value, linker => $linker, attr => $attr );
410 0         0 $attr->set_raw_value($instance, $thunk);
411             } else {
412 15104         20829 my @pair = ( $attr, undef );
413              
414 15104 50       51506 $linker->inflate_data($value, \$pair[1]) if ref $value;
415 15104         30356 push @values, \@pair;
416             }
417             } else {
418 7548         28693 $attr->set_raw_value($instance, $value);
419             }
420             }
421              
422             $linker->queue_finalizer(sub {
423 5233         7982 foreach my $pair ( @values ) {
424 15104         77352 my ( $attr, $value ) = @$pair;
425 15104         30228 $attr->set_raw_value($instance, $value);
426 15104 100       714887 $attr->_weaken_value($instance) if $attr->is_weak_ref;
427             }
428 5233         91253 });
429              
430 5233         35088 return $instance;
431             }
432 556         5694 }
433              
434             sub reconstruct_anon_class {
435 35     35 0 60 my ( $self, $entry ) = @_;
436              
437 35         857 $self->inflate_class_meta(
438             superclasses => [ $entry->class ],
439 35         817 %{ $entry->class_meta },
440             );
441             }
442              
443             sub inflate_class_meta {
444 35     35 0 126 my ( $self, %meta ) = @_;
445              
446 35         66 foreach my $super ( @{ $meta{superclasses} } ) {
  35         103  
447 35 50       240 $super = $self->inflate_class_meta(%$super)->name if ref $super;
448             }
449              
450             # FIXME should probably get_meta_by_name($entry->class)
451             Moose::Meta::Class->create_anon_class(
452 35         455 cache => 1,
453             %meta,
454             );
455             }
456              
457             sub compile_id {
458 278     278 0 568 my ( $self, $class ) = @_;
459              
460 278 100       716 if ( does_role(Class::MOP::get_metaclass_by_name($class), "KiokuDB::Role::ID") ) {
461             return sub {
462 234     234   505 my ( $self, $object ) = @_;
463 234         1086 return $object->kiokudb_object_id;
464             }
465 70         9505 } else {
466 208         23027 return "generate_uuid";
467             }
468             }
469              
470             sub should_compile_intrinsic {
471 278     278 0 505 my ( $self, $class, @args ) = @_;
472              
473 278         682 my $meta = Class::MOP::get_metaclass_by_name($class);
474              
475 278 100       10809 if ( $self->has_intrinsic ) {
    100          
476 4         127 return $self->intrinsic;
477             } elsif ( does_role($meta, "KiokuDB::Role::Intrinsic") ) {
478 2         293 return 1;
479             } else {
480 272         46214 return 0;
481             }
482             }
483              
484             __PACKAGE__->meta->make_immutable;
485              
486             __PACKAGE__
487              
488             __END__
489              
490             =pod
491              
492             =head1 NAME
493              
494             KiokuDB::TypeMap::Entry::MOP - A L<KiokuDB::TypeMap> entry for objects with a
495             metaclass.
496              
497             =head1 SYNOPSIS
498              
499             KiokuDB::TypeMap->new(
500             entries => {
501             'My::Class' => KiokuDB::TypeMap::Entry::MOP->new(
502             intrinsic => 1,
503             ),
504             },
505             );
506              
507             =head1 DESCRIPTION
508              
509             This typemap entry handles collapsing and expanding of L<Moose> based objects.
510              
511             It supports anonymous classes with runtime roles, the L<KiokuDB::Role::ID> role.
512              
513             Code for immutable classes is cached and performs several orders of magnitude
514             better, so make use of L<Moose::Meta::Class/make_immutable>.
515              
516             =head1 ATTRIBUTES
517              
518             =over 4
519              
520             =item intrinsic
521              
522             If true the object will be collapsed as part of its parent, without an ID.
523              
524             =item check_class_versions
525              
526             If true (the default) then class versions will be checked on load and if there
527             is a mismatch between the stored version number and the current version number,
528             the version upgrade handler tables will be used to convert the out of date
529             entry.
530              
531             =item version_table
532              
533             =item class_version_table
534              
535             Tables of handlers.
536              
537             See also L<KiokuDB::Role::Upgrade::Data> and
538             L<KiokuDB::Role::Upgrade::Handlers::Table> for convenience roles that do not
539             require a central table.
540              
541             The first is a global version table (useful when the typemap entry is only
542             handling one class) and the second is a table of tables keyed by the class name.
543              
544             The tables are keyed by version number (as a string, C<undef> and C<""> are
545             considered the same), and the value can be either a code reference that
546             processes the entry to bring it up to date, a hash reference of overridden
547             fields, or a string denoting a version number that this version is equivalent
548             to.
549              
550             Version numbers have no actual ordinal meaning, they are taken as simple string
551             identifiers.
552              
553             If we had 3 versions, C<1.0>, C<1.1> and C<2.0>, where C<1.1> is a minor update
554             to the class that requires no structural changes from C<1.0>, our table could
555             be written like this:
556              
557             {
558             '1.0' => '1.1', # upgrading the data from 1.0 to 1.1 is a noop
559             '1.1' => sub {
560             my ( $self, %args ) = @_;
561              
562             # manually convert the entry data
563             return $entry->clone(
564             class_version => '2.0',
565             prev => $entry,
566             data => ...,
567             ),
568             },
569             }
570              
571             When an object that was stored as version C<1.0> is retrieved from the
572             database, and the current definition of the class has C<$VERSION> C<2.0>,
573             table declares C<1.0> is the same as C<1.1>, so we search for the handler for
574             C<1.1> and apply it.
575              
576             The resulting class has the version C<2.0> which is the same as what we have
577             now, so this object can be thawed.
578              
579             The callback is invoked with the following arguments:
580              
581             =over 4
582              
583             =item entry
584              
585             The entry to upgrade.
586              
587             =item from_version
588              
589             The key under which the handler was found (not necessarily the same as
590             C<< $entry->class_version >>).
591              
592             =item meta
593              
594             The L<Class::MOP::Class> of the entry's class.
595              
596             =item linker
597              
598             The L<KiokuDB::Linker> instance that is inflating this object.
599              
600             Can be used to retrieve additional required objects (cycles are not a problem
601             but be aware that the objects might not be usable yet at the time of the
602             callback's invocation).
603              
604             =back
605              
606             When a hash is provided as a handler it'll be used to create an entry like
607             this:
608              
609             $entry->derive(
610             %$handler,
611             data => {
612             %{ $entry->data },
613             %{ $handler->{data} || {} },
614             },
615             );
616              
617             The field C<class_version> is required, and C<data> must contain a hash:
618              
619             KiokuDB->connect(
620             class_version_table => {
621             Foo => {
622             "0.02" => {
623             class_version => "0.03", # upgrade 0.02 to 0.03
624             data => {
625             a_new_field => "default_value",
626             },
627             },
628             },
629             },
630             );
631              
632             =item write_upgrades
633              
634             If true, after applying version upgrade handlers, the updated entry will be
635             written back to the database.
636              
637             Defaults to false but might default to true in future versions (unless the
638             database is in readonly mode).
639              
640             =back