/[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.6 - (show annotations)
Mon Dec 16 20:41:51 2002 UTC (21 years, 6 months ago) by joko
Branch: MAIN
Changes since 1.5: +1 -0 lines
+ fix: don't die when Data::UUID is not available

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

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