File Coverage

blib/lib/Pixie.pm
Criterion Covered Total %
statement 211 312 67.6
branch 38 76 50.0
condition 7 21 33.3
subroutine 52 72 72.2
pod 9 50 18.0
total 317 531 59.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Pixie - The magic data pixie
4              
5             =head1 SYNOPSIS
6              
7             use Pixie;
8              
9             my $pixie = Pixie->new->connect( 'memory' );
10             my $obj = SomeObject->new;
11              
12             # Note: this API will be changing! See below for details.
13              
14             # Store an object
15             my $cookie = $pixie->insert( $obj );
16              
17             undef( $obj );
18              
19             # Fetch it back
20             my $obj = $pixie->get( $cookie );
21              
22             # Give it a name
23             $pixie->bind_name( "Some Name" => $obj );
24             my $obj2 = $pixie->get_object_named( "Some Name" );
25              
26             # Delete it
27             $pixie->delete( $cookie ) || warn "eek!";
28              
29             # some stores need deploying before you can use them:
30             $pixie = Pixie->deploy( 'dbi:mysql:dbname=px_test' );
31              
32             =head1 DESCRIPTION
33              
34             Pixie is yet another object persistence tool. The basic goal of Pixie
35             is that it should be possible to throw any object you want at a data
36             pixie and the pixie will just tuck it away in its magic sack, giving
37             you a cookie in exchange. Then, minutes, hours or days later, you can
38             show the pixie your cookie and get the object back.
39              
40             No schemas. No complex querying. No refusing to handle blessed arrays.
41              
42             How does pixie do this? Well... when we said 'any object' we were
43             being slightly disingenuous. As far as Pixie is concerned 'any object'
44             means 'any object that satisfies any of these criteria':
45              
46             =over 4
47              
48             =item *
49              
50             The inserted object is a blessed hash.
51              
52             =item *
53              
54             The inserted object is a blessed array
55              
56             =item *
57              
58             The inserted object is 'complicit' with Pixie, see L
59              
60             =back
61              
62             You'll note that we don't include 'blessed arbitrary scalars' in this
63             list. This is because, during testing we found that the majority of
64             objects that are represented as blessed scalars are often using XS to
65             store extra data that Storable and Data::Dumper can't see, which leads
66             to all sorts of problems later. So, if you use a blessed scalar as
67             your object representation then you'll have to use the complicity
68             features. Sorry.
69              
70             Pixie can additionally be used to name objects in the store, and fetch them
71             later on with that name.
72              
73             =cut
74              
75             package Pixie;
76              
77 18     18   762267 use strict;
  18         49  
  18         649  
78 18     18   103 use warnings::register;
  18         43  
  18         3308  
79              
80 18     18   101 use Carp qw( carp confess );
  18         34  
  18         1439  
81              
82 18     18   12153 use Data::UUID;
  18         11532  
  18         1269  
83 18     18   118 use Scalar::Util qw( blessed reftype isweak );
  18         41  
  18         2075  
84 18     18   11129 use Pixie::Proxy;
  18         62  
  18         557  
85 18     18   42808 use Data::Dumper;
  18         179209  
  18         1578  
86              
87 18     18   11797 use Pixie::Store;
  18         56  
  18         589  
88 18     18   126 use Pixie::ObjectInfo;
  18         32  
  18         424  
89 18     18   91 use Pixie::ObjectGraph;
  18         31  
  18         323  
90              
91 18     18   10833 use Pixie::LiveObjectManager;
  18         50  
  18         513  
92 18     18   116 use Pixie::Complicity;
  18         38  
  18         565  
93              
94 18     18   10759 use Pixie::LockStrat::Null;
  18         42  
  18         530  
95              
96 18     18   97 use base qw( Pixie::Object );
  18         31  
  18         96034  
