/[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.7 - (hide annotations)
Thu Dec 19 16:37:54 2002 UTC (21 years, 6 months ago) by joko
Branch: MAIN
Changes since 1.6: +17 -4 lines
+ enhanced 'makeGuid': trying to use Data::UUID, else tries to fallback to Data::UUID::PurePerl, else croaks

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    
709     # ??? $self->{-residue} = \@row;
710    
711     return $obj;
712     }
713    
714     sub reload
715     {
716     my $self = shift;
717    
718     return map { scalar $self->load( $_ ) } @_ if wantarray;
719    
720     my $obj = shift;
721     my $id = $self->id($obj) or die "'$obj' is not persistent";
722     my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
723    
724     my $row = _fetch_object_state($self, $id, $class);
725     _row_to_object($self, $obj, $id, $class->{name}, $row);
726    
727     return $obj;
728     }
729    
730     sub welcome
731     {
732     my ($self, $obj, $id) = @_;
733     $self->{set_id}->($obj, $id);
734     Tangram::weaken( $self->{objects}{$id} = $obj );
735     }
736    
737     sub goodbye
738     {
739     my ($self, $obj, $id) = @_;
740     $self->{set_id}->($obj, undef) if $obj;
741     delete $self->{objects}{$id};
742     delete $self->{PREFETCH}{$id};
743     }
744    
745     sub shrink
746     {
747     my ($self) = @_;
748    
749     my $objects = $self->{objects};
750     my $prefetch = $self->{prefetch};
751    
752     for my $id (keys %$objects)
753     {
754     next if $objects->{$id};
755     delete $objects->{$id};
756     delete $prefetch->{$id};
757     }
758     }
759    
760     sub read_object
761     {
762     my ($self, $id, $class, $row, @parts) = @_;
763    
764     my $schema = $self->{schema};
765    
766     my $obj = $schema->{make_object}->($class);
767    
768     unless (exists $self->{objects}{$id} && defined $self->{objects}{$id}) {
769     # do this only if object is not loaded yet
770     # otherwise we're just skipping columns in $row
771     $self->welcome($obj, $id);
772     }
773    
774     _row_to_object($self, $obj, $id, $class, $row, @parts);
775    
776     return $obj;
777     }
778    
779     sub _row_to_object
780     {
781     my ($self, $obj, $id, $class, $row) = @_;
782     $self->{engine}->get_import_cache($self->{schema}->classdef($class))
783     ->($obj, $row, { storage => $self, id => $id, layout1 => $self->{layout1} });
784     return $obj;
785     }
786    
787     sub _fetch_object_state
788     {
789     my ($self, $id, $class) = @_;
790    
791     my $sth = $self->{LOAD_STH}{$class->{name}} ||=
792     $self->prepare($self->{engine}->get_instance_select($class));
793    
794     $sth->execute($self->{export_id}->($id));
795     my $state = [ $sth->fetchrow_array() ];
796     $sth->finish();
797    
798     return $state;
799     }
800    
801     sub get_polymorphic_select
802     {
803     my ($self, $class) = @_;
804     return $self->{engine}->get_polymorphic_select($self->{schema}->classdef($class), $self);
805     }
806    
807     sub select {
808     croak "valid only in list context" unless wantarray;
809    
810     my ($self, $target, @args) = @_;
811    
812     unless (ref($target) eq 'ARRAY') {
813     my $cursor = Tangram::Cursor->new($self, $target, $self->{db});
814     return $cursor->select(@args);
815     }
816    
817     my ($first, @others) = @$target;
818    
819     my @cache = map { $self->select( $_, @args ) } @others;
820    
821     my $cursor = Tangram::Cursor->new($self, $first, $self->{db});
822     $cursor->retrieve( map { $_->{_IID_}, $_->{_TYPE_ } } @others );
823    
824     my $obj = $cursor->select( @args );
825     my @results;
826    
827     while ($obj) {
828     my @tuple = $obj;
829     my @residue = $cursor->residue;
830    
831     while (my $id = shift @residue) {
832     push @tuple, $self->load($self->combine_ids($id, shift @residue));
833     }
834    
835     push @results, \@tuple;
836     $obj = $cursor->next;
837     }
838    
839     return @results;
840     }
841    
842     sub cursor_object
843     {
844     my ($self, $class) = @_;
845     $self->{IMPLICIT}{$class} ||= Tangram::RDBObject->new($self, $class)
846     }
847    
848     sub query_objects
849     {
850     my ($self, @classes) = @_;
851     map { Tangram::QueryObject->new(Tangram::RDBObject->new($self, $_)) } @classes;
852     }
853    
854     sub remote
855     {
856     my ($self, @classes) = @_;
857     wantarray ? $self->query_objects(@classes) : (&remote)[0]
858     }
859    
860     sub expr
861     {
862     my $self = shift;
863     return shift->expr( @_ );
864     }
865    
866     sub object
867     {
868     carp "cannot be called in list context; use objects instead" if wantarray;
869     my $self = shift;
870     my ($obj) = $self->query_objects(@_);
871     $obj;
872     }
873    
874     sub count
875     {
876     my $self = shift;
877    
878     my ($target, $filter);
879     my $objects = Set::Object->new;
880    
881     if (@_ == 1)
882     {
883     $target = '*';
884     $filter = shift;
885     }
886     else
887     {
888     my $expr = shift;
889     $target = $expr->{expr};
890     $objects->insert($expr->objects);
891     $filter = shift;
892     }
893    
894     my @filter_expr;
895    
896     if ($filter)
897     {
898     $objects->insert($filter->objects);
899     @filter_expr = ( "($filter->{expr})" );
900     }
901    
902     my $sql = "SELECT COUNT($target) FROM " . join(', ', map { $_->from } $objects->members);
903    
904     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
905    
906     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
907    
908     return ($self->{db}->selectrow_array($sql))[0];
909     }
910    
911     sub sum
912     {
913     my ($self, $expr, $filter) = @_;
914    
915     my $objects = Set::Object->new($expr->objects);
916    
917     my @filter_expr;
918    
919     if ($filter)
920     {
921     $objects->insert($filter->objects);
922     @filter_expr = ( "($filter->{expr})" );
923     }
924    
925     my $sql = "SELECT SUM($expr->{expr}) FROM " . join(', ', map { $_->from } $objects->members);
926    
927     $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
928    
929     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
930    
931     return ($self->{db}->selectrow_array($sql))[0];
932     }
933    
934     sub id
935     {
936     my ($self, $obj) = @_;
937     return $self->{get_id}->($obj);
938     }
939    
940     sub disconnect
941     {
942     my ($self) = @_;
943    
944     unless ($self->{no_tx})
945     {
946     if (@{ $self->{tx} })
947     {
948     $self->{db}->rollback;
949     }
950     else
951     {
952     $self->{db}->commit;
953     }
954     }
955    
956     $self->{db}->disconnect;
957    
958     %$self = ();
959     }
960    
961     sub _kind_class_ids
962     {
963     my ($self, $class) = @_;
964    
965     my $schema = $self->{schema};
966     my $classes = $self->{schema}{classes};
967     my $class2id = $self->{class2id};
968    
969     my @ids;
970    
971     push @ids, $self->class_id($class) unless $classes->{$class}{abstract};
972    
973     $schema->for_each_spec($class,
974     sub { my $spec = shift; push @ids, $class2id->{$spec} unless $classes->{$spec}{abstract} } );
975    
976     return @ids;
977     }
978    
979     sub is_persistent
980     {
981     my ($self, $obj) = @_;
982     return $self->{schema}->is_persistent($obj) && $self->id($obj);
983     }
984    
985     sub prefetch
986     {
987     my ($self, $remote, $member, $filter) = @_;
988    
989     my $class;
990    
991     if (ref $remote)
992     {
993     $class = $remote->class();
994     }
995     else
996     {
997     $class = $remote;
998     $remote = $self->remote($class);
999     }
1000    
1001     my $schema = $self->{schema};
1002    
1003     my $member_class = $schema->find_member_class($class, $member)
1004     or die "no member '$member' in class '$class'";
1005    
1006     my $classdef = $schema->{classes}{$member_class};
1007     my $type = $classdef->{member_type}{$member};
1008     my $memdef = $classdef->{MEMDEFS}{$member};
1009    
1010     $type->prefetch($self, $memdef, $remote, $class, $member, $filter);
1011     }
1012    
1013     sub connect
1014     {
1015     my ($pkg, $schema, $cs, $user, $pw, $opts) = @_;
1016    
1017     my $self = $pkg->new;
1018    
1019     $opts ||= {};
1020    
1021     my $db = $opts->{dbh} || DBI->connect($cs, $user, $pw);
1022    
1023     eval { $db->{AutoCommit} = 0 };
1024    
1025     $self->{no_tx} = $db->{AutoCommit};
1026    
1027     $self->{db} = $db;
1028    
1029     @$self{ -cs, -user, -pw } = ($cs, $user, $pw);
1030    
1031     $self->{cid_size} = $schema->{sql}{cid_size};
1032    
1033     $self->_open($schema);
1034    
1035     $self->{engine} = Tangram::Relational::Engine->new($schema, layout1 => $self->{layout1});
1036    
1037     return $self;
1038     }
1039    
1040     sub sql_do
1041     {
1042     my ($self, $sql) = @_;
1043     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1044     my $rows_affected = $self->{db}->do($sql);
1045     return defined($rows_affected) ? $rows_affected
1046     : croak $DBI::errstr;
1047     }
1048    
1049     sub sql_selectall_arrayref
1050     {
1051     my ($self, $sql, $dbh) = @_;
1052     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1053     ($dbh || $self->{db})->selectall_arrayref($sql);
1054     }
1055    
1056     sub sql_prepare
1057     {
1058     my ($self, $sql, $connection) = @_;
1059     confess unless $connection;
1060     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1061     return $connection->prepare($sql) or die;
1062     }
1063    
1064     sub sql_cursor
1065     {
1066     my ($self, $sql, $connection) = @_;
1067    
1068     confess unless $connection;
1069    
1070     print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1071    
1072     my $sth = $connection->prepare($sql) or die;
1073     $sth->execute() or confess;
1074    
1075     Tangram::Storage::Statement->new( statement => $sth, storage => $self,
1076     connection => $connection );
1077     }
1078    
1079     sub unload
1080     {
1081     my $self = shift;
1082     my $objects = $self->{objects};
1083    
1084     if (@_) {
1085     for my $item (@_) {
1086     if (ref $item) {
1087     $self->goodbye($item, $self->{get_id}->($item));
1088     } else {
1089     $self->goodbye($objects->{$item}, $item);
1090     }
1091     }
1092     } else {
1093     for my $id (keys %$objects) {
1094     $self->goodbye($objects->{$id}, $id);
1095     }
1096     }
1097     }
1098    
1099     *reset = \&unload; # deprecated, use unload() instead
1100    
1101     sub DESTROY
1102     {
1103     my $self = shift;
1104     $self->{db}->disconnect if $self->{db};
1105     }
1106    
1107     package Tangram::Storage::Statement;
1108    
1109     sub new
1110     {
1111     my $class = shift;
1112     bless { @_ }, $class;
1113     }
1114    
1115     sub fetchrow
1116     {
1117     return shift->{statement}->fetchrow;
1118     }
1119    
1120     sub close
1121     {
1122     my $self = shift;
1123    
1124     if ($self->{storage})
1125     {
1126     $self->{statement}->finish;
1127     $self->{storage}->close_connection($self->{connection});
1128     %$self = ();
1129     }
1130     }
1131    
1132     1;

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