Commit | Line | Data |
86a51471 |
1 | use strict; |
2 | use warnings; |
3 | |
4 | use Test::More; |
5 | use Test::Exception; |
632d1e0f |
6 | use Test::Warn; |
2aeb3c7f |
7 | use Time::HiRes 'time'; |
f3b1224b |
8 | use Math::BigInt; |
67b35a45 |
9 | |
86a51471 |
10 | use lib qw(t/lib); |
11 | use DBICTest; |
052a832c |
12 | use DBIx::Class::_Util qw(sigwarn_silencer modver_gt_or_eq); |
86a51471 |
13 | |
67b35a45 |
14 | # savepoints test |
15 | { |
16 | my $schema = DBICTest->init_schema(auto_savepoint => 1); |
86a51471 |
17 | |
67b35a45 |
18 | my $ars = $schema->resultset('Artist'); |
86a51471 |
19 | |
67b35a45 |
20 | # test two-phase commit and inner transaction rollback from nested transactions |
86a51471 |
21 | $schema->txn_do(sub { |
67b35a45 |
22 | $ars->create({ name => 'in_outer_transaction' }); |
86a51471 |
23 | $schema->txn_do(sub { |
67b35a45 |
24 | $ars->create({ name => 'in_inner_transaction' }); |
86a51471 |
25 | }); |
67b35a45 |
26 | ok($ars->search({ name => 'in_inner_transaction' })->first, |
27 | 'commit from inner transaction visible in outer transaction'); |
28 | throws_ok { |
29 | $schema->txn_do(sub { |
30 | $ars->create({ name => 'in_inner_transaction_rolling_back' }); |
31 | die 'rolling back inner transaction'; |
32 | }); |
33 | } qr/rolling back inner transaction/, 'inner transaction rollback executed'; |
34 | $ars->create({ name => 'in_outer_transaction2' }); |
35 | }); |
36 | |
37 | ok($ars->search({ name => 'in_outer_transaction' })->first, |
38 | 'commit from outer transaction'); |
39 | ok($ars->search({ name => 'in_outer_transaction2' })->first, |
40 | 'second commit from outer transaction'); |
41 | ok($ars->search({ name => 'in_inner_transaction' })->first, |
42 | 'commit from inner transaction'); |
43 | is $ars->search({ name => 'in_inner_transaction_rolling_back' })->first, |
44 | undef, |
45 | 'rollback from inner transaction'; |
46 | } |
632d1e0f |
47 | |
2aeb3c7f |
48 | # check that we work somewhat OK with braindead SQLite transaction handling |
49 | # |
50 | # As per https://metacpan.org/source/ADAMK/DBD-SQLite-1.37/lib/DBD/SQLite.pm#L921 |
51 | # SQLite does *not* try to synchronize |
ab0b0a09 |
52 | # |
53 | # However DBD::SQLite 1.38_02 seems to fix this, with an accompanying test: |
54 | # https://metacpan.org/source/ADAMK/DBD-SQLite-1.38_02/t/54_literal_txn.t |
55 | |
56270bba |
56 | require DBD::SQLite; |
b1dbf716 |
57 | my $lit_txn_todo = modver_gt_or_eq('DBD::SQLite', '1.38_02') |
ab0b0a09 |
58 | ? undef |
59 | : "DBD::SQLite before 1.38_02 is retarded wrt detecting literal BEGIN/COMMIT statements" |
60 | ; |
2aeb3c7f |
61 | |
62 | for my $prefix_comment (qw/Begin_only Commit_only Begin_and_Commit/) { |
63 | note "Testing with comment prefixes on $prefix_comment"; |
64 | |
65 | # FIXME warning won't help us for the time being |
66 | # perhaps when (if ever) DBD::SQLite gets fixed, |
67 | # we can do something extra here |
052a832c |
68 | local $SIG{__WARN__} = sigwarn_silencer( qr/Internal transaction state .+? does not seem to match/ ) |
ab0b0a09 |
69 | if ( $lit_txn_todo && !$ENV{TEST_VERBOSE} ); |
2aeb3c7f |
70 | |
71 | my ($c_begin, $c_commit) = map { $prefix_comment =~ $_ ? 1 : 0 } (qr/Begin/, qr/Commit/); |
72 | |
73 | my $schema = DBICTest->init_schema( no_deploy => 1 ); |
74 | my $ars = $schema->resultset('Artist'); |
75 | |
76 | ok (! $schema->storage->connected, 'No connection yet'); |
77 | |
78 | $schema->storage->dbh->do(<<'DDL'); |
79 | CREATE TABLE artist ( |
80 | artistid INTEGER PRIMARY KEY NOT NULL, |
81 | name varchar(100), |
82 | rank integer DEFAULT 13, |
83 | charfield char(10) NULL |
84 | ); |
85 | DDL |
86 | |
87 | my $artist = $ars->create({ name => 'Artist_' . time() }); |
88 | is ($ars->count, 1, 'Inserted artist ' . $artist->name); |
89 | |
90 | ok ($schema->storage->connected, 'Connected'); |
91 | ok ($schema->storage->_dbh->{AutoCommit}, 'DBD not in txn yet'); |
92 | |
93 | $schema->storage->dbh->do(join "\n", |
94 | $c_begin ? '-- comment' : (), |
95 | 'BEGIN TRANSACTION' |
96 | ); |
97 | ok ($schema->storage->connected, 'Still connected'); |
98 | { |
ab0b0a09 |
99 | local $TODO = $lit_txn_todo if $c_begin; |
2aeb3c7f |
100 | ok (! $schema->storage->_dbh->{AutoCommit}, "DBD aware of txn begin with comments on $prefix_comment"); |
101 | } |
102 | |
103 | $schema->storage->dbh->do(join "\n", |
104 | $c_commit ? '-- comment' : (), |
105 | 'COMMIT' |
106 | ); |
107 | ok ($schema->storage->connected, 'Still connected'); |
108 | { |
ab0b0a09 |
109 | local $TODO = $lit_txn_todo if $c_commit and ! $c_begin; |
2aeb3c7f |
110 | ok ($schema->storage->_dbh->{AutoCommit}, "DBD aware txn ended with comments on $prefix_comment"); |
111 | } |
112 | |
113 | is ($ars->count, 1, 'Inserted artists still there'); |
114 | |
115 | { |
116 | # this never worked in the 1st place |
ab0b0a09 |
117 | local $TODO = $lit_txn_todo if ! $c_begin and $c_commit; |
2aeb3c7f |
118 | |
08615cfb |
119 | # odd argument passing, because such nested crefs leak on 5.8 |
2aeb3c7f |
120 | lives_ok { |
121 | $schema->storage->txn_do (sub { |
08615cfb |
122 | ok ($_[0]->find({ name => $_[1] }), "Artist still where we left it after cycle with comments on $prefix_comment"); |
123 | }, $ars, $artist->name ); |
2aeb3c7f |
124 | } "Succesfull transaction with comments on $prefix_comment"; |
125 | } |
126 | } |
127 | |
398215b1 |
128 | # test blank begin/svp/commit/begin cycle |
129 | warnings_are { |
130 | my $schema = DBICTest->init_schema( no_populate => 1 ); |
131 | my $rs = $schema->resultset('Artist'); |
132 | is ($rs->count, 0, 'Start with empty table'); |
133 | |
134 | for my $do_commit (1, 0) { |
135 | $schema->txn_begin; |
136 | $schema->svp_begin; |
137 | $schema->svp_rollback; |
138 | |
139 | $schema->svp_begin; |
140 | $schema->svp_rollback; |
141 | |
142 | $schema->svp_release; |
143 | |
144 | $schema->svp_begin; |
145 | |
146 | $schema->txn_rollback; |
147 | |
148 | $schema->txn_begin; |
149 | $schema->svp_begin; |
150 | $schema->svp_rollback; |
151 | |
152 | $schema->svp_begin; |
153 | $schema->svp_rollback; |
154 | |
155 | $schema->svp_release; |
156 | |
157 | $schema->svp_begin; |
158 | |
159 | $do_commit ? $schema->txn_commit : $schema->txn_rollback; |
160 | |
161 | is_deeply $schema->storage->savepoints, [], 'Savepoint names cleared away' |
162 | } |
163 | |
164 | $schema->txn_do(sub { |
165 | ok (1, 'all seems fine'); |
166 | }); |
167 | } [], 'No warnings emitted'; |
2aeb3c7f |
168 | |
67b35a45 |
169 | my $schema = DBICTest->init_schema(); |
86a51471 |
170 | |
632d1e0f |
171 | # make sure the side-effects of RT#67581 do not result in data loss |
172 | my $row; |
67b35a45 |
173 | warnings_exist { $row = $schema->resultset('Artist')->create ({ name => 'alpha rank', rank => 'abc' }) } |
445bc0cd |
174 | [qr/Non-integer value supplied for column 'rank' despite the integer datatype/], |
632d1e0f |
175 | 'proper warning on string insertion into an numeric column' |
176 | ; |
177 | $row->discard_changes; |
178 | is ($row->rank, 'abc', 'proper rank inserted into database'); |
179 | |
67b35a45 |
180 | # and make sure we do not lose actual bigints |
181 | { |
182 | package DBICTest::BigIntArtist; |
183 | use base 'DBICTest::Schema::Artist'; |
184 | __PACKAGE__->table('artist'); |
185 | __PACKAGE__->add_column(bigint => { data_type => 'bigint' }); |
186 | } |
187 | $schema->register_class(BigIntArtist => 'DBICTest::BigIntArtist'); |
188 | $schema->storage->dbh_do(sub { |
189 | $_[1]->do('ALTER TABLE artist ADD COLUMN bigint BIGINT'); |
190 | }); |
191 | |
f3b1224b |
192 | my $sqlite_broken_bigint = ( |
193 | modver_gt_or_eq('DBD::SQLite', '1.34') and ! modver_gt_or_eq('DBD::SQLite', '1.37') |
194 | ); |
195 | |
196 | # 63 bit integer |
197 | my $many_bits = (Math::BigInt->new(2) ** 62); |
198 | |
67b35a45 |
199 | # test upper/lower boundaries for sqlite and some values inbetween |
200 | # range is -(2**63) .. 2**63 - 1 |
f3b1224b |
201 | # |
202 | # Not testing -0 - it seems to overflow to ~0 on some combinations, |
203 | # thus not triggering the >32 bit guards |
204 | # interesting read: https://en.wikipedia.org/wiki/Signed_zero#Representations |
04ab4eb1 |
205 | for my $bi ( qw( |
f3b1224b |
206 | -2 |
207 | -1 |
208 | 0 |
209 | +0 |
210 | 1 |
211 | 2 |
212 | |
04ab4eb1 |
213 | -9223372036854775808 |
214 | -9223372036854775807 |
215 | -8694837494948124658 |
216 | -6848440844435891639 |
217 | -5664812265578554454 |
218 | -5380388020020483213 |
219 | -2564279463598428141 |
220 | 2442753333597784273 |
221 | 4790993557925631491 |
222 | 6773854980030157393 |
223 | 7627910776496326154 |
224 | 8297530189347439311 |
225 | 9223372036854775806 |
226 | 9223372036854775807 |
227 | |
228 | 4294967295 |
229 | 4294967296 |
230 | |
231 | -4294967296 |
232 | -4294967295 |
233 | -4294967294 |
234 | |
235 | -2147483649 |
236 | -2147483648 |
237 | -2147483647 |
238 | -2147483646 |
239 | |
240 | 2147483646 |
241 | 2147483647 |
242 | ), |
243 | # these values cause exceptions even with all workarounds in place on these |
244 | # fucked DBD::SQLite versions *regardless* of ivsize >.< |
f3b1224b |
245 | $sqlite_broken_bigint |
04ab4eb1 |
246 | ? () |
247 | : ( '2147483648', '2147483649' ) |
248 | ) { |
249 | # unsigned 32 bit ints have a range of −2,147,483,648 to 2,147,483,647 |
250 | # alternatively expressed as the hexadecimal numbers below |
251 | # the comparison math will come out right regardless of ivsize, since |
252 | # we are operating within 31 bits |
253 | # P.S. 31 because one bit is lost for the sign |
254 | my $v_bits = ($bi > 0x7fff_ffff || $bi < -0x8000_0000) ? 64 : 32; |
255 | |
256 | my $v_desc = sprintf '%s (%d bit signed int)', $bi, $v_bits; |
257 | |
1363f0f5 |
258 | my @w; |
113322e5 |
259 | local $SIG{__WARN__} = sub { |
260 | if ($_[0] =~ /datatype mismatch/) { |
261 | push @w, @_; |
262 | } |
263 | elsif ($_[0] =~ /An integer value occupying more than 32 bits was supplied .+ can not bind properly so DBIC will treat it as a string instead/ ) { |
264 | # do nothing, this warning will pop up here and there depending on |
265 | # DBD/bitness combination |
266 | # we don't want to test for it explicitly, we are just interested |
267 | # in the results matching at the end |
268 | } |
269 | else { |
270 | warn @_; |
271 | } |
272 | }; |
1363f0f5 |
273 | |
f3b1224b |
274 | # some combinations of SQLite 1.35 and older 5.8 faimly is wonky |
275 | # instead of a warning we get a full exception. Sod it |
276 | eval { |
2e092442 |
277 | $row = $schema->resultset('BigIntArtist')->create({ bigint => $bi }); |
f3b1224b |
278 | } or do { |
279 | fail("Exception on inserting $v_desc") unless $sqlite_broken_bigint; |
280 | next; |
281 | }; |
04ab4eb1 |
282 | |
283 | # explicitly using eq, to make sure we did not nummify the argument |
284 | # which can be an issue on 32 bit ivsize |
285 | cmp_ok ($row->bigint, 'eq', $bi, "value in object correct ($v_desc)"); |
286 | |
287 | $row->discard_changes; |
288 | |
289 | cmp_ok ( |
290 | $row->bigint, |
291 | |
292 | # the test will not pass an == if we are running under 32 bit ivsize |
293 | # use 'eq' on the numified (and possibly "scientificied") returned value |
f3b1224b |
294 | (DBIx::Class::_ENV_::IV_SIZE < 8 and $v_bits > 32) ? 'eq' : '==', |
04ab4eb1 |
295 | |
296 | # in 1.37 DBD::SQLite switched to proper losless representation of bigints |
297 | # regardless of ivize |
298 | # before this use 'eq' (from above) on the numified (and possibly |
299 | # "scientificied") returned value |
300 | (DBIx::Class::_ENV_::IV_SIZE < 8 and ! modver_gt_or_eq('DBD::SQLite', '1.37')) ? $bi+0 : $bi, |
301 | |
302 | "value in database correct ($v_desc)" |
303 | ); |
1363f0f5 |
304 | |
f3b1224b |
305 | # FIXME - temporary smoke-only escape |
306 | SKIP: { |
307 | skip 'Potential for false negatives - investigation pending', 1 |
308 | if DBICTest::RunMode->is_plain; |
309 | |
310 | # check if math works |
311 | # start by adding/subtracting a 50 bit integer, and then divide by 2 for good measure |
312 | my ($sqlop, $expect) = $bi < 0 |
313 | ? ( '(bigint + ? )', ($bi + $many_bits) ) |
314 | : ( '(bigint - ? )', ($bi - $many_bits) ) |
315 | ; |
316 | |
317 | $expect = ($expect + ($expect % 2)) / 2; |
318 | |
319 | # read https://en.wikipedia.org/wiki/Modulo_operation#Common_pitfalls |
320 | # and check the tables on the right side of the article for an |
321 | # enlightening journey on why a mere bigint % 2 won't work |
322 | $sqlop = "( $sqlop + ( ((bigint % 2)+2)%2 ) ) / 2"; |
323 | |
324 | for my $dtype (undef, \'int', \'bigint') { |
325 | |
326 | # FIXME - the double-load should not be needed |
327 | # will fix in the future |
328 | $row->update({ bigint => $bi }); |
329 | $row->discard_changes; |
330 | $row->update({ bigint => \[ $sqlop, [ $dtype => $many_bits ] ] }); |
331 | $row->discard_changes; |
332 | |
333 | # can't use cmp_ok - will not engage the M::BI overload of $many_bits |
334 | ok ( |
335 | $row->bigint |
336 | |
337 | == |
338 | |
339 | (DBIx::Class::_ENV_::IV_SIZE < 8 and ! modver_gt_or_eq('DBD::SQLite', '1.37')) ? $expect->bstr + 0 : $expect |
340 | , "simple integer math with@{[ $dtype ? '' : 'out' ]} bindtype in database correct (base $v_desc)") |
341 | or diag sprintf '%s != %s', $row->bigint, $expect; |
342 | } |
343 | # end of fixme |
344 | } |
345 | |
346 | is_deeply (\@w, [], "No mismatch warnings on bigint operations ($v_desc)" ); |
67b35a45 |
347 | } |
348 | |
86a51471 |
349 | done_testing; |
350 | |
351 | # vim:sts=2 sw=2: |