/[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.5 - (hide annotations)
Mon Dec 16 05:50:07 2002 UTC (21 years, 6 months ago) by jonen
Branch: MAIN
Changes since 1.4: +2 -6 lines
- removed debug dumper

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     eval ("use Data::UUID;");
257     my $ug = Data::UUID->new();
258     my $guid = $ug->create_str();
259    
260     return $guid;
261     }
262    
263 jonen 1.1 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 jonen 1.5 # insert global unique identifier in object to persist across re-deploys
472     $obj->{guid} = $self->make_guid();
473 jonen 1.4
474 jonen 1.1 $saving->insert($obj);
475    
476     my $class_name = ref $obj;
477     my $classId = $self->{class2id}{$class_name} or unknown_classid $class_name;
478     my $class = $self->{schema}->classdef($class_name);
479    
480     my $id = $self->make_id($classId);
481    
482     $self->welcome($obj, $id);
483     $self->tx_on_rollback( sub { $self->goodbye($obj, $id) } );
484    
485     my $dbh = $self->{db};
486     my $engine = $self->{engine};
487     my $cache = $engine->get_save_cache($class);
488    
489     my $sths = $self->{INSERT_STHS}{$class_name} ||=
490     [ map { $self->prepare($_) } @{ $cache->{INSERTS} } ];
491    
492     my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
493     my @state = ( $self->{export_id}->($id), $classId, $cache->{EXPORTER}->($obj, $context) );
494    
495     my $fields = $cache->{INSERT_FIELDS};
496    
497     use integer;
498    
499     for my $i (0..$#$sths) {
500    
501     if ($Tangram::TRACE) {
502     printf $Tangram::TRACE "executing %s with (%s)\n",
503 jonen 1.4 $cache->{INSERTS}[$i],
504 jonen 1.1 join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
505     }
506    
507     my $sth = $sths->[$i];
508     $sth->execute(@state[ @{ $fields->[$i] } ]);
509     $sth->finish();
510     }
511    
512     return $id;
513     }
514    
515     #############################################################################
516     # update
517    
518     sub update
519     {
520     # public - write objects to storage
521    
522     my ($self, @objs) = @_;
523    
524     $self->tx_do(
525     sub
526     {
527     my ($self, @objs) = @_;
528     foreach my $obj (@objs)
529     {
530     local $self->{defered} = [];
531    
532     $self->_update($obj, Set::Object->new() );
533     $self->do_defered;
534     }
535     }, $self, @objs);
536     }
537    
538     sub _update
539     {
540     my ($self, $obj, $saving) = @_;
541    
542     die unless $saving;
543    
544     my $id = $self->id($obj) or confess "$obj must be persistent";
545    
546     $saving->insert($obj);
547    
548     my $class = $self->{schema}->classdef(ref $obj);
549     my $dbh = $self->{db};
550     my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
551    
552     my $cache = $self->{engine}->get_save_cache($class);
553     my @state = ( $self->{export_id}->($id), substr($id, -$self->{cid_size}), $cache->{EXPORTER}->($obj, $context) );
554    
555     my $fields = $cache->{UPDATE_FIELDS};
556    
557     my $sths = $self->{UPDATE_STHS}{$class->{name}} ||=
558     [ map {
559     print $Tangram::TRACE "preparing $_\n" if $Tangram::TRACE;
560     $self->prepare($_)
561     } @{ $cache->{UPDATES} } ];
562    
563     use integer;
564    
565     for my $i (0..$#$sths) {
566    
567     if ($Tangram::TRACE) {
568     printf $Tangram::TRACE "executing %s with (%s)\n",
569     $cache->{UPDATES}[$i],
570     join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
571     }
572    
573     my $sth = $sths->[$i];
574     $sth->execute(@state[ @{ $fields->[$i] } ]);
575     $sth->finish();
576     }
577     }
578    
579     #############################################################################
580     # save
581    
582     sub save
583     {
584     my $self = shift;
585    
586     foreach my $obj (@_) {
587     if ($self->id($obj)) {
588     $self->update($obj)
589     } else {
590     $self->insert($obj)
591     }
592     }
593     }
594    
595     sub _save
596     {
597     my ($self, $obj, $saving) = @_;
598    
599     if ($self->id($obj)) {
600     $self->_update($obj, $saving)
601     } else {
602     $self->_insert($obj, $saving)
603     }
604     }
605    
606    
607     #############################################################################
608     # erase
609    
610     sub erase
611     {
612     my ($self, @objs) = @_;
613    
614     $self->tx_do(
615     sub
616     {
617     my ($self, @objs) = @_;
618     my $schema = $self->{schema};
619     my $classes = $self->{schema}{classes};
620    
621     foreach my $obj (@objs)
622     {
623     my $id = $self->id($obj) or confess "object $obj is not persistent";
624     my $class = $schema->classdef(ref $obj);
625    
626     local $self->{defered} = [];
627    
628     $schema->visit_down(ref($obj),
629     sub
630     {
631     my $class = shift;
632     my $classdef = $classes->{$class};
633    
634     foreach my $typetag (keys %{$classdef->{members}}) {
635     my $members = $classdef->{members}{$typetag};
636     my $type = $schema->{types}{$typetag};
637     $type->erase($self, $obj, $members, $id);
638     }
639     } );
640    
641     my $sths = $self->{DELETE_STHS}{$class->{name}} ||=
642     [ map { $self->prepare($_) } @{ $self->{engine}->get_deletes($class) } ];
643    
644     my $eid = $self->{export_id}->($id);
645    
646     for my $sth (@$sths) {
647     $sth->execute($eid);
648     $sth->finish();
649     }
650    
651     $self->do_defered;
652    
653     $self->goodbye($obj, $id);
654     $self->tx_on_rollback( sub { $self->welcome($obj, $id) } );
655     }
656     }, $self, @objs );
657     }
658    
659     sub do_defered
660     {
661     my ($self) = @_;
662    
663     foreach my $defered (@{$self->{defered}})
664     {
665     $defered->($self);
666     }
667    
668     $self->{defered} = [];
669     }
670    
671     sub defer
672     {
673     my ($self, $action) = @_;
674     push @{$self->{defered}}, $action;
675     }
676    
677     sub load
678     {
679     my $self = shift;
680    
681     return map { scalar $self->load( $_ ) } @_ if wantarray;
682    
683     my $id = shift;
684     die if @_;
685    
686     return $self->{objects}{$id}
687     if exists $self->{objects}{$id} && defined $self->{objects}{$id};
688    
689     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
690    
691     my $row = _fetch_object_state($self, $id, $class);
692    
693     my $obj = $self->read_object($id, $class->{name}, $row);
694    
695     # ??? $self->{-residue} = \@row;
696    
697     return $obj;
698     }
699    
700     sub reload
701     {
702     my $self = shift;
703    
704     return map { scalar $self->load( $_ ) } @_ if wantarray;
705    
706     my $obj = shift;
707     my $id = $self->id($obj) or die "'$obj' is not persistent";
708     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
709    
710     my $row = _fetch_object_state($self, $id, $class);
711     _row_to_object($self, $obj, $id, $class->{name}, $row);
712    
713     return $obj;
714     }
715    
716     sub welcome
717     {
718     my ($self, $obj, $id) = @_;
719     $self->{set_id}->($obj, $id);
720     Tangram::weaken( $self->{objects}{$id} = $obj );
721     }
722    
723     sub goodbye
724     {
725     my ($self, $obj, $id) = @_;
726     $self->{set_id}->($obj, undef) if $obj;
727     delete $self->{objects}{$id};
728     delete $self->{PREFETCH}{$id};
729     }
730    
731     sub shrink
732     {
733     my ($self) = @_;
734    
735     my $objects = $self->{objects};
736     my $prefetch = $self->{prefetch};
737    
738     for my $id (keys %$objects)
739     {
740     next if $objects->{$id};
741     delete $objects->{$id};
742     delete $prefetch->{$id};
743     }
744     }
745    
746     sub read_object
747     {
748     my ($self, $id, $class, $row, @parts) = @_;
749    
750     my $schema = $self->{schema};
751    
752     my $obj = $schema->{make_object}->($class);
753    
754     unless (exists $self->{objects}{$id} && defined $self->{objects}{$id}) {
755     # do this only if object is not loaded yet
756     # otherwise we're just skipping columns in $row
757     $self->welcome($obj, $id);
758     }
759    
760     _row_to_object($self, $obj, $id, $class, $row, @parts);
761    
762     return $obj;
763     }
764    
765     sub _row_to_object
766     {
767     my ($self, $obj, $id, $class, $row) = @_;
768     $self->{engine}->get_import_cache($self->{schema}->classdef($class))
769     ->($obj, $row, { storage => $self, id => $id, layout1 => $self->{layout1} });
770     return $obj;
771     }
772    
773     sub _fetch_object_state
774     {
775     my ($self, $id, $class) = @_;
776    
777     my $sth = $self->{LOAD_STH}{$class->{name}} ||=
778     $self->prepare($self->{engine}->get_instance_select($class));
779    
780     $sth->execute($self->{export_id}->($id));
781     my $state = [ $sth->fetchrow_array() ];
782     $sth->finish();
783    
784     return $state;
785     }
786    
787     sub get_polymorphic_select
788     {
789     my ($self, $class) = @_;
790     return $self->{engine}->get_polymorphic_select($self->{schema}->classdef($class), $self);
791     }
792    
793     sub select {
794     croak "valid only in list context" unless wantarray;
795    
796     my ($self, $target, @args) = @_;
797    
798     unless (ref($target) eq 'ARRAY') {
799     my $cursor = Tangram::Cursor->new($self, $target, $self->{db});
800     return $cursor->select(@args);
801     }
802    
803     my ($first, @others) = @$target;
804    
805     my @cache = map { $self->select( $_, @args ) } @others;
806    
807     my $cursor = Tangram::Cursor->new($self, $first, $self->{db});
808     $cursor->retrieve( map { $_->{_IID_}, $_->{_TYPE_ } } @others );
809    
810     my $obj = $cursor->select( @args );
811     my @results;
812    
813     while ($obj) {
814     my @tuple = $obj;
815     my @residue = $cursor->residue;
816    
817     while (my $id = shift @residue) {
818     push @tuple, $self->load($self->combine_ids($id, shift @residue));
819     }
820    
821     push @results, \@tuple;
822     $obj = $cursor->next;
823     }
824    
825     return @results;
826     }
827    
828     sub cursor_object
829     {
830     my ($self, $class) = @_;
831     $self->{IMPLICIT}{$class} ||= Tangram::RDBObject->new($self, $class)
832     }
833    
834     sub query_objects
835     {
836     my ($self, @classes) = @_;
837     map { Tangram::QueryObject->new(Tangram::RDBObject->new($self, $_)) } @classes;
838     }
839    
840     sub remote
841     {
842     my ($self, @classes) = @_;
843     wantarray ? $self->query_objects(@classes) : (&remote)[0]
844     }
845    
846     sub expr
847     {
848     my $self = shift;
849     return shift->expr( @_ );
850     }
851    
852     sub object
853     {
854     carp "cannot be called in list context; use objects instead" if wantarray;
855     my $self = shift;
856     my ($obj) = $self->query_objects(@_);
857     $obj;
858     }
859    
860     sub count
861     {
862     my $self = shift;
863    
864     my ($target, $filter);
865     my $objects = Set::Object->new;
866    
867     if (@_ == 1)
868     {
869     $target = '*';
870     $filter = shift;
871     }
872     else
873     {
874     my $expr = shift;
875     $target = $expr->{expr};
876     $objects->insert($expr->objects);
877     $filter = shift;
878     }
879    
880     my @filter_expr;
881    
882     if ($filter)
883     {
884     $objects->insert($filter->objects);
885     @filter_expr = ( "($filter->{expr})" );
886     }
887    
888     my $sql = "SELECT COUNT($target) FROM " . join(', ', map { $_->from } $objects->members);
889    
890     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
891    
892     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
893    
894     return ($self->{db}->selectrow_array($sql))[0];
895     }
896    
897     sub sum
898     {
899     my ($self, $expr, $filter) = @_;
900    
901     my $objects = Set::Object->new($expr->objects);
902    
903     my @filter_expr;
904    
905     if ($filter)
906     {
907     $objects->insert($filter->objects);
908     @filter_expr = ( "($filter->{expr})" );
909     }
910    
911     my $sql = "SELECT SUM($expr->{expr}) FROM " . join(', ', map { $_->from } $objects->members);
912    
913     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
914    
915     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
916    
917     return ($self->{db}->selectrow_array($sql))[0];
918     }
919    
920     sub id
921     {
922     my ($self, $obj) = @_;
923     return $self->{get_id}->($obj);
924     }
925    
926     sub disconnect
927     {
928     my ($self) = @_;
929    
930     unless ($self->{no_tx})
931     {
932     if (@{ $self->{tx} })
933     {
934     $self->{db}->rollback;
935     }
936     else
937     {
938     $self->{db}->commit;
939     }
940     }
941    
942     $self->{db}->disconnect;
943    
944     %$self = ();
945     }
946    
947     sub _kind_class_ids
948     {
949     my ($self, $class) = @_;
950    
951     my $schema = $self->{schema};
952     my $classes = $self->{schema}{classes};
953     my $class2id = $self->{class2id};
954    
955     my @ids;
956    
957     push @ids, $self->class_id($class) unless $classes->{$class}{abstract};
958    
959     $schema->for_each_spec($class,
960     sub { my $spec = shift; push @ids, $class2id->{$spec} unless $classes->{$spec}{abstract} } );
961    
962     return @ids;
963     }
964    
965     sub is_persistent
966     {
967     my ($self, $obj) = @_;
968     return $self->{schema}->is_persistent($obj) && $self->id($obj);
969     }
970    
971     sub prefetch
972     {
973     my ($self, $remote, $member, $filter) = @_;
974    
975     my $class;
976    
977     if (ref $remote)
978     {
979     $class = $remote->class();
980     }
981     else
982     {
983     $class = $remote;
984     $remote = $self->remote($class);
985     }
986    
987     my $schema = $self->{schema};
988    
989     my $member_class = $schema->find_member_class($class, $member)
990     or die "no member '$member' in class '$class'";
991    
992     my $classdef = $schema->{classes}{$member_class};
993     my $type = $classdef->{member_type}{$member};
994     my $memdef = $classdef->{MEMDEFS}{$member};
995    
996     $type->prefetch($self, $memdef, $remote, $class, $member, $filter);
997     }
998    
999     sub connect
1000     {
1001     my ($pkg, $schema, $cs, $user, $pw, $opts) = @_;
1002    
1003     my $self = $pkg->new;
1004    
1005     $opts ||= {};
1006    
1007     my $db = $opts->{dbh} || DBI->connect($cs, $user, $pw);
1008    
1009     eval { $db->{AutoCommit} = 0 };
1010    
1011     $self->{no_tx} = $db->{AutoCommit};
1012    
1013     $self->{db} = $db;
1014    
1015     @$self{ -cs, -user, -pw } = ($cs, $user, $pw);
1016    
1017     $self->{cid_size} = $schema->{sql}{cid_size};
1018    
1019     $self->_open($schema);
1020    
1021     $self->{engine} = Tangram::Relational::Engine->new($schema, layout1 => $self->{layout1});
1022    
1023     return $self;
1024     }
1025    
1026     sub sql_do
1027     {
1028     my ($self, $sql) = @_;
1029     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1030     my $rows_affected = $self->{db}->do($sql);
1031     return defined($rows_affected) ? $rows_affected
1032     : croak $DBI::errstr;
1033     }
1034    
1035     sub sql_selectall_arrayref
1036     {
1037     my ($self, $sql, $dbh) = @_;
1038     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1039     ($dbh || $self->{db})->selectall_arrayref($sql);
1040     }
1041    
1042     sub sql_prepare
1043     {
1044     my ($self, $sql, $connection) = @_;
1045     confess unless $connection;
1046     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1047     return $connection->prepare($sql) or die;
1048     }
1049    
1050     sub sql_cursor
1051     {
1052     my ($self, $sql, $connection) = @_;
1053    
1054     confess unless $connection;
1055    
1056     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1057    
1058     my $sth = $connection->prepare($sql) or die;
1059     $sth->execute() or confess;
1060    
1061     Tangram::Storage::Statement->new( statement => $sth, storage => $self,
1062     connection => $connection );
1063     }
1064    
1065     sub unload
1066     {
1067     my $self = shift;
1068     my $objects = $self->{objects};
1069    
1070     if (@_) {
1071     for my $item (@_) {
1072     if (ref $item) {
1073     $self->goodbye($item, $self->{get_id}->($item));
1074     } else {
1075     $self->goodbye($objects->{$item}, $item);
1076     }
1077     }
1078     } else {
1079     for my $id (keys %$objects) {
1080     $self->goodbye($objects->{$id}, $id);
1081     }
1082     }
1083     }
1084    
1085     *reset = \&unload; # deprecated, use unload() instead
1086    
1087     sub DESTROY
1088     {
1089     my $self = shift;
1090     $self->{db}->disconnect if $self->{db};
1091     }
1092    
1093     package Tangram::Storage::Statement;
1094    
1095     sub new
1096     {
1097     my $class = shift;
1098     bless { @_ }, $class;
1099     }
1100    
1101     sub fetchrow
1102     {
1103     return shift->{statement}->fetchrow;
1104     }
1105    
1106     sub close
1107     {
1108     my $self = shift;
1109    
1110     if ($self->{storage})
1111     {
1112     $self->{statement}->finish;
1113     $self->{storage}->close_connection($self->{connection});
1114     %$self = ();
1115     }
1116     }
1117    
1118     1;

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