Commit | Line | Data |
c0329273 |
1 | BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) } |
2 | |
ddf66ced |
3 | use strict; |
4 | use warnings; |
5 | |
6 | use Test::More; |
cf1d16d8 |
7 | use Test::Exception; |
bbf6a9a5 |
8 | use DBIx::Class::_Util qw(modver_gt_or_eq sigwarn_silencer scope_guard); |
2cfc22dd |
9 | |
c0329273 |
10 | |
2cfc22dd |
11 | use 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 |
28 | my $env2optdep = { |
cf1d16d8 |
29 | DBICTEST_PG => 'test_rdbms_pg', |
ae1d3ea1 |
30 | DBICTEST_MYSQL => 'test_rdbms_mysql', |
31 | }; |
ddf66ced |
32 | |
ae1d3ea1 |
33 | my $schema; |
ddf66ced |
34 | |
cf1d16d8 |
35 | for ('', 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 |
233 | SKIP: { |
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 |
257 | done_testing; |
ddf66ced |
258 | |
65d35121 |
259 | END { |
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 | } |