97              
98             our $VERSION = "2.08_02";
99             our $the_current_pixie;
100             our $the_current_oid;
101             our $the_current_lock_strategy;
102             our $the_current_object_graph;
103              
104             #use overload
105             # '""' => 'as_string';
106              
107             #BEGIN { $Data::Dumper::Useperl = 1 }
108              
109             #------------------------------------------------------------------------------
110             # Class methods
111             #------------------------------------------------------------------------------
112              
113             sub get_the_current_pixie {
114 102     102 0 166 my $class = shift;
115 102         525 return $the_current_pixie;
116             }
117              
118             sub get_the_current_oid {
119 2     2 0 5 my $class = shift;
120 2         8 return $the_current_oid;
121             }
122              
123             sub get_the_current_lock_strategy {
124 3     3 0 25 return $the_current_lock_strategy;
125             }
126              
127             sub get_the_current_object_graph {
128 1     1   5 return $the_current_object_graph;
129             }
130              
131             ## TODO: return new object
132             sub deploy {
133 4     4 1 14085 my $class = shift;
134 4         32 Pixie::Store->deploy( @_ );
135 4         13 return $class;
136             }
137              
138             #------------------------------------------------------------------------------
139             # Instance methods
140             #------------------------------------------------------------------------------
141              
142             sub init {
143 25     25 0 54 my $self = shift;
144 25         112 $self->connect('memory');
145 25         234 $self->{_objectmanager} = Pixie::LiveObjectManager->new->set_pixie($self);
146 25         76 return $self;
147             }
148              
149             sub connect {
150 39     39 1 68 my $self = shift;
151 39 50       244 $self = $self->new unless blessed( $self );
152 39         296 $self->store( Pixie::Store->connect(@_) );
153             }
154              
155             sub clear_storage {
156 8     8 0 1354 my $self = shift;
157 8         28 $self->store->clear;
158             }
159              
160             #------------------------------------------------------------------------------
161             # accessor-kinda methods
162              
163             sub _oid {
164 6     6   12 my $self = shift;
165 6   66     42 $self->{_oid} ||= do {
166 4         21 require Data::UUID;
167 4         35632 Data::UUID->new()->create_str();
168             }
169             }
170              
171             ## TODO: rename to _store or the_store
172             sub store {
173 188     188 1 1742 my $self = shift;
174 188 100       417 if (@_) {
175 39         160 $self->{store} = shift;
176 39         173 return $self;
177             } else {
178 149         1001 return $self->{store};
179             }
180             }
181              
182             sub clear_store {
183 1     1 0 3 my $self = shift;
184 1         4 $self->{store} = undef;
185 1         13 return $self;
186             }
187              
188             sub lock_strategy {
189 9     9 0 1445 my $self = shift;
190 9 100       53 if (@_) {
191 2         6 $self->{lock_strategy} = shift;
192 2         9 $self;
193             }
194             else {
195 7   66     78 $self->{lock_strategy} ||= Pixie::LockStrat::Null->new;
196             }
197             }
198              
199             ## basically an accessor for live obj manager...
200             sub lock_strategy_for {
201 2     2 0 563 my $self = shift;
202 2         4 my $obj_or_oid = shift;
203              
204 2 100       6 if (@_) {
205 1         7 $self->{_objectmanager}->lock_strategy_for($obj_or_oid, @_);
206 1         56 return $self;
207             }
208             else {
209 1         13 return $self->{_objectmanager}->lock_strategy_for($obj_or_oid);
210             }
211             }
212              
213             #------------------------------------------------------------------------------
214             # storage methods
215              
216             ## TODO: is this actually being used?
217             sub store_individual {
218 2     2 0 8 my $self = shift;
219 2         3 my $real = shift;
220              
221             confess( "Can't store a Pixie::ObjectInfo" )
222 2 100       4 if eval { $real->isa('Pixie::ObjectInfo') };
  2         41  
223              
224 1         6 my $oid = $real->PIXIE::oid;
225 1 50       3 if (defined $oid) {
226 1         6 $self->store_individual_at($real, $oid);
227             }
228             else {
229             # TODO: when are we *not* gonna have an oid? Should die really.
230 0         0 return $real;
231             }
232             }
233              
234             ## TODO: fix order of params: everywhere else uses oid => obj.
235             sub store_individual_at {
236 2     2 0 5 my $self = shift;
237 2         4 my($obj, $oid, $strategy) = @_;
238 2   33     10 $strategy ||= $self->lock_strategy;
239             # TODO: is %Pixie::Stored actually used?
240 2 50       5 if ($Pixie::Stored{$oid}) {
241 0         0 return $Pixie::Stored{$oid};
242             }
243             else {
244 2         9 return Pixie::Proxy->
245             px_make_proxy( $self->store->store_at($oid, $obj, $strategy) );
246             }
247             }
248              
249             #------------------------------------------------------------------------------
250             # Insert methods
251              
252             sub insert {
253 21     21 1 6275 my $self = shift;
254 21         153 my $graph = Pixie::ObjectGraph->new;
255              
256 21         45 my $ret = eval {
257 21         45 local $the_current_object_graph = $graph;
258 21         89 $self->_insert(@_)
259             };
260 21 50       333 $self->bail_out($@) if $@;
261              
262 0 0       0 $self->_insert($self->object_graph->add_graph($graph))
263             unless $_[0]->isa('Pixie::ObjectGraph');
264 0         0 $self->add_to_rootset(@_);
265              
266 0         0 return $ret;
267             }
268              
269             ##
270             # How Object Freezing Works
271             #
272             # To insert a tree of objects without disturbing the original objects
273             # themselves we need to take a deep copy of the tree and extract each object
274             # to be stored individualy.
275             #
276             # We use Data::Dumper to do this, by dumping to a string, and eval'ing it
277             # straight away to get the new object tree. We use Dumper's call-back hooks
278             # Freezer & Toaster, which are used for each object in the tree.
279             #
280             # When an object is frozen it's wrapped in an Object Holder so we can preserve
281             # its oid.
282             #
283             # When an object is thawed we take it out of the Holder and store it.
284             ##
285              
286             sub _insert {
287 21     21   43 my $self = shift;
288 21         41 my $this = shift;
289              
290 21         41 local %Pixie::Stored; # TODO: is %Pixie::Stored actually used?
291 21         54 local $Data::Dumper::Freezer = '_px_insertion_freeze';
292 21         46 local $Data::Dumper::Toaster = '_px_insertion_thaw';
293              
294 21         39 local %PIXIE::freeze_cache;
295 21         87 my $proxy = $self->do_dump_and_eval($this, 1);
296              
297 0 0       0 return defined($proxy) ? $proxy->_oid : undef;
298             }
299              
300             ## TODO: rename deep_copy_using_data_dumper()
301             sub do_dump_and_eval {
302 22     22 0 67 my $self = shift;
303 22         50 my($thing, $do_lock) = @_;
304              
305 22         58 local $Data::Dumper::Deepcopy = 1;
306 22         42 local $the_current_pixie = $self;
307              
308 22         35 my $data_string;
309             {
310 22         40 my $dump_warn;
  22         39  
311 22   33 2   182 local $SIG{__WARN__} = sub { $dump_warn ||= join '', @_ };
  2         37  
312             # HACK: sometimes dumper fails dumping ObjectGraphs
313             # doing this twice reduces the probability of getting a 0-length string
314 22         145 $data_string = Dumper($thing);
315 22   33     360 $data_string ||= Dumper($thing);
316 22 100       98 die $dump_warn if $dump_warn;
317 20 50       178 die "Something went wrong with the Dump" unless length($data_string);
318             }
319              
320 20         41 my $VAR1;
321 20 100       109 $self->lock_store if $do_lock;
322 20         2625 eval $data_string;
323 20 100       424 die $@ if $@;
324 1 50       5 $self->unlock_store if $do_lock;
325              
326 1         5 return $VAR1;
327             }
328              
329             ## TODO: split up into smaller methods with better names
330             sub insertion_freeze {
331 29     29 0 67 my $self = shift;
332 29         49 my $thing = shift;
333              
334 29         491 $self->ensure_storability($thing);
335 27         187 my $oid = $thing->PIXIE::oid;
336              
337 27 50       109 return $PIXIE::freeze_cache{$oid} if defined $PIXIE::freeze_cache{$oid};
338              
339 27         145 $self->cache_insert($thing);
340 27         193 $thing = $thing->px_freeze;
341              
342 27         307 my $obj_holder = bless( {oid => $oid,
343             class => blessed( $thing ),
344             content => $thing->px_as_rawstruct },
345             'Pixie::ObjHolder' );
346 27         97 $PIXIE::freeze_cache{$oid} = $obj_holder;
347              
348 27         913 return $obj_holder;
349             }
350              
351             ## TODO: split up into smaller methods with better names
352             sub insertion_thaw {
353 20     20 0 153 my $self = shift;
354 20         136 my $obj_holder = shift;
355 20 100       423 die "Object is not a Pixie::ObjHolder" unless $obj_holder->isa('Pixie::ObjHolder');
356              
357 1         6 my $thing = bless $obj_holder->{content}, $obj_holder->{class};
358 1         3 my $thing_oid = $obj_holder->{oid};
359              
360 1         8 $self->{_objectmanager}->bind_object_to_oid($thing, $thing_oid);
361             # TODO: this has already been ensured in insertion_freeze:
362 1         5 $self->ensure_storability($thing);
363 1         7 my $retval = $self->store_individual_at($thing, $obj_holder->{oid});
364              
365             # Set up GC stuff
366 1 50       7 if (my $graph = Pixie->get_the_current_object_graph) {
367 1         17 $graph->add_edge($thing_oid => $_) for
368             $self->proxied_content($obj_holder);
369             }
370 1         14 bless $thing, 'Class::Whitehole';
371              
372 1         7 return $retval;
373             }
374              
375             sub proxied_content {
376 2     2 0 4 my $self = shift;
377 2         4 my $obj_holder = shift;
378              
379 2         5 local %Pixie::neighbours;
380              
381             # Turn off deepcopy or things get *very* slow.
382 2         5 local $Data::Dumper::Deepcopy = 0;
383 2         5 local $Data::Dumper::Freezer = 'Pixie::proxy_finder';
384 2         3 local $Data::Dumper::Toaster = undef;
385 2         14 Data::Dumper::DumperX($obj_holder);
386 2         4367 return keys %Pixie::neighbours;
387             }
388              
389             sub proxy_finder {
390 1     1 0 8 my $obj = shift;
391 1 50       8 $Pixie::neighbours{$obj->_oid} = 1 if blessed( $obj )->isa( 'Pixie::Proxy' );
392 1         6 return $obj;
393             }
394              
395             #------------------------------------------------------------------------------
396             # Get methods
397              
398             sub get {
399 0     0 1 0 my $self = shift;
400 0         0 my($oid) = @_;
401 0         0 $self->get_with_strategy($oid, $self->lock_strategy);
402             }
403              
404             sub get_with_strategy {
405 0     0 0 0 my $self = shift;
406 0         0 my($oid, $strategy) = @_;
407              
408 0   0     0 $strategy ||= do {
409 0         0 carp "Called with blank strategy";
410 0         0 $self->lock_strategy;
411             };
412              
413 0         0 local $the_current_lock_strategy = $strategy;
414              
415 0         0 $self->lock_store;
416 0         0 $strategy->pre_get($oid, $self);
417 0         0 my $res = eval {$self->_get($oid)};
  0         0  
418 0         0 my $err = $@;
419 0         0 $strategy->post_get($oid, $self);
420 0 0       0 $self->bail_out($err) if $err;
421 0         0 $self->unlock_store;
422              
423 0         0 return $res;
424             }
425              
426             sub _get {
427 0     0   0 my $self = shift;
428 0         0 my $oid = shift;
429              
430 0 0       0 return undef unless defined $oid;
431 0         0 my $cached_struct = $self->cache_get($oid);
432 0 0 0     0 return $cached_struct if defined($cached_struct)
433             && ! $cached_struct->isa('Pixie::Object');
434              
435 0         0 local $Data::Dumper::Freezer = '_px_extraction_freeze';
436 0         0 local $Data::Dumper::Toaster = '_px_extraction_thaw';
437 0         0 local $the_current_oid = $oid;
438              
439 0         0 my $rawstruct = $self->store->get_object_at( $oid );
440 0 0       0 return unless defined($rawstruct);
441              
442 0         0 my $newstruct = $self->do_dump_and_eval($rawstruct);
443 0         0 bless $rawstruct, 'Class::Whitehole';
444 0         0 return scalar $self->cache_insert($newstruct);
445             }
446              
447             sub extraction_freeze {
448 1     1 0 9 my $self = shift;
449 1         2 my $thing = shift;
450 1         6 return $thing;
451             }
452              
453             sub extraction_thaw {
454 1     1 0 3 my $self = shift;
455 1         2 my $thing = shift;
456 1         5 my $oid = Pixie->get_the_current_oid;
457              
458 1         11 $thing = $thing->px_thaw;
459              
460             # this usually calls 'make_new_object':
461 1         9 my $real_obj = $thing->px_do_final_restoration;
462              
463 1 50       9 bless( $thing, 'Class::Whitehole' )
464             unless $thing->PIXIE::address == $real_obj->PIXIE::address;
465              
466 1         7 $self->{_objectmanager}->bind_object_to_oid($real_obj => $oid);
467 1 50       6 $real_obj->PIXIE::oid eq $oid or die "Bad OID stuff";
468 1         4 $self->cache_insert($real_obj);
469              
470 1         3 return $real_obj;
471             }
472              
473             sub make_new_object {
474 2     2 0 20 my $self = shift;
475 2         5 my($struct, $class) = @_;
476              
477 2         4 my $real = eval { $class->px_empty_new };
  2         17  
478 2 50       15 if ($@) {
479 0         0 $real = bless $struct, $class;
480             }
481             else {
482 2         8 my $type = reftype($struct);
483              
484 2 50       13 if ($type eq 'SCALAR') {
    50          
    50          
485 0         0 $$real = $$struct;
486             }
487             elsif ($type eq 'ARRAY') {
488 0         0 @$real = @$struct;
489             }
490             elsif ($type eq 'HASH') {
491 2         10 %$real = %$struct;
492             }
493             else {
494 0         0 return $struct;
495             }
496             }
497 2         7 return $real;
498             }
499              
500             sub bail_out {
501 21     21 0 45 my $self = shift;
502 21         76 $self->rollback_store;
503 21         73 $self->unlock_store;
504 21         1574 die @_;
505             }
506              
507             sub delete {
508 0     0 1 0 my $self = shift;
509 0         0 my $obj_or_oid = shift;
510 0 0       0 my $oid = blessed( $obj_or_oid ) ? $obj_or_oid->PIXIE::oid : $obj_or_oid;
511 0         0 $self->cache_delete($oid);
512 0         0 $self->store->remove_from_store($oid);
513             }
514              
515             sub forget_about {
516 0     0 0 0 my $self = shift;
517 0 0       0 return unless blessed( $self );
518 0         0 my $obj = shift;
519 0         0 $obj->PIXIE::set_info(undef);
520             }
521              
522             sub manages_object {
523 2     2 0 10 my $self = shift;
524 2         4 my($obj) = @_;
525              
526 2         25 $self->_oid eq $obj->PIXIE::get_info->pixie_id;
527             }
528              
529             #------------------------------------------------------------------------------
530             # Caching related methods
531              
532             sub cache_insert {
533 28     28 0 56 my $self = shift;
534 28         161 $self->{_objectmanager}->cache_insert(@_);
535             }
536              
537             sub cache_size {
538 9     9 0 32722 my $self = shift;
539 9         45 $self->{_objectmanager}->cache_size;
540             }
541              
542             sub cache_get {
543 0     0 0 0 my $self = shift;
544 0 0       0 return undef unless defined $self->{_objectmanager};
545 0         0 $self->{_objectmanager}->cache_get(@_);
546             }
547              
548             sub cache_delete {
549 23     23 0 43 my $self = shift;
550 23 100       259 $self->{_objectmanager}->cache_delete(@_) if defined $self->{_objectmanager};
551             }
552              
553             sub get_cached_keys {
554 0     0 0 0 my $self = shift;
555 0         0 $self->{_objectmanager}->cache_keys;
556             }
557              
558             #------------------------------------------------------------------------------
559             # The naming section
560              
561             ## TODO: just use Pixie::Name by default.
562             sub bind_name {
563 1     1 1 1118 my $self = shift;
564 1         3 my($name, @objects) = @_;
565              
566 1         7 require Pixie::Name;
567 1         8 Pixie::Name->name_object_in($name, \@objects, $self);
568             }
569              
570             sub unbind_name {
571 0     0 1 0 my $self = shift;
572 0         0 my($name) = @_;
573              
574 0         0 require Pixie::Name;
575 0         0 Pixie::Name->remove_name_from($name, $self);
576             }
577              
578             sub get_object_named {
579 0     0 1 0 my $self = shift;
580 0         0 my($name, $strategy) = @_;
581 0         0 require Pixie::Name;
582 0         0 Pixie::Name->get_object_from($name, $self, $strategy);
583             }
584              
585              
586             #------------------------------------------------------------------------------
587             # Garbage Collection & related
588              
589             sub rootset {
590 0     0 0 0 my $self = shift;
591 0         0 $self->{store}->rootset;
592             }
593              
594             sub add_to_rootset {
595 0     0 0 0 my $self = shift;
596 0         0 $self->store->add_to_rootset($_) for grep $_->px_in_rootset, @_ ;
597 0         0 return $self;
598             }
599              
600             sub neighbours {
601 0     0 0 0 my $self = shift;
602 0         0 my $oid = shift;
603 0         0 $self->object_graph->neighbours($oid);
604             }
605              
606             sub run_GC {
607 0     0 0 0 my $self = shift;
608 0         0 $self->store->lock_for_GC;
609 0         0 my %live = map { $_ => 1 } $self->live_set;
  0         0  
610 0         0 for ($self->working_set) {
611 0 0       0 $self->delete($_) unless $live{$_};
612             }
613 0         0 $self->store->unlock_after_GC;
614 0         0 return $self;
615             }
616              
617             sub live_set {
618 0     0 0 0 my $self = shift;
619 0         0 my $graph = $self->object_graph;
620 0         0 my %seen = ();
621 0         0 my @nodes_to_process = $self->rootset;
622              
623 0         0 while (@nodes_to_process) {
624 0         0 my $node = pop @nodes_to_process;
625 0 0       0 next if $seen{$node};
626 0         0 $seen{$node} = 1;
627 0         0 push @nodes_to_process, $graph->neighbours($node);
628             }
629              
630 0         0 return keys %seen;
631             }
632              
633             sub object_graph {
634 0     0 0 0 my $self = shift;
635 0         0 $self->store->object_graph_for( $self );
636             }
637              
638             sub working_set {
639 0     0 0 0 my $self = shift;
640 0         0 $self->store->working_set_for( $self );
641             }
642              
643             sub ensure_storability {
644 30     30 0 48 my $self = shift;
645 30         69 my $obj = shift;
646 30 100       215 $obj->px_is_storable or die "Pixie cannot store a ", blessed( $obj );
647             }
648              
649             #------------------------------------------------------------------------------
650             # Locking methods
651              
652 19     19   87 sub lock_store { $_[0]->store->lock; }
653 21     21 0 62 sub unlock_store { $_[0]->store->unlock; }
654 21     21 0 74 sub rollback_store { $_[0]->store->rollback; }
655              
656             sub lock_object {
657 0     0 0 0 my $self = shift;
658 0         0 $self->{_objectmanager}->lock_object(@_);
659             }
660              
661             sub unlock_object {
662 0     0 0 0 my $self = shift;
663 0         0 $self->{_objectmanager}->unlock_object(@_);
664             }
665              
666             #------------------------------------------------------------------------------
667             # Pixie::Complicity methods
668              
669             sub px_freeze {
670 0     0 0 0 my $self = shift;
671 0         0 return bless {}, blessed( $self );
672             }
673              
674             sub _px_extraction_thaw {
675 0     0   0 my $self = shift;
676 0         0 $self->get_the_current_pixie;
677             }
678              
679             #------------------------------------------------------------------------------
680             # Miscellaneous methods
681              
682             sub DESTROY {
683 22     22   7704 my $self = shift;
684 22 100       71 $self->store->release_all_locks if defined $self->store;
685 22         153 delete $self->{_objectmanager};
686             }
687              
688             sub as_string {
689 2     2 0 10 my $self = shift;
690 2         16 my $str = blessed( $self ) . ": " . $self->_oid . "\n";
691 2 50       80466 $str .= " " . $self->store->as_string . "\n" if $self->store;
692             }
693              
694             1;
695              
696             __END__