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

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