Microoptimize various "is this string empty" checks
[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;
3d56e026 6use Scalar::Util qw(weaken blessed refaddr);
b4ad8a74 7use DBIx::Class;
e1d9e578 8use DBIx::Class::_Util qw(is_exception detected_reinvoked_destructor);
70c28808 9use DBIx::Class::Carp;
9c1700e3 10use namespace::clean;
11
1bc193ac 12sub new {
13 my ($class, $storage) = @_;
14
f62c5724 15 my $guard = {
16 inactivated => 0,
17 storage => $storage,
18 };
19
20 # we are starting with an already set $@ - in order for things to work we need to
21 # be able to recognize it upon destruction - store its weakref
22 # recording it before doing the txn_begin stuff
6e102c8f 23 #
24 # FIXME FRAGILE - any eval that fails but *does not* rethrow between here
25 # and the unwind will trample over $@ and invalidate the entire mechanism
26 # There got to be a saner way of doing this...
841efcb3 27 if (is_exception $@) {
6e102c8f 28 weaken(
d52c4a75 29 $guard->{existing_exception_ref} = (length ref $@) ? $@ : \$@
6e102c8f 30 );
f62c5724 31 }
32
1bc193ac 33 $storage->txn_begin;
1f870d5a 34
6e102c8f 35 weaken( $guard->{dbh} = $storage->_dbh );
f62c5724 36
37 bless $guard, ref $class || $class;
1f870d5a 38
7d216b10 39 $guard;
1bc193ac 40}
41
42sub commit {
43 my $self = shift;
44
f62c5724 45 $self->{storage}->throw_exception("Refusing to execute multiple commits on scope guard $self")
46 if $self->{inactivated};
47
0bec44d5 48 # FIXME - this assumption may be premature: a commit may fail and a rollback
49 # *still* be necessary. Currently I am not aware of such scenarious, but I
50 # also know the deferred constraint handling is *severely* undertested.
51 # Making the change of "fire txn and never come back to this" in order to
52 # address RT#107159, but this *MUST* be reevaluated later.
f62c5724 53 $self->{inactivated} = 1;
0bec44d5 54 $self->{storage}->txn_commit;
1bc193ac 55}
56
57sub DESTROY {
e1d9e578 58 return if &detected_reinvoked_destructor;
3d56e026 59
f62c5724 60 my $self = shift;
1bc193ac 61
f62c5724 62 return if $self->{inactivated};
3b7f3eac 63
f62c5724 64 # if our dbh is not ours anymore, the $dbh weakref will go undef
0d8817bc 65 $self->{storage}->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
f62c5724 66 return unless $self->{dbh};
7d216b10 67
f62c5724 68 my $exception = $@ if (
841efcb3 69 is_exception $@
f62c5724 70 and
71 (
72 ! defined $self->{existing_exception_ref}
73 or
d52c4a75 74 refaddr( (length ref $@) ? $@ : \$@ ) != refaddr($self->{existing_exception_ref})
f62c5724 75 )
76 );
c6e27318 77
a778f387 78 {
79 local $@;
36099e8c 80
81 carp 'A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back.'
f62c5724 82 unless defined $exception;
36099e8c 83
ed7ab0f4 84 my $rollback_exception;
7d216b10 85 # do minimal connectivity check due to weird shit like
86 # https://rt.cpan.org/Public/Bug/Display.html?id=62370
f62c5724 87 try { $self->{storage}->_seems_connected && $self->{storage}->txn_rollback }
ed7ab0f4 88 catch { $rollback_exception = shift };
c6e27318 89
90d7422f 90 if ( $rollback_exception and (
91 ! defined blessed $rollback_exception
92 or
93 ! $rollback_exception->isa('DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION')
94 ) ) {
1f870d5a 95 # append our text - THIS IS A TEMPORARY FIXUP!
96 # a real stackable exception object is in the works
97 if (ref $exception eq 'DBIx::Class::Exception') {
98 $exception->{msg} = "Transaction aborted: $exception->{msg} "
99 ."Rollback failed: ${rollback_exception}";
100 }
101 elsif ($exception) {
36099e8c 102 $exception = "Transaction aborted: ${exception} "
c6e27318 103 ."Rollback failed: ${rollback_exception}";
36099e8c 104 }
105 else {
106 carp (join ' ',
107 "********************* ROLLBACK FAILED!!! ********************",
108 "\nA rollback operation failed after the guard went out of scope.",
109 'This is potentially a disastrous situation, check your data for',
110 "consistency: $rollback_exception"
111 );
112 }
c6e27318 113 }
3b7f3eac 114 }
36099e8c 115
5815ffb0 116 $@ = $exception;
1bc193ac 117}
118
1191;
120
121__END__
122
123=head1 NAME
124
6936e902 125DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling
1bc193ac 126
127=head1 SYNOPSIS
128
129 sub foo {
130 my ($self, $schema) = @_;
131
132 my $guard = $schema->txn_scope_guard;
133
134 # Multiple database operations here
135
136 $guard->commit;
137 }
138
139=head1 DESCRIPTION
140
141An object that behaves much like L<Scope::Guard>, but hardcoded to do the
fd323bf1 142right thing with transactions in DBIx::Class.
1bc193ac 143
144=head1 METHODS
145
146=head2 new
147
6936e902 148Creating an instance of this class will start a new transaction (by
149implicitly calling L<DBIx::Class::Storage/txn_begin>. Expects a
1bc193ac 150L<DBIx::Class::Storage> object as its only argument.
151
152=head2 commit
153
154Commit the transaction, and stop guarding the scope. If this method is not
48580715 155called and this object goes out of scope (e.g. an exception is thrown) then
6936e902 156the transaction is rolled back, via L<DBIx::Class::Storage/txn_rollback>
1bc193ac 157
158=cut
159
160=head1 SEE ALSO
161
162L<DBIx::Class::Schema/txn_scope_guard>.
163
a2bd3796 164L<Scope::Guard> by chocolateboy (inspiration for this module)
1bc193ac 165
a2bd3796 166=head1 FURTHER QUESTIONS?
1bc193ac 167
a2bd3796 168Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
1bc193ac 169
a2bd3796 170=head1 COPYRIGHT AND LICENSE
1bc193ac 171
a2bd3796 172This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
173by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
174redistribute it and/or modify it under the same terms as the
175L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.