summaryrefslogtreecommitdiff
path: root/FS/FS/Misc/Savepoint.pm
blob: b15b36ded7c151aeac6fb5b16215b6fbe7d01bf4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package FS::Misc::Savepoint;

use strict;
use warnings;

use Exporter;
use vars qw( @ISA @EXPORT @EXPORT_OK );
@ISA = qw( Exporter );
@EXPORT = qw( savepoint_create savepoint_release savepoint_rollback );

use FS::UID qw( dbh );
use Carp qw( croak );

=head1 NAME

FS::Misc::Savepoint - Provides methods for SQL Savepoints

=head1 SYNOPSIS

  use FS::Misc::Savepoint;
  
  # Only valid within a transaction
  local $FS::UID::AutoCommit = 0;
  
  savepoint_create( 'savepoint_label' );
  
  my $error_msg = do_some_things();
  
  if ( $error_msg ) {
    savepoint_rollback_and_release( 'savepoint_label' );
  } else {
    savepoint_release( 'savepoint_label' );
  }


=head1 DESCRIPTION

Provides methods for SQL Savepoints

Using a savepoint allows for a partial roll-back of SQL statements without
forcing a rollback of the entire enclosing transaction.

=head1 METHODS

=over 4

=item savepoint_create LABEL

=item savepoint_create { label => LABEL, dbh => DBH }

Executes SQL to create a savepoint named LABEL.

Savepoints cannot work while AutoCommit is enabled.

Savepoint labels must be valid sql identifiers.  If your choice of label
would not make a valid column name, it probably will not make a valid label.

Savepint labels must be unique within the transaction.

=cut

sub savepoint_create {
  my %param = _parse_params( @_ );

  $param{dbh}->do("SAVEPOINT $param{label}")
    or die $param{dbh}->errstr;
}

=item savepoint_release LABEL

=item savepoint_release { label => LABEL, dbh => DBH }

Release the savepoint - preserves the SQL statements issued since the
savepoint was created, but does not commit the transaction.

The savepoint label is freed for future use.

=cut

sub savepoint_release {
  my %param = _parse_params( @_ );

  $param{dbh}->do("RELEASE SAVEPOINT $param{label}")
    or die $param{dbh}->errstr;
}

=item savepoint_rollback LABEL

=item savepoint_rollback { label => LABEL, dbh => DBH }

Roll back the savepoint - forgets all SQL statements issues since the
savepoint was created, but does not commit or roll back the transaction.

The savepoint still exists.  Additional statements may be executed,
and savepoint_rollback called again.

=cut

sub savepoint_rollback {
  my %param = _parse_params( @_ );

  $param{dbh}->do("ROLLBACK TO SAVEPOINT $param{label}")
    or die $param{dbh}->errstr;
}

=item savepoint_rollback_and_release LABEL

=item savepoint_rollback_and_release { label => LABEL, dbh => DBH }

Rollback and release the savepoint

=cut

sub savepoint_rollback_and_release {
  savepoint_rollback( @_ );
  savepoint_release( @_ );
}

=back

=head1 METHODS - Internal

=over 4

=item _parse_params

Create %params from function input

Basic savepoint label validation

Complain when trying to use savepoints without disabling AutoCommit

=cut

sub _parse_params {
  my %param = ref $_[0] ? %{ $_[0] } : ( label => $_[0] );
  $param{dbh} ||= dbh;

  # Savepoints may be any valid SQL identifier up to 64 characters
  $param{label} =~ /^\w+$/
    or croak sprintf(
      'Invalid savepont label(%s) - use only numbers, letters, _',
      $param{label}
    );

  croak sprintf( 'Savepoint(%s) failed - AutoCommit=1', $param{label} )
    if $FS::UID::AutoCommit;

  %param;
}

=back

=head1 BUGS

=head1 SEE ALSO

=cut

1;