973f31b63fa38b4b5e82f73ea62b38bef67e762d
[dbsrgits/DBIx-Class-Historic.git] / lib / DBIx / Class / Storage.pm
1 package DBIx::Class::Storage;
2
3 use strict;
4 use warnings;
5
6 package # Hide from PAUSE
7     DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION;
8
9 use overload '"' => sub {
10   'DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION'
11 };
12
13 sub new {
14   my $class = shift;
15   my $self = {};
16   return bless $self, $class;
17 }
18
19 package DBIx::Class::Storage;
20
21 sub new { die "Virtual method!" }
22 sub set_schema { die "Virtual method!" }
23 sub debug { die "Virtual method!" }
24 sub debugcb { die "Virtual method!" }
25 sub debugfh { die "Virtual method!" }
26 sub debugobj { die "Virtual method!" }
27 sub cursor { die "Virtual method!" }
28 sub disconnect { die "Virtual method!" }
29 sub connected { die "Virtual method!" }
30 sub ensure_connected { die "Virtual method!" }
31 sub on_connect_do { die "Virtual method!" }
32 sub connect_info { die "Virtual method!" }
33 sub sql_maker { die "Virtual method!" }
34 sub txn_begin { die "Virtual method!" }
35 sub txn_commit { die "Virtual method!" }
36 sub txn_rollback { die "Virtual method!" }
37 sub insert { die "Virtual method!" }
38 sub update { die "Virtual method!" }
39 sub delete { die "Virtual method!" }
40 sub select { die "Virtual method!" }
41 sub select_single { die "Virtual method!" }
42 sub columns_info_for { die "Virtual method!" }
43 sub throw_exception { die "Virtual method!" }
44
45 =head2 txn_do
46
47 =over 4
48
49 =item Arguments: C<$coderef>, @coderef_args?
50
51 =item Return Value: The return value of $coderef
52
53 =back
54
55 Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
56 returning its result (if any). If an exception is caught, a rollback is issued
57 and the exception is rethrown. If the rollback fails, (i.e. throws an
58 exception) an exception is thrown that includes a "Rollback failed" message.
59
60 For example,
61
62   my $author_rs = $schema->resultset('Author')->find(1);
63   my @titles = qw/Night Day It/;
64
65   my $coderef = sub {
66     # If any one of these fails, the entire transaction fails
67     $author_rs->create_related('books', {
68       title => $_
69     }) foreach (@titles);
70
71     return $author->books;
72   };
73
74   my $rs;
75   eval {
76     $rs = $schema->txn_do($coderef);
77   };
78
79   if ($@) {                                  # Transaction failed
80     die "something terrible has happened!"   #
81       if ($@ =~ /Rollback failed/);          # Rollback failed
82
83     deal_with_failed_transaction();
84   }
85
86 In a nested transaction (calling txn_do() from within a txn_do() coderef) only
87 the outermost transaction will issue a L</txn_commit>, and txn_do() can be
88 called in void, scalar and list context and it will behave as expected.
89
90 =cut
91
92 sub txn_do {
93   my ($self, $coderef, @args) = @_;
94
95   ref $coderef eq 'CODE' or $self->throw_exception
96     ('$coderef must be a CODE reference');
97
98   my (@return_values, $return_value);
99
100   $self->txn_begin; # If this throws an exception, no rollback is needed
101
102   my $wantarray = wantarray; # Need to save this since the context
103                              # inside the eval{} block is independent
104                              # of the context that called txn_do()
105   eval {
106
107     # Need to differentiate between scalar/list context to allow for
108     # returning a list in scalar context to get the size of the list
109     if ($wantarray) {
110       # list context
111       @return_values = $coderef->(@args);
112     } elsif (defined $wantarray) {
113       # scalar context
114       $return_value = $coderef->(@args);
115     } else {
116       # void context
117       $coderef->(@args);
118     }
119     $self->txn_commit;
120   };
121
122   if ($@) {
123     my $error = $@;
124
125     eval {
126       $self->txn_rollback;
127     };
128
129     if ($@) {
130       my $rollback_error = $@;
131       my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION";
132       $self->throw_exception($error)  # propagate nested rollback
133         if $rollback_error =~ /$exception_class/;
134
135       $self->throw_exception(
136         "Transaction aborted: $error. Rollback failed: ${rollback_error}"
137       );
138     } else {
139       $self->throw_exception($error); # txn failed but rollback succeeded
140     }
141   }
142
143   return $wantarray ? @return_values : $return_value;
144 }
145
146 1;