6 use DBIx::Class::_Util qw(modver_gt_or_eq sigwarn_silencer scope_guard);
13 DBICTest::SVPTracerObj;
15 use base 'DBIx::Class::Storage::Statistics';
17 sub query_start { 'do notning'}
18 sub callback { 'dummy '}
20 for my $svpcall (map { "svp_$_" } qw(begin rollback release)) {
22 *$svpcall = sub { $_[0]{uc $svpcall}++ };
27 DBICTEST_PG => 'test_rdbms_pg',
28 DBICTEST_MYSQL => 'test_rdbms_mysql',
33 for ('', keys %$env2optdep) { SKIP: {
38 my ($dsn, $user, $pass) = map { $ENV{"${prefix}_$_"} } qw/DSN USER PASS/;
40 skip ("Skipping tests with $prefix: set \$ENV{${prefix}_DSN} _USER and _PASS", 1)
43 skip ("Testing with ${prefix}_DSN needs " . DBIx::Class::Optional::Dependencies->req_missing_for( $env2optdep->{$prefix} ), 1)
44 unless DBIx::Class::Optional::Dependencies->req_ok_for($env2optdep->{$prefix});
46 $schema = DBICTest::Schema->connect ($dsn,$user,$pass,{ auto_savepoint => 1 });
49 $schema->storage->ensure_connected;
50 if ($schema->storage->isa('DBIx::Class::Storage::DBI::Pg')) {
51 $create_sql = "CREATE TABLE artist (artistid serial PRIMARY KEY, name VARCHAR(100), rank INTEGER NOT NULL DEFAULT '13', charfield CHAR(10))";
52 $schema->storage->dbh->do('SET client_min_messages=WARNING');
54 elsif ($schema->storage->isa('DBIx::Class::Storage::DBI::mysql')) {
55 $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 skip( 'Untested driver ' . $schema->storage, 1 );
61 $schema->storage->dbh_do (sub {
62 $_[1]->do('DROP TABLE IF EXISTS artist');
63 $_[1]->do($create_sql);
67 $prefix = 'SQLite Internal DB';
68 $schema = DBICTest->init_schema( no_populate => 1, auto_savepoint => 1 );
71 note "Testing $prefix";
73 # can not use local() due to an unknown number of storages
75 my $orig_states = { map
76 { $_ => $schema->storage->$_ }
77 qw(debugcb debugobj debug)
79 my $sg = scope_guard {
80 $schema->storage->$_ ( $orig_states->{$_} ) for keys %$orig_states;
82 $schema->storage->debugobj (my $stats = DBICTest::SVPTracerObj->new);
83 $schema->storage->debug (1);
85 $schema->resultset('Artist')->create({ name => 'foo' });
89 my $arty = $schema->resultset('Artist')->find(1);
91 my $name = $arty->name;
93 # First off, test a generated savepoint name
96 cmp_ok($stats->{'SVP_BEGIN'}, '==', 1, 'Statistics svp_begin tickled');
98 $arty->update({ name => 'Jheephizzy' });
100 $arty->discard_changes;
102 cmp_ok($arty->name, 'eq', 'Jheephizzy', 'Name changed');
104 # Rollback the generated name
106 $schema->svp_rollback;
108 cmp_ok($stats->{'SVP_ROLLBACK'}, '==', 1, 'Statistics svp_rollback tickled');
110 $arty->discard_changes;
112 cmp_ok($arty->name, 'eq', $name, 'Name rolled back');
114 $arty->update({ name => 'Jheephizzy'});
117 $schema->svp_begin('testing1');
119 $arty->update({ name => 'yourmom' });
122 $schema->svp_begin('testing2');
124 $arty->update({ name => 'gphat' });
125 $arty->discard_changes;
126 cmp_ok($arty->name, 'eq', 'gphat', 'name changed');
129 # Rollback doesn't DESTROY the savepoint, it just rolls back to the value
131 $schema->svp_rollback('testing2');
132 $arty->discard_changes;
133 cmp_ok($arty->name, 'eq', 'yourmom', 'testing2 reverted');
136 $schema->svp_begin('testing3');
137 $arty->update({ name => 'coryg' });
140 $schema->svp_begin('testing4');
141 $arty->update({ name => 'watson' });
143 # Release 3, which implicitly releases 4
145 $schema->svp_release('testing3');
147 $arty->discard_changes;
148 cmp_ok($arty->name, 'eq', 'watson', 'release left data');
150 # This rolls back savepoint 2
152 $schema->svp_rollback;
154 $arty->discard_changes;
155 cmp_ok($arty->name, 'eq', 'yourmom', 'rolled back to 2');
157 # Rollback the original savepoint, taking us back to the beginning, implicitly
158 # rolling back savepoint 1 and 2
159 $schema->svp_rollback('savepoint_0');
160 $arty->discard_changes;
161 cmp_ok($arty->name, 'eq', 'foo', 'rolled back to start');
165 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
167 # And now to see if txn_do will behave correctly
168 $schema->txn_do (sub {
171 $schema->txn_do (sub {
172 $artycp->name ('Muff');
177 $schema->txn_do (sub {
178 $artycp->name ('Moff');
180 $artycp->discard_changes;
181 is($artycp->name,'Moff','Value updated in nested transaction');
182 $schema->storage->dbh->do ("GUARANTEED TO PHAIL");
186 ok ($@,'Nested transaction failed (good)');
188 $arty->discard_changes;
190 is($arty->name,'Muff','auto_savepoint rollback worked');
192 $arty->name ('Miff');
197 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
199 $arty->discard_changes;
201 is($arty->name,'Miff','auto_savepoint worked');
203 cmp_ok($stats->{'SVP_BEGIN'},'==',7,'Correct number of savepoints created');
205 cmp_ok($stats->{'SVP_RELEASE'},'==',3,'Correct number of savepoints released');
207 cmp_ok($stats->{'SVP_ROLLBACK'},'==',5,'Correct number of savepoint rollbacks');
209 ### test originally written for SQLite exclusively (git blame -w -C -M)
210 # test two-phase commit and inner transaction rollback from nested transactions
211 my $ars = $schema->resultset('Artist');
213 $schema->txn_do(sub {
214 $ars->create({ name => 'in_outer_transaction' });
215 $schema->txn_do(sub {
216 $ars->create({ name => 'in_inner_transaction' });
218 ok($ars->search({ name => 'in_inner_transaction' })->first,
219 'commit from inner transaction visible in outer transaction');
221 $schema->txn_do(sub {
222 $ars->create({ name => 'in_inner_transaction_rolling_back' });
223 die 'rolling back inner transaction';
225 } qr/rolling back inner transaction/, 'inner transaction rollback executed';
226 $ars->create({ name => 'in_outer_transaction2' });
229 is_deeply( $schema->storage->savepoints, [], 'All savepoints forgotten' );
232 skip "Reading inexplicably fails on very old replicated DBD::SQLite<1.33", 1 if (
233 $ENV{DBICTEST_VIA_REPLICATED}
235 $prefix eq 'SQLite Internal DB'
237 ! modver_gt_or_eq('DBD::SQLite', '1.33')
240 ok($ars->search({ name => 'in_outer_transaction' })->first,
241 'commit from outer transaction');
242 ok($ars->search({ name => 'in_outer_transaction2' })->first,
243 'second commit from outer transaction');
244 ok($ars->search({ name => 'in_inner_transaction' })->first,
245 'commit from inner transaction');
246 is $ars->search({ name => 'in_inner_transaction_rolling_back' })->first,
248 'rollback from inner transaction';
252 $schema->storage->dbh_do(sub { $_[1]->do("DROP TABLE artist") });
258 local $SIG{__WARN__} = sigwarn_silencer( qr/Internal transaction state of handle/ )
259 unless modver_gt_or_eq('DBD::SQLite', '1.33');
260 eval { $schema->storage->dbh_do(sub { $_[1]->do("DROP TABLE artist") }) } if defined $schema;