File Coverage

blib/lib/Class/Persist.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Class::Persist - Persistency framework for objects
4              
5             =head1 SYNOPSIS
6              
7             package My::Person;
8             use base qw( Class::Persist );
9             __PACKAGE__->dbh( $dbh );
10             __PACKAGE__->simple_db_spec(
11             first_name => 'CHAR(30)',
12             last_name => 'CHAR(30)',
13             address => "My::Address", # has_a relationship
14             phones => [ "My::Phone" ], # has_many relationship
15             );
16              
17             my $person = My::Person->new( first_name => "Dave" );
18             $person->addesss( My::Address->new );
19             $person->store;
20            
21              
22             =head1 DESCRIPTION
23              
24             Provides the framework to persist the objects in a DB in a Class::DBI style
25              
26             =head1 INHERITANCE
27              
28             Class::Persist::Base
29              
30             =head1 METHODS
31              
32             =cut
33              
34             package Class::Persist;
35 3     3   65767 use strict;
  3         6  
  3         98  
36 3     3   16 use warnings;
  3         6  
  3         94  
37 3     3   4009 use Class::ISA;
  0            
  0            
38             use DateTime;
39             use Scalar::Util qw(blessed);
40             use Data::Structure::Util;
41              
42             use DBI;
43             use Class::Persist::Proxy;
44             use Class::Persist::Proxy::Collection;
45              
46             use base qw(Class::Persist::Base Class::Data::Inheritable);
47              
48             our $VERSION = '0.02';
49              
50             our $ID_FIELD = "OI";
51              
52             our $SQL; # sql cache
53             our $SCHEME = {}; # mapping class <=> db
54              
55             # Maximum number of rows to return.
56             our $LIMIT = 10_000;
57              
58             Class::Persist->mk_classdata('dbh');
59              
60             __PACKAGE__->db_fields( $Class::Persist::ID_FIELD, qw( creation_date timestamp owner ) );
61             __PACKAGE__->mk_accessors(qw( _from_db creation_date timestamp ));
62             __PACKAGE__->db_fields_spec(
63             $ID_FIELD.' CHAR(36) PRIMARY KEY',
64             'timestamp TIMESTAMP',
65             'creation_date CHAR(30) NOT NULL',
66             'owner CHAR(36)',
67             );
68              
69             require Class::Persist::Tracker;
70             require Class::Persist::Deleted;
71              
72             exception Class::Persist::Error::DB extends => 'Class::Persist::Error';
73             exception Class::Persist::Error::DB::Connection extends => 'Class::Persist::Error::DB';
74             exception Class::Persist::Error::DB::Request extends => 'Class::Persist::Error::DB';
75             exception Class::Persist::Error::DB::NotFound extends => 'Class::Persist::Error::DB';
76             exception Class::Persist::Error::DB::Duplicate extends => 'Class::Persist::Error::DB';
77             exception Class::Persist::Error::DB::UTF8 extends => 'Class::Persist::Error::DB';
78              
79             =head2 creation_date
80              
81             A string representing when this object was originally created.
82              
83             =cut
84              
85             sub init {
86             my $self = shift;
87             $self->SUPER::init(@_) or return;
88              
89             unless ($self->creation_date) {
90             my $now = DateTime->now;
91             my $string = $now->ymd('-') . ' ' . $now->hms(':');
92             $self->creation_date( $string );
93             }
94              
95             return $self->_setup_relationships;
96             }
97              
98             sub _populate {
99             my $self = shift;
100             $self->SUPER::_populate(@_);
101             return $self->_setup_relationships;
102             }
103              
104             # put placeholders in the has_many, etc, slots.
105             # called after init and populate.
106             sub _setup_relationships {
107             my $self = shift;
108              
109             my $methods = $self->might_have_all;
110             foreach my $method (keys %$methods) {
111             my $proxy = Class::Persist::Proxy->new();
112             $proxy->class( $methods->{$method} );
113             $proxy->owner( $self );
114             $self->set( $method => $proxy );
115             }
116             $methods = $self->has_many_all;
117             foreach my $method (keys %$methods) {
118             my $proxy = Class::Persist::Proxy::Collection->new();
119             $proxy->class( $methods->{$method} );
120             $proxy->owner( $self );
121             $self->set( $method => $proxy );
122             }
123             $self->inflate();
124              
125             return $self;
126             }
127              
128              
129             =head2 load( $id )
130              
131             Loads an object from the database. Can be used in three different ways -
132              
133             =over 4
134              
135             =item Class::Persist->load( $id )
136              
137             loads an object based on its oid
138              
139             =item Class::Persist->load( key => $value )
140              
141             get the first match of single key test
142              
143             Person->load( name => "Dave" );
144              
145             =item $obj->load()
146              
147             loads an object based on its current state, eg -
148              
149             my $person = Person->new;
150             $person->name('Harry');
151             $person->load or die "There's noone called Harry";
152             print $person->email;
153              
154             =back
155              
156             =cut
157              
158             sub load {
159             my $class = shift;
160             # If it is an instance call, replace by loaded object
161             if (ref $class) {
162             my $real_class = ref $class;
163             my $self = $real_class->_load( $ID_FIELD => $class->oid ) or return;
164             $class->same_than($self) or return;
165             return $class->_duplicate_from($self);
166             }
167             # Class call
168              
169             # load by owner for might_have relationships
170             if (blessed( $_[0] )) {
171             my $self = $class->_load( owner => $_[0]->oid ) or return;
172             $self->owner($_[0]);
173             return $self;
174             }
175              
176             $class->_load($ID_FIELD, @_);
177             }
178              
179              
180             sub _load {
181             my $class = shift;
182              
183             my $id = pop or return $class->record('Class::Persist::Error::InvalidParameters', "Need an id to load object", 1);
184             my $idField = pop or return $class->record('Class::Persist::Error::InvalidParameters', "Need an id to load object", 1);
185              
186             my (@got) = $class->sql("$idField=?", $id);
187             if (@got != 1) {
188             return $class->record('Class::Persist::Error::DB::NotFound', "No object $id loaded for $class");
189             }
190             $got[0];
191             }
192              
193             =head2 revert()
194              
195             revert an object back to its state in the database.
196              
197             TODO - make recursive
198              
199             =cut
200              
201             sub revert {
202             my $self = shift;
203             Class::Persist::Error->throw( text => "Can only revert objects" )
204             unless ref($self);
205              
206             Class::Persist::Error::DB->throw( text => "Object is not from the database" )
207             unless $self->_from_db;
208              
209             my $reverted = ref($self)->_load( $ID_FIELD => $self->oid )
210             or Class::Persist::Error::DB->throw( text => "No object with that oid in DB");
211              
212             return $self->_duplicate_from($reverted);
213             }
214              
215              
216             =head2 store()
217              
218             Store the object in DB and all objects within, whether it is a new object or an update
219              
220             =cut
221              
222             sub store {
223             my $self = shift;
224             $self->check_store(@_) or return; # check_store records errors;
225              
226             $self->store_might_have() or return;
227             $self->store_has_many() or return;
228             $self->deflate();
229              
230             if ($self->_from_db) {
231             $self->db_update() or return;
232             }
233             else {
234             $self->db_insert() or return;
235             $self->track();
236             }
237             $self->_from_db(1);
238              
239             $self->inflate();
240             }
241              
242              
243             =head2 delete()
244              
245             Deletes the object and returns true if successful.
246             It will delete recursively all objects within.
247              
248             =cut
249              
250             sub delete {
251             my $self = shift;
252             return $self->record('Class::Persist::Error', "Can't delete a non stored object", 1) unless $self->_from_db;
253              
254             my $methods = $self->might_have_all;
255             foreach my $method (keys %$methods) {
256             my $obj = $self->get( $method ) or next;
257             $obj->delete() or next;
258             Class::Persist::Proxy->proxy($obj);
259             }
260             $methods = $self->has_many_all;
261             foreach my $method (keys %$methods) {
262             my $obj = $self->get( $method ) or next;
263             $obj->delete();
264             }
265             $methods = $self->has_a_all;
266             foreach my $method (keys %$methods) {
267             my $obj = $self->get( $method ) or next;
268             $obj->delete();
269             Class::Persist::Proxy->proxy($obj);
270             }
271              
272             $self->deleteThis();
273             }
274              
275              
276             =head2 deleteThis()
277              
278             Deletes the object and returns true if successful.
279             Does not delete recursively any objects within.
280              
281              
282             =cut
283              
284             sub deleteThis {
285             my $self = shift;
286             my $dbh = $self->dbh;
287             my $table = $self->db_table;
288              
289             my $sql = "DELETE FROM $table WHERE $ID_FIELD=?";
290              
291             my $r = $dbh->prepare_cached($sql) or Class::Persist::Error::DB::Request->throw(text => "Could not prepare $sql - $DBI::errstr");
292             $r->execute($self->oid) or Class::Persist::Error::DB::Request->throw(text => "Could not execute $sql - $DBI::errstr");
293             $r->finish;
294              
295             $self->store_deleted();
296             $self->_from_db(0);
297             }
298              
299             =head2 owner( $obj )
300              
301             =cut
302              
303             sub owner {
304             my $self = shift;
305             if (my ($owner) = @_) {
306             if (blessed($owner) and ! $owner->isa('Class::Persist::Proxy')) {
307             my $proxy = Class::Persist::Proxy->new();
308             $proxy->class( ref $owner );
309             $proxy->real_id( $owner->oid );
310             $owner = $proxy;
311             }
312             return $self->set('owner', $owner);
313             }
314             return $self->get('owner');
315             }
316              
317             =head2 oids_for_owner( $owner )
318              
319             =cut
320              
321             sub oids_for_owner {
322             my $self = shift;
323             my $owner = shift or Class::Persist::Error::InvalidParameters->throw(text => "A owner should be passed");
324              
325             my $dbh = $self->dbh;
326             my $table = $self->db_table;
327             my $sql = "SELECT $ID_FIELD FROM $table WHERE owner=? LIMIT $LIMIT";
328              
329             my $r = $dbh->prepare_cached($sql) or Class::Persist::Error::DB::Request->throw(text => "Could not prepare $sql - $DBI::errstr");
330             $r->execute($owner->oid) or Class::Persist::Error::DB::Request->throw(text => "Could not execute $sql - $DBI::errstr");
331             my $rows = $r->fetchall_arrayref or return $self->record('Class::Persist::Error::DB::NotFound', "No object loaded");
332             $r->finish();
333              
334             Class::Persist::Error::DB::Request->throw(text => "Limit reached in $sql") if (@$rows == $LIMIT);
335             [ map $_->[0], @$rows ];
336             }
337              
338             =head2 track()
339              
340             Store the class and oid to make future retrieval easier
341              
342             =cut
343              
344             sub track {
345             my $self = shift;
346             Class::Persist::Tracker->new()->object( $self )->store(); # or die "can't track $self";
347             }
348              
349              
350             =head2 store_deleted()
351              
352             Stores the object in the deleted object table.
353              
354             =cut
355              
356             sub store_deleted {
357             Class::Persist::Deleted->new()->object( shift )->store();
358             }
359              
360              
361             =head2 store_might_have()
362              
363             Stores all objects in a might-have relationship with this class.
364              
365             =cut
366              
367             sub store_might_have {
368             my $self = shift;
369             foreach my $key ( keys %{ $self->might_have_all } ) {
370             my $obj = $self->get($key) or next;
371             next if $obj->isa('Class::Persist::Proxy');
372             $obj->isa('Class::Persist') or Class::Persist::Error->throw(text => "Object not a Class::Persist");
373             $obj->owner( $self );
374             $obj->store() or return;
375             Class::Persist::Proxy->proxy($obj, $self);
376             }
377             $self;
378             }
379              
380              
381             =head2 store_has_many()
382              
383             Stores all objects in a one-to-many relationship with this class.
384              
385             =cut
386              
387             sub store_has_many {
388             my $self = shift;
389             foreach my $key ( keys %{ $self->has_many_all } ) {
390             my $obj = $self->get( $key ) or next;
391             $obj->isa('Class::Persist::Proxy') or Class::Persist::Error->throw(text => "Object not a Class::Persist::Proxy");
392             $obj->owner( $self );
393             $obj->store() or return;
394             }
395             $self;
396             }
397              
398             =head2 deflate()
399              
400             Store the object, and replace an object with a Class::Persist::Proxy
401             pointing at it in the database.
402              
403             =cut
404              
405             sub deflate {
406             my $self = shift;
407             my $methods = $self->has_a_all;
408             foreach my $method (keys %$methods) {
409             my $obj = $self->get($method) or next;
410             next unless ref($obj);
411             $obj->store() or return;
412             $self->set( $method => $obj->oid );
413             }
414              
415             if (my $owner = $self->owner) {
416             $self->owner( $owner->oid ) if ref($owner);
417             }
418              
419             $self;
420             }
421              
422              
423             =head2 inflate()
424              
425             Replace oids by a proxy
426              
427             =cut
428              
429             sub inflate {
430             my $self = shift;
431             my $methods = $self->has_a_all;
432             foreach my $method (keys %$methods) {
433             my $oid = $self->get($method) or next;
434             next if ref($oid);
435             my $proxy = Class::Persist::Proxy->new;
436             $proxy->oid($oid);
437             $proxy->class( $methods->{$method} );
438             $proxy->real_id( $oid );
439             $self->set( $method => $proxy );
440             }
441              
442             if (my $owner = $self->owner) {
443             unless (ref $owner) {
444             my $proxy = Class::Persist::Proxy->new();
445             $proxy->real_id( $owner );
446             $self->owner( $proxy );
447             }
448             }
449              
450             $self;
451             }
452              
453              
454             =head2 check_store()
455              
456             =cut
457              
458             sub check_store {
459             my $self = shift;
460             $self->validate() or return $self->record('Class::Persist::Error::InvalidParameters', "validation of $self failed", 1);
461             $self->unique() or return $self->record('Class::Persist::Error::DB::Duplicate', "duplicate of $self found", 1);
462             1;
463             }
464              
465             =head2 clone()
466              
467             Deep-clones the object - any child objects will also be cloned. All new objects
468             will have new oids.
469              
470             =cut
471              
472             sub clone {
473             my $self = shift;
474              
475             # de-proxificate more than once, because loading might create more
476             # proxies
477             my $deproxificated = 1;
478             while ($deproxificated) {
479             $deproxificated = 0;
480             foreach my $object (@{ Data::Structure::Util::get_blessed($self) }) {
481             if ($object->isa('Class::Persist::Proxy')) {
482             $object->load
483             or die "Can't load $object with oid ".$object->real_id." : $@\n";
484             $deproxificated++;
485             }
486             }
487             }
488              
489             my $clone = $self->SUPER::clone(@_);
490              
491             foreach my $object (@{ Data::Structure::Util::get_blessed($clone) }) {
492             if ($object->isa('Class::Persist')) {
493             $object->_from_db(0);
494             }
495             }
496              
497             return $clone;
498             }
499              
500             =head2 validate()
501              
502             Returns true if the object is in a good, consistent state and can be stored.
503             Override this method if you want to make sure your objects are consistent
504             before storing.
505              
506             =cut
507              
508             sub validate { 1 }
509              
510              
511             =head2 unique()
512              
513             Returns true if the current object is unique, ie there is no other row in
514             the database that has the same value as this object. The query that is
515             used to check for uniqueness is defined by the L method.
516              
517             Only checked for unstored objects - objects that have come from the database
518             are presumed to be unique.
519              
520             =cut
521              
522             # _WHY_ are they presumed to be unique?
523              
524             sub unique {
525             my $self = shift;
526             return 1 if $self->_from_db; # shortcut - no need to test if obj is from db
527             my $dbh = $self->dbh;
528             my @params = $self->unique_params;
529             ! ($dbh->selectrow_array(shift @params, undef, @params))[0];
530             }
531              
532              
533              
534             =head2 same_than( $obj )
535              
536             Compares all the fields containing a scalar except oid
537              
538             =cut
539              
540             sub same_than {
541             my $self = shift;
542             my $other = shift;
543             foreach my $key ($self->db_fields) {
544             next if ($key eq $ID_FIELD);
545             next if ( !$self->get($key) and !$other->get($key) );
546             next if ref($self->get($key));
547             next if ($self->get($key) eq $other->get($key));
548             return $self->record('Class::Persist::Error::InvalidParameters', "Parameter $key mismatch", 1);
549             }
550             1;
551             }
552              
553              
554             =head2 might_have( $method => $class )
555              
556             =cut
557              
558             sub might_have {
559             my $self = shift;
560             my $class = ref($self) || $self;
561             if (my $method = shift) {
562             my $target = shift;
563             $SCHEME->{$class}->{this}->{might_have}->{$method} = $target;
564             }
565             $SCHEME->{$class}->{this}->{might_have};
566             }
567              
568              
569             =head2 might_have_all()
570              
571             =cut
572              
573             sub might_have_all {
574             my $self = shift;
575             my $class = ref($self) || $self;
576              
577             unless ( $SCHEME->{$class}->{all}->{might_have} ) {
578             $SCHEME->{$class}->{all}->{might_have} = {};
579             foreach my $isa ( reverse $class, Class::ISA::super_path($class) ) {
580             exists $SCHEME->{$isa} or next;
581             my $methods = $SCHEME->{$isa}->{this}->{might_have} or next;
582             %{$SCHEME->{$class}->{all}->{might_have}} = (%{$SCHEME->{$class}->{all}->{might_have}}, %$methods);
583             }
584             }
585              
586             $SCHEME->{$class}->{all}->{might_have};
587             }
588              
589              
590             =head2 has_a( $method => $class )
591              
592             Class method. Defines a has_a relationship with another class.
593              
594             Person::Body->has_a( head => "Person::Head" );
595             my $nose = $body->head->nose;
596              
597             Allows you to store references to other Class::Persist objects. They will
598             be serialised when stored in the database.
599              
600             =cut
601              
602             sub has_a {
603             my $self = shift;
604             my $class = ref($self) || $self;
605             if (my $method = shift) {
606             my $target = shift;
607             $SCHEME->{$class}->{this}->{has_a}->{$method} = $target;
608             }
609             $SCHEME->{$class}->{this}->{has_a};
610             }
611              
612              
613             =head2 has_a_all()
614              
615             =cut
616              
617             sub has_a_all {
618             my $self = shift;
619             my $class = ref($self) || $self;
620              
621             unless ( $SCHEME->{$class}->{all}->{has_a} ) {
622             $SCHEME->{$class}->{all}->{has_a} = {};
623             foreach my $isa ( reverse $class, Class::ISA::super_path($class) ) {
624             exists $SCHEME->{$isa} or next;
625             my $methods = $SCHEME->{$isa}->{this}->{has_a} or next;
626             %{$SCHEME->{$class}->{all}->{has_a}} = (%{$SCHEME->{$class}->{all}->{has_a}}, %$methods);
627             }
628             }
629              
630             $SCHEME->{$class}->{all}->{has_a};
631             }
632              
633              
634             =head2 has_many( $method => $class )
635              
636             Class method. Defineds a one to many relationship with another class.
637              
638             Person::Body->has_many( arms => 'Person::Arm' );
639             my $number_of_arms = $body->arms->count;
640              
641             Allows you to manipulate a number of other Class::Persist objects that are
642             associated with this one. This method will return a
643             L that handles the child objects, it
644             provides push, pop, count, etc, methods to add and remove objects from the
645             list.
646              
647             my $left_arm = Person::Arm->new;
648             $body->arms->push( $left_arm );
649              
650             =cut
651              
652             sub has_many {
653             my $self = shift;
654             my $class = ref($self) || $self;
655             if (my $method = shift) {
656             my $target = shift;
657             $SCHEME->{$class}->{this}->{has_many}->{$method} = $target;
658             }
659             $SCHEME->{$class}->{this}->{has_many};
660             }
661              
662              
663             =head2 has_many_all()
664              
665             =cut
666              
667             sub has_many_all {
668             my $self = shift;
669             my $class = ref($self) || $self;
670              
671             unless ( $SCHEME->{$class}->{all}->{has_many} ) {
672             $SCHEME->{$class}->{all}->{has_many} = {};
673             foreach my $isa ( reverse $class, Class::ISA::super_path($class) ) {
674             exists $SCHEME->{$isa} or next;
675             my $methods = $SCHEME->{$isa}->{this}->{has_many} or next;
676             %{$SCHEME->{$class}->{all}->{has_many}} = (%{$SCHEME->{$class}->{all}->{has_many}}, %$methods);
677             }
678             }
679              
680             $SCHEME->{$class}->{all}->{has_many};
681             }
682              
683              
684             =head2 unique_params()
685              
686             SQL query and binding params used to check unicity of object in DB
687              
688             =cut
689              
690             sub unique_params {
691             my $self = shift;
692             my $table = $self->db_table;
693             ("SELECT 1 FROM $table WHERE $ID_FIELD=?", $self->oid);
694             }
695              
696              
697             =head2 db_table( $table )
698              
699             Get/set accessor for the DB table used to store this class.
700              
701             =cut
702              
703             sub db_table {
704             my $self = shift;
705             my $class = ref($self) || $self;
706             if (my $table = shift) {
707             $SCHEME->{$class}->{table} = $table;
708             }
709             $SCHEME->{$class}->{table};
710             }
711              
712              
713             =head2 db_fields( @fields )
714              
715             Get/set accessor for the DB fields used to store the attributes specific to
716             class (but not its parent(s)). Override this in your class to define the scalar
717             properties of your object that should be stored in columns of the database.
718              
719             =cut
720              
721             sub db_fields {
722             my $self = shift;
723             my $class = ref($self) || $self;
724             if (my @fields = @_) {
725             $SCHEME->{$class}->{this}->{fields} = \@fields;
726             }
727             @{$SCHEME->{$class}->{this}->{fields} || []};
728             }
729              
730              
731             =head2 db_fields_all()
732              
733             Get/set accessor for all the DB fields used to store this class.
734              
735             =cut
736              
737             sub db_fields_all {
738             my $self = shift;
739             my $class = ref($self) || $self;
740             if (my @fields = @_) {
741             $SCHEME->{$class}->{all}->{fields} = \@fields;
742             }
743              
744             unless ( $SCHEME->{$class}->{all}->{fields} ) {
745             $SCHEME->{$class}->{all}->{fields} = [];
746             foreach my $isa ( reverse $class, Class::ISA::super_path($class) ) {
747             exists $SCHEME->{$isa} or next;
748             if (my $fields = $SCHEME->{$isa}->{this}->{fields}) {
749             push @{$SCHEME->{$class}->{all}->{fields}}, @$fields;
750             }
751             if (my $fields = $SCHEME->{$isa}->{this}->{has_a}) {
752             push @{$SCHEME->{$class}->{all}->{fields}}, keys(%$fields);
753             }
754             }
755             }
756              
757             my %unique = map { $_ => 1 } @{$SCHEME->{$class}->{all}->{fields}};
758             @{$SCHEME->{$class}->{all}->{fields}} = sort(keys(%unique));
759             }
760              
761             =head2 binary_fields( @fields )
762              
763             =cut
764              
765             sub binary_fields {
766             my $self = shift;
767             my $class = ref($self) || $self;
768             if (my @fields = @_) {
769             $SCHEME->{$class}->{this}->{binary} = \@fields;
770             }
771             @{$SCHEME->{$class}->{this}->{binary}};
772             }
773              
774              
775             =head2 binary_fields_all()
776              
777             =cut
778              
779             sub binary_fields_all {
780             my $self = shift;
781             my $class = ref($self) || $self;
782             if (my @binary = @_) {
783             $SCHEME->{$class}->{all}->{binary} = \@binary;
784             }
785              
786             unless ( $SCHEME->{$class}->{all}->{binary} ) {
787             $SCHEME->{$class}->{all}->{binary} = [];
788             foreach my $isa ( reverse $class, Class::ISA::super_path($class) ) {
789             exists $SCHEME->{$isa} or next;
790             if (my $binary = $SCHEME->{$isa}->{this}->{binary}) {
791             push @{$SCHEME->{$class}->{all}->{binary}}, @$binary;
792             }
793             }
794             }
795              
796             my %unique = map { $_ => 1 } @{$SCHEME->{$class}->{all}->{binary}};
797             @{$SCHEME->{$class}->{all}->{binary}} = sort(keys(%unique));
798             }
799              
800             =head2 db_insert()
801              
802             Insert the object in the DB as a new entry
803              
804             =cut
805              
806             sub db_insert {
807             my $self = shift;
808              
809             my $dbh = $self->dbh;
810             my $sql = $self->db_insert_sql;
811             my @fields = $self->db_fields_all;
812              
813             my %binary = map { $_ => 1 } $self->binary_fields_all;
814             my @values;
815             for my $field (@fields) {
816             my $value = $self->get($field);
817             utf8::encode($value) unless ($binary{$field} or !defined($value));
818             push @values, $value;
819             }
820              
821             my $r = $dbh->prepare_cached($sql)
822             or Class::Persist::Error::DB::Request->throw(
823             text => "Could not prepare $sql - $DBI::errstr");
824              
825             $r->execute(@values)
826             or Class::Persist::Error::DB::Request->throw(
827             text => "Could not execute $sql - $DBI::errstr");
828              
829             $r->finish;
830             }
831              
832              
833             =head2 db_update()
834              
835             Update the object in the DB
836              
837             =cut
838              
839             sub db_update {
840             my $self = shift;
841              
842             my $dbh = $self->dbh;
843             my $sql = $self->db_update_sql;
844             my @fields = $self->db_fields_all;
845              
846             my %binary = map { $_ => 1 } $self->binary_fields_all;
847             my @values;
848             for my $field (@fields) {
849             my $value = $self->get($field);
850             utf8::encode($value) unless ($binary{$field} or !defined($value));
851             push @values, $value;
852             }
853              
854             my $r = $dbh->prepare_cached($sql) or Class::Persist::Error::DB::Request->throw(text => "Could not prepare $sql - $DBI::errstr");
855             $r->execute(@values, $self->oid) or Class::Persist::Error::DB::Request->throw(text => "Could not execute $sql - $DBI::errstr");
856             $r->finish;
857             }
858              
859              
860             =head2 db_insert_sql()
861              
862             Generate SQL for an insert statement for this object
863              
864             =cut
865              
866             sub db_insert_sql {
867             my $self = shift;
868              
869             my $table = $self->db_table;
870             my $sql = $SQL->{$table}->{insert};
871             unless ($sql) {
872             my @fields = $self->db_fields_all;
873             my $columns = join(',', @fields);
874             my $holders = join(',', ('?') x scalar(@fields));
875             $sql = "INSERT INTO $table ($columns) VALUES ($holders)";
876             $SQL->{$table}->{insert} = $sql;
877             }
878             $sql;
879             }
880              
881              
882             =head2 db_update_sql()
883              
884             Generate SQL for an update statement for this object
885              
886             =cut
887              
888             sub db_update_sql {
889             my $self = shift;
890              
891             my $table = $self->db_table;
892             my $sql = $SQL->{$table}->{update};
893             unless ($sql) {
894             my @fields = $self->db_fields_all;
895             my $set = join(',', map { "$_=?" } @fields);
896             $sql = "UPDATE $table SET $set WHERE $ID_FIELD=?";
897             $SQL->{$table}->{update} = $sql;
898             }
899             $sql;
900             }
901              
902              
903             =head2 db_table_sql()
904              
905             =cut
906              
907             sub db_table_sql {
908             my $self = shift;
909             "(". join(', ', $self->db_fields_spec_all) .")";
910             }
911              
912             =head2 db_fields_spec()
913              
914             SQL to specificy the database columns needed to store the attributes of this
915             class - all parent class(es) columns are aggregated and used to build an SQL
916             create table statement. Override this to specify the columns used by your class,
917             if you want Class::Persist to be able to create your table for you.
918             Remember to call the superclass db_fields_spec as well, though.
919              
920             sub db_fields_spec(
921             shift->SUPER::db_fields_spec,
922             'Colour VARCHAR(63)',
923             'Mass VARCHAR(63)',
924             );
925              
926              
927             =cut
928              
929             sub db_fields_spec {
930             my $self = shift;
931             my $class = ref($self) || $self;
932             if (my @spec = @_) {
933             $SCHEME->{$class}->{this}->{db_fields_spec} = \@spec;
934             return $self;
935             }
936             return @{ $SCHEME->{$class}->{this}->{db_fields_spec} || [] };
937             }
938              
939             sub db_fields_spec_all {
940             my $self = shift;
941             my $class = ref($self) || $self;
942              
943             unless ( $SCHEME->{$class}->{all}->{db_fields_spec} ) {
944             my @list;
945             foreach my $isa ( reverse $class, Class::ISA::super_path($class) ) {
946             $isa->can('db_fields_spec') or next;
947             push @list, $isa->db_fields_spec;
948             }
949             my %u = map { $_ => 1 } @list;
950             @list = sort keys %u;
951             $SCHEME->{$class}->{all}->{db_fields_spec} = \@list;
952             }
953              
954             @{ $SCHEME->{$class}->{all}->{db_fields_spec} };
955             }
956              
957             =head2 simple_db_spec
958              
959             An alternative way of specifying the database spec, combining the field list,
960             has_a and has_many relationships and the database spec in one command.
961              
962             Person::Foot->simple_db_spec(
963             digits => 'INT',
964             name => 'CHAR(10)',
965             leg => 'Person::Leg',
966             hairs => [ 'Person::Leg::Hair' ],
967             );
968              
969             For each colnm as the keys of the passed hash, specify a simple DB field
970             with a DB type, a has_a relationship with a class name, and a has_many
971             relationship with a listref continain a single element - the class name.
972              
973             This will also automatically create a name for the database table, if you
974             don't want to supply one yourself. The name will be based on the package name.
975              
976             =cut
977              
978             sub simple_db_spec {
979             my $class = shift;
980             my %spec = ref($_[0]) ? %{$_[0]} : @_;
981             die "simple_db_spec is a class method" if ref($class);
982              
983             # make up a table name if needed
984             unless ($class->db_table) {
985             my $table = lc($class);
986             $table =~ s/::/_/g;
987             $class->db_table( $table );
988             }
989              
990              
991             # walk the spec, interpret minilanguage
992             # class names are turned into has_a relationships,
993             # listrefs become has_many relationships.
994             my @simple;
995             for my $col (keys %spec) {
996              
997             if (ref($spec{$col}) eq 'ARRAY') {
998             $class->has_many( $col, @{ $spec{$col} } );
999             delete $spec{$col};
1000              
1001             } elsif ($spec{$col} =~ /::/) {
1002             $spec{$col} =~ s/::$//;
1003             eval "use $spec{$col}"; die "Can't eval class $spec{$col} => $@\n" if $@;
1004             $class->has_a( $col => $spec{$col} );
1005             $spec{$col} = "CHAR(36)";
1006              
1007             } else {
1008             push @simple, $col;
1009             }
1010             }
1011              
1012             $class->db_fields(@simple);
1013             $class->db_fields_spec( map { "$_ $spec{$_}" } keys %spec );
1014              
1015             }
1016              
1017              
1018              
1019             =head2 drop_table()
1020              
1021             Drop the table for this class.
1022              
1023             =cut
1024              
1025             sub drop_table {
1026             my $self = shift;
1027             my $dbh = $self->dbh;
1028             my $table = $self->db_table or die "No table name";
1029             # XXX can't portably IF EXISTS
1030             $dbh->do("DROP TABLE $table"); # or warn "Could not execute - $DBI::errstr";
1031             }
1032              
1033              
1034             =head2 create_table
1035              
1036             Create the table for this class.
1037              
1038             =cut
1039              
1040             sub create_table {
1041             my $self = shift;
1042             my $dbh = $self->dbh;
1043             my $table = $self->db_table or die "No table name";
1044             my $sql = $self->db_table_sql or die "No table sql for $table";
1045              
1046             $dbh->do("CREATE TABLE $table $sql") or die "Could not execute $sql - $DBI::errstr";
1047             }
1048              
1049             =head2 setup_DB_infrastructure
1050              
1051             Class::Persist needs the existence of 2 tables in addition to the ones used
1052             to store object data. This method will create the tables in the database for
1053             this object.
1054              
1055             =cut
1056              
1057             sub setup_DB_infrastructure {
1058             Class::Persist::Tracker->create_table() and
1059             Class::Persist::Deleted->create_table();
1060             }
1061              
1062             =head2 destroy_DB_infrastructure
1063              
1064             Class::Persist needs the existence of 2 tables in addition to the ones used
1065             to store object data. This method will remove the tables from the database for
1066             this object.
1067              
1068             =cut
1069              
1070             sub destroy_DB_infrastructure {
1071             Class::Persist::Tracker->drop_table() and
1072             Class::Persist::Deleted->drop_table()
1073             }
1074              
1075             =head2 get_all
1076              
1077             Returns a list of all the objects in this classes table in the database.
1078              
1079             =cut
1080              
1081             sub get_all {
1082             my $class = shift;
1083             return $class->search();
1084             }
1085              
1086             =head2 search
1087              
1088             Takes a hash of attribute=>value pairs. Values of undef become IS NULL tests.
1089             Returns a list of objects in the database of this class which match these
1090             criteria.
1091              
1092             my $pears = Fruit->search( shape => 'pear' );
1093              
1094             The special parameter 'order_by' will not be used as part of the search, but
1095             will order the results by that column.
1096              
1097             my $sorted_pears = Fruit->search( shape => 'pear', order_by => 'size' );
1098              
1099             =cut
1100              
1101             sub search {
1102             my $class = shift;
1103             my $param = ref($_[0]) ? $_[0] : { @_ };
1104              
1105             for (values(%$param)) {
1106             $_ = $_->oid if (blessed($_));
1107             }
1108              
1109             my $order_by = delete($param->{order_by});
1110              
1111             my $sql = "";
1112             if (keys(%$param)) {
1113             $sql = join( " AND ", map {
1114             defined($param->{$_}) ? "$_ = ?" : "$_ IS NULL"
1115             } keys(%$param) );
1116             } else {
1117             $sql = "1=1";
1118             }
1119             $sql .= ' ORDER BY '.$order_by if $order_by;
1120              
1121             return $class->sql( $sql, values(%$param) );
1122             }
1123              
1124              
1125             =head2 sql( sql, [placeholder values] )
1126              
1127             Free-form search based on a SQL query. Returns a list of objects from the
1128             database for each row of the passed SQL 'WHERE' clause. You can use placeholders
1129             in this string, passing the values for the placeholders as the 2nd, etc, params
1130              
1131             Person->sql("name LIKE '%ob%' AND age > ? ORDER BY height", $min_age)
1132              
1133             =cut
1134              
1135             sub sql {
1136             my $class = shift;
1137             my $query = shift;
1138              
1139             my $dbh = $class->dbh;
1140             my $table = $class->db_table;
1141             my @fields = $class->db_fields_all;
1142              
1143             # We have to go through this game of selecting all the fields explicitly
1144             # (and in a known order) rather than simply using fetchrow_arrayref because
1145             # DBD::Pg appears not to be case-preserving the column names.
1146             # Without doing this tests will fail on Pg when attributes are not all lower
1147             # case.
1148             my $sql = "SELECT " . join (',', @fields) . " FROM $table";
1149              
1150             if ($query) {
1151             $sql .= " WHERE $query";
1152             }
1153              
1154             my $r = $dbh->prepare_cached($sql)
1155             or Class::Persist::Error::DB::Request->throw(
1156             text => "Could not prepare $sql - $DBI::errstr");
1157              
1158             my @placeholders = grep { defined($_) } @_;
1159             utf8::encode $_ foreach @placeholders;
1160              
1161             $r->execute( @placeholders )
1162             or Class::Persist::Error::DB::Request->throw(
1163             text => "Could not execute $sql - $DBI::errstr");
1164              
1165             my @return;
1166              
1167             my $limit = $LIMIT; # arbitrary limits. Bah.
1168             # Do this out here to avoid recreating hash each time.
1169             my %temp;
1170             while (my $row = $r->fetchrow_arrayref() and --$limit) {
1171             @temp{@fields} = @$row;
1172             # Do it this way round to avoid a bug in DBI, where DBI doesn't reset
1173             # the utf8 flag on the array it reuses for fetchrow_arrayref
1174             # We're now doing it here on copies of the data
1175             my %binary = map { $_ => 1 } $class->binary_fields_all;
1176             for (keys(%temp)) {
1177             next if $binary{$_};
1178             unless (utf8::decode($temp{$_})) {
1179             Class::Persist::Error::DB::UTF8->throw(
1180             text => "Non-utf8 data in column $_ returned by $sql");
1181             }
1182             }
1183             push(@return, $class->new->_populate(\%temp)->_from_db(1));
1184             }
1185              
1186             $r->finish();
1187              
1188             return @return;
1189             }
1190              
1191             =head2 advanced_search
1192              
1193             when search() isn't good enough, and even sql() isn't good enough, you
1194             want advanced_search. You pass a complete SQL statement that will return
1195             a number of rows. It is assumed that the left-most column will contain
1196             oids. These oids will be inflated from the database and returned in a
1197             list.
1198              
1199             As with the sql method, you can use placeholders and pass the values as
1200             the remaining parameters.
1201              
1202             People->advanced_sql('
1203             SELECT artist.oid FROM artist,track
1204             WHERE track.artist_name = artist.name
1205             AND track.length > ?
1206             ORDER BY artist.name',
1207             100 );
1208              
1209             This will be slower than sql - there will be another SQL query on the db
1210             for every row returned. That's life. There is much scope here for
1211             optimization - the simplest thing to do might be to return a list of
1212             proxies instead..
1213              
1214             Also consider that the SQL statement you're passing will be just thrown
1215             at the database. You can call Object->advanced_sql('DROP DATABASE
1216             people') and bad things will happen. This is, of course, almost equally
1217             true for the sql method, but it's easier to break things with this one.
1218              
1219             =cut
1220              
1221             sub advanced_search {
1222             my $class = shift;
1223             my $sql = shift;
1224              
1225             my $dbh = $class->dbh;
1226              
1227             my $r = $dbh->prepare_cached($sql)
1228             or Class::Persist::Error::DB::Request->throw(
1229             text => "Could not prepare $sql - $DBI::errstr");
1230              
1231             my @placeholders = grep { defined($_) } @_;
1232             utf8::encode $_ foreach @placeholders;
1233              
1234             $r->execute( @placeholders )
1235             or Class::Persist::Error::DB::Request->throw(
1236             text => "Could not execute $sql - $DBI::errstr");
1237              
1238             my @return;
1239              
1240             my $limit = $LIMIT; # arbitrary limits. Bah.
1241             # Do this out here to avoid recreating hash each time.
1242             my %row;
1243             while (my $row = $r->fetchrow_arrayref() and --$limit) {
1244             my $oid = $row->[0];
1245             push( @return, $class->load($oid) );
1246             }
1247              
1248             $r->finish();
1249              
1250             return @return;
1251             }
1252              
1253             1;
1254             __END__