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

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