File Coverage

blib/lib/KiokuDB/Entry.pm
Criterion Covered Total %
statement 2 4 50.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 4 6 66.6


line stmt bran cond sub pod time code
1             package KiokuDB::Entry;
2             BEGIN {
3 2     2   95005 $KiokuDB::Entry::AUTHORITY = 'cpan:NUFFIN';
4             }
5             $KiokuDB::Entry::VERSION = '0.57';
6 2     2   5778 use Moose;
  0            
  0            
7             # ABSTRACT: An entry in the database
8              
9             use Moose::Util::TypeConstraints;
10              
11             use namespace::clean -except => 'meta';
12              
13             with 'MooseX::Clone' => { -version => 0.04 };
14              
15             has id => (
16             isa => "Str",
17             is => "ro",
18             writer => "_id",
19             clearer => "clear_id",
20             predicate => "has_id",
21             );
22              
23             has root => (
24             isa => "Bool",
25             is => "rw",
26             lazy_build => 1,
27             );
28              
29             sub _build_root {
30             my $self = shift;
31              
32             if ( $self->has_id and my $prev = $self->prev ) {
33             return $prev->root;
34             } else {
35             return 0;
36             }
37             }
38              
39             has deleted => (
40             isa => "Bool",
41             is => "ro",
42             writer => "_deleted",
43             );
44              
45             has data => (
46             is => "ro",
47             writer => "_data",
48             predicate => "has_data",
49             );
50              
51             has class => (
52             isa => "Str",
53             is => "ro",
54             writer => "_class",
55             predicate => "has_class",
56             );
57              
58             has class_meta => (
59             isa => "HashRef",
60             is => "ro",
61             writer => "_class_meta",
62             predicate => "has_class_meta",
63             );
64              
65             has class_version => (
66             isa => "Str",
67             is => "ro",
68             writer => "_class_version",
69             predicate => "has_class_version",
70             );
71              
72             my @tied = ( map { substr($_, 0, 1) } qw(HASH SCALAR ARRAY GLOB) );
73              
74             has tied => (
75             is => "ro",
76             writer => "_tied",
77             predicate => "has_tied",
78             );
79              
80             has backend_data => (
81             is => "rw",
82             predicate => "has_backend_data",
83             clearer => "clear_backend_data",
84             );
85              
86             has prev => (
87             isa => __PACKAGE__,
88             is => "rw",
89             predicate => "has_prev",
90             clearer => "clear_prev",
91             );
92              
93             sub root_prev {
94             my $self = shift;
95              
96             if ( $self->has_prev ) {
97             return $self->prev->root_prev;
98             } else {
99             return $self;
100             }
101             }
102              
103             has object => (
104             traits => [qw(NoClone)],
105             is => "rw",
106             weak_ref => 1,
107             predicate => "has_object",
108             clearer => "clear_object",
109             );
110              
111             sub deletion_entry {
112             my $self = shift;
113              
114             ( ref $self )->new(
115             id => $self->id,
116             prev => $self,
117             deleted => 1,
118             ( $self->has_object ? ( object => $self->object ) : () ),
119             ( $self->has_backend_data ? ( backend_data => $self->backend_data ) : () ),
120             );
121             }
122              
123             sub derive {
124             my ( $self, @args ) = @_;
125              
126             $self->clone(
127             prev => $self,
128             @args,
129             );
130             }
131              
132             has _references => (
133             traits => [qw(NoClone)],
134             isa => "ArrayRef",
135             is => "ro",
136             lazy_build => 1,
137             );
138              
139             sub _build__references {
140             my $self = shift;
141              
142             no warnings 'uninitialized';
143             if ( $self->class eq 'KiokuDB::Set::Stored' ) { # FIXME should the typemap somehow handle this?
144             return [ map { KiokuDB::Reference->new( id => $_ ) } @{ $self->data } ];
145             } else {
146             my @refs;
147              
148             my @queue = $self->data;
149              
150             while ( @queue ) {
151             my $next = pop @queue;
152              
153             my $ref = ref $next;
154             if ( $ref eq 'HASH' ) {
155             push @queue, grep { ref } values %$next;
156             } elsif ( $ref eq 'ARRAY' ) {
157             push @queue, grep { ref } @$next;
158             } elsif ( $ref eq 'KiokuDB::Entry' ) {
159             push @refs, $next->references;
160             } elsif ( $ref eq 'KiokuDB::Reference' ) {
161             push @refs, $next;
162             }
163             }
164              
165             return \@refs;
166             }
167             }
168              
169             sub references {
170             my $self = shift;
171              
172             return @{ $self->_references };
173             }
174              
175             has _referenced_ids => (
176             traits => [qw(NoClone)],
177             isa => "ArrayRef",
178             is => "ro",
179             lazy_build => 1,
180             );
181              
182             sub _build__referenced_ids {
183             my $self = shift;
184              
185             no warnings 'uninitialized';
186             if ( $self->class eq 'KiokuDB::Set::Stored' ) { # FIXME should the typemap somehow handle this?
187             return $self->data;
188             } else {
189             return [ map { $_->id } $self->references ];
190             }
191             }
192              
193             sub referenced_ids {
194             my $self = shift;
195              
196             @{ $self->_referenced_ids };
197             }
198              
199             use constant _version => 1;
200              
201             use constant _root_b => 0x01;
202             use constant _deleted_b => 0x02;
203              
204             use constant _tied_shift => 2;
205             use constant _tied_mask => 0x03 << _tied_shift;
206              
207             my %tied; @tied{@tied} = ( 1 .. scalar(@tied) );
208              
209             sub _pack {
210             my $self = shift;
211              
212             my $flags = 0;
213              
214             $flags |= _root_b if $self->root;
215             $flags |= _deleted_b if $self->deleted;
216              
217             if ( $self->has_tied ) {
218             $flags |= $tied{$self->tied} << _tied_shift;
219             }
220              
221             no warnings 'uninitialized';
222             pack( "C C w/a* w/a*", _version, $flags, $self->id, $self->class );
223             }
224              
225             sub _unpack {
226             my ( $self, $packed ) = @_;
227              
228             my ( $v, $body ) = unpack("C a*", $packed);
229              
230             if ( $v == _version ) {
231             my ( $flags, $id, $class, $extra ) = unpack("C w/a w/a a*", $body);
232              
233             return $self->_unpack_old($packed) if length($extra);
234              
235             $self->_id($id) if length($id);
236              
237             $self->_class($class) if length($class);
238              
239             $self->root($flags & _root_b);
240             $self->_deleted(1) if $flags & _deleted_b;
241              
242             if ( my $tied = ( $flags & _tied_mask ) >> _tied_shift ) {
243             $self->_tied( $tied[$tied - 1] );
244             }
245             } else {
246             $self->_unpack_old($packed);
247             }
248             }
249              
250              
251             sub _pack_old {
252             my $self = shift;
253              
254             no warnings 'uninitialized';
255             join(",",
256             $self->id,
257             !!$self->root,
258             $self->class,
259             $self->tied,
260             !!$self->deleted,
261             );
262             }
263              
264             sub _unpack_old {
265             my ( $self, $packed ) = @_;
266              
267             my ( $id, $root, $class, $tied, $deleted ) = split ',', $packed;
268              
269             die "bad entry format: $packed" if $root and $root ne '1';
270             die "bad entry format: $packed" if $deleted and $deleted ne '1';
271              
272             $self->_id($id) if $id;
273             $self->root(1) if $root;
274             $self->_class($class) if $class;
275             $self->_tied(substr($tied, 0, 1)) if $tied;
276             $self->_deleted(1) if $deleted;
277             }
278              
279             sub STORABLE_freeze {
280             my ( $self, $cloning ) = @_;
281              
282             return (
283             $self->_pack,
284             [
285             ( $self->has_data ? $self->data : undef ),
286             ( $self->has_backend_data ? $self->backend_data : undef ),
287             ( $self->has_class_meta ? $self->class_meta : undef ),
288             ( $self->has_class_version ? $self->class_version : undef ),
289             ],
290             );
291             }
292              
293             sub STORABLE_thaw {
294             my ( $self, $cloning, $attrs, $refs ) = @_;
295              
296             $self->_unpack($attrs);
297              
298             if ( $refs ) {
299             my ( $data, $backend_data, $meta, $version ) = @$refs;
300             $self->_data($data) if defined $data;
301             $self->backend_data($backend_data) if ref $backend_data;
302             $self->_class_meta($meta) if ref $meta;
303             $self->_class_version($version) if defined $version;
304             }
305             }
306              
307             __PACKAGE__->meta->make_immutable;
308              
309             __PACKAGE__
310              
311             __END__
312              
313             =pod
314              
315             =encoding UTF-8
316              
317             =head1 NAME
318              
319             KiokuDB::Entry - An entry in the database
320              
321             =head1 VERSION
322              
323             version 0.57
324              
325             =head1 SYNOPSIS
326              
327             KiokuDB::Entry->new(
328             id => ...,
329             data => ...
330             );
331              
332             =head1 DESCRIPTION
333              
334             This object provides the meta data for a single storage entry.
335              
336             =head1 ATTRIBUTES
337              
338             =over 4
339              
340             =item id
341              
342             The UUID for the entry.
343              
344             If there is no ID then the entry is intrinsic.
345              
346             =item root
347              
348             Whether or not this is a member of the root set (not subject to garbage
349             collection, because storage was explicitly requested).
350              
351             =item data
352              
353             A simplified data structure modeling this object/reference. This is a tree, not
354             a graph, and has no shared data (JSON compliant). All references are symbolic,
355             using a L<KiokuDB::Reference> object with UIDs as the
356             address space.
357              
358             =item class
359              
360             If the entry is blessed, this contains the class of that object.
361              
362             In the future this might be a complex structure for anonymous classes, e.g. the
363             class and the runtime roles.
364              
365             =item class_meta
366              
367             Optional information such as runtime roles to be applied to the object is
368             stored in this hashref.
369              
370             =item tied
371              
372             One of C<HASH>, C<ARRAY>, C<SCALAR> or C<GLOB>.
373              
374             C<data> is assumed to be a reference or an intrinsic entry for the object
375             driving the tied structure (e.g. the C<tied(%hash)>).
376              
377             =item prev
378              
379             Contains a link to a L<KiokuDB::Entry> objects that precedes this one.
380              
381             The last entry that was loaded from the store, or successfully written to the
382             store for a given UUID is kept in the live object set.
383              
384             The collapser creates transient Entry objects, which if written to the store
385             successfully replace the previous one.
386              
387             =item backend_data
388              
389             Backends can use this to store additional meta data as they see fit.
390              
391             For instance, this is used in the CouchDB backend to track entry revisions for
392             the opportunistic locking, and in L<KiokuDB::Backend::BDB::GIN> to to store
393             extracted keys.
394              
395             =item deleted
396              
397             Used for marking entries for deletion.
398              
399             Deletion entries can be generated using the C<deletion_entry> method, which
400             creates a new derived entry with no data but retaining the ID.
401              
402             =back
403              
404             =head1 AUTHOR
405              
406             Yuval Kogman <nothingmuch@woobling.org>
407              
408             =head1 COPYRIGHT AND LICENSE
409              
410             This software is copyright (c) 2014 by Yuval Kogman, Infinity Interactive.
411              
412             This is free software; you can redistribute it and/or modify it under
413             the same terms as the Perl 5 programming language system itself.
414              
415             =cut