/[cvs]/nfo/perl/libs/Tangram/Storage.pm
ViewVC logotype

Annotation of /nfo/perl/libs/Tangram/Storage.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (hide annotations)
Thu Apr 24 00:21:27 2003 UTC (21 years, 2 months ago) by joko
Branch: MAIN
Changes since 1.8: +5 -4 lines
modified 'sub _fetch_object_state':
included patch from Michael Lloyd: "Bogus objects returned on load" [2001-09-21]
[http://sourceforge.net/mailarchive/forum.php?thread_id=391064&forum_id=7137]

1 jonen 1.1 # (c) Sound Object Logic 2000-2001
2    
3     use strict;
4    
5     package Tangram::Storage;
6     use DBI;
7     use Carp;
8    
9     use vars qw( %storage_class );
10    
11     BEGIN {
12    
13     eval { require 'WeakRef.pm' };
14    
15     if ($@) {
16     *Tangram::weaken = sub { };
17     $Tangram::no_weakrefs = 1;
18     } else {
19     *Tangram::weaken = \&WeakRef::weaken;
20     $Tangram::no_weakrefs = 0;
21     }
22     }
23    
24     sub new
25     {
26     my $pkg = shift;
27     return bless { @_ }, $pkg;
28     }
29    
30     sub schema
31     {
32     shift->{schema}
33     }
34    
35     sub export_object
36     {
37     my ($self, $obj) = @_;
38     return $self->{export_id}->($self->{get_id}->($obj));
39     }
40    
41     sub split_id
42     {
43     carp unless wantarray;
44     my ($self, $id) = @_;
45     my $cid_size = $self->{cid_size};
46     return ( substr($id, 0, -$cid_size), substr($id, -$cid_size) );
47     }
48    
49     sub combine_ids
50     {
51     my $self = shift;
52     return $self->{layout1} ? shift : sprintf("%d%0$self->{cid_size}d", @_);
53     }
54    
55     sub _open
56     {
57     my ($self, $schema) = @_;
58    
59     my $dbh = $self->{db};
60    
61     $self->{table_top} = 0;
62     $self->{free_tables} = [];
63    
64     $self->{tx} = [];
65    
66     $self->{schema} = $schema;
67    
68     {
69     local $dbh->{PrintError} = 0;
70     my $control = $dbh->selectall_arrayref("SELECT * FROM $schema->{control}");
71    
72     $self->{id_col} = $schema->{sql}{id_col};
73    
74     if ($control) {
75     $self->{class_col} = $schema->{sql}{class_col} || 'type';
76     $self->{import_id} = sub { shift() . sprintf("%0$self->{cid_size}d", shift()) };
77     $self->{export_id} = sub { substr shift(), 0, -$self->{cid_size} };
78     } else {
79     $self->{class_col} = 'classId';
80     $self->{layout1} = 1;
81     $self->{import_id} = sub { shift() };
82     $self->{export_id} = sub { shift() };
83     }
84     }
85    
86     my %id2class;
87    
88     if ($self->{layout1}) {
89     # compatibility with version 1.x
90     %id2class = map { @$_ } @{ $self->{db}->selectall_arrayref("SELECT classId, className FROM $schema->{class_table}") };
91     } else {
92     my $classes = $schema->{classes};
93     %id2class = map { $classes->{$_}{id}, $_ } keys %$classes;
94     }
95    
96     $self->{id2class} = \%id2class;
97     @{ $self->{class2id} }{ values %id2class } = keys %id2class;
98    
99     $self->{set_id} = $schema->{set_id} ||
100     sub
101     {
102     my ($obj, $id) = @_;
103    
104     if ($id) {
105     $self->{ids}{0 + $obj} = $id;
106     } else {
107     delete $self->{ids}{0 + $obj};
108     }
109     };
110    
111     $self->{get_id} = $schema->{get_id} || sub {
112     my $address = 0 + shift();
113     my $id = $self->{ids}{$address};
114     return undef unless $id;
115     return $id if $self->{objects}{$id};
116     delete $self->{ids}{$address};
117     delete $self->{objects}{$id};
118     return undef;
119     };
120    
121     return $self;
122     }
123    
124     sub alloc_table
125     {
126     my ($self) = @_;
127    
128     return @{$self->{free_tables}} > 0
129     ? pop @{$self->{free_tables}}
130     : ++$self->{table_top};
131     }
132    
133     sub free_table
134     {
135     my $self = shift;
136     push @{$self->{free_tables}}, grep { $_ } @_;
137     }
138    
139     sub open_connection
140     {
141     # private - open a new connection to DB for read
142    
143     my $self = shift;
144     DBI->connect($self->{-cs}, $self->{-user}, $self->{-pw}) or die;
145     }
146    
147     sub close_connection
148     {
149     # private - close read connection to DB unless it's the default one
150    
151     my ($self, $conn) = @_;
152    
153     return unless $conn && $self->{db};
154    
155     if ($conn == $self->{db})
156     {
157     $conn->commit unless $self->{no_tx} || @{ $self->{tx} };
158     }
159     else
160     {
161     $conn->disconnect;
162     }
163     }
164    
165     sub cursor
166     {
167     my ($self, $class, @args) = @_;
168     my $cursor = Tangram::Cursor->new($self, $class, $self->open_connection());
169     $cursor->select(@args);
170     return $cursor;
171     }
172    
173     sub my_cursor
174     {
175     my ($self, $class, @args) = @_;
176     my $cursor = Tangram::Cursor->new($self, $class, $self->{db});
177     $cursor->select(@args);
178     return $cursor;
179     }
180    
181     sub select_data
182     {
183     my $self = shift;
184     Tangram::Select->new(@_)->execute($self, $self->open_connection());
185     }
186    
187     sub selectall_arrayref
188     {
189     shift->select_data(@_)->fetchall_arrayref();
190     }
191    
192     sub my_select_data
193     {
194     my $self = shift;
195     Tangram::Select->new(@_)->execute($self, $self->{db});
196     }
197    
198     my $psi = 1;
199    
200     sub prepare
201     {
202     my ($self, $sql) = @_;
203    
204     print $Tangram::TRACE "preparing [@{[ $psi++ ]}] $sql\n" if $Tangram::TRACE;
205     $self->{db}->prepare($sql);
206     }
207    
208     *prepare_insert = \&prepare;
209     *prepare_update = \&prepare;
210     *prepare_select = \&prepare;
211    
212     sub make_id
213     {
214     my ($self, $class_id) = @_;
215    
216     unless ($self->{layout1}) {
217     my $id;
218    
219     if (exists $self->{mark}) {
220     $id = $self->{mark}++;
221     $self->{set_mark} = 1; # cleared by tx_start
222     } else {
223     $id = $self->make_1st_id_in_tx();
224     }
225    
226     return sprintf "%d%0$self->{cid_size}d", $id, $class_id;
227     }
228    
229     # ------------------------------
230     # compatibility with version 1.x
231    
232     my $alloc_id = $self->{alloc_id} ||= {};
233    
234     my $id = $alloc_id->{$class_id};
235    
236     if ($id) {
237     $id = -$id if $id < 0;
238     $alloc_id->{$class_id} = ++$id;
239     } else {
240     my $table = $self->{schema}{class_table};
241     $self->sql_do("UPDATE $table SET lastObjectId = lastObjectId + 1 WHERE classId = $class_id");
242     $id = $self
243     ->sql_selectall_arrayref("SELECT lastObjectId from $table WHERE classId = $class_id")->[0][0];
244     $alloc_id->{$class_id} = -$id;
245     }
246    
247     return sprintf "%d%0$self->{cid_size}d", $id, $class_id;
248     }
249    
250 jonen 1.4
251     # create global unique identifers using Data::UUID
252     sub make_guid
253     {
254     my $self = shift;
255    
256 joko 1.7 my $guid;
257    
258     # try to use Data::UUID first ...
259     eval("use Data::UUID;");
260     if (!$@) {
261     my $ug = Data::UUID->new();
262     $guid = $ug->create_str();
263    
264     # ... if this fails, try to fallback to Data::UUID::PurePerl instead ...
265     } else {
266     eval("use Data::UUID::PurePerl;");
267     if (!$@) {
268     $guid = Data::UUID::PurePerl::generate_id();
269     } else {
270     croak "couldn't create globally unique identifier";
271     }
272     }
273 jonen 1.4
274     return $guid;
275     }
276    
277 jonen 1.1 sub make_1st_id_in_tx
278     {
279     my ($self) = @_;
280    
281     unless ($self->{make_id}) {
282     my $table = $self->{schema}{control};
283     my $dbh = $self->{db};
284     $self->{make_id}{inc} = $self->prepare("UPDATE $table SET mark = mark + 1");
285     $self->{make_id}{set} = $self->prepare("UPDATE $table SET mark = ?");
286     $self->{make_id}{get} = $self->prepare("SELECT mark from $table");
287     }
288    
289     my $sth;
290    
291     $sth = $self->{make_id}{inc};
292     $sth->execute();
293     $sth->finish();
294    
295     $sth = $self->{make_id}{get};
296     $sth->execute();
297     my $id = $sth->fetchrow_arrayref()->[0];
298     $sth->finish();
299    
300     return $id;
301     }
302    
303     sub update_id_in_tx
304     {
305     my ($self, $mark) = @_;
306     my $sth = $self->{make_id}{set};
307     $sth->execute($mark);
308     $sth->finish();
309     }
310    
311     sub unknown_classid
312     {
313     my $class = shift;
314     confess "class '$class' doesn't exist in this storage"
315     }
316    
317     sub class_id
318     {
319     my ($self, $class) = @_;
320     $self->{class2id}{$class} or unknown_classid $class;
321     }
322    
323     #############################################################################
324     # Transaction
325    
326     my $error_no_transaction = 'no transaction is currently active';
327    
328     sub tx_start
329     {
330     my $self = shift;
331    
332     unless (@{ $self->{tx} }) {
333     delete $self->{set_mark};
334     delete $self->{mark};
335     }
336    
337     push @{ $self->{tx} }, [];
338     }
339    
340     sub tx_commit
341     {
342     # public - commit current transaction
343    
344     my $self = shift;
345    
346     carp $error_no_transaction unless @{ $self->{tx} };
347    
348     # update lastObjectId's
349    
350     if ($self->{set_mark}) {
351     $self->update_id_in_tx($self->{mark});
352     }
353    
354     # ------------------------------
355     # compatibility with version 1.x
356    
357     if (my $alloc_id = $self->{alloc_id}) {
358     my $table = $self->{schema}{class_table};
359    
360     for my $class_id (keys %$alloc_id)
361     {
362     my $id = $alloc_id->{$class_id};
363     next if $id < 0;
364     $self->sql_do("UPDATE $table SET lastObjectId = $id WHERE classId = $class_id");
365     }
366    
367     delete $self->{alloc_id};
368     }
369    
370     # compatibility with version 1.x
371     # ------------------------------
372    
373     unless ($self->{no_tx} || @{ $self->{tx} } > 1) {
374     # committing outer tx: commit to db
375     $self->{db}->commit;
376     }
377    
378     pop @{ $self->{tx} }; # drop rollback subs
379     }
380    
381     sub tx_rollback
382     {
383     my $self = shift;
384    
385     carp $error_no_transaction unless @{ $self->{tx} };
386    
387     if ($self->{no_tx})
388     {
389     pop @{ $self->{tx} };
390     }
391     else
392     {
393     $self->{db}->rollback if @{ $self->{tx} } == 1; # don't rollback db if nested tx
394    
395     # execute rollback subs in reverse order
396    
397     foreach my $rollback ( @{ pop @{ $self->{tx} } } )
398     {
399     $rollback->($self);
400     }
401     }
402     }
403    
404     sub tx_do
405     {
406     # public - execute subroutine inside tx
407    
408     my ($self, $sub, @params) = @_;
409    
410     $self->tx_start();
411    
412     my ($results, @results);
413     my $wantarray = wantarray();
414    
415     eval
416     {
417     if ($wantarray)
418     {
419     @results = $sub->(@params);
420     }
421     else
422     {
423     $results = $sub->(@params);
424     }
425     };
426    
427     if ($@)
428     {
429     $self->tx_rollback();
430     die $@;
431     }
432     else
433     {
434     $self->tx_commit();
435     }
436    
437     return wantarray ? @results : $results;
438     }
439    
440     sub tx_on_rollback
441     {
442     # private - register a sub that will be called if/when the tx is rolled back
443    
444     my ($self, $rollback) = @_;
445     carp $error_no_transaction if $^W && !@{ $self->{tx} };
446     unshift @{ $self->{tx}[0] }, $rollback; # rollback subs are executed in reverse order
447     }
448    
449     #############################################################################
450     # insertion
451    
452     sub insert
453     {
454     # public - insert objects into storage; return their assigned ids
455    
456     my ($self, @objs) = @_;
457    
458     my @ids = $self->tx_do(
459     sub
460     {
461     my ($self, @objs) = @_;
462     map
463     {
464     local $self->{defered} = [];
465     my $id = $self->_insert($_, Set::Object->new() );
466     $self->do_defered;
467     $id;
468     } @objs;
469     }, $self, @objs );
470    
471     return wantarray ? @ids : shift @ids;
472     }
473    
474     sub _insert
475     {
476     my ($self, $obj, $saving) = @_;
477    
478     die unless $saving;
479    
480     my $schema = $self->{schema};
481    
482     return $self->id($obj)
483     if $self->id($obj);
484    
485 jonen 1.5 # insert global unique identifier in object to persist across re-deploys
486     $obj->{guid} = $self->make_guid();
487 jonen 1.4
488 jonen 1.1 $saving->insert($obj);
489    
490     my $class_name = ref $obj;
491     my $classId = $self->{class2id}{$class_name} or unknown_classid $class_name;
492     my $class = $self->{schema}->classdef($class_name);
493    
494     my $id = $self->make_id($classId);
495    
496     $self->welcome($obj, $id);
497     $self->tx_on_rollback( sub { $self->goodbye($obj, $id) } );
498    
499     my $dbh = $self->{db};
500     my $engine = $self->{engine};
501     my $cache = $engine->get_save_cache($class);
502    
503     my $sths = $self->{INSERT_STHS}{$class_name} ||=
504     [ map { $self->prepare($_) } @{ $cache->{INSERTS} } ];
505    
506     my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
507     my @state = ( $self->{export_id}->($id), $classId, $cache->{EXPORTER}->($obj, $context) );
508    
509     my $fields = $cache->{INSERT_FIELDS};
510    
511     use integer;
512    
513     for my $i (0..$#$sths) {
514    
515     if ($Tangram::TRACE) {
516     printf $Tangram::TRACE "executing %s with (%s)\n",
517 jonen 1.4 $cache->{INSERTS}[$i],
518 jonen 1.1 join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
519     }
520    
521     my $sth = $sths->[$i];
522     $sth->execute(@state[ @{ $fields->[$i] } ]);
523     $sth->finish();
524     }
525    
526     return $id;
527     }
528    
529     #############################################################################
530     # update
531    
532     sub update
533     {
534     # public - write objects to storage
535    
536     my ($self, @objs) = @_;
537    
538     $self->tx_do(
539     sub
540     {
541     my ($self, @objs) = @_;
542     foreach my $obj (@objs)
543     {
544     local $self->{defered} = [];
545    
546     $self->_update($obj, Set::Object->new() );
547     $self->do_defered;
548     }
549     }, $self, @objs);
550     }
551    
552     sub _update
553     {
554     my ($self, $obj, $saving) = @_;
555    
556     die unless $saving;
557    
558     my $id = $self->id($obj) or confess "$obj must be persistent";
559    
560     $saving->insert($obj);
561    
562     my $class = $self->{schema}->classdef(ref $obj);
563     my $dbh = $self->{db};
564     my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
565    
566     my $cache = $self->{engine}->get_save_cache($class);
567     my @state = ( $self->{export_id}->($id), substr($id, -$self->{cid_size}), $cache->{EXPORTER}->($obj, $context) );
568    
569     my $fields = $cache->{UPDATE_FIELDS};
570    
571     my $sths = $self->{UPDATE_STHS}{$class->{name}} ||=
572     [ map {
573     print $Tangram::TRACE "preparing $_\n" if $Tangram::TRACE;
574     $self->prepare($_)
575     } @{ $cache->{UPDATES} } ];
576    
577     use integer;
578    
579     for my $i (0..$#$sths) {
580    
581     if ($Tangram::TRACE) {
582     printf $Tangram::TRACE "executing %s with (%s)\n",
583     $cache->{UPDATES}[$i],
584     join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
585     }
586    
587     my $sth = $sths->[$i];
588     $sth->execute(@state[ @{ $fields->[$i] } ]);
589     $sth->finish();
590     }
591     }
592    
593     #############################################################################
594     # save
595    
596     sub save
597     {
598     my $self = shift;
599    
600     foreach my $obj (@_) {
601     if ($self->id($obj)) {
602     $self->update($obj)
603     } else {
604     $self->insert($obj)
605     }
606     }
607     }
608    
609     sub _save
610     {
611     my ($self, $obj, $saving) = @_;
612    
613     if ($self->id($obj)) {
614     $self->_update($obj, $saving)
615     } else {
616     $self->_insert($obj, $saving)
617     }
618     }
619    
620    
621     #############################################################################
622     # erase
623    
624     sub erase
625     {
626     my ($self, @objs) = @_;
627    
628     $self->tx_do(
629     sub
630     {
631     my ($self, @objs) = @_;
632     my $schema = $self->{schema};
633     my $classes = $self->{schema}{classes};
634    
635     foreach my $obj (@objs)
636     {
637     my $id = $self->id($obj) or confess "object $obj is not persistent";
638     my $class = $schema->classdef(ref $obj);
639    
640     local $self->{defered} = [];
641    
642     $schema->visit_down(ref($obj),
643     sub
644     {
645     my $class = shift;
646     my $classdef = $classes->{$class};
647    
648     foreach my $typetag (keys %{$classdef->{members}}) {
649     my $members = $classdef->{members}{$typetag};
650     my $type = $schema->{types}{$typetag};
651     $type->erase($self, $obj, $members, $id);
652     }
653     } );
654    
655     my $sths = $self->{DELETE_STHS}{$class->{name}} ||=
656     [ map { $self->prepare($_) } @{ $self->{engine}->get_deletes($class) } ];
657    
658     my $eid = $self->{export_id}->($id);
659    
660     for my $sth (@$sths) {
661     $sth->execute($eid);
662     $sth->finish();
663     }
664    
665     $self->do_defered;
666    
667     $self->goodbye($obj, $id);
668     $self->tx_on_rollback( sub { $self->welcome($obj, $id) } );
669     }
670     }, $self, @objs );
671     }
672    
673     sub do_defered
674     {
675     my ($self) = @_;
676    
677     foreach my $defered (@{$self->{defered}})
678     {
679     $defered->($self);
680     }
681    
682     $self->{defered} = [];
683     }
684    
685     sub defer
686     {
687     my ($self, $action) = @_;
688     push @{$self->{defered}}, $action;
689     }
690    
691     sub load
692     {
693     my $self = shift;
694    
695     return map { scalar $self->load( $_ ) } @_ if wantarray;
696    
697     my $id = shift;
698     die if @_;
699    
700     return $self->{objects}{$id}
701     if exists $self->{objects}{$id} && defined $self->{objects}{$id};
702    
703     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
704    
705     my $row = _fetch_object_state($self, $id, $class);
706    
707     my $obj = $self->read_object($id, $class->{name}, $row);
708 joko 1.8 return undef unless defined $row;
709 jonen 1.1
710     # ??? $self->{-residue} = \@row;
711    
712     return $obj;
713     }
714    
715     sub reload
716     {
717     my $self = shift;
718    
719     return map { scalar $self->load( $_ ) } @_ if wantarray;
720    
721     my $obj = shift;
722     my $id = $self->id($obj) or die "'$obj' is not persistent";
723     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
724    
725     my $row = _fetch_object_state($self, $id, $class);
726 joko 1.8 return undef unless defined $row;
727 jonen 1.1 _row_to_object($self, $obj, $id, $class->{name}, $row);
728    
729     return $obj;
730     }
731    
732     sub welcome
733     {
734     my ($self, $obj, $id) = @_;
735     $self->{set_id}->($obj, $id);
736     Tangram::weaken( $self->{objects}{$id} = $obj );
737     }
738    
739     sub goodbye
740     {
741     my ($self, $obj, $id) = @_;
742     $self->{set_id}->($obj, undef) if $obj;
743     delete $self->{objects}{$id};
744     delete $self->{PREFETCH}{$id};
745     }
746    
747     sub shrink
748     {
749     my ($self) = @_;
750    
751     my $objects = $self->{objects};
752     my $prefetch = $self->{prefetch};
753    
754     for my $id (keys %$objects)
755     {
756     next if $objects->{$id};
757     delete $objects->{$id};
758     delete $prefetch->{$id};
759     }
760     }
761    
762     sub read_object
763     {
764     my ($self, $id, $class, $row, @parts) = @_;
765    
766     my $schema = $self->{schema};
767    
768     my $obj = $schema->{make_object}->($class);
769    
770     unless (exists $self->{objects}{$id} && defined $self->{objects}{$id}) {
771     # do this only if object is not loaded yet
772     # otherwise we're just skipping columns in $row
773     $self->welcome($obj, $id);
774     }
775    
776     _row_to_object($self, $obj, $id, $class, $row, @parts);
777    
778     return $obj;
779     }
780    
781     sub _row_to_object
782     {
783     my ($self, $obj, $id, $class, $row) = @_;
784     $self->{engine}->get_import_cache($self->{schema}->classdef($class))
785     ->($obj, $row, { storage => $self, id => $id, layout1 => $self->{layout1} });
786     return $obj;
787     }
788    
789     sub _fetch_object_state
790     {
791     my ($self, $id, $class) = @_;
792    
793     my $sth = $self->{LOAD_STH}{$class->{name}} ||=
794     $self->prepare($self->{engine}->get_instance_select($class));
795    
796     $sth->execute($self->{export_id}->($id));
797     my $state = [ $sth->fetchrow_array() ];
798 joko 1.9 croak "no object with id $id" unless (@{$state});
799     unless (@$state) {
800     return undef unless $sth->err;
801     croak "error during load of object id=$id: $sth->err";
802     }
803 jonen 1.1 $sth->finish();
804    
805     return $state;
806     }
807    
808     sub get_polymorphic_select
809     {
810     my ($self, $class) = @_;
811     return $self->{engine}->get_polymorphic_select($self->{schema}->classdef($class), $self);
812     }
813    
814     sub select {
815     croak "valid only in list context" unless wantarray;
816    
817     my ($self, $target, @args) = @_;
818    
819     unless (ref($target) eq 'ARRAY') {
820     my $cursor = Tangram::Cursor->new($self, $target, $self->{db});
821     return $cursor->select(@args);
822     }
823    
824     my ($first, @others) = @$target;
825    
826     my @cache = map { $self->select( $_, @args ) } @others;
827    
828     my $cursor = Tangram::Cursor->new($self, $first, $self->{db});
829     $cursor->retrieve( map { $_->{_IID_}, $_->{_TYPE_ } } @others );
830    
831     my $obj = $cursor->select( @args );
832     my @results;
833    
834     while ($obj) {
835     my @tuple = $obj;
836     my @residue = $cursor->residue;
837    
838     while (my $id = shift @residue) {
839     push @tuple, $self->load($self->combine_ids($id, shift @residue));
840     }
841    
842     push @results, \@tuple;
843     $obj = $cursor->next;
844     }
845    
846     return @results;
847     }
848    
849     sub cursor_object
850     {
851     my ($self, $class) = @_;
852     $self->{IMPLICIT}{$class} ||= Tangram::RDBObject->new($self, $class)
853     }
854    
855     sub query_objects
856     {
857     my ($self, @classes) = @_;
858     map { Tangram::QueryObject->new(Tangram::RDBObject->new($self, $_)) } @classes;
859     }
860    
861     sub remote
862     {
863     my ($self, @classes) = @_;
864     wantarray ? $self->query_objects(@classes) : (&remote)[0]
865     }
866    
867     sub expr
868     {
869     my $self = shift;
870     return shift->expr( @_ );
871     }
872    
873     sub object
874     {
875     carp "cannot be called in list context; use objects instead" if wantarray;
876     my $self = shift;
877     my ($obj) = $self->query_objects(@_);
878     $obj;
879     }
880    
881     sub count
882     {
883     my $self = shift;
884    
885     my ($target, $filter);
886     my $objects = Set::Object->new;
887    
888     if (@_ == 1)
889     {
890     $target = '*';
891     $filter = shift;
892     }
893     else
894     {
895     my $expr = shift;
896     $target = $expr->{expr};
897     $objects->insert($expr->objects);
898     $filter = shift;
899     }
900    
901     my @filter_expr;
902    
903     if ($filter)
904     {
905     $objects->insert($filter->objects);
906     @filter_expr = ( "($filter->{expr})" );
907     }
908    
909     my $sql = "SELECT COUNT($target) FROM " . join(', ', map { $_->from } $objects->members);
910    
911     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
912    
913     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
914    
915     return ($self->{db}->selectrow_array($sql))[0];
916     }
917    
918     sub sum
919     {
920     my ($self, $expr, $filter) = @_;
921    
922     my $objects = Set::Object->new($expr->objects);
923    
924     my @filter_expr;
925    
926     if ($filter)
927     {
928     $objects->insert($filter->objects);
929     @filter_expr = ( "($filter->{expr})" );
930     }
931    
932     my $sql = "SELECT SUM($expr->{expr}) FROM " . join(', ', map { $_->from } $objects->members);
933    
934     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
935    
936     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
937    
938     return ($self->{db}->selectrow_array($sql))[0];
939     }
940    
941     sub id
942     {
943     my ($self, $obj) = @_;
944     return $self->{get_id}->($obj);
945     }
946    
947     sub disconnect
948     {
949     my ($self) = @_;
950    
951     unless ($self->{no_tx})
952     {
953     if (@{ $self->{tx} })
954     {
955     $self->{db}->rollback;
956     }
957     else
958     {
959     $self->{db}->commit;
960     }
961     }
962    
963     $self->{db}->disconnect;
964    
965     %$self = ();
966     }
967    
968     sub _kind_class_ids
969     {
970     my ($self, $class) = @_;
971    
972     my $schema = $self->{schema};
973     my $classes = $self->{schema}{classes};
974     my $class2id = $self->{class2id};
975    
976     my @ids;
977    
978     push @ids, $self->class_id($class) unless $classes->{$class}{abstract};
979    
980     $schema->for_each_spec($class,
981     sub { my $spec = shift; push @ids, $class2id->{$spec} unless $classes->{$spec}{abstract} } );
982    
983     return @ids;
984     }
985    
986     sub is_persistent
987     {
988     my ($self, $obj) = @_;
989     return $self->{schema}->is_persistent($obj) && $self->id($obj);
990     }
991    
992     sub prefetch
993     {
994     my ($self, $remote, $member, $filter) = @_;
995    
996     my $class;
997    
998     if (ref $remote)
999     {
1000     $class = $remote->class();
1001     }
1002     else
1003     {
1004     $class = $remote;
1005     $remote = $self->remote($class);
1006     }
1007    
1008     my $schema = $self->{schema};
1009    
1010     my $member_class = $schema->find_member_class($class, $member)
1011     or die "no member '$member' in class '$class'";
1012    
1013     my $classdef = $schema->{classes}{$member_class};
1014     my $type = $classdef->{member_type}{$member};
1015     my $memdef = $classdef->{MEMDEFS}{$member};
1016    
1017     $type->prefetch($self, $memdef, $remote, $class, $member, $filter);
1018     }
1019    
1020     sub connect
1021     {
1022     my ($pkg, $schema, $cs, $user, $pw, $opts) = @_;
1023    
1024     my $self = $pkg->new;
1025    
1026     $opts ||= {};
1027    
1028     my $db = $opts->{dbh} || DBI->connect($cs, $user, $pw);
1029    
1030     eval { $db->{AutoCommit} = 0 };
1031    
1032     $self->{no_tx} = $db->{AutoCommit};
1033    
1034     $self->{db} = $db;
1035    
1036     @$self{ -cs, -user, -pw } = ($cs, $user, $pw);
1037    
1038     $self->{cid_size} = $schema->{sql}{cid_size};
1039    
1040     $self->_open($schema);
1041    
1042     $self->{engine} = Tangram::Relational::Engine->new($schema, layout1 => $self->{layout1});
1043    
1044     return $self;
1045     }
1046    
1047     sub sql_do
1048     {
1049     my ($self, $sql) = @_;
1050     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1051     my $rows_affected = $self->{db}->do($sql);
1052     return defined($rows_affected) ? $rows_affected
1053     : croak $DBI::errstr;
1054     }
1055    
1056     sub sql_selectall_arrayref
1057     {
1058     my ($self, $sql, $dbh) = @_;
1059     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1060     ($dbh || $self->{db})->selectall_arrayref($sql);
1061     }
1062    
1063     sub sql_prepare
1064     {
1065     my ($self, $sql, $connection) = @_;
1066     confess unless $connection;
1067     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1068     return $connection->prepare($sql) or die;
1069     }
1070    
1071     sub sql_cursor
1072     {
1073     my ($self, $sql, $connection) = @_;
1074    
1075     confess unless $connection;
1076    
1077     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1078    
1079     my $sth = $connection->prepare($sql) or die;
1080     $sth->execute() or confess;
1081    
1082     Tangram::Storage::Statement->new( statement => $sth, storage => $self,
1083     connection => $connection );
1084     }
1085    
1086     sub unload
1087     {
1088     my $self = shift;
1089     my $objects = $self->{objects};
1090    
1091     if (@_) {
1092     for my $item (@_) {
1093     if (ref $item) {
1094     $self->goodbye($item, $self->{get_id}->($item));
1095     } else {
1096     $self->goodbye($objects->{$item}, $item);
1097     }
1098     }
1099     } else {
1100     for my $id (keys %$objects) {
1101     $self->goodbye($objects->{$id}, $id);
1102     }
1103     }
1104     }
1105    
1106     *reset = \&unload; # deprecated, use unload() instead
1107    
1108     sub DESTROY
1109     {
1110     my $self = shift;
1111     $self->{db}->disconnect if $self->{db};
1112     }
1113    
1114     package Tangram::Storage::Statement;
1115    
1116     sub new
1117     {
1118     my $class = shift;
1119     bless { @_ }, $class;
1120     }
1121    
1122     sub fetchrow
1123     {
1124     return shift->{statement}->fetchrow;
1125     }
1126    
1127     sub close
1128     {
1129     my $self = shift;
1130    
1131     if ($self->{storage})
1132     {
1133     $self->{statement}->finish;
1134     $self->{storage}->close_connection($self->{connection});
1135     %$self = ();
1136     }
1137     }
1138    
1139     1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed