/[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.6 - (hide annotations)
Mon Dec 16 20:41:51 2002 UTC (21 years, 6 months ago) by joko
Branch: MAIN
Changes since 1.5: +1 -0 lines
+ fix: don't die when Data::UUID is not available

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

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