Fix incorrect > vs >= ver. check, causing 5.13.1 fails
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / TxnScopeGuard.pm
CommitLineData
6936e902 1package DBIx::Class::Storage::TxnScopeGuard;
1bc193ac 2
3use strict;
4use warnings;
ed7ab0f4 5use Try::Tiny;
1f870d5a 6use Scalar::Util qw/weaken blessed/;
b4ad8a74 7use DBIx::Class;
1f870d5a 8use DBIx::Class::Exception;
70c28808 9use DBIx::Class::Carp;
9c1700e3 10use namespace::clean;
11
1f870d5a 12my ($guards_count, $compat_handler, $foreign_handler);
13
1bc193ac 14sub new {
15 my ($class, $storage) = @_;
16
17 $storage->txn_begin;
7d216b10 18 my $guard = bless [ 0, $storage, $storage->_dbh ], ref $class || $class;
1f870d5a 19
20
21 # install a callback carefully
b4ad8a74 22 if (DBIx::Class::_ENV_::INVISIBLE_DOLLAR_AT and !$guards_count) {
1f870d5a 23
24 # if the thrown exception is a plain string, wrap it in our
25 # own exception class
26 # this is actually a pretty cool idea, may very well keep it
27 # after perl is fixed
28 $compat_handler ||= bless(
29 sub {
30 $@ = (blessed($_[0]) or ref($_[0]))
31 ? $_[0]
32 : bless ( { msg => $_[0] }, 'DBIx::Class::Exception')
33 ;
34 die;
35 },
36 '__TxnScopeGuard__FIXUP__',
37 );
38
39 if ($foreign_handler = $SIG{__DIE__}) {
40 $SIG{__DIE__} = bless (
41 sub {
42 # we trust the foreign handler to do whatever it wants, all we do is set $@
43 eval { $compat_handler->(@_) };
44 $foreign_handler->(@_);
45 },
46 '__TxnScopeGuard__FIXUP__',
47 );
48 }
49 else {
50 $SIG{__DIE__} = $compat_handler;
51 }
52 }
53
54 $guards_count++;
55
7d216b10 56 weaken ($guard->[2]);
57 $guard;
1bc193ac 58}
59
60sub commit {
61 my $self = shift;
62
63 $self->[1]->txn_commit;
64 $self->[0] = 1;
65}
66
67sub DESTROY {
68 my ($dismiss, $storage) = @{$_[0]};
69
1f870d5a 70 $guards_count--;
71
72 # don't touch unless it's ours, and there are no more of us left
73 if (
b4ad8a74 74 DBIx::Class::_ENV_::INVISIBLE_DOLLAR_AT
1f870d5a 75 and
76 !$guards_count
77 ) {
78
79 if (ref $SIG{__DIE__} eq '__TxnScopeGuard__FIXUP__') {
80 # restore what we saved
81 if ($foreign_handler) {
82 $SIG{__DIE__} = $foreign_handler;
83 }
84 else {
85 delete $SIG{__DIE__};
86 }
87 }
88
89 # make sure we do not leak the foreign one in case it exists
90 undef $foreign_handler;
91 }
92
3b7f3eac 93 return if $dismiss;
94
7d216b10 95 # if our dbh is not ours anymore, the weakref will go undef
ec6415a9 96 $storage->_verify_pid;
7d216b10 97 return unless $_[0]->[2];
98
3b7f3eac 99 my $exception = $@;
c6e27318 100
a778f387 101 {
102 local $@;
36099e8c 103
104 carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
105 unless $exception;
106
ed7ab0f4 107 my $rollback_exception;
7d216b10 108 # do minimal connectivity check due to weird shit like
109 # https://rt.cpan.org/Public/Bug/Display.html?id=62370
110 try { $storage->_seems_connected && $storage->txn_rollback }
ed7ab0f4 111 catch { $rollback_exception = shift };
c6e27318 112
90d7422f 113 if ( $rollback_exception and (
114 ! defined blessed $rollback_exception
115 or
116 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
117 ) ) {
1f870d5a 118 # append our text - THIS IS A TEMPORARY FIXUP!
119 # a real stackable exception object is in the works
120 if (ref $exception eq 'DBIx::Class::Exception') {
121 $exception->{msg} = "Transaction aborted: $exception->{msg} "
122 ."Rollback failed: ${rollback_exception}";
123 }
124 elsif ($exception) {
36099e8c 125 $exception = "Transaction aborted: ${exception} "
c6e27318 126 ."Rollback failed: ${rollback_exception}";
36099e8c 127 }
128 else {
129 carp (join ' ',
130 "********************* ROLLBACK FAILED!!! ********************",
131 "\nA rollback operation failed after the guard went out of scope.",
132 'This is potentially a disastrous situation, check your data for',
133 "consistency: $rollback_exception"
134 );
135 }
c6e27318 136 }
3b7f3eac 137 }
36099e8c 138
b4ad8a74 139 $@ = $exception unless DBIx::Class::_ENV_::INVISIBLE_DOLLAR_AT;
1bc193ac 140}
141
1421;
143
144__END__
145
146=head1 NAME
147
6936e902 148DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
1bc193ac 149
150=head1 SYNOPSIS
151
152 sub foo {
153 my ($self, $schema) = @_;
154
155 my $guard = $schema->txn_scope_guard;
156
157 # Multiple database operations here
158
159 $guard->commit;
160 }
161
162=head1 DESCRIPTION
163
164An object that behaves much like L<Scope::Guard>, but hardcoded to do the
fd323bf1 165right thing with transactions in DBIx::Class.
1bc193ac 166
167=head1 METHODS
168
169=head2 new
170
6936e902 171Creating an instance of this class will start a new transaction (by
172implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
1bc193ac 173L<DBIx::Class::Storage> object as its only argument.
174
175=head2 commit
176
177Commit the transaction, and stop guarding the scope. If this method is not
48580715 178called and this object goes out of scope (e.g. an exception is thrown) then
6936e902 179the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
1bc193ac 180
181=cut
182
183=head1 SEE ALSO
184
185L<DBIx::Class::Schema/txn_scope_guard>.
186
187=head1 AUTHOR
188
189Ash Berlin, 2008.
190
48580715 191Inspired by L<Scope::Guard> by chocolateboy.
1bc193ac 192
193This module is free software. It may be used, redistributed and/or modified
194under the same terms as Perl itself.
195
196=cut