/[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.1 - (hide annotations)
Mon Dec 16 05:01:13 2002 UTC (21 years, 6 months ago) by jonen
Branch: MAIN
+ inital checkin
   copied orginal Storage.pm here

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    
251     # create global unique identifers using Data::UUID
252     sub make_guid
253     {
254     my $self = shift;
255    
256     eval ("use Data::UUID;");
257     my $ug = Data::UUID->new();
258     my $guid = $ug->create_str();
259    
260     return $guid;
261     }
262    
263     sub make_1st_id_in_tx
264     {
265     my ($self) = @_;
266    
267     unless ($self->{make_id}) {
268     my $table = $self->{schema}{control};
269     my $dbh = $self->{db};
270     $self->{make_id}{inc} = $self->prepare("UPDATE $table SET mark = mark + 1");
271     $self->{make_id}{set} = $self->prepare("UPDATE $table SET mark = ?");
272     $self->{make_id}{get} = $self->prepare("SELECT mark from $table");
273     }
274    
275     my $sth;
276    
277     $sth = $self->{make_id}{inc};
278     $sth->execute();
279     $sth->finish();
280    
281     $sth = $self->{make_id}{get};
282     $sth->execute();
283     my $id = $sth->fetchrow_arrayref()->[0];
284     $sth->finish();
285    
286     return $id;
287     }
288    
289     sub update_id_in_tx
290     {
291     my ($self, $mark) = @_;
292     my $sth = $self->{make_id}{set};
293     $sth->execute($mark);
294     $sth->finish();
295     }
296    
297     sub unknown_classid
298     {
299     my $class = shift;
300     confess "class '$class' doesn't exist in this storage"
301     }
302    
303     sub class_id
304     {
305     my ($self, $class) = @_;
306     $self->{class2id}{$class} or unknown_classid $class;
307     }
308    
309     #############################################################################
310     # Transaction
311    
312     my $error_no_transaction = 'no transaction is currently active';
313    
314     sub tx_start
315     {
316     my $self = shift;
317    
318     unless (@{ $self->{tx} }) {
319     delete $self->{set_mark};
320     delete $self->{mark};
321     }
322    
323     push @{ $self->{tx} }, [];
324     }
325    
326     sub tx_commit
327     {
328     # public - commit current transaction
329    
330     my $self = shift;
331    
332     carp $error_no_transaction unless @{ $self->{tx} };
333    
334     # update lastObjectId's
335    
336     if ($self->{set_mark}) {
337     $self->update_id_in_tx($self->{mark});
338     }
339    
340     # ------------------------------
341     # compatibility with version 1.x
342    
343     if (my $alloc_id = $self->{alloc_id}) {
344     my $table = $self->{schema}{class_table};
345    
346     for my $class_id (keys %$alloc_id)
347     {
348     my $id = $alloc_id->{$class_id};
349     next if $id < 0;
350     $self->sql_do("UPDATE $table SET lastObjectId = $id WHERE classId = $class_id");
351     }
352    
353     delete $self->{alloc_id};
354     }
355    
356     # compatibility with version 1.x
357     # ------------------------------
358    
359     unless ($self->{no_tx} || @{ $self->{tx} } > 1) {
360     # committing outer tx: commit to db
361     $self->{db}->commit;
362     }
363    
364     pop @{ $self->{tx} }; # drop rollback subs
365     }
366    
367     sub tx_rollback
368     {
369     my $self = shift;
370    
371     carp $error_no_transaction unless @{ $self->{tx} };
372    
373     if ($self->{no_tx})
374     {
375     pop @{ $self->{tx} };
376     }
377     else
378     {
379     $self->{db}->rollback if @{ $self->{tx} } == 1; # don't rollback db if nested tx
380    
381     # execute rollback subs in reverse order
382    
383     foreach my $rollback ( @{ pop @{ $self->{tx} } } )
384     {
385     $rollback->($self);
386     }
387     }
388     }
389    
390     sub tx_do
391     {
392     # public - execute subroutine inside tx
393    
394     my ($self, $sub, @params) = @_;
395    
396     $self->tx_start();
397    
398     my ($results, @results);
399     my $wantarray = wantarray();
400    
401     eval
402     {
403     if ($wantarray)
404     {
405     @results = $sub->(@params);
406     }
407     else
408     {
409     $results = $sub->(@params);
410     }
411     };
412    
413     if ($@)
414     {
415     $self->tx_rollback();
416     die $@;
417     }
418     else
419     {
420     $self->tx_commit();
421     }
422    
423     return wantarray ? @results : $results;
424     }
425    
426     sub tx_on_rollback
427     {
428     # private - register a sub that will be called if/when the tx is rolled back
429    
430     my ($self, $rollback) = @_;
431     carp $error_no_transaction if $^W && !@{ $self->{tx} };
432     unshift @{ $self->{tx}[0] }, $rollback; # rollback subs are executed in reverse order
433     }
434    
435     #############################################################################
436     # insertion
437    
438     sub insert
439     {
440     # public - insert objects into storage; return their assigned ids
441    
442     my ($self, @objs) = @_;
443    
444     my @ids = $self->tx_do(
445     sub
446     {
447     my ($self, @objs) = @_;
448     map
449     {
450     local $self->{defered} = [];
451     my $id = $self->_insert($_, Set::Object->new() );
452     $self->do_defered;
453     $id;
454     } @objs;
455     }, $self, @objs );
456    
457     return wantarray ? @ids : shift @ids;
458     }
459    
460     sub _insert
461     {
462     my ($self, $obj, $saving) = @_;
463    
464     die unless $saving;
465    
466     my $schema = $self->{schema};
467    
468     return $self->id($obj)
469     if $self->id($obj);
470    
471     # insert global unique identifier in object to persist across re-deploys
472     $obj->{guid} = $self->make_guid();
473    
474     # debug
475     use Data::Dumper;
476     print Dumper($obj);
477    
478     $saving->insert($obj);
479    
480     my $class_name = ref $obj;
481     my $classId = $self->{class2id}{$class_name} or unknown_classid $class_name;
482     my $class = $self->{schema}->classdef($class_name);
483    
484     my $id = $self->make_id($classId);
485    
486     $self->welcome($obj, $id);
487     $self->tx_on_rollback( sub { $self->goodbye($obj, $id) } );
488    
489     my $dbh = $self->{db};
490     my $engine = $self->{engine};
491     my $cache = $engine->get_save_cache($class);
492    
493     my $sths = $self->{INSERT_STHS}{$class_name} ||=
494     [ map { $self->prepare($_) } @{ $cache->{INSERTS} } ];
495    
496     my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
497     my @state = ( $self->{export_id}->($id), $classId, $cache->{EXPORTER}->($obj, $context) );
498    
499     my $fields = $cache->{INSERT_FIELDS};
500    
501     use integer;
502    
503     for my $i (0..$#$sths) {
504    
505     if ($Tangram::TRACE) {
506     printf $Tangram::TRACE "executing %s with (%s)\n",
507     $cache->{INSERTS}[$i],
508     join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
509     }
510    
511     my $sth = $sths->[$i];
512     $sth->execute(@state[ @{ $fields->[$i] } ]);
513     $sth->finish();
514     }
515    
516     return $id;
517     }
518    
519     #############################################################################
520     # update
521    
522     sub update
523     {
524     # public - write objects to storage
525    
526     my ($self, @objs) = @_;
527    
528     $self->tx_do(
529     sub
530     {
531     my ($self, @objs) = @_;
532     foreach my $obj (@objs)
533     {
534     local $self->{defered} = [];
535    
536     $self->_update($obj, Set::Object->new() );
537     $self->do_defered;
538     }
539     }, $self, @objs);
540     }
541    
542     sub _update
543     {
544     my ($self, $obj, $saving) = @_;
545    
546     die unless $saving;
547    
548     my $id = $self->id($obj) or confess "$obj must be persistent";
549    
550     $saving->insert($obj);
551    
552     my $class = $self->{schema}->classdef(ref $obj);
553     my $dbh = $self->{db};
554     my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
555    
556     my $cache = $self->{engine}->get_save_cache($class);
557     my @state = ( $self->{export_id}->($id), substr($id, -$self->{cid_size}), $cache->{EXPORTER}->($obj, $context) );
558    
559     my $fields = $cache->{UPDATE_FIELDS};
560    
561     my $sths = $self->{UPDATE_STHS}{$class->{name}} ||=
562     [ map {
563     print $Tangram::TRACE "preparing $_\n" if $Tangram::TRACE;
564     $self->prepare($_)
565     } @{ $cache->{UPDATES} } ];
566    
567     use integer;
568    
569     for my $i (0..$#$sths) {
570    
571     if ($Tangram::TRACE) {
572     printf $Tangram::TRACE "executing %s with (%s)\n",
573     $cache->{UPDATES}[$i],
574     join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
575     }
576    
577     my $sth = $sths->[$i];
578     $sth->execute(@state[ @{ $fields->[$i] } ]);
579     $sth->finish();
580     }
581     }
582    
583     #############################################################################
584     # save
585    
586     sub save
587     {
588     my $self = shift;
589    
590     foreach my $obj (@_) {
591     if ($self->id($obj)) {
592     $self->update($obj)
593     } else {
594     $self->insert($obj)
595     }
596     }
597     }
598    
599     sub _save
600     {
601     my ($self, $obj, $saving) = @_;
602    
603     if ($self->id($obj)) {
604     $self->_update($obj, $saving)
605     } else {
606     $self->_insert($obj, $saving)
607     }
608     }
609    
610    
611     #############################################################################
612     # erase
613    
614     sub erase
615     {
616     my ($self, @objs) = @_;
617    
618     $self->tx_do(
619     sub
620     {
621     my ($self, @objs) = @_;
622     my $schema = $self->{schema};
623     my $classes = $self->{schema}{classes};
624    
625     foreach my $obj (@objs)
626     {
627     my $id = $self->id($obj) or confess "object $obj is not persistent";
628     my $class = $schema->classdef(ref $obj);
629    
630     local $self->{defered} = [];
631    
632     $schema->visit_down(ref($obj),
633     sub
634     {
635     my $class = shift;
636     my $classdef = $classes->{$class};
637    
638     foreach my $typetag (keys %{$classdef->{members}}) {
639     my $members = $classdef->{members}{$typetag};
640     my $type = $schema->{types}{$typetag};
641     $type->erase($self, $obj, $members, $id);
642     }
643     } );
644    
645     my $sths = $self->{DELETE_STHS}{$class->{name}} ||=
646     [ map { $self->prepare($_) } @{ $self->{engine}->get_deletes($class) } ];
647    
648     my $eid = $self->{export_id}->($id);
649    
650     for my $sth (@$sths) {
651     $sth->execute($eid);
652     $sth->finish();
653     }
654    
655     $self->do_defered;
656    
657     $self->goodbye($obj, $id);
658     $self->tx_on_rollback( sub { $self->welcome($obj, $id) } );
659     }
660     }, $self, @objs );
661     }
662    
663     sub do_defered
664     {
665     my ($self) = @_;
666    
667     foreach my $defered (@{$self->{defered}})
668     {
669     $defered->($self);
670     }
671    
672     $self->{defered} = [];
673     }
674    
675     sub defer
676     {
677     my ($self, $action) = @_;
678     push @{$self->{defered}}, $action;
679     }
680    
681     sub load
682     {
683     my $self = shift;
684    
685     return map { scalar $self->load( $_ ) } @_ if wantarray;
686    
687     my $id = shift;
688     die if @_;
689    
690     return $self->{objects}{$id}
691     if exists $self->{objects}{$id} && defined $self->{objects}{$id};
692    
693     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
694    
695     my $row = _fetch_object_state($self, $id, $class);
696    
697     my $obj = $self->read_object($id, $class->{name}, $row);
698    
699     # ??? $self->{-residue} = \@row;
700    
701     return $obj;
702     }
703    
704     sub reload
705     {
706     my $self = shift;
707    
708     return map { scalar $self->load( $_ ) } @_ if wantarray;
709    
710     my $obj = shift;
711     my $id = $self->id($obj) or die "'$obj' is not persistent";
712     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
713    
714     my $row = _fetch_object_state($self, $id, $class);
715     _row_to_object($self, $obj, $id, $class->{name}, $row);
716    
717     return $obj;
718     }
719    
720     sub welcome
721     {
722     my ($self, $obj, $id) = @_;
723     $self->{set_id}->($obj, $id);
724     Tangram::weaken( $self->{objects}{$id} = $obj );
725     }
726    
727     sub goodbye
728     {
729     my ($self, $obj, $id) = @_;
730     $self->{set_id}->($obj, undef) if $obj;
731     delete $self->{objects}{$id};
732     delete $self->{PREFETCH}{$id};
733     }
734    
735     sub shrink
736     {
737     my ($self) = @_;
738    
739     my $objects = $self->{objects};
740     my $prefetch = $self->{prefetch};
741    
742     for my $id (keys %$objects)
743     {
744     next if $objects->{$id};
745     delete $objects->{$id};
746     delete $prefetch->{$id};
747     }
748     }
749    
750     sub read_object
751     {
752     my ($self, $id, $class, $row, @parts) = @_;
753    
754     my $schema = $self->{schema};
755    
756     my $obj = $schema->{make_object}->($class);
757    
758     unless (exists $self->{objects}{$id} && defined $self->{objects}{$id}) {
759     # do this only if object is not loaded yet
760     # otherwise we're just skipping columns in $row
761     $self->welcome($obj, $id);
762     }
763    
764     _row_to_object($self, $obj, $id, $class, $row, @parts);
765    
766     return $obj;
767     }
768    
769     sub _row_to_object
770     {
771     my ($self, $obj, $id, $class, $row) = @_;
772     $self->{engine}->get_import_cache($self->{schema}->classdef($class))
773     ->($obj, $row, { storage => $self, id => $id, layout1 => $self->{layout1} });
774     return $obj;
775     }
776    
777     sub _fetch_object_state
778     {
779     my ($self, $id, $class) = @_;
780    
781     my $sth = $self->{LOAD_STH}{$class->{name}} ||=
782     $self->prepare($self->{engine}->get_instance_select($class));
783    
784     $sth->execute($self->{export_id}->($id));
785     my $state = [ $sth->fetchrow_array() ];
786     $sth->finish();
787    
788     return $state;
789     }
790    
791     sub get_polymorphic_select
792     {
793     my ($self, $class) = @_;
794     return $self->{engine}->get_polymorphic_select($self->{schema}->classdef($class), $self);
795     }
796    
797     sub select {
798     croak "valid only in list context" unless wantarray;
799    
800     my ($self, $target, @args) = @_;
801    
802     unless (ref($target) eq 'ARRAY') {
803     my $cursor = Tangram::Cursor->new($self, $target, $self->{db});
804     return $cursor->select(@args);
805     }
806    
807     my ($first, @others) = @$target;
808    
809     my @cache = map { $self->select( $_, @args ) } @others;
810    
811     my $cursor = Tangram::Cursor->new($self, $first, $self->{db});
812     $cursor->retrieve( map { $_->{_IID_}, $_->{_TYPE_ } } @others );
813    
814     my $obj = $cursor->select( @args );
815     my @results;
816    
817     while ($obj) {
818     my @tuple = $obj;
819     my @residue = $cursor->residue;
820    
821     while (my $id = shift @residue) {
822     push @tuple, $self->load($self->combine_ids($id, shift @residue));
823     }
824    
825     push @results, \@tuple;
826     $obj = $cursor->next;
827     }
828    
829     return @results;
830     }
831    
832     sub cursor_object
833     {
834     my ($self, $class) = @_;
835     $self->{IMPLICIT}{$class} ||= Tangram::RDBObject->new($self, $class)
836     }
837    
838     sub query_objects
839     {
840     my ($self, @classes) = @_;
841     map { Tangram::QueryObject->new(Tangram::RDBObject->new($self, $_)) } @classes;
842     }
843    
844     sub remote
845     {
846     my ($self, @classes) = @_;
847     wantarray ? $self->query_objects(@classes) : (&remote)[0]
848     }
849    
850     sub expr
851     {
852     my $self = shift;
853     return shift->expr( @_ );
854     }
855    
856     sub object
857     {
858     carp "cannot be called in list context; use objects instead" if wantarray;
859     my $self = shift;
860     my ($obj) = $self->query_objects(@_);
861     $obj;
862     }
863    
864     sub count
865     {
866     my $self = shift;
867    
868     my ($target, $filter);
869     my $objects = Set::Object->new;
870    
871     if (@_ == 1)
872     {
873     $target = '*';
874     $filter = shift;
875     }
876     else
877     {
878     my $expr = shift;
879     $target = $expr->{expr};
880     $objects->insert($expr->objects);
881     $filter = shift;
882     }
883    
884     my @filter_expr;
885    
886     if ($filter)
887     {
888     $objects->insert($filter->objects);
889     @filter_expr = ( "($filter->{expr})" );
890     }
891    
892     my $sql = "SELECT COUNT($target) FROM " . join(', ', map { $_->from } $objects->members);
893    
894     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
895    
896     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
897    
898     return ($self->{db}->selectrow_array($sql))[0];
899     }
900    
901     sub sum
902     {
903     my ($self, $expr, $filter) = @_;
904    
905     my $objects = Set::Object->new($expr->objects);
906    
907     my @filter_expr;
908    
909     if ($filter)
910     {
911     $objects->insert($filter->objects);
912     @filter_expr = ( "($filter->{expr})" );
913     }
914    
915     my $sql = "SELECT SUM($expr->{expr}) FROM " . join(', ', map { $_->from } $objects->members);
916    
917     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
918    
919     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
920    
921     return ($self->{db}->selectrow_array($sql))[0];
922     }
923    
924     sub id
925     {
926     my ($self, $obj) = @_;
927     return $self->{get_id}->($obj);
928     }
929    
930     sub disconnect
931     {
932     my ($self) = @_;
933    
934     unless ($self->{no_tx})
935     {
936     if (@{ $self->{tx} })
937     {
938     $self->{db}->rollback;
939     }
940     else
941     {
942     $self->{db}->commit;
943     }
944     }
945    
946     $self->{db}->disconnect;
947    
948     %$self = ();
949     }
950    
951     sub _kind_class_ids
952     {
953     my ($self, $class) = @_;
954    
955     my $schema = $self->{schema};
956     my $classes = $self->{schema}{classes};
957     my $class2id = $self->{class2id};
958    
959     my @ids;
960    
961     push @ids, $self->class_id($class) unless $classes->{$class}{abstract};
962    
963     $schema->for_each_spec($class,
964     sub { my $spec = shift; push @ids, $class2id->{$spec} unless $classes->{$spec}{abstract} } );
965    
966     return @ids;
967     }
968    
969     sub is_persistent
970     {
971     my ($self, $obj) = @_;
972     return $self->{schema}->is_persistent($obj) && $self->id($obj);
973     }
974    
975     sub prefetch
976     {
977     my ($self, $remote, $member, $filter) = @_;
978    
979     my $class;
980    
981     if (ref $remote)
982     {
983     $class = $remote->class();
984     }
985     else
986     {
987     $class = $remote;
988     $remote = $self->remote($class);
989     }
990    
991     my $schema = $self->{schema};
992    
993     my $member_class = $schema->find_member_class($class, $member)
994     or die "no member '$member' in class '$class'";
995    
996     my $classdef = $schema->{classes}{$member_class};
997     my $type = $classdef->{member_type}{$member};
998     my $memdef = $classdef->{MEMDEFS}{$member};
999    
1000     $type->prefetch($self, $memdef, $remote, $class, $member, $filter);
1001     }
1002    
1003     sub connect
1004     {
1005     my ($pkg, $schema, $cs, $user, $pw, $opts) = @_;
1006    
1007     my $self = $pkg->new;
1008    
1009     $opts ||= {};
1010    
1011     my $db = $opts->{dbh} || DBI->connect($cs, $user, $pw);
1012    
1013     eval { $db->{AutoCommit} = 0 };
1014    
1015     $self->{no_tx} = $db->{AutoCommit};
1016    
1017     $self->{db} = $db;
1018    
1019     @$self{ -cs, -user, -pw } = ($cs, $user, $pw);
1020    
1021     $self->{cid_size} = $schema->{sql}{cid_size};
1022    
1023     $self->_open($schema);
1024    
1025     $self->{engine} = Tangram::Relational::Engine->new($schema, layout1 => $self->{layout1});
1026    
1027     return $self;
1028     }
1029    
1030     sub sql_do
1031     {
1032     my ($self, $sql) = @_;
1033     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1034     my $rows_affected = $self->{db}->do($sql);
1035     return defined($rows_affected) ? $rows_affected
1036     : croak $DBI::errstr;
1037     }
1038    
1039     sub sql_selectall_arrayref
1040     {
1041     my ($self, $sql, $dbh) = @_;
1042     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1043     ($dbh || $self->{db})->selectall_arrayref($sql);
1044     }
1045    
1046     sub sql_prepare
1047     {
1048     my ($self, $sql, $connection) = @_;
1049     confess unless $connection;
1050     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1051     return $connection->prepare($sql) or die;
1052     }
1053    
1054     sub sql_cursor
1055     {
1056     my ($self, $sql, $connection) = @_;
1057    
1058     confess unless $connection;
1059    
1060     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1061    
1062     my $sth = $connection->prepare($sql) or die;
1063     $sth->execute() or confess;
1064    
1065     Tangram::Storage::Statement->new( statement => $sth, storage => $self,
1066     connection => $connection );
1067     }
1068    
1069     sub unload
1070     {
1071     my $self = shift;
1072     my $objects = $self->{objects};
1073    
1074     if (@_) {
1075     for my $item (@_) {
1076     if (ref $item) {
1077     $self->goodbye($item, $self->{get_id}->($item));
1078     } else {
1079     $self->goodbye($objects->{$item}, $item);
1080     }
1081     }
1082     } else {
1083     for my $id (keys %$objects) {
1084     $self->goodbye($objects->{$id}, $id);
1085     }
1086     }
1087     }
1088    
1089     *reset = \&unload; # deprecated, use unload() instead
1090    
1091     sub DESTROY
1092     {
1093     my $self = shift;
1094     $self->{db}->disconnect if $self->{db};
1095     }
1096    
1097     package Tangram::Storage::Statement;
1098    
1099     sub new
1100     {
1101     my $class = shift;
1102     bless { @_ }, $class;
1103     }
1104    
1105     sub fetchrow
1106     {
1107     return shift->{statement}->fetchrow;
1108     }
1109    
1110     sub close
1111     {
1112     my $self = shift;
1113    
1114     if ($self->{storage})
1115     {
1116     $self->{statement}->finish;
1117     $self->{storage}->close_connection($self->{connection});
1118     %$self = ();
1119     }
1120     }
1121    
1122     1;

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