File Coverage

blib/lib/Pixie.pm
Criterion Covered Total %
statement 132 306 43.1
branch 17 76 22.3
condition 3 18 16.6
subroutine 38 71 53.5
pod 0 51 0.0
total 190 522 36.4


line stmt bran cond sub pod time code
1             package Pixie;
2              
3             =head1 NAME
4              
5             Pixie - The magic data pixie
6              
7             =head1 SYNOPSIS
8              
9             use Pixie;
10              
11             my $pixie = Pixie->new->connect('dbi:mysql:dbname=test', user => $user, pass => $pass);
12              
13             # Save an object
14             my $cookie = $pixie->insert($some_object);
15              
16             undef($some_object);
17              
18             # Get it back
19             my $some_object = $pixie->get($cookie);
20              
21             $pixie->bind_name( "Some Name" => $some_object );
22             my $result = $pixie->get_object_named( "Some Name" );
23              
24             =head1 DESCRIPTION
25              
26             Pixie is yet another object persistence tool. The basic goal of Pixie
27             is that it should be possible to throw any object you want at a data
28             pixie and the pixie will just tuck it away in its magic sack, giving
29             you a cookie in exchange. Then, minutes, hours or days later, you can
30             show the pixie your cookie and get the object back.
31              
32             No schemas. No complex querying. No refusing to handle blessed arrays.
33              
34             How does pixie do this? Well... when we said 'any object' we were
35             being slightly disingenuous. As far as Pixie is concerned 'any object'
36             means 'any object that satisfies any of these criteria':
37              
38             =over 4
39              
40             =item *
41              
42             The inserted object is a blessed hash.
43              
44             =item *
45              
46             The inserted object is a blessed array
47              
48             =item *
49              
50             The inserted object is 'complicit' with Pixie, see L
51              
52             =back
53              
54             You'll note that we don't include 'blessed arbitrary scalars' in this
55             list. This is because, during testing we found that the majority of
56             objects that are represented as blessed scalars are often using XS to
57             store extra data that Storable and Data::Dumper can't see, which leads
58             to all sorts of problems later. So, if you use a blessed scalar as
59             your object representation then you'll have to use the complicity
60             features. Sorry.
61              
62             Pixie can additionally be used to name objects in the store, and fetch them
63             later on with that name.
64              
65             =cut
66              
67 16     16   2659590 use strict;
  16         41  
  16         838  
68 16     16   89 use warnings::register;
  16         30  
  16         2990  
69 16     16   96 use Carp;
  16         31  
  16         1403  
70              
71 16     16   13590 use Data::UUID;
  16         11172  
  16         1124  
72 16     16   10003 use Pixie::Proxy;
  16         54  
  16         506  
73 16     16   20774 use Data::Dumper;
  16         155509  
  16         1312  
74 16     16   434 use Scalar::Util qw/ blessed reftype isweak /;
  16         33  
  16         1009  
75            
76 16     16   10103 use Pixie::Store;
  16         135  
  16         489  
77 16     16   8291 use Pixie::ObjectInfo;
  16         43  
  16         402  
78 16     16   8122 use Pixie::ObjectGraph;
  16         41  
  16         409  
79              
80 16     16   8603 use Pixie::LiveObjectManager;
  16         41  
  16         451  
81 16     16   100 use Pixie::Complicity;
  16         32  
  16         325  
82              
83 16     16   9273 use Pixie::LockStrat::Null;
  16         35  
  16         1066  
84              
85             our $VERSION="2.06";
86             our $the_current_pixie;
87             our $the_current_oid;
88             our $the_current_lock_strategy;
89             our $the_current_object_graph;
90              
91 16     16   91 use base 'Pixie::Object';
  16         25  
  16         63424  
92              
93             #use overload
94             # '""' => 'as_string';
95              
96              
97             sub as_string {
98 1     1 0 9 my $self = shift;
99 1         8 my $str = ref($self) . ": " . $self->_oid . "\n";
100 1 50       128 $str .= " " . $self->store->as_string . "\n" if $self->store;
101             }
102              
103             #BEGIN { $Data::Dumper::Useperl = 1 }
104              
105             ## CLASS METHODS
106             sub get_the_current_pixie {
107 100     100 0 186 my $class = shift;
108 100         521 return $the_current_pixie;
109             }
110              
111             sub get_the_current_oid {
112 0     0 0 0 my $class = shift;
113 0         0 return $the_current_oid;
114             }
115              
116             sub get_the_current_lock_strategy {
117 0     0 0 0 return $the_current_lock_strategy;
118             }
119              
120             sub get_the_current_object_graph {
121 0     0 0 0 return $the_current_object_graph;
122             }
123              
124              
125             sub init {
126 24     24 0 42 my $self = shift;
127              
128 24         95 $self->connect('memory');
129 24         257 $self->{_objectmanager} = Pixie::LiveObjectManager->new->set_pixie($self);
130 24         65 return $self;
131             }
132              
133             sub connect {
134 46     46 0 70 my $self = shift;
135 46 50       166 $self = $self->new unless ref $self;
136 46         281 $self->store( Pixie::Store->connect(@_) );
137             }
138              
139             sub clear_storage {
140 7     7 0 1418 my $self = shift;
141 7         29 $self->store->clear;
142             }
143              
144             sub store {
145 173     173 0 270 my $self = shift;
146 173 100       372 if (@_) {
147 37         172 $self->{store} = shift;
148 37         188 return $self;
149             } else {
150 136         863 return $self->{store};
151             }
152             }
153              
154             sub clear_store {
155 0     0 0 0 my $self = shift;
156 0         0 $self->{store} = undef;
157             }
158              
159             sub lock_strategy {
160 0     0 0 0 my $self = shift;
161 0 0       0 if (@_) {
162 0         0 $self->{lock_strategy} = shift;
163 0         0 $self;
164             }
165             else {
166 0   0     0 $self->{lock_strategy} ||= Pixie::LockStrat::Null->new;
167             }
168             }
169              
170             sub lock_strategy_for {
171 0     0 0 0 my $self = shift;
172 0         0 my $obj_or_oid = shift;
173              
174 0 0       0 if (@_) {
175 0         0 $self->{_objectmanager}->lock_strategy_for($obj_or_oid, @_);
176 0         0 return $self;
177             }
178             else {
179 0         0 return $self->{_objectmanager}->lock_strategy_for($obj_or_oid);
180             }
181             }
182              
183             sub store_individual {
184 0     0 0 0 my $self = shift;
185 0         0 my $real = shift;
186              
187             die "Trying to store something unstorable" if
188 0 0       0 eval { $real->isa('Pixie::ObjectInfo') };
  0         0  
189 0         0 my $oid = $real->PIXIE::oid;
190 0 0       0 if (defined $oid) {
191 0         0 $self->store_individual_at($real, $oid);
192             }
193             else {
194 0         0 return $real;
195             }
196             }
197              
198              
199             sub store_individual_at {
200 0     0 0 0 my $self = shift;
201 0         0 my($obj, $oid, $strategy) = @_;
202 0   0     0 $strategy ||= $self->lock_strategy;
203 0 0       0 if ($Pixie::Stored{$oid}) {
204 0         0 return $Pixie::Stored{$oid};
205             }
206             else {
207 0         0 return Pixie::Proxy->
208             px_make_proxy($self->store->store_at($oid, $obj,$strategy));
209             }
210             }
211              
212             sub _oid {
213 11     11   20 my $self = shift;
214 11   66     104 $self->{_oid} ||= do {
215 3         23 require Data::UUID;
216 3         949 Data::UUID->new()->create_str();
217             }
218             }
219              
220             sub do_dump_and_eval {
221 19     19 0 47 my $self = shift;
222 19         164 my($thing, $do_lock) = @_;
223              
224 19         46 local $Data::Dumper::Deepcopy = 1;
225 19         38 local $the_current_pixie = $self;
226              
227 19         30 my $data_string;
228             {
229 19         39 my $dump_warn;
  19         45  
230 19   33 2   176 local $SIG{__WARN__} = sub { $dump_warn ||= join '', @_ };
  2         38  
231 19         140 $data_string = Dumper($thing);
232 19 100       245 die $dump_warn if $dump_warn;
233 17 50       169 die "Something went wrong with the Dump" unless length($data_string);
234             }
235              
236 17         32 my $VAR1;
237 17 50       146 $self->lock_store if $do_lock;
238 17         2359 eval $data_string;
239 17 50       253 die $@ if $@;
240 0 0       0 $self->unlock_store if $do_lock;
241 0         0 return $VAR1;
242             }
243              
244             sub _insert{
245 19     19   47 my $self = shift;
246 19         93 my $this = shift;
247              
248 19         49 local %Pixie::Stored;
249 19         47 local $Data::Dumper::Freezer = '_px_insertion_freeze';
250 19         78 local $Data::Dumper::Toaster = '_px_insertion_thaw';
251              
252 19         57 local %PIXIE::freeze_cache;
253 19         106 my $proxy = $self->do_dump_and_eval($this, 1);
254              
255 0 0       0 return defined($proxy) ? $proxy->_oid : undef;
256             }
257              
258             sub insertion_freeze {
259 30     30 0 58 my $self = shift;
260 30         53 my $thing = shift;
261 30         151 $self->ensure_storability($thing);
262 28         188 my $oid = $thing->PIXIE::oid;
263 28 50       118 return $PIXIE::freeze_cache{$oid} if defined $PIXIE::freeze_cache{$oid};
264 28         114 $self->cache_insert($thing);
265 28         214 $thing = $thing->px_freeze;
266 28         276 my $obj_holder = bless {oid => $oid,
267             class => ref($thing),
268             content => $thing->px_as_rawstruct },
269             'Pixie::ObjHolder';
270 28         1154 $PIXIE::freeze_cache{$oid} = $obj_holder;
271             }
272              
273              
274             sub insertion_thaw {
275 17     17 0 36 my $self = shift;
276 17         33 my $obj_holder = shift;
277 17 50       418 die "Object is not a Pixie::ObjHolder" unless $obj_holder->isa('Pixie::ObjHolder');
278              
279 0         0 my $thing = bless $obj_holder->{content}, $obj_holder->{class};
280 0         0 my $thing_oid = $obj_holder->{oid};
281              
282 0         0 $self->{_objectmanager}->bind_object_to_oid($thing, $thing_oid);
283 0         0 $self->ensure_storability($thing);
284 0         0 my $retval = $self->store_individual_at($thing, $obj_holder->{oid});
285              
286             # Set up GC stuff
287 0 0       0 if (my $graph = Pixie->get_the_current_object_graph) {
288 0         0 $graph->add_edge($thing_oid => $_) for
289             $self->proxied_content($obj_holder);
290             }
291 0         0 bless $thing, 'Class::Whitehole';
292 0         0 return $retval;
293             }
294              
295              
296             sub insert {
297 19     19 0 915741 my $self = shift;
298 19         162 my $graph = Pixie::ObjectGraph->new;
299              
300 19         45 my $ret = eval {
301 19         56 local $the_current_object_graph = $graph;
302 19         107 $self->_insert(@_)
303             };
304 19 50       359 $self->bail_out($@) if $@;
305 0 0       0 $self->_insert($self->object_graph->add_graph($graph))
306             unless $_[0]->isa('Pixie::ObjectGraph');
307 0         0 $self->add_to_rootset(@_);
308 0         0 return $ret;
309             }
310              
311             sub get {
312 0     0 0 0 my $self = shift;
313 0         0 my($oid) = @_;
314 0         0 $self->get_with_strategy($oid, $self->lock_strategy);
315             }
316              
317             sub get_with_strategy {
318 0     0 0 0 my $self = shift;
319 0         0 my($oid, $strategy) = @_;
320 0   0     0 $strategy ||= do {
321 0         0 carp "Called with blank strategy";
322 0         0 $self->lock_strategy;
323             };
324 0         0 local $the_current_lock_strategy = $strategy;
325              
326 0         0 $self->lock_store;
327 0         0 $strategy->pre_get($oid, $self);
328 0         0 my $res = eval {$self->_get($oid)};
  0         0  
329 0         0 my $err = $@;
330 0         0 $strategy->post_get($oid, $self);
331 0 0       0 $self->bail_out($err) if $err;
332 0         0 $self->unlock_store;
333 0         0 return $res;
334             }
335              
336             sub bail_out {
337 19     19 0 52 my $self = shift;
338 19         81 $self->rollback_store;
339 19         142 $self->unlock_store;
340 19         1399 die @_;
341             }
342              
343             sub delete {
344 0     0 0 0 my $self = shift;
345 0         0 my $obj_or_oid = shift;
346              
347 0 0       0 my $oid = ref($obj_or_oid) ? $obj_or_oid->PIXIE::oid : $obj_or_oid;
348 0         0 $self->cache_delete($oid);
349 0         0 $self->store->remove_from_store($oid);
350             }
351              
352             sub forget_about {
353 0     0 0 0 my $self = shift;
354 0 0       0 return unless ref($self);
355 0         0 my $obj = shift;
356 0         0 $obj->PIXIE::set_info(undef);
357             }
358              
359 17     17 0 84 sub lock_store { $_[0]->store->lock; }
360 19     19 0 74 sub unlock_store { $_[0]->store->unlock; }
361 19     19 0 64 sub rollback_store { $_[0]->store->rollback; }
362              
363             sub _get {
364 0     0   0 my $self = shift;
365 0         0 my $oid = shift;
366              
367 0 0       0 return undef unless defined $oid;
368 0         0 my $cached_struct = $self->cache_get($oid);
369 0 0 0     0 return $cached_struct if defined($cached_struct)
370             && ! $cached_struct->isa('Pixie::Object');
371              
372 0         0 local $Data::Dumper::Freezer = '_px_extraction_freeze';
373 0         0 local $Data::Dumper::Toaster = '_px_extraction_thaw';
374 0         0 local $the_current_oid = $oid;
375              
376 0         0 my $rawstruct = $self->store->get_object_at( $oid );
377 0 0       0 return unless defined($rawstruct);
378              
379 0         0 my $newstruct = $self->do_dump_and_eval($rawstruct);
380 0         0 bless $rawstruct, 'Class::Whitehole';
381 0         0 return scalar $self->cache_insert($newstruct);
382             }
383              
384             sub extraction_freeze {
385 0     0 0 0 my $self = shift;
386 0         0 my $thing = shift;
387 0         0 return $thing;
388             }
389              
390             sub extraction_thaw {
391 0     0 0 0 my $self = shift;
392 0         0 my $thing = shift;
393              
394 0         0 my $oid = Pixie->get_the_current_oid;
395 0         0 $thing = $thing->px_thaw;
396              
397 0         0 my $real_obj = $thing->px_do_final_restoration;
398              
399 0 0       0 bless $thing, 'Class::Whitehole' unless
400             $thing->PIXIE::address == $real_obj->PIXIE::address;
401              
402 0         0 $self->{_objectmanager}->bind_object_to_oid($real_obj => $oid);
403 0 0       0 $real_obj->PIXIE::oid eq $oid or die "Bad OID stuff";
404 0         0 $self->cache_insert($real_obj);
405 0         0 return $real_obj;
406             }
407              
408             sub make_new_object {
409 0     0 0 0 my $self = shift;
410 0         0 my($struct, $class) = @_;
411              
412 0         0 my $real = eval { $class->px_empty_new };
  0         0  
413 0 0       0 if ($@) {
414 0         0 $real = bless $struct, $class;
415             }
416             else {
417 0         0 my $type = reftype($struct);
418              
419 0 0       0 if ($type eq 'SCALAR') {
    0          
    0          
420 0         0 $$real = $$struct;
421             }
422             elsif ($type eq 'ARRAY') {
423 0         0 @$real = @$struct;
424             }
425             elsif ($type eq 'HASH') {
426 0         0 %$real = %$struct;
427             }
428             else {
429 0         0 return $struct;
430             }
431             }
432 0         0 return $real;
433             }
434              
435             sub manages_object {
436 5     5 0 7 my $self = shift;
437 5         8 my($obj) = @_;
438              
439 5         11 $self->_oid eq $obj->PIXIE::get_info->pixie_id;
440             }
441              
442              
443             sub cache_insert {
444 28     28 0 55 my $self = shift;
445 28         178 $self->{_objectmanager}->cache_insert(@_);
446             }
447              
448             sub cache_size {
449 9     9 0 1161706 my $self = shift;
450 9         50 $self->{_objectmanager}->cache_size;
451             }
452              
453             sub cache_get {
454 0     0 0 0 my $self = shift;
455 0 0       0 return undef unless defined $self->{_objectmanager};
456 0         0 $self->{_objectmanager}->cache_get(@_);
457             }
458              
459             sub cache_delete {
460 25     25 0 44 my $self = shift;
461 25 100       178 $self->{_objectmanager}->cache_delete(@_) if defined $self->{_objectmanager};
462             }
463              
464             sub get_cached_keys {
465 0     0 0 0 my $self = shift;
466 0         0 $self->{_objectmanager}->cache_keys;
467             }
468              
469             # The naming section
470              
471             sub bind_name {
472 1     1 0 1731 my $self = shift;
473 1         4 my($name, @objects) = @_;
474              
475 1         480 require Pixie::Name;
476 1         9 Pixie::Name->name_object_in($name, \@objects, $self);
477             }
478              
479             sub unbind_name {
480 0     0 0 0 my $self = shift;
481 0         0 my($name) = @_;
482              
483 0         0 require Pixie::Name;
484 0         0 Pixie::Name->remove_name_from($name, $self);
485             }
486              
487             sub get_object_named {
488 0     0 0 0 my $self = shift;
489 0         0 my($name, $strategy) = @_;
490 0         0 require Pixie::Name;
491 0         0 Pixie::Name->get_object_from($name, $self, $strategy);
492             }
493              
494              
495              
496             # GC related stuff
497              
498             sub rootset {
499 0     0 0 0 my $self = shift;
500 0         0 $self->{store}->rootset;
501             }
502              
503             sub add_to_rootset {
504 0     0 0 0 my $self = shift;
505 0         0 $self->store->add_to_rootset($_) for grep $_->px_in_rootset, @_ ;
506 0         0 return $self;
507             }
508              
509             sub proxy_finder {
510 0     0 0 0 my $obj = shift;
511 0 0       0 $Pixie::neighbours{$obj->_oid} = 1 if ref($obj)->isa('Pixie::Proxy');
512 0         0 return $obj;
513             }
514              
515             sub proxied_content {
516 0     0 0 0 my $self = shift;
517 0         0 my $obj_holder = shift;
518              
519 0         0 local %Pixie::neighbours;
520              
521             # Turn off deepcopy or things get *very* slow.
522 0         0 local $Data::Dumper::Deepcopy = 0;
523 0         0 local $Data::Dumper::Freezer = 'Pixie::proxy_finder';
524 0         0 local $Data::Dumper::Toaster = undef;
525 0         0 Data::Dumper::DumperX($obj_holder);
526 0         0 return keys %Pixie::neighbours;
527             }
528              
529             sub neighbours {
530 0     0 0 0 my $self = shift;
531 0         0 my $oid = shift;
532 0         0 $self->object_graph->neighbours($oid);
533             }
534              
535              
536             sub run_GC {
537 0     0 0 0 my $self = shift;
538 0         0 $self->store->lock_for_GC;
539 0         0 my %live = map { $_ => 1 } $self->live_set;
  0         0  
540 0         0 for ($self->working_set) {
541 0 0       0 $self->delete($_) unless $live{$_};
542             }
543 0         0 $self->store->unlock_after_GC;
544 0         0 return $self;
545             }
546              
547             sub live_set {
548 0     0 0 0 my $self = shift;
549 0         0 my $graph = $self->object_graph;
550 0         0 my %seen = ();
551 0         0 my @nodes_to_process = $self->rootset;
552              
553 0         0 while (@nodes_to_process) {
554 0         0 my $node = pop @nodes_to_process;
555 0 0       0 next if $seen{$node};
556 0         0 $seen{$node} = 1;
557 0         0 push @nodes_to_process, $graph->neighbours($node);
558             }
559 0         0 return keys %seen;
560             }
561              
562             sub object_graph {
563 0     0 0 0 my $self = shift;
564 0         0 $self->store->object_graph_for($self);
565             }
566              
567             sub working_set {
568 0     0 0 0 my $self = shift;
569 0         0 $self->store->working_set_for($self);
570             }
571              
572             sub ensure_storability {
573 30     30 0 49 my $self = shift;
574 30         52 my $obj = shift;
575              
576 30 100       238 $obj->px_is_storable or die "Pixie cannot store a ", ref($obj);
577             }
578              
579             sub lock_object {
580 0     0 0 0 my $self = shift;
581 0         0 $self->{_objectmanager}->lock_object(@_);
582             }
583              
584             sub unlock_object {
585 0     0 0 0 my $self = shift;
586 0         0 $self->{_objectmanager}->unlock_object(@_);
587             }
588              
589             sub DESTROY {
590 20     20   4555 my $self = shift;
591 20 50       63 $self->store->release_all_locks if defined $self->store;
592 20         127 delete $self->{_objectmanager};
593             }
594              
595             sub px_freeze {
596 0     0 0   my $self = shift;
597 0           return bless {}, ref($self);
598             }
599              
600             sub _px_extraction_thaw {
601 0     0     my $self = shift;
602 0           $self->get_the_current_pixie;
603             }
604              
605              
606             =head1 SEE ALSO
607              
608             L -- Sometimes Pixie can't make an object
609             persistent without help from the object's class. In that case you need
610             to make the class 'complicit' with Pixie. You'll typically need to do
611             this with XS based classes that use a simple scalar as their perl
612             visible object representation, or with closure based classes.
613              
614             L -- There are some methods that Pixie requires
615             to behave in a particular way, not subject to the vagaries of
616             overloading. One option would be to write a bunch of private
617             subroutines and methods within Pixie, but very often it makes sense to
618             move the behaviour onto the objects being
619             stored. L describes how we achieve this.
620              
621             L is the abstract interface to physical storage. If you
622             want to write a new backend for pixie, start here.
623              
624             =head1 WITH THANKS
625              
626             Jean Louis Leroy, author of Tangram, for letting us use ideas and code from
627             the Tangram test suite.
628              
629             =head1 AUTHOR
630              
631             Pixie sprang from the mind of James Duncan . Piers
632             Cawley and Leon Brocard are his
633             co conspiritors.
634              
635             =head1 COPYRIGHT
636              
637             Copyright 2002 Fotango Ltd
638              
639             This software is released under the same license as Perl itself.
640              
641             =cut
642              
643             1;