/[cvs]/nfo/patches/cpan/Tangram/Storage.pm
ViewVC logotype

Contents of /nfo/patches/cpan/Tangram/Storage.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.10 - (show annotations)
Wed May 7 16:20:26 2003 UTC (21 years, 7 months ago) by joko
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +7 -7 lines
restored version w/o patches from mailing-list

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

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