Institute a central "load this first in testing" package
[dbsrgits/DBIx-Class-Historic.git] / t / storage / savepoints.t
CommitLineData
c0329273 1BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
2
ddf66ced 3use strict;
4use warnings;
5
6use Test::More;
cf1d16d8 7use Test::Exception;
bbf6a9a5 8use DBIx::Class::_Util qw(modver_gt_or_eq sigwarn_silencer scope_guard);
2cfc22dd 9
c0329273 10
2cfc22dd 11use DBICTest;
12
13{
14 package # moar hide
15 DBICTest::SVPTracerObj;
16
17 use base 'DBIx::Class::Storage::Statistics';
18
19 sub query_start { 'do notning'}
20 sub callback { 'dummy '}
21
22 for my $svpcall (map { "svp_$_" } qw(begin rollback release)) {
23 no strict 'refs';
24 *$svpcall = sub { $_[0]{uc $svpcall}++ };
25 }
26}
ddf66ced 27
ae1d3ea1 28my $env2optdep = {
cf1d16d8 29 DBICTEST_PG => 'test_rdbms_pg',
ae1d3ea1 30 DBICTEST_MYSQL => 'test_rdbms_mysql',
31};
ddf66ced 32
ae1d3ea1 33my $schema;
ddf66ced 34
cf1d16d8 35for ('', keys %$env2optdep) { SKIP: {
ddf66ced 36
cf1d16d8 37 my $prefix;
ddf66ced 38
cf1d16d8 39 if ($prefix = $_) {
40 my ($dsn, $user, $pass) = map { $ENV{"${prefix}_$_"} } qw/DSN USER PASS/;
ddf66ced 41
cf1d16d8 42 skip ("Skipping tests with $prefix: set \$ENV{${prefix}_DSN} _USER and _PASS", 1)
43 unless $dsn;
ddf66ced 44
cf1d16d8 45 skip ("Testing with ${prefix}_DSN needs " . DBIx::Class::Optional::Dependencies->req_missing_for( $env2optdep->{$prefix} ), 1)
46 unless DBIx::Class::Optional::Dependencies->req_ok_for($env2optdep->{$prefix});
47
c7e85630 48 $schema = DBICTest::Schema->connect ($dsn,$user,$pass,{ auto_savepoint => 1 });
cf1d16d8 49
50 my $create_sql;
51 $schema->storage->ensure_connected;
52 if ($schema->storage->isa('DBIx::Class::Storage::DBI::Pg')) {
53 $create_sql = "CREATE TABLE artist (artistid serial PRIMARY KEY, name VARCHAR(100), rank INTEGER NOT NULL DEFAULT '13', charfield CHAR(10))";
54 $schema->storage->dbh->do('SET client_min_messages=WARNING');
55 }
56 elsif ($schema->storage->isa('DBIx::Class::Storage::DBI::mysql')) {
57 $create_sql = "CREATE TABLE artist (artistid INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), rank INTEGER NOT NULL DEFAULT '13', charfield CHAR(10)) ENGINE=InnoDB";
58 }
59 else {
60 skip( 'Untested driver ' . $schema->storage, 1 );
61 }
62
63 $schema->storage->dbh_do (sub {
64 $_[1]->do('DROP TABLE IF EXISTS artist');
65 $_[1]->do($create_sql);
66 });
ae1d3ea1 67 }
68 else {
cf1d16d8 69 $prefix = 'SQLite Internal DB';
70 $schema = DBICTest->init_schema( no_populate => 1, auto_savepoint => 1 );
ae1d3ea1 71 }
ddf66ced 72
ae1d3ea1 73 note "Testing $prefix";
ddf66ced 74
b74b15b0 75 # can not use local() due to an unknown number of storages
76 # (think replicated)
77 my $orig_states = { map
78 { $_ => $schema->storage->$_ }
79 qw(debugcb debugobj debug)
80 };
bbf6a9a5 81 my $sg = scope_guard {
b74b15b0 82 $schema->storage->$_ ( $orig_states->{$_} ) for keys %$orig_states;
bbf6a9a5 83 };
b74b15b0 84 $schema->storage->debugobj (my $stats = DBICTest::SVPTracerObj->new);
85 $schema->storage->debug (1);
ddf66ced 86
ae1d3ea1 87 $schema->resultset('Artist')->create({ name => 'foo' });
ddf66ced 88
ae1d3ea1 89 $schema->txn_begin;
ddf66ced 90
ae1d3ea1 91 my $arty = $schema->resultset('Artist')->find(1);
ddf66ced 92
ae1d3ea1 93 my $name = $arty->name;
ddf66ced 94
ae1d3ea1 95 # First off, test a generated savepoint name
96 $schema->svp_begin;
ddf66ced 97
ae1d3ea1 98 cmp_ok($stats->{'SVP_BEGIN'}, '==', 1, 'Statistics svp_begin tickled');
ddf66ced 99
ae1d3ea1 100 $arty->update({ name => 'Jheephizzy' });
ddf66ced 101
ae1d3ea1 102 $arty->discard_changes;
ddf66ced 103
ae1d3ea1 104 cmp_ok($arty->name, 'eq', 'Jheephizzy', 'Name changed');
ddf66ced 105
ae1d3ea1 106 # Rollback the generated name
107 # Active: 0
108 $schema->svp_rollback;
ddf66ced 109
ae1d3ea1 110 cmp_ok($stats->{'SVP_ROLLBACK'}, '==', 1, 'Statistics svp_rollback tickled');
ddf66ced 111
ae1d3ea1 112 $arty->discard_changes;
ddf66ced 113
ae1d3ea1 114 cmp_ok($arty->name, 'eq', $name, 'Name rolled back');
ddf66ced 115
ae1d3ea1 116 $arty->update({ name => 'Jheephizzy'});
ddf66ced 117
ae1d3ea1 118 # Active: 0 1
119 $schema->svp_begin('testing1');
ddf66ced 120
ae1d3ea1 121 $arty->update({ name => 'yourmom' });
ddf66ced 122
ae1d3ea1 123 # Active: 0 1 2
124 $schema->svp_begin('testing2');
ddf66ced 125
ae1d3ea1 126 $arty->update({ name => 'gphat' });
127 $arty->discard_changes;
128 cmp_ok($arty->name, 'eq', 'gphat', 'name changed');
cf1d16d8 129
ae1d3ea1 130 # Active: 0 1 2
131 # Rollback doesn't DESTROY the savepoint, it just rolls back to the value
3334d204 132 # at its conception
ae1d3ea1 133 $schema->svp_rollback('testing2');
134 $arty->discard_changes;
135 cmp_ok($arty->name, 'eq', 'yourmom', 'testing2 reverted');
ddf66ced 136
ae1d3ea1 137 # Active: 0 1 2 3
138 $schema->svp_begin('testing3');
139 $arty->update({ name => 'coryg' });
cf1d16d8 140
ae1d3ea1 141 # Active: 0 1 2 3 4
142 $schema->svp_begin('testing4');
143 $arty->update({ name => 'watson' });
ddf66ced 144
ae1d3ea1 145 # Release 3, which implicitly releases 4
146 # Active: 0 1 2
147 $schema->svp_release('testing3');
cf1d16d8 148
ae1d3ea1 149 $arty->discard_changes;
150 cmp_ok($arty->name, 'eq', 'watson', 'release left data');
cf1d16d8 151
ae1d3ea1 152 # This rolls back savepoint 2
153 # Active: 0 1 2
154 $schema->svp_rollback;
cf1d16d8 155
ae1d3ea1 156 $arty->discard_changes;
157 cmp_ok($arty->name, 'eq', 'yourmom', 'rolled back to 2');
ddf66ced 158
ae1d3ea1 159 # Rollback the original savepoint, taking us back to the beginning, implicitly
160 # rolling back savepoint 1 and 2
161 $schema->svp_rollback('savepoint_0');
162 $arty->discard_changes;
163 cmp_ok($arty->name, 'eq', 'foo', 'rolled back to start');
ddf66ced 164
ae1d3ea1 165 $schema->txn_commit;
166
cf1d16d8 167 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
168
ae1d3ea1 169 # And now to see if txn_do will behave correctly
170 $schema->txn_do (sub {
65d35121 171 my $artycp = $arty;
172
ae1d3ea1 173 $schema->txn_do (sub {
65d35121 174 $artycp->name ('Muff');
175 $artycp->update;
ae1d3ea1 176 });
ddf66ced 177
178 eval {
179 $schema->txn_do (sub {
65d35121 180 $artycp->name ('Moff');
181 $artycp->update;
182 $artycp->discard_changes;
183 is($artycp->name,'Moff','Value updated in nested transaction');
ae1d3ea1 184 $schema->storage->dbh->do ("GUARANTEED TO PHAIL");
185 });
ddf66ced 186 };
187
188 ok ($@,'Nested transaction failed (good)');
189
190 $arty->discard_changes;
191
192 is($arty->name,'Muff','auto_savepoint rollback worked');
193
194 $arty->name ('Miff');
195
196 $arty->update;
197 });
198
cf1d16d8 199 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
200
ae1d3ea1 201 $arty->discard_changes;
202
203 is($arty->name,'Miff','auto_savepoint worked');
204
205 cmp_ok($stats->{'SVP_BEGIN'},'==',7,'Correct number of savepoints created');
ddf66ced 206
ae1d3ea1 207 cmp_ok($stats->{'SVP_RELEASE'},'==',3,'Correct number of savepoints released');
ddf66ced 208
ae1d3ea1 209 cmp_ok($stats->{'SVP_ROLLBACK'},'==',5,'Correct number of savepoint rollbacks');
ddf66ced 210
cf1d16d8 211### test originally written for SQLite exclusively (git blame -w -C -M)
212 # test two-phase commit and inner transaction rollback from nested transactions
213 my $ars = $schema->resultset('Artist');
214
215 $schema->txn_do(sub {
216 $ars->create({ name => 'in_outer_transaction' });
217 $schema->txn_do(sub {
218 $ars->create({ name => 'in_inner_transaction' });
219 });
220 ok($ars->search({ name => 'in_inner_transaction' })->first,
221 'commit from inner transaction visible in outer transaction');
222 throws_ok {
223 $schema->txn_do(sub {
224 $ars->create({ name => 'in_inner_transaction_rolling_back' });
225 die 'rolling back inner transaction';
226 });
227 } qr/rolling back inner transaction/, 'inner transaction rollback executed';
228 $ars->create({ name => 'in_outer_transaction2' });
229 });
230
231 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
232
8b60b921 233SKIP: {
234 skip "Reading inexplicably fails on very old replicated DBD::SQLite<1.33", 1 if (
235 $ENV{DBICTEST_VIA_REPLICATED}
236 and
237 $prefix eq 'SQLite Internal DB'
238 and
239 ! modver_gt_or_eq('DBD::SQLite', '1.33')
240 );
241
cf1d16d8 242 ok($ars->search({ name => 'in_outer_transaction' })->first,
243 'commit from outer transaction');
244 ok($ars->search({ name => 'in_outer_transaction2' })->first,
245 'second commit from outer transaction');
246 ok($ars->search({ name => 'in_inner_transaction' })->first,
247 'commit from inner transaction');
248 is $ars->search({ name => 'in_inner_transaction_rolling_back' })->first,
249 undef,
250 'rollback from inner transaction';
8b60b921 251}
cf1d16d8 252
253### cleanupz
b74b15b0 254 $schema->storage->dbh_do(sub { $_[1]->do("DROP TABLE artist") });
ae1d3ea1 255}}
ddf66ced 256
ae1d3ea1 257done_testing;
ddf66ced 258
65d35121 259END {
8b60b921 260 local $SIG{__WARN__} = sigwarn_silencer( qr/Internal transaction state of handle/ )
261 unless modver_gt_or_eq('DBD::SQLite', '1.33');
b74b15b0 262 eval { $schema->storage->dbh_do(sub { $_[1]->do("DROP TABLE artist") }) } if defined $schema;
65d35121 263 undef $schema;
264}