/[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.3 - (show annotations)
Mon Dec 16 05:08:22 2002 UTC (21 years, 6 months ago) by jonen
Branch: MAIN
Changes since 1.2: +1 -21 lines
+ inital checkin
  copied orginal Storage.pm here

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 $cache->{INSERTS}[$i],
488 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