File Coverage

blib/lib/Coat/Persistent.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Coat::Persistent;
2              
3             # Coat & friends
4 23     23   717278 use Coat;
  0            
  0            
5             use Coat::Meta;
6             use Coat::Persistent::Meta;
7             use Coat::Persistent::Constraint;
8             use Carp 'confess';
9              
10             use Data::Dumper;
11              
12             # Low-level helpers
13             use Digest::MD5 qw(md5_base64);
14             use Scalar::Util qw(blessed looks_like_number);
15             use List::Compare;
16              
17             # DBI & SQL related
18             use DBI;
19             use DBIx::Sequence;
20             use SQL::Abstract;
21              
22             # Constants
23             use constant CP_ENTRY_NEW => 0;
24             use constant CP_ENTRY_EXISTS => 1;
25              
26             # Module meta-data
27             use vars qw($VERSION @EXPORT $AUTHORITY);
28             use base qw(Exporter);
29              
30             $VERSION = '0.223';
31             $AUTHORITY = 'cpan:SUKRIA';
32             @EXPORT = qw(has_p has_one has_many);
33              
34             # The SQL::Abstract object
35             my $sql_abstract = SQL::Abstract->new;
36              
37             # configuration place-holders
38             my $MAPPINGS = {};
39              
40             # static accessors
41             sub mappings { $MAPPINGS }
42             sub dbh {
43             $MAPPINGS->{'!dbh'}{ $_[0] } ||
44             $MAPPINGS->{'!dbh'}{'!default'} ||
45             undef
46             }
47             sub driver {
48             $MAPPINGS->{'!driver'}{ $_[0] } ||
49             $MAPPINGS->{'!driver'}{'!default'} ||
50             undef;
51             }
52             sub cache {
53             $MAPPINGS->{'!cache'}{ $_[0] } ||
54             $MAPPINGS->{'!cache'}{'!default'} ||
55             undef;
56             }
57              
58              
59             # The internel sequence engine (DBIx::Sequence)
60             # If disabled, nothing will be done for the primary keys, their values
61             # should be set by the underlying DB.
62             my $USE_INTERNAL_SEQUENCE_ENGINE = 1;
63             sub has_internal_sequence_engine { $USE_INTERNAL_SEQUENCE_ENGINE }
64             sub enable_internal_sequence_engine { $USE_INTERNAL_SEQUENCE_ENGINE = 1 }
65             sub disable_internal_sequence_engine { $USE_INTERNAL_SEQUENCE_ENGINE = 0 }
66              
67             # Access to the constraint meta data for the current class
68             sub has_unique_constraint {
69             my ($class, $attr) = @_;
70             $class->has_constraint($attr, 'unique');
71             }
72              
73             sub has_constraint {
74             my ($class, $attr, $constraint) = @_;
75             Coat::Persistent::Constraint->get_constraint($constraint, $class, $attr) || 0;
76             }
77              
78             sub enable_cache {
79             my ($class, %options) = @_;
80             $class = '!default' if $class eq 'Coat::Persistent';
81              
82             # first, try to use Cache::FastMmap
83             eval "use Cache::FastMmap";
84             confess "Unable to load Cache::FastMmap : $@" if $@;
85              
86             # importing the module
87             Cache::FastMmap->import;
88              
89             # default cache configuration
90             $options{expire_time} ||= '1h';
91             $options{cache_size} ||= '10m';
92              
93             $MAPPINGS->{'!cache'}{$class} = Cache::FastMmap->new( %options );
94             }
95              
96             sub disable_cache {
97             my ($class) = @_;
98             $class = '!default' if $class eq 'Coat::Persistent';
99             undef $MAPPINGS->{'!cache'}{$class};
100             }
101              
102             # A singleton that stores the driver/module mappings
103             # The ones here are default drivers that are known to be compliant
104             # with Coat::Persistent.
105             # Any DBI driver should work though.
106             my $drivers = {
107             csv => 'DBI:CSV',
108             mysql => 'dbi:mysql',
109             sqlite => 'dbi:SQLite',
110             };
111             sub drivers { $drivers }
112              
113             # Accessor to a driver
114             sub get_driver {
115             my ($class, $driver) = @_;
116             confess "driver needed" unless $driver;
117             return $class->drivers->{$driver};
118             }
119              
120             # This lets you add the DBI driver you want to use
121             sub add_driver {
122             my ($class, $driver, $module) = @_;
123             confess "driver and module needed" unless $driver and $module;
124             $class->drivers->{$driver} = $module;
125             }
126              
127             # This is the configration stuff, you basically bind a class to
128             # a DBI driver
129             sub map_to_dbi {
130             my ( $class, $driver, @options ) = @_;
131             confess "Static method cannot be called from instance" if ref $class;
132             my $connect_options = { PrintError => 0, RaiseError => 0 };
133              
134             # if map_to_dbi is called from Coat::Persistent, this is the default dbh
135             $class = '!default' if $class eq 'Coat::Persistent';
136              
137             my $drivers = Coat::Persistent->drivers;
138              
139            
140             confess "No such driver : $driver, please register the driver first with add_driver()"
141             unless exists $drivers->{$driver};
142              
143             # the csv driver needs to load the appropriate DBD module
144             if ($driver eq 'csv') {
145             eval "use DBD::CSV 0.22";
146             confess "Unable to load DBD::CSV : $@" if $@;
147             DBD::CSV->import;
148             $connect_options->{csv_null} = 1; # since version 0.25 we have to do that to preserve undef values
149             }
150              
151             $MAPPINGS->{'!driver'}{$class} = $driver;
152              
153             my ( $table, $user, $pass ) = @options;
154             $driver = $drivers->{$driver};
155             $MAPPINGS->{'!dbh'}{$class} =
156             DBI->connect( "${driver}:${table}", $user, $pass, $connect_options);
157            
158             confess "Can't connect to database ${DBI::err} : ${DBI::errstr}"
159             unless $MAPPINGS->{'!dbh'}{$class};
160              
161             # if the DBIx::Sequence tables don't exist, create them
162             _create_dbix_sequence_tables($MAPPINGS->{'!dbh'}{$class}) if has_internal_sequence_engine();
163             }
164              
165             # This is used if you already have a dbh instead of creating one with
166             # map_to_dbi
167             sub set_dbh {
168             my ($class, $driver, $dbh) = @_;
169             confess "Cannot set an undefined dbh"
170             unless defined $dbh;
171             confess "Driver '$driver' is not supported"
172             unless defined exists $class->drivers->{$driver};
173              
174             $class = '!default' if $class eq 'Coat::Persistent';
175             $MAPPINGS->{'!dbh'}{$class} = $dbh;
176             $MAPPINGS->{'!driver'}{$class} = $driver;
177            
178             _create_dbix_sequence_tables($MAPPINGS->{'!dbh'}{$class})
179             if has_internal_sequence_engine();
180             }
181              
182             # This is done to wrap the original Coat::has method so we can
183             # generate finders for each attribute declared
184             #
185             # ActiveRecord chose to make attribute's finders dynamic, the functions are built
186             # at runtime whenever they're called. In Perl this could have been done with
187             # AUTOLOAD, but that sucks. Doing that would mean crappy performances;
188             # defining the method in the package's namespace is far more efficient.
189             #
190             # The only case where I see AUTOLOAD is the good choice is for finders
191             # made by mixing more than one attribute (find_by_foo_and_bar).
192             # Then, yes AUTOLOAD is a good choice, but for all the ones we know we need
193             # them, I disagree.
194             sub has_p {
195             my ( $attr, %options ) = @_;
196             my $caller = $options{'!caller'} || caller;
197             confess "package main called has_p" if $caller eq 'main';
198              
199             # unique field ?
200             if ($options{'unique'}) {
201             Coat::Persistent::Constraint->add_constraint('unique', $caller, $attr, 1);
202             }
203            
204             # specific storage type ?
205             if ($options{'store_as'}) {
206             # We need bi-directional coercion for this "store_as" feature ...
207             my $storage_type = Coat::Types::find_type_constraint($options{'store_as'});
208             confess "Unknown type \"".$options{'store_as'}."\" for storage"
209             unless defined $storage_type;
210             confess "No coercion defined for storage type \"".$options{'store_as'}."\""
211             unless $storage_type->has_coercion;
212              
213             my $type = Coat::Types::find_type_constraint($options{isa});
214             confess "No cercion for attribute type : \"".$options{isa}."\""
215             unless $type->has_coercion;
216              
217             Coat::Persistent::Constraint->add_constraint('store_as', $caller, $attr, $options{'store_as'});
218             $options{coerce} = 1;
219             }
220              
221             Coat::has( $attr, ( '!caller' => $caller, %options ) );
222             Coat::Persistent::Meta->attribute($caller, $attr);
223              
224             # find_by_
225             my $sub_find_by = sub {
226             my ( $class, $value ) = @_;
227             confess "Cannot be called from an instance" if ref $class;
228             confess "Cannot find without a value" unless defined $value;
229             my $table = Coat::Persistent::Meta->table_name($class);
230             my ($sql, @values) = $sql_abstract->select($table, '*', {$attr => $value});
231             return $class->find_by_sql($sql, @values);
232             };
233             _bind_code_to_symbol( $sub_find_by,
234             "${caller}::find_by_${attr}" );
235              
236             # find_or_create_by_
237             my $sub_find_or_create = sub {
238              
239             # if 2 args : we're given the value of $attr only
240             if (@_ == 2) {
241             my ($class, $value) = @_;
242             my $obj = $class->find(["$attr = ?", $value]);
243             return $obj if defined $obj;
244             $class->create($attr => $value);
245             }
246             # more than 2 args : this is a hash of attributes to look for
247             else {
248             my ($class, %attrs) = @_;
249             confess "Cannot find_or_create_by_$attr without $attr"
250             unless exists $attrs{$attr};
251             my $obj = $class->find(["$attr = ?", $attrs{$attr}]);
252             return $obj if defined $obj;
253             $class->create(%attrs);
254             }
255             };
256             _bind_code_to_symbol( $sub_find_or_create,
257             "${caller}::find_or_create_by_${attr}" );
258              
259             # find_or_initialize_by_
260             my $sub_find_or_initialize = sub {
261             # if 2 args : we're given the value of $attr only
262             if (@_ == 2) {
263             my ($class, $value) = @_;
264             my $obj = $class->find(["$attr = ?", $value]);
265             return $obj if defined $obj;
266             $class->new($attr => $value);
267             }
268             # more than 2 args : this is a hash of attributes to look for
269             else {
270             my ($class, %attrs) = @_;
271             confess "Cannot find_or_initialize_by_$attr without $attr"
272             unless exists $attrs{$attr};
273             my $obj = $class->find(["$attr = ?", $attrs{$attr}]);
274             return $obj if defined $obj;
275             $class->new(%attrs);
276             }
277             };
278             _bind_code_to_symbol( $sub_find_or_initialize,
279             "${caller}::find_or_initialize_by_${attr}" );
280             }
281              
282             # let's you define a relation like A.b_id -> B
283             # this will builds an accessor called "b" that will
284             # do a B->find(A->b_id)
285             # example :
286             # package A;
287             # ...
288             # has_one 'foo';
289             # ...
290             # my $a = new A;
291             # my $f = $a->foo
292             #
293             # TODO : later let the user override the bindings
294              
295             sub has_one {
296             my ($name, %options) = @_;
297             my $class = caller;
298              
299             my $owned_class = $options{class_name} || $name;
300             my $owned_table_name = Coat::Persistent::Meta->table_name($owned_class);
301             my $owned_primary_key = Coat::Persistent::Meta->primary_key($owned_class);
302              
303             confess "The class \"$owned_class\" does not have a primary key."
304             unless defined $owned_primary_key;
305            
306             my $attr_name = (defined $options{class_name}) ? $name : $owned_table_name ;
307              
308             # record the foreign key
309             my $foreign_key = $options{foreign_key} || ($owned_table_name . '_' . $owned_primary_key);
310             has_p $foreign_key => ( isa => 'Int', '!caller' => $class );
311              
312             my $symbol = "${class}::${attr_name}";
313             my $code = sub {
314             my ( $self, $object ) = @_;
315              
316             # want to set the subobject
317             if ( @_ == 2 ) {
318             if ( defined $object ) {
319             $self->$foreign_key( $object->$owned_primary_key );
320             }
321             else {
322             $self->$foreign_key(undef);
323             }
324             }
325              
326             # want to get the subobject
327             else {
328             return undef unless defined $self->$foreign_key;
329             $owned_class->find( $self->$foreign_key );
330             }
331             };
332             _bind_code_to_symbol( $code, $symbol );
333              
334             # save the accessor defined for that subobject
335             Coat::Persistent::Meta->accessor( $class => $attr_name );
336             }
337              
338             # many relations means an instance of class A owns many instances
339             # of class B:
340             # $a->bs returns B->find_by_a_id($a->id)
341             # * B must provide a 'has_one A' statement for this to work
342             sub has_many {
343             my ($name, %options) = @_;
344             my $class = caller;
345              
346             my $owned_class = $options{class_name} || $name;
347              
348             # get the SQL table names and primary keys we need
349             my $table_name = Coat::Persistent::Meta->table_name($class);
350             my $primary_key = Coat::Persistent::Meta->primary_key($class);
351             my $owned_table_name = Coat::Persistent::Meta->table_name($owned_class);
352             my $owned_primary_key = Coat::Persistent::Meta->primary_key($owned_class);
353            
354             confess "The class \"$owned_class\" does not have a primary key."
355             unless defined $owned_primary_key;
356            
357            
358             my $attr_name = (defined $options{class_name})
359             ? $name
360             : $owned_table_name.'s' ;
361              
362             # FIXME : have to pluralize properly and let the user
363             # disable the pluralisation.
364             # the accessor : $obj->things for subobject "Thing"
365             my $code = sub {
366             my ( $self, @list ) = @_;
367              
368             # a get
369             if ( @_ == 1 ) {
370             my $accessor = "find_by_${table_name}_${primary_key}";
371             return $owned_class->$accessor( $self->$primary_key );
372             }
373              
374             # a set
375             else {
376             foreach my $obj (@list) {
377             # is the object made of something appropriate?
378             confess "Not an object reference, expected $owned_class, got ($obj)"
379             unless defined blessed $obj;
380             confess "Not an object of class $owned_class (got "
381             . blessed($obj) . ")"
382             unless blessed $obj eq $owned_class;
383            
384             # then set
385             my $accessor = Coat::Persistent::Meta->accessor( $owned_class) || $table_name;
386             $obj->$accessor($self);
387             push @{ $self->{_subobjects} }, $obj;
388             }
389             return scalar(@list) == scalar(@{$self->{_subobjects}});
390             }
391             };
392             _bind_code_to_symbol( $code, "${class}::${attr_name}" );
393             }
394              
395             # When Coat::Persistent is imported, a couple of actions have to be
396             # done. Mostly: declare the default primary key of the model, the table
397             # name it maps.
398             sub import {
399             my ($class, @stuff) = @_;
400             my %options;
401             %options = @stuff if @stuff % 2 == 0;
402              
403             # Don't do our automagick inheritance if main is calling us or if the
404             # class has already been registered
405             my $caller = caller;
406             return if $caller eq 'main';
407             return if defined Coat::Persistent::Meta->registry( $class );
408            
409             # now, our caller inherits from Coat::Persistent
410             eval { Coat::_extends_class( ['Coat::Persistent'], $caller ) };
411              
412             # is the primary_key disabled?
413             if (exists($options{primary_key}) && (not defined $options{primary_key})) {
414             $options{primary_key} = undef;
415             }
416             else {
417             $options{primary_key} ||= 'id';
418             }
419              
420             # the table_name if not defined is taken from the model name
421             $options{table_name} ||= $caller->_to_sql;
422              
423             # save the meta information obout the model mapping
424             Coat::Persistent::Meta->table_name($caller, $options{table_name});
425             Coat::Persistent::Meta->primary_key($caller, $options{primary_key});
426              
427             # if the primary_key is defined
428             if (defined $options{primary_key}) {
429             has_p $options{primary_key} => ( isa => 'Int', '!caller' => $caller );
430             }
431              
432             # we have a couple of symbols to export outside
433             Coat::Persistent->export_to_level( 1, ($class, @EXPORT) );
434             }
435              
436             # find() is a polymorphic method that can behaves in several ways accroding
437             # to the arguments passed.
438             #
439             # Class->find() : returns all rows (select * from class)
440             # Class->find(12) : returns the row where id = 12
441             # Class->find("condition") : returns the row(s) where condition
442             # Class->find(["condition ?", $val]) returns the row(s) where condition
443             #
444             # You can also pass an as your last argument, this will be the options
445             # Class->find(..., \%options)
446              
447             sub find {
448             # first of all, if the last arg is a HASH, its our options
449             # then, pop it so it's not processed anymore.
450             my %options;
451             %options = %{ pop @_ }
452             if (defined $_[$#_] && ref($_[$#_]) eq 'HASH');
453              
454             # then, fetch the args
455             my ( $class, $value, @rest ) = @_;
456             confess "Cannot be called from an instance" if ref $class;
457              
458             # get the corresponfing SQL names
459             my $primary_key = Coat::Persistent::Meta->primary_key($class);
460             my $table_name = Coat::Persistent::Meta->table_name($class);
461              
462             # handling of the options given
463             my $select = $options{'select'} || '*';
464             my $from = $options{'from'} || $table_name;
465             my $group = "GROUP BY " . $options{group} if defined $options{group};
466             my $order = "ORDER BY " . $options{order} if defined $options{order};
467             my $limit = "LIMIT " . $options{limit} if defined $options{limit};
468              
469            
470             # now building the sql tail of our future query
471             my $tail = " ";
472             $tail .= "$group " if defined $group;
473             $tail .= "$order " if defined $order;
474             $tail .= "$limit " if defined $limit;
475              
476             if (defined $value) {
477             if (ref $value) {
478             confess "Cannot handle non-array references" if ref($value) ne 'ARRAY';
479             # we don't use SQL::Abstract there, because we have a SQL
480             # statement with "?" and a list of values
481             my ($sql, @values) = @$value;
482             $class->find_by_sql(
483             "select $select from $from where $sql $tail", @values);
484             }
485             # we don't have a list, so let's find out what's given
486             else {
487             # the first item looks like a number (then it's an ID)
488             if (looks_like_number $value) {
489            
490             # can I haz primary_key?
491             confess "Cannot use find(ID) queries without a primary key defined"
492             unless defined $primary_key;
493              
494             my ($sql, @values) = $sql_abstract->select(
495             $from,
496             $select,
497             { $primary_key => [$value, @rest] });
498             return $class->find_by_sql($sql.$tail, @values);
499             }
500             # else, it a user-defined SQL condition
501             else {
502             my ($sql, @values) = $sql_abstract->select($from, $select, $value);
503             $class->find_by_sql($sql.$tail, @values);
504             }
505             }
506             }
507             else {
508             $class->find_by_sql( $sql_abstract->select( $from, $select ).$tail);
509             }
510             }
511              
512             # The generic SQL finder, takes a SQL query and map rows returned
513             # to objects of the class
514             sub find_by_sql {
515             my ( $class, $sql, @values ) = @_;
516             my @objects;
517              
518             # if cached, try to returned a cached value
519             if (defined $class->cache) {
520             my $cache_key = md5_base64($sql . (@values ? join(',', @values) : ''));
521             my $value = $class->cache->get($cache_key);
522             @objects = @$value if defined $value;
523             }
524              
525             # no cache found, perform the query
526             unless (@objects) {
527             my $dbh = $class->dbh;
528             my $sth = $dbh->prepare($sql);
529             $sth->execute(@values)
530             or confess "Unable to execute query $sql : " .
531             $DBI::err . ' : ' . $DBI::errstr;
532             my $rows = $sth->fetchall_arrayref( {} );
533              
534             # if any rows, let's process them
535             if (@$rows) {
536             # we have to find out which fields are real attributes
537             my @attrs = Coat::Persistent::Meta->linearized_attributes( $class );
538             my $lc = new List::Compare(\@attrs, [keys %{ $rows->[0] }]);
539             my @given_attr = $lc->get_intersection;
540             my @virtual_attr = $lc->get_symdiff;
541              
542             # create the object with attributes, and set virtual ones
543             foreach my $r (@$rows) {
544              
545             my %attributes = map { ($_ => $r->{$_}) } @given_attr;
546              
547             my $obj = $class->new(%attributes);
548             $obj->init_on_find();
549             foreach my $field (@virtual_attr) {
550             $obj->{$field} = $r->{$field};
551             }
552              
553             $obj->{_db_state} = CP_ENTRY_EXISTS;
554             push @objects, $obj;
555             }
556             }
557            
558             # save to the cache if needed
559             if (defined $class->cache) {
560             my $cache_key = md5_base64($sql . (@values ? join(',', @values) : ''));
561             unless ($class->cache->set($cache_key, \@objects)) {
562             warn "Unable to write to cache for key : $cache_key ".
563             "; maybe upgrade the cache_size : $!";
564             }
565             }
566             }
567              
568             return wantarray
569             ? @objects
570             : $objects[0];
571             }
572              
573              
574             sub init_on_find {
575             }
576              
577             sub BUILD {
578             my ($self) = @_;
579             $self->{_db_state} = CP_ENTRY_NEW;
580             }
581              
582             sub validate {
583             my ($self, @args) = @_;
584             my $class = ref($self);
585             my $table_name = Coat::Persistent::Meta->table_name($class);
586            
587             foreach my $attr (Coat::Persistent::Meta->linearized_attributes($class) ) {
588            
589             # checking for unique attributes on inserting (new objects)
590             if ($class->has_unique_constraint($attr)) {
591             # look for other instances that already have that attribute
592             my @items = $class->find(["$attr = ?", $self->$attr]);
593             confess "Value ".$self->$attr." violates unique constraint "
594             . "for attribute $attr (class $class)"
595             if @items;
596             }
597             }
598             }
599              
600             sub delete {
601             my ($self, $id) = @_;
602             my $class = ref $self || $self;
603             my $dbh = $class->dbh;
604             my $table_name = Coat::Persistent::Meta->table_name($class);
605             my $primary_key = Coat::Persistent::Meta->primary_key($class);
606              
607             # TODO : we should provide a delete_by_$attr method for each attribute
608             # and a also delete('condition SQL') support.
609             confess "Cannot delete an entry without a primary_key defined"
610             unless defined $primary_key;
611              
612             confess "Cannot delete without an id"
613             if (!ref $self && !defined $id);
614            
615             confess "Cannot delete without a mapping defined for class " . ref $self
616             unless defined $dbh;
617              
618             # if the argument given is an object, fetch its id
619             $id = $self->$primary_key if ref($self);
620              
621             # at this, point, we must have an id
622             confess "Cannot delete without a defined id"
623             unless defined $id;
624              
625             # delete the stuff
626             $dbh->do("delete from ".$table_name." where $primary_key = $id");
627             }
628              
629             # create is an alias for new + save, it can hande simple
630             # and multiple creation.
631             # Class->create( foo => 'x', bar => 'y'); # simple creation
632             # Class->create([ { foo => 'x' }, {...}, ... ]); # multiple creation
633             sub create {
634             # if only two args, we should have an ARRAY containing HASH
635              
636             if (@_ == 2) {
637             my ($class, $values) = @_;
638             confess "create received only two args but no ARRAY"
639             unless ref($values) eq 'ARRAY';
640             $class->create(%$_) for @$values;
641             }
642             else {
643             my ($class, %values) = @_;
644             my $obj = $class->new(%values);
645             $obj->save;
646             $obj;
647             }
648             }
649              
650             # This will return the value as to be stored in the underlying database
651             # Most of the time it's just the value of the atrtribute, but it can
652             # be different if a 'store_as' type is defined.
653             sub get_storage_value_for {
654             my ($self, $attr_name) = @_;
655             my $class = ref $self;
656              
657             my $attr = Coat::Meta->attribute($class, $attr_name);
658              
659             if ($attr->{store_as}) {
660             my $storing_type = Coat::Types::find_type_constraint($attr->{store_as});
661             return $storing_type->coerce($self->$attr_name);
662             }
663             else {
664             return $self->$attr_name;
665             }
666             }
667              
668             # serialize the instance and save it with the mapper defined
669             sub save {
670             my ($self, $conditions) = @_;
671             my $class = ref $self;
672             my $dbh = $class->dbh;
673             my $table_name = Coat::Persistent::Meta->table_name($class);
674             my $primary_key = Coat::Persistent::Meta->primary_key($class);
675             #warn "save\n\ttable_name: $table_name\n\tprimary_key: $primary_key\n";
676              
677             confess "Cannot save without a mapping defined for class " . ref $self
678             unless defined $dbh;
679              
680             # make sure the object is sane
681             $self->validate();
682              
683             # all the attributes of the class
684             my @fields = Coat::Persistent::Meta->linearized_attributes( ref $self );
685              
686             # a hash containing attr/value pairs for the current object
687             my %values = map { $_ => $self->get_storage_value_for($_) } @fields;
688             # foreach my $k (keys %values) {
689             # delete $values{$k} if not defined $values{$k};
690             # }
691              
692             # if not a new object, we have to update
693             if ( $self->_db_state == CP_ENTRY_EXISTS ) {
694              
695             # In order to update and entry, we need either a primary key or a sql
696             # condition
697             confess "cannot update without a primary key or a SQL condition"
698             if (not defined $primary_key) and (not defined $conditions);
699              
700             # generate the SQL
701             my ($sql, @values);
702             if (defined $primary_key) {
703             ($sql, @values) = $sql_abstract->update(
704             $table_name, \%values, { $primary_key => $self->$primary_key});
705             }
706             else {
707             ($sql, @values) = $sql_abstract->update(
708             $table_name, \%values, $conditions);
709             }
710             # execute the query
711             my $sth = $dbh->prepare($sql);
712             $sth->execute( @values )
713             or confess "Unable to execute query \"$sql\" : $DBI::errstr";
714             }
715              
716             # new object, insert
717             else {
718             my ($sql, @values);
719            
720             confess "Primary key \"$primary_key\" has been set on a newborn object of class ".ref($self)
721             if (defined $primary_key && $self->$primary_key);
722              
723             if (defined $primary_key && has_internal_sequence_engine()) {
724             # get our ID from the sequence
725             $self->$primary_key( $self->_next_id );
726            
727             # generate the SQL
728             ($sql, @values) = $sql_abstract->insert(
729             $table_name, { %values, $primary_key => $self->$primary_key });
730             }
731             else {
732             map { delete $values{$_} unless defined $values{$_} } keys %values;
733             #warn "values: ".join(", ", keys(%values));
734             ($sql, @values) = $sql_abstract->insert($table_name, \%values);
735             }
736            
737             # execute the query
738             #warn "sql: $sql ".join(', ', @values);
739             my $sth = $dbh->prepare($sql);
740             $sth->execute( @values )
741             or confess "Unable to execute query \"$sql\" : $DBI::errstr";
742              
743             # Retrieve the primary key's value
744             $self->$primary_key($class->get_last_insert_id($sth))
745             if (defined $primary_key && !has_internal_sequence_engine());
746              
747             $self->{_db_state} = CP_ENTRY_EXISTS;
748             }
749              
750             # if subobjects defined, save them
751             if ( $self->{_subobjects} ) {
752             foreach my $obj ( @{ $self->{_subobjects} } ) {
753             $obj->save;
754             }
755             delete $self->{_subobjects};
756             }
757              
758             return $self->$primary_key if defined $primary_key;
759             return 'saved';
760             }
761              
762              
763             ##############################################################################
764             # Private methods
765              
766             # return the last insert id for any DBD supported
767             # raise an exception if the DBD is not supported
768             sub get_last_insert_id {
769             my ($class, $sth) = @_;
770             my $dbh = $class->dbh;
771             my $driver = $class->driver;
772              
773             if ($driver eq 'mysql') {
774             return $sth->{mysql_insertid} || $sth->{insertid};
775             }
776             elsif ($driver eq 'sqlite') {
777             return $dbh->func('last_insert_rowid');
778             }
779             else {
780             confess "DB driver '$driver' is not supported for last_insert_id";
781             }
782             }
783              
784             # instance method & stuff
785             sub _bind_code_to_symbol {
786             my ( $code, $symbol ) = @_;
787              
788             {
789             no strict 'refs';
790             no warnings 'redefine', 'prototype';
791             *$symbol = $code;
792             }
793             }
794              
795             sub _to_class {
796             join '::', map { ucfirst $_ } split '_', $_[0];
797             }
798              
799             # Takes a classname and translates it into a database table name.
800             # Ex: Class::Foo -> class_foo
801             sub _to_sql {
802             my $table = ( ref $_[0] ) ? lc ref $_[0] : lc $_[0];
803             $table =~ s/::/_/g;
804             return $table;
805             }
806              
807             sub _lock_write {
808             my ($self) = @_;
809             my $class = ref $self;
810             return 1 if $class->driver ne 'mysql';
811              
812             my $dbh = $class->dbh;
813             my $table = Coat::Persistent::Meta->table_name($class);
814             $dbh->do("LOCK TABLE $table WRITE")
815             or confess "Unable to lock table $table";
816             }
817              
818             sub _unlock {
819             my ($self) = @_;
820             my $class = ref $self;
821             return 1 if $class->driver ne 'mysql';
822              
823             my $dbh = $class->dbh;
824             $dbh->do("UNLOCK TABLES")
825             or confess "Unable to lock tables";
826             }
827              
828             sub _next_id {
829             my ($self) = @_;
830             my $class = ref $self;
831            
832             my $table = Coat::Persistent::Meta->table_name($class);
833             my $dbh = $class->dbh;
834              
835             my $sequence = new DBIx::Sequence({ dbh => $dbh });
836             my $id = $sequence->Next($table);
837             return $id;
838             }
839              
840             # Returns a constant describing if the object exists or not
841             # already in the underlying DB
842             sub _db_state {
843             my ($self) = @_;
844             return $self->{_db_state} ||= CP_ENTRY_NEW;
845             }
846              
847             # DBIx::Sequence needs two tables in the schema,
848             # this private function create them if needed.
849             sub _create_dbix_sequence_tables($) {
850             my ($dbh) = @_;
851              
852             # dbix_sequence_state exists ?
853             unless (_table_exists($dbh, 'dbix_sequence_state')) {
854             # nope, create!
855             $dbh->do("CREATE TABLE dbix_sequence_state (dataset varchar(50), state_id int(11))")
856             or confess "Unable to create table dbix_sequence_state $DBI::errstr";
857             }
858              
859             # dbix_sequence_release exists ?
860             unless (_table_exists($dbh, 'dbix_sequence_release')) {
861             # nope, create!
862             $dbh->do("CREATE TABLE dbix_sequence_release (dataset varchar(50), released_id int(11))")
863             or confess "Unable to create table dbix_sequence_release $DBI::errstr";
864             }
865             }
866              
867             # This is the best way I found to check if a table exists, with a portable SQL
868             # If you have better, tell me!
869             sub _table_exists($$) {
870             my ($dbh, $table) = @_;
871             my $sth = $dbh->prepare("select count(*) from $table");
872             return 0 unless defined $sth;
873             $sth->execute or return 0;
874             my $nb_rows = $sth->fetchrow_hashref;
875             return defined $nb_rows;
876             }
877              
878             1;
879             __END__