/[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.8 - (show annotations)
Wed Apr 23 23:40:58 2003 UTC (21 years, 2 months ago) by joko
Branch: MAIN
Changes since 1.7: +6 -0 lines
modified 'sub load':
included patch from Charles Owens: "RFC: fixing Storage::load()" [2002-02-05]
[http://sourceforge.net/mailarchive/forum.php?thread_id=466488&forum_id=7137]

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

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