/[cvs]/nfo/perl/libs/Tangram/Storage.pm
ViewVC logotype

Contents of /nfo/perl/libs/Tangram/Storage.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (show 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 # (c) Sound Object Logic 2000-2001
2
3 use strict;
4
5 package Tangram::Storage;
6 use DBI;
7 use Carp;
8
9 use vars qw( %storage_class );
10
11 BEGIN {
12
13 eval { require 'WeakRef.pm' };
14
15 if ($@) {
16 *Tangram::weaken = sub { };
17 $Tangram::no_weakrefs = 1;
18 } else {
19 *Tangram::weaken = \&WeakRef::weaken;
20 $Tangram::no_weakrefs = 0;
21 }
22 }
23
24 sub new
25 {
26 my $pkg = shift;
27 return bless { @_ }, $pkg;
28 }
29
30 sub schema
31 {
32 shift->{schema}
33 }
34
35 sub export_object
36 {
37 my ($self, $obj) = @_;
38 return $self->{export_id}->($self->{get_id}->($obj));
39 }
40
41 sub split_id
42 {
43 carp unless wantarray;
44 my ($self, $id) = @_;
45 my $cid_size = $self->{cid_size};
46 return ( substr($id, 0, -$cid_size), substr($id, -$cid_size) );
47 }
48
49 sub combine_ids
50 {
51 my $self = shift;
52 return $self->{layout1} ? shift : sprintf("%d%0$self->{cid_size}d", @_);
53 }
54
55 sub _open
56 {
57 my ($self, $schema) = @_;
58
59 my $dbh = $self->{db};
60
61 $self->{table_top} = 0;
62 $self->{free_tables} = [];
63
64 $self->{tx} = [];
65
66 $self->{schema} = $schema;
67
68 {
69 local $dbh->{PrintError} = 0;
70 my $control = $dbh->selectall_arrayref("SELECT * FROM $schema->{control}");
71
72 $self->{id_col} = $schema->{sql}{id_col};
73
74 if ($control) {
75 $self->{class_col} = $schema->{sql}{class_col} || 'type';
76 $self->{import_id} = sub { shift() . sprintf("%0$self->{cid_size}d", shift()) };
77 $self->{export_id} = sub { substr shift(), 0, -$self->{cid_size} };
78 } else {
79 $self->{class_col} = 'classId';
80 $self->{layout1} = 1;
81 $self->{import_id} = sub { shift() };
82 $self->{export_id} = sub { shift() };
83 }
84 }
85
86 my %id2class;
87
88 if ($self->{layout1}) {
89 # compatibility with version 1.x
90 %id2class = map { @$_ } @{ $self->{db}->selectall_arrayref("SELECT classId, className FROM $schema->{class_table}") };
91 } else {
92 my $classes = $schema->{classes};
93 %id2class = map { $classes->{$_}{id}, $_ } keys %$classes;
94 }
95
96 $self->{id2class} = \%id2class;
97 @{ $self->{class2id} }{ values %id2class } = keys %id2class;
98
99 $self->{set_id} = $schema->{set_id} ||
100 sub
101 {
102 my ($obj, $id) = @_;
103
104 if ($id) {
105 $self->{ids}{0 + $obj} = $id;
106 } else {
107 delete $self->{ids}{0 + $obj};
108 }
109 };
110
111 $self->{get_id} = $schema->{get_id} || sub {
112 my $address = 0 + shift();
113 my $id = $self->{ids}{$address};
114 return undef unless $id;
115 return $id if $self->{objects}{$id};
116 delete $self->{ids}{$address};
117 delete $self->{objects}{$id};
118 return undef;
119 };
120
121 return $self;
122 }
123
124 sub alloc_table
125 {
126 my ($self) = @_;
127
128 return @{$self->{free_tables}} > 0
129 ? pop @{$self->{free_tables}}
130 : ++$self->{table_top};
131 }
132
133 sub free_table
134 {
135 my $self = shift;
136 push @{$self->{free_tables}}, grep { $_ } @_;
137 }
138
139 sub open_connection
140 {
141 # private - open a new connection to DB for read
142
143 my $self = shift;
144 DBI->connect($self->{-cs}, $self->{-user}, $self->{-pw}) or die;
145 }
146
147 sub close_connection
148 {
149 # private - close read connection to DB unless it's the default one
150
151 my ($self, $conn) = @_;
152
153 return unless $conn && $self->{db};
154
155 if ($conn == $self->{db})
156 {
157 $conn->commit unless $self->{no_tx} || @{ $self->{tx} };
158 }
159 else
160 {
161 $conn->disconnect;
162 }
163 }
164
165 sub cursor
166 {
167 my ($self, $class, @args) = @_;
168 my $cursor = Tangram::Cursor->new($self, $class, $self->open_connection());
169 $cursor->select(@args);
170 return $cursor;
171 }
172
173 sub my_cursor
174 {
175 my ($self, $class, @args) = @_;
176 my $cursor = Tangram::Cursor->new($self, $class, $self->{db});
177 $cursor->select(@args);
178 return $cursor;
179 }
180
181 sub select_data
182 {
183 my $self = shift;
184 Tangram::Select->new(@_)->execute($self, $self->open_connection());
185 }
186
187 sub selectall_arrayref
188 {
189 shift->select_data(@_)->fetchall_arrayref();
190 }
191
192 sub my_select_data
193 {
194 my $self = shift;
195 Tangram::Select->new(@_)->execute($self, $self->{db});
196 }
197
198 my $psi = 1;
199
200 sub prepare
201 {
202 my ($self, $sql) = @_;
203
204 print $Tangram::TRACE "preparing [@{[ $psi++ ]}] $sql\n" if $Tangram::TRACE;
205 $self->{db}->prepare($sql);
206 }
207
208 *prepare_insert = \&prepare;
209 *prepare_update = \&prepare;
210 *prepare_select = \&prepare;
211
212 sub make_id
213 {
214 my ($self, $class_id) = @_;
215
216 unless ($self->{layout1}) {
217 my $id;
218
219 if (exists $self->{mark}) {
220 $id = $self->{mark}++;
221 $self->{set_mark} = 1; # cleared by tx_start
222 } else {
223 $id = $self->make_1st_id_in_tx();
224 }
225
226 return sprintf "%d%0$self->{cid_size}d", $id, $class_id;
227 }
228
229 # ------------------------------
230 # compatibility with version 1.x
231
232 my $alloc_id = $self->{alloc_id} ||= {};
233
234 my $id = $alloc_id->{$class_id};
235
236 if ($id) {
237 $id = -$id if $id < 0;
238 $alloc_id->{$class_id} = ++$id;
239 } else {
240 my $table = $self->{schema}{class_table};
241 $self->sql_do("UPDATE $table SET lastObjectId = lastObjectId + 1 WHERE classId = $class_id");
242 $id = $self
243 ->sql_selectall_arrayref("SELECT lastObjectId from $table WHERE classId = $class_id")->[0][0];
244 $alloc_id->{$class_id} = -$id;
245 }
246
247 return sprintf "%d%0$self->{cid_size}d", $id, $class_id;
248 }
249
250
251 # create global unique identifers using Data::UUID
252 sub make_guid
253 {
254 my $self = shift;
255
256 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
274 return $guid;
275 }
276
277 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 # insert global unique identifier in object to persist across re-deploys
486 $obj->{guid} = $self->make_guid();
487
488 $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 $cache->{INSERTS}[$i],
518 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