6 use DBIx::Class::Optional::Dependencies;
7 use DBIx::Class::_Util qw(sigwarn_silencer scope_guard);
8 use Scalar::Util 'weaken';
15 DBICTest::SVPTracerObj;
17 use base 'DBIx::Class::Storage::Statistics';
19 sub query_start { 'do notning'}
20 sub callback { 'dummy '}
22 for my $svpcall (map { "svp_$_" } qw(begin rollback release)) {
24 *$svpcall = sub { $_[0]{uc $svpcall}++ };
29 DBICTEST_PG => 'test_rdbms_pg',
30 DBICTEST_MYSQL => 'test_rdbms_mysql',
35 for ('', keys %$env2optdep) { SKIP: {
40 my ($dsn, $user, $pass) = map { $ENV{"${prefix}_$_"} } qw/DSN USER PASS/;
42 skip ("Skipping tests with $prefix: set \$ENV{${prefix}_DSN} _USER and _PASS", 1)
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});
48 $schema = DBICTest::Schema->connect ($dsn,$user,$pass,{ auto_savepoint => 1 });
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');
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";
60 skip( 'Untested driver ' . $schema->storage, 1 );
63 $schema->storage->dbh_do (sub {
64 $_[1]->do('DROP TABLE IF EXISTS artist');
65 $_[1]->do($create_sql);
69 $prefix = 'SQLite Internal DB';
70 $schema = DBICTest->init_schema( no_populate => 1, auto_savepoint => 1 );
73 note "Testing $prefix";
75 local $schema->storage->{debugobj} = my $stats = DBICTest::SVPTracerObj->new;
76 local $schema->storage->{debug} = 1;
78 $schema->resultset('Artist')->create({ name => 'foo' });
82 my $arty = $schema->resultset('Artist')->find(1);
84 my $name = $arty->name;
86 # First off, test a generated savepoint name
89 cmp_ok($stats->{'SVP_BEGIN'}, '==', 1, 'Statistics svp_begin tickled');
91 $arty->update({ name => 'Jheephizzy' });
93 $arty->discard_changes;
95 cmp_ok($arty->name, 'eq', 'Jheephizzy', 'Name changed');
97 # Rollback the generated name
99 $schema->svp_rollback;
101 cmp_ok($stats->{'SVP_ROLLBACK'}, '==', 1, 'Statistics svp_rollback tickled');
103 $arty->discard_changes;
105 cmp_ok($arty->name, 'eq', $name, 'Name rolled back');
107 $arty->update({ name => 'Jheephizzy'});
110 $schema->svp_begin('testing1');
112 $arty->update({ name => 'yourmom' });
115 $schema->svp_begin('testing2');
117 $arty->update({ name => 'gphat' });
118 $arty->discard_changes;
119 cmp_ok($arty->name, 'eq', 'gphat', 'name changed');
122 # Rollback doesn't DESTROY the savepoint, it just rolls back to the value
124 $schema->svp_rollback('testing2');
125 $arty->discard_changes;
126 cmp_ok($arty->name, 'eq', 'yourmom', 'testing2 reverted');
129 $schema->svp_begin('testing3');
130 $arty->update({ name => 'coryg' });
133 $schema->svp_begin('testing4');
134 $arty->update({ name => 'watson' });
136 # Release 3, which implicitly releases 4
138 $schema->svp_release('testing3');
140 $arty->discard_changes;
141 cmp_ok($arty->name, 'eq', 'watson', 'release left data');
143 # This rolls back savepoint 2
145 $schema->svp_rollback;
147 $arty->discard_changes;
148 cmp_ok($arty->name, 'eq', 'yourmom', 'rolled back to 2');
150 # Rollback the original savepoint, taking us back to the beginning, implicitly
151 # rolling back savepoint 1 and 2
152 $schema->svp_rollback('savepoint_0');
153 $arty->discard_changes;
154 cmp_ok($arty->name, 'eq', 'foo', 'rolled back to start');
158 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
160 # And now to see if txn_do will behave correctly
161 $schema->txn_do (sub {
164 $schema->txn_do (sub {
165 $artycp->name ('Muff');
170 $schema->txn_do (sub {
171 $artycp->name ('Moff');
173 $artycp->discard_changes;
174 is($artycp->name,'Moff','Value updated in nested transaction');
175 $schema->storage->dbh->do ("GUARANTEED TO PHAIL");
179 ok ($@,'Nested transaction failed (good)');
181 $arty->discard_changes;
183 is($arty->name,'Muff','auto_savepoint rollback worked');
185 $arty->name ('Miff');
190 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
192 $arty->discard_changes;
194 is($arty->name,'Miff','auto_savepoint worked');
196 cmp_ok($stats->{'SVP_BEGIN'},'==',7,'Correct number of savepoints created');
198 cmp_ok($stats->{'SVP_RELEASE'},'==',3,'Correct number of savepoints released');
200 cmp_ok($stats->{'SVP_ROLLBACK'},'==',5,'Correct number of savepoint rollbacks');
202 ### test originally written for SQLite exclusively (git blame -w -C -M)
203 # test two-phase commit and inner transaction rollback from nested transactions
204 my $ars = $schema->resultset('Artist');
206 $schema->txn_do(sub {
207 $ars->create({ name => 'in_outer_transaction' });
208 $schema->txn_do(sub {
209 $ars->create({ name => 'in_inner_transaction' });
211 ok($ars->search({ name => 'in_inner_transaction' })->first,
212 'commit from inner transaction visible in outer transaction');
214 $schema->txn_do(sub {
215 $ars->create({ name => 'in_inner_transaction_rolling_back' });
216 die 'rolling back inner transaction';
218 } qr/rolling back inner transaction/, 'inner transaction rollback executed';
219 $ars->create({ name => 'in_outer_transaction2' });
222 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
224 ok($ars->search({ name => 'in_outer_transaction' })->first,
225 'commit from outer transaction');
226 ok($ars->search({ name => 'in_outer_transaction2' })->first,
227 'second commit from outer transaction');
228 ok($ars->search({ name => 'in_inner_transaction' })->first,
229 'commit from inner transaction');
230 is $ars->search({ name => 'in_inner_transaction_rolling_back' })->first,
232 'rollback from inner transaction';
234 # make sure a fresh txn will work after above
235 $schema->storage->txn_do(sub { ok "noop" } );
237 ### Make sure non-existend savepoint release doesn't infloop itself
239 weaken( my $s = $schema );
242 $s->storage->txn_do(sub { $s->svp_release('wibble') })
243 } qr/Savepoint 'wibble' does not exist/,
244 "Calling svp_release on a non-existant savepoint throws expected error"
249 $schema->storage->dbh->do ("DROP TABLE artist");
255 eval { $schema->storage->dbh_do(sub { $_[1]->do("DROP TABLE artist") }) } if defined $schema;