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