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