/[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.5 - (show annotations)
Mon Dec 16 05:50:07 2002 UTC (21 years, 6 months ago) by jonen
Branch: MAIN
Changes since 1.4: +2 -6 lines
- removed debug dumper

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 $saving->insert($obj);
475
476 my $class_name = ref $obj;
477 my $classId = $self->{class2id}{$class_name} or unknown_classid $class_name;
478 my $class = $self->{schema}->classdef($class_name);
479
480 my $id = $self->make_id($classId);
481
482 $self->welcome($obj, $id);
483 $self->tx_on_rollback( sub { $self->goodbye($obj, $id) } );
484
485 my $dbh = $self->{db};
486 my $engine = $self->{engine};
487 my $cache = $engine->get_save_cache($class);
488
489 my $sths = $self->{INSERT_STHS}{$class_name} ||=
490 [ map { $self->prepare($_) } @{ $cache->{INSERTS} } ];
491
492 my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
493 my @state = ( $self->{export_id}->($id), $classId, $cache->{EXPORTER}->($obj, $context) );
494
495 my $fields = $cache->{INSERT_FIELDS};
496
497 use integer;
498
499 for my $i (0..$#$sths) {
500
501 if ($Tangram::TRACE) {
502 printf $Tangram::TRACE "executing %s with (%s)\n",
503 $cache->{INSERTS}[$i],
504 join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
505 }
506
507 my $sth = $sths->[$i];
508 $sth->execute(@state[ @{ $fields->[$i] } ]);
509 $sth->finish();
510 }
511
512 return $id;
513 }
514
515 #############################################################################
516 # update
517
518 sub update
519 {
520 # public - write objects to storage
521
522 my ($self, @objs) = @_;
523
524 $self->tx_do(
525 sub
526 {
527 my ($self, @objs) = @_;
528 foreach my $obj (@objs)
529 {
530 local $self->{defered} = [];
531
532 $self->_update($obj, Set::Object->new() );
533 $self->do_defered;
534 }
535 }, $self, @objs);
536 }
537
538 sub _update
539 {
540 my ($self, $obj, $saving) = @_;
541
542 die unless $saving;
543
544 my $id = $self->id($obj) or confess "$obj must be persistent";
545
546 $saving->insert($obj);
547
548 my $class = $self->{schema}->classdef(ref $obj);
549 my $dbh = $self->{db};
550 my $context = { storage => $self, dbh => $dbh, id => $id, SAVING => $saving };
551
552 my $cache = $self->{engine}->get_save_cache($class);
553 my @state = ( $self->{export_id}->($id), substr($id, -$self->{cid_size}), $cache->{EXPORTER}->($obj, $context) );
554
555 my $fields = $cache->{UPDATE_FIELDS};
556
557 my $sths = $self->{UPDATE_STHS}{$class->{name}} ||=
558 [ map {
559 print $Tangram::TRACE "preparing $_\n" if $Tangram::TRACE;
560 $self->prepare($_)
561 } @{ $cache->{UPDATES} } ];
562
563 use integer;
564
565 for my $i (0..$#$sths) {
566
567 if ($Tangram::TRACE) {
568 printf $Tangram::TRACE "executing %s with (%s)\n",
569 $cache->{UPDATES}[$i],
570 join(', ', map { $_ || 'NULL' } @state[ @{ $fields->[$i] } ] )
571 }
572
573 my $sth = $sths->[$i];
574 $sth->execute(@state[ @{ $fields->[$i] } ]);
575 $sth->finish();
576 }
577 }
578
579 #############################################################################
580 # save
581
582 sub save
583 {
584 my $self = shift;
585
586 foreach my $obj (@_) {
587 if ($self->id($obj)) {
588 $self->update($obj)
589 } else {
590 $self->insert($obj)
591 }
592 }
593 }
594
595 sub _save
596 {
597 my ($self, $obj, $saving) = @_;
598
599 if ($self->id($obj)) {
600 $self->_update($obj, $saving)
601 } else {
602 $self->_insert($obj, $saving)
603 }
604 }
605
606
607 #############################################################################
608 # erase
609
610 sub erase
611 {
612 my ($self, @objs) = @_;
613
614 $self->tx_do(
615 sub
616 {
617 my ($self, @objs) = @_;
618 my $schema = $self->{schema};
619 my $classes = $self->{schema}{classes};
620
621 foreach my $obj (@objs)
622 {
623 my $id = $self->id($obj) or confess "object $obj is not persistent";
624 my $class = $schema->classdef(ref $obj);
625
626 local $self->{defered} = [];
627
628 $schema->visit_down(ref($obj),
629 sub
630 {
631 my $class = shift;
632 my $classdef = $classes->{$class};
633
634 foreach my $typetag (keys %{$classdef->{members}}) {
635 my $members = $classdef->{members}{$typetag};
636 my $type = $schema->{types}{$typetag};
637 $type->erase($self, $obj, $members, $id);
638 }
639 } );
640
641 my $sths = $self->{DELETE_STHS}{$class->{name}} ||=
642 [ map { $self->prepare($_) } @{ $self->{engine}->get_deletes($class) } ];
643
644 my $eid = $self->{export_id}->($id);
645
646 for my $sth (@$sths) {
647 $sth->execute($eid);
648 $sth->finish();
649 }
650
651 $self->do_defered;
652
653 $self->goodbye($obj, $id);
654 $self->tx_on_rollback( sub { $self->welcome($obj, $id) } );
655 }
656 }, $self, @objs );
657 }
658
659 sub do_defered
660 {
661 my ($self) = @_;
662
663 foreach my $defered (@{$self->{defered}})
664 {
665 $defered->($self);
666 }
667
668 $self->{defered} = [];
669 }
670
671 sub defer
672 {
673 my ($self, $action) = @_;
674 push @{$self->{defered}}, $action;
675 }
676
677 sub load
678 {
679 my $self = shift;
680
681 return map { scalar $self->load( $_ ) } @_ if wantarray;
682
683 my $id = shift;
684 die if @_;
685
686 return $self->{objects}{$id}
687 if exists $self->{objects}{$id} && defined $self->{objects}{$id};
688
689 my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
690
691 my $row = _fetch_object_state($self, $id, $class);
692
693 my $obj = $self->read_object($id, $class->{name}, $row);
694
695 # ??? $self->{-residue} = \@row;
696
697 return $obj;
698 }
699
700 sub reload
701 {
702 my $self = shift;
703
704 return map { scalar $self->load( $_ ) } @_ if wantarray;
705
706 my $obj = shift;
707 my $id = $self->id($obj) or die "'$obj' is not persistent";
708 my $class = $self->{schema}->classdef( $self->{id2class}{ int(substr($id, -$self->{cid_size})) } );
709
710 my $row = _fetch_object_state($self, $id, $class);
711 _row_to_object($self, $obj, $id, $class->{name}, $row);
712
713 return $obj;
714 }
715
716 sub welcome
717 {
718 my ($self, $obj, $id) = @_;
719 $self->{set_id}->($obj, $id);
720 Tangram::weaken( $self->{objects}{$id} = $obj );
721 }
722
723 sub goodbye
724 {
725 my ($self, $obj, $id) = @_;
726 $self->{set_id}->($obj, undef) if $obj;
727 delete $self->{objects}{$id};
728 delete $self->{PREFETCH}{$id};
729 }
730
731 sub shrink
732 {
733 my ($self) = @_;
734
735 my $objects = $self->{objects};
736 my $prefetch = $self->{prefetch};
737
738 for my $id (keys %$objects)
739 {
740 next if $objects->{$id};
741 delete $objects->{$id};
742 delete $prefetch->{$id};
743 }
744 }
745
746 sub read_object
747 {
748 my ($self, $id, $class, $row, @parts) = @_;
749
750 my $schema = $self->{schema};
751
752 my $obj = $schema->{make_object}->($class);
753
754 unless (exists $self->{objects}{$id} && defined $self->{objects}{$id}) {
755 # do this only if object is not loaded yet
756 # otherwise we're just skipping columns in $row
757 $self->welcome($obj, $id);
758 }
759
760 _row_to_object($self, $obj, $id, $class, $row, @parts);
761
762 return $obj;
763 }
764
765 sub _row_to_object
766 {
767 my ($self, $obj, $id, $class, $row) = @_;
768 $self->{engine}->get_import_cache($self->{schema}->classdef($class))
769 ->($obj, $row, { storage => $self, id => $id, layout1 => $self->{layout1} });
770 return $obj;
771 }
772
773 sub _fetch_object_state
774 {
775 my ($self, $id, $class) = @_;
776
777 my $sth = $self->{LOAD_STH}{$class->{name}} ||=
778 $self->prepare($self->{engine}->get_instance_select($class));
779
780 $sth->execute($self->{export_id}->($id));
781 my $state = [ $sth->fetchrow_array() ];
782 $sth->finish();
783
784 return $state;
785 }
786
787 sub get_polymorphic_select
788 {
789 my ($self, $class) = @_;
790 return $self->{engine}->get_polymorphic_select($self->{schema}->classdef($class), $self);
791 }
792
793 sub select {
794 croak "valid only in list context" unless wantarray;
795
796 my ($self, $target, @args) = @_;
797
798 unless (ref($target) eq 'ARRAY') {
799 my $cursor = Tangram::Cursor->new($self, $target, $self->{db});
800 return $cursor->select(@args);
801 }
802
803 my ($first, @others) = @$target;
804
805 my @cache = map { $self->select( $_, @args ) } @others;
806
807 my $cursor = Tangram::Cursor->new($self, $first, $self->{db});
808 $cursor->retrieve( map { $_->{_IID_}, $_->{_TYPE_ } } @others );
809
810 my $obj = $cursor->select( @args );
811 my @results;
812
813 while ($obj) {
814 my @tuple = $obj;
815 my @residue = $cursor->residue;
816
817 while (my $id = shift @residue) {
818 push @tuple, $self->load($self->combine_ids($id, shift @residue));
819 }
820
821 push @results, \@tuple;
822 $obj = $cursor->next;
823 }
824
825 return @results;
826 }
827
828 sub cursor_object
829 {
830 my ($self, $class) = @_;
831 $self->{IMPLICIT}{$class} ||= Tangram::RDBObject->new($self, $class)
832 }
833
834 sub query_objects
835 {
836 my ($self, @classes) = @_;
837 map { Tangram::QueryObject->new(Tangram::RDBObject->new($self, $_)) } @classes;
838 }
839
840 sub remote
841 {
842 my ($self, @classes) = @_;
843 wantarray ? $self->query_objects(@classes) : (&remote)[0]
844 }
845
846 sub expr
847 {
848 my $self = shift;
849 return shift->expr( @_ );
850 }
851
852 sub object
853 {
854 carp "cannot be called in list context; use objects instead" if wantarray;
855 my $self = shift;
856 my ($obj) = $self->query_objects(@_);
857 $obj;
858 }
859
860 sub count
861 {
862 my $self = shift;
863
864 my ($target, $filter);
865 my $objects = Set::Object->new;
866
867 if (@_ == 1)
868 {
869 $target = '*';
870 $filter = shift;
871 }
872 else
873 {
874 my $expr = shift;
875 $target = $expr->{expr};
876 $objects->insert($expr->objects);
877 $filter = shift;
878 }
879
880 my @filter_expr;
881
882 if ($filter)
883 {
884 $objects->insert($filter->objects);
885 @filter_expr = ( "($filter->{expr})" );
886 }
887
888 my $sql = "SELECT COUNT($target) FROM " . join(', ', map { $_->from } $objects->members);
889
890 $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
891
892 print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
893
894 return ($self->{db}->selectrow_array($sql))[0];
895 }
896
897 sub sum
898 {
899 my ($self, $expr, $filter) = @_;
900
901 my $objects = Set::Object->new($expr->objects);
902
903 my @filter_expr;
904
905 if ($filter)
906 {
907 $objects->insert($filter->objects);
908 @filter_expr = ( "($filter->{expr})" );
909 }
910
911 my $sql = "SELECT SUM($expr->{expr}) FROM " . join(', ', map { $_->from } $objects->members);
912
913 $sql .= "\nWHERE " . join(' AND ', @filter_expr, map { $_->where } $objects->members);
914
915 print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
916
917 return ($self->{db}->selectrow_array($sql))[0];
918 }
919
920 sub id
921 {
922 my ($self, $obj) = @_;
923 return $self->{get_id}->($obj);
924 }
925
926 sub disconnect
927 {
928 my ($self) = @_;
929
930 unless ($self->{no_tx})
931 {
932 if (@{ $self->{tx} })
933 {
934 $self->{db}->rollback;
935 }
936 else
937 {
938 $self->{db}->commit;
939 }
940 }
941
942 $self->{db}->disconnect;
943
944 %$self = ();
945 }
946
947 sub _kind_class_ids
948 {
949 my ($self, $class) = @_;
950
951 my $schema = $self->{schema};
952 my $classes = $self->{schema}{classes};
953 my $class2id = $self->{class2id};
954
955 my @ids;
956
957 push @ids, $self->class_id($class) unless $classes->{$class}{abstract};
958
959 $schema->for_each_spec($class,
960 sub { my $spec = shift; push @ids, $class2id->{$spec} unless $classes->{$spec}{abstract} } );
961
962 return @ids;
963 }
964
965 sub is_persistent
966 {
967 my ($self, $obj) = @_;
968 return $self->{schema}->is_persistent($obj) && $self->id($obj);
969 }
970
971 sub prefetch
972 {
973 my ($self, $remote, $member, $filter) = @_;
974
975 my $class;
976
977 if (ref $remote)
978 {
979 $class = $remote->class();
980 }
981 else
982 {
983 $class = $remote;
984 $remote = $self->remote($class);
985 }
986
987 my $schema = $self->{schema};
988
989 my $member_class = $schema->find_member_class($class, $member)
990 or die "no member '$member' in class '$class'";
991
992 my $classdef = $schema->{classes}{$member_class};
993 my $type = $classdef->{member_type}{$member};
994 my $memdef = $classdef->{MEMDEFS}{$member};
995
996 $type->prefetch($self, $memdef, $remote, $class, $member, $filter);
997 }
998
999 sub connect
1000 {
1001 my ($pkg, $schema, $cs, $user, $pw, $opts) = @_;
1002
1003 my $self = $pkg->new;
1004
1005 $opts ||= {};
1006
1007 my $db = $opts->{dbh} || DBI->connect($cs, $user, $pw);
1008
1009 eval { $db->{AutoCommit} = 0 };
1010
1011 $self->{no_tx} = $db->{AutoCommit};
1012
1013 $self->{db} = $db;
1014
1015 @$self{ -cs, -user, -pw } = ($cs, $user, $pw);
1016
1017 $self->{cid_size} = $schema->{sql}{cid_size};
1018
1019 $self->_open($schema);
1020
1021 $self->{engine} = Tangram::Relational::Engine->new($schema, layout1 => $self->{layout1});
1022
1023 return $self;
1024 }
1025
1026 sub sql_do
1027 {
1028 my ($self, $sql) = @_;
1029 print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1030 my $rows_affected = $self->{db}->do($sql);
1031 return defined($rows_affected) ? $rows_affected
1032 : croak $DBI::errstr;
1033 }
1034
1035 sub sql_selectall_arrayref
1036 {
1037 my ($self, $sql, $dbh) = @_;
1038 print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1039 ($dbh || $self->{db})->selectall_arrayref($sql);
1040 }
1041
1042 sub sql_prepare
1043 {
1044 my ($self, $sql, $connection) = @_;
1045 confess unless $connection;
1046 print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1047 return $connection->prepare($sql) or die;
1048 }
1049
1050 sub sql_cursor
1051 {
1052 my ($self, $sql, $connection) = @_;
1053
1054 confess unless $connection;
1055
1056 print $Tangram::TRACE "$sql\n" if $Tangram::TRACE;
1057
1058 my $sth = $connection->prepare($sql) or die;
1059 $sth->execute() or confess;
1060
1061 Tangram::Storage::Statement->new( statement => $sth, storage => $self,
1062 connection => $connection );
1063 }
1064
1065 sub unload
1066 {
1067 my $self = shift;
1068 my $objects = $self->{objects};
1069
1070 if (@_) {
1071 for my $item (@_) {
1072 if (ref $item) {
1073 $self->goodbye($item, $self->{get_id}->($item));
1074 } else {
1075 $self->goodbye($objects->{$item}, $item);
1076 }
1077 }
1078 } else {
1079 for my $id (keys %$objects) {
1080 $self->goodbye($objects->{$id}, $id);
1081 }
1082 }
1083 }
1084
1085 *reset = \&unload; # deprecated, use unload() instead
1086
1087 sub DESTROY
1088 {
1089 my $self = shift;
1090 $self->{db}->disconnect if $self->{db};
1091 }
1092
1093 package Tangram::Storage::Statement;
1094
1095 sub new
1096 {
1097 my $class = shift;
1098 bless { @_ }, $class;
1099 }
1100
1101 sub fetchrow
1102 {
1103 return shift->{statement}->fetchrow;
1104 }
1105
1106 sub close
1107 {
1108 my $self = shift;
1109
1110 if ($self->{storage})
1111 {
1112 $self->{statement}->finish;
1113 $self->{storage}->close_connection($self->{connection});
1114 %$self = ();
1115 }
1116 }
1117
1118 1;

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