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