File: 752sqlite.t

package info (click to toggle)
libdbix-class-perl 0.082843-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 5,320 kB
  • sloc: perl: 27,215; sql: 322; sh: 29; makefile: 16
file content (336 lines) | stat: -rw-r--r-- 10,129 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
use strict;
use warnings;

use Test::More;
use Test::Exception;
use Test::Warn;
use Time::HiRes 'time';
use Math::BigInt;

use lib qw(t/lib);
use DBICTest;
use DBIx::Class::_Util qw( sigwarn_silencer modver_gt_or_eq modver_gt_or_eq_and_lt );

# make one deploy() round before we load anything else - need this in order
# to prime SQLT if we are using it (deep depchain is deep)
DBICTest->init_schema( no_populate => 1 );

# check that we work somewhat OK with braindead SQLite transaction handling
#
# As per https://metacpan.org/source/ADAMK/DBD-SQLite-1.37/lib/DBD/SQLite.pm#L921
# SQLite does *not* try to synchronize
#
# However DBD::SQLite 1.38_02 seems to fix this, with an accompanying test:
# https://metacpan.org/source/ADAMK/DBD-SQLite-1.38_02/t/54_literal_txn.t
my $lit_txn_todo = modver_gt_or_eq('DBD::SQLite', '1.38_02')
  ? undef
  : "DBD::SQLite before 1.38_02 is retarded wrt detecting literal BEGIN/COMMIT statements"
;

for my $prefix_comment (qw/Begin_only Commit_only Begin_and_Commit/) {
  note "Testing with comment prefixes on $prefix_comment";

  # FIXME warning won't help us for the time being
  # perhaps when (if ever) DBD::SQLite gets fixed,
  # we can do something extra here
  local $SIG{__WARN__} = sigwarn_silencer( qr/Internal transaction state .+? does not seem to match/ )
    if ( $lit_txn_todo && !$ENV{TEST_VERBOSE} );

  my ($c_begin, $c_commit) = map { $prefix_comment =~ $_ ? 1 : 0 } (qr/Begin/, qr/Commit/);

  my $schema = DBICTest->init_schema( no_deploy => 1 );
  my $ars = $schema->resultset('Artist');

  ok (! $schema->storage->connected, 'No connection yet');

  $schema->storage->dbh->do(<<'DDL');
CREATE TABLE artist (
  artistid INTEGER PRIMARY KEY NOT NULL,
  name varchar(100),
  rank integer DEFAULT 13,
  charfield char(10) NULL
);
DDL

  my $artist = $ars->create({ name => 'Artist_' . time() });
  is ($ars->count, 1, 'Inserted artist ' . $artist->name);

  ok ($schema->storage->connected, 'Connected');
  ok ($schema->storage->_dbh->{AutoCommit}, 'DBD not in txn yet');

  $schema->storage->dbh->do(join "\n",
    $c_begin ? '-- comment' : (),
    'BEGIN TRANSACTION'
  );
  ok ($schema->storage->connected, 'Still connected');
  {
    local $TODO = $lit_txn_todo if $c_begin;
    ok (! $schema->storage->_dbh->{AutoCommit}, "DBD aware of txn begin with comments on $prefix_comment");
  }

  $schema->storage->dbh->do(join "\n",
    $c_commit ? '-- comment' : (),
    'COMMIT'
  );
  ok ($schema->storage->connected, 'Still connected');
  {
    local $TODO = $lit_txn_todo if $c_commit and ! $c_begin;
    ok ($schema->storage->_dbh->{AutoCommit}, "DBD aware txn ended with comments on $prefix_comment");
  }

  is ($ars->count, 1, 'Inserted artists still there');

  {
    # this never worked in the 1st place
    local $TODO = $lit_txn_todo if ! $c_begin and $c_commit;

    # odd argument passing, because such nested crefs leak on 5.8
    lives_ok {
      $schema->storage->txn_do (sub {
        ok ($_[0]->find({ name => $_[1] }), "Artist still where we left it after cycle with comments on $prefix_comment");
      }, $ars, $artist->name );
    } "Succesfull transaction with comments on $prefix_comment";
  }
}

# test blank begin/svp/commit/begin cycle
#
# need to prime this for exotic testing scenarios
# before testing for lack of warnings
modver_gt_or_eq('DBD::SQLite', '1.33');

warnings_are {
  my $schema = DBICTest->init_schema( no_populate => 1 );
  my $rs = $schema->resultset('Artist');
  is ($rs->count, 0, 'Start with empty table');

  for my $do_commit (1, 0) {
    $schema->txn_begin;
    $schema->svp_begin;
    $schema->svp_rollback;

    $schema->svp_begin;
    $schema->svp_rollback;

    $schema->svp_release;

    $schema->svp_begin;

    $schema->txn_rollback;

    $schema->txn_begin;
    $schema->svp_begin;
    $schema->svp_rollback;

    $schema->svp_begin;
    $schema->svp_rollback;

    $schema->svp_release;

    $schema->svp_begin;

    $do_commit ? $schema->txn_commit : $schema->txn_rollback;

    is_deeply $schema->storage->savepoints, [], 'Savepoint names cleared away'
  }

  $schema->txn_do(sub {
    ok (1, 'all seems fine');
  });
} [], 'No warnings emitted';

my $schema = DBICTest->init_schema();

# make sure the side-effects of RT#67581 do not result in data loss
my $row;
warnings_exist { $row = $schema->resultset('Artist')->create ({ name => 'alpha rank', rank => 'abc' }) }
  [qr/Non-integer value supplied for column 'rank' despite the integer datatype/],
  'proper warning on string insertion into an numeric column'
;
$row->discard_changes;
is ($row->rank, 'abc', 'proper rank inserted into database');

# and make sure we do not lose actual bigints
SKIP: {

skip "Not testing bigint handling on known broken DBD::SQLite trial versions", 1
  if( modver_gt_or_eq('DBD::SQLite', '1.45') and ! modver_gt_or_eq('DBD::SQLite', '1.45_03') );

{
  package DBICTest::BigIntArtist;
  use base 'DBICTest::Schema::Artist';
  __PACKAGE__->table('artist');
  __PACKAGE__->add_column(bigint => { data_type => 'bigint' });
}
$schema->register_class(BigIntArtist => 'DBICTest::BigIntArtist');
$schema->storage->dbh_do(sub {
  $_[1]->do('ALTER TABLE artist ADD COLUMN bigint BIGINT');
});

my $sqlite_broken_bigint = modver_gt_or_eq_and_lt( 'DBD::SQLite', '1.34', '1.37' );

# 63 bit integer
my $many_bits = (Math::BigInt->new(2) ** 62);

# test upper/lower boundaries for sqlite and some values inbetween
# range is -(2**63) .. 2**63 - 1
#
# Not testing -0 - it seems to overflow to ~0 on some combinations,
# thus not triggering the >32 bit guards
# interesting read: https://en.wikipedia.org/wiki/Signed_zero#Representations
for my $bi ( qw(
  -2
  -1
  0
  +0
  1
  2

  -9223372036854775807
  -8694837494948124658
  -6848440844435891639
  -5664812265578554454
  -5380388020020483213
  -2564279463598428141
  2442753333597784273
  4790993557925631491
  6773854980030157393
  7627910776496326154
  8297530189347439311
  9223372036854775806
  9223372036854775807

  4294967295
  4294967296

  -4294967296
  -4294967295
  -4294967294

  -2147483649
  -2147483648
  -2147483647
  -2147483646

  2147483646
  2147483647
),
  # these values cause exceptions even with all workarounds in place on these
  # fucked DBD::SQLite versions *regardless* of ivsize >.<
  $sqlite_broken_bigint
    ? ()
    : ( '2147483648', '2147483649' )
  ,

  # with newer compilers ( gcc 4.9+ ) older DBD::SQLite does not
  # play well with the "Most Negative Number"
  modver_gt_or_eq( 'DBD::SQLite', '1.33' )
    ? ( '-9223372036854775808' )
    : ()
  ,

) {
  # unsigned 32 bit ints have a range of −2,147,483,648 to 2,147,483,647
  # alternatively expressed as the hexadecimal numbers below
  # the comparison math will come out right regardless of ivsize, since
  # we are operating within 31 bits
  # P.S. 31 because one bit is lost for the sign
  my $v_bits = ($bi > 0x7fff_ffff || $bi < -0x8000_0000) ? 64 : 32;

  my $v_desc = sprintf '%s (%d bit signed int)', $bi, $v_bits;

  my @w;
  local $SIG{__WARN__} = sub {
    if ($_[0] =~ /datatype mismatch/) {
      push @w, @_;
    }
    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/ ) {
      # do nothing, this warning will pop up here and there depending on
      # DBD/bitness combination
      # we don't want to test for it explicitly, we are just interested
      # in the results matching at the end
    }
    else {
      warn @_;
    }
  };

  # some combinations of SQLite 1.35 and older 5.8 faimly is wonky
  # instead of a warning we get a full exception. Sod it
  eval {
    $row = $schema->resultset('BigIntArtist')->create({ bigint => $bi });
  } or do {
    fail("Exception on inserting $v_desc: $@") unless $sqlite_broken_bigint;
    next;
  };

  # explicitly using eq, to make sure we did not nummify the argument
  # which can be an issue on 32 bit ivsize
  cmp_ok ($row->bigint, 'eq', $bi, "value in object correct ($v_desc)");

  $row->discard_changes;

  cmp_ok (
    $row->bigint,

    # the test will not pass an == if we are running under 32 bit ivsize
    # use 'eq' on the numified (and possibly "scientificied") returned value
    (DBIx::Class::_ENV_::IV_SIZE < 8 and $v_bits > 32) ? 'eq' : '==',

    # in 1.37 DBD::SQLite switched to proper losless representation of bigints
    # regardless of ivize
    # before this use 'eq' (from above) on the numified (and possibly
    # "scientificied") returned value
    (DBIx::Class::_ENV_::IV_SIZE < 8 and ! modver_gt_or_eq('DBD::SQLite', '1.37')) ? $bi+0 : $bi,

    "value in database correct ($v_desc)"
  );

# FIXME - temporary smoke-only escape
SKIP: {
  skip 'Potential for false negatives - investigation pending', 1
    if DBICTest::RunMode->is_plain;

  # check if math works
  # start by adding/subtracting a 50 bit integer, and then divide by 2 for good measure
  my ($sqlop, $expect) = $bi < 0
    ? ( '(bigint + ? )', ($bi + $many_bits) )
    : ( '(bigint - ? )', ($bi - $many_bits) )
  ;

  $expect = ($expect + ($expect % 2)) / 2;

  # read https://en.wikipedia.org/wiki/Modulo_operation#Common_pitfalls
  # and check the tables on the right side of the article for an
  # enlightening journey on why a mere bigint % 2 won't work
  $sqlop = "( $sqlop + ( ((bigint % 2)+2)%2 ) ) / 2";

  for my $dtype (undef, \'int', \'bigint') {

    # FIXME - the double-load should not be needed
    # will fix in the future
    $row->update({ bigint => $bi });
    $row->discard_changes;
    $row->update({ bigint => \[ $sqlop, [ $dtype => $many_bits ] ] });
    $row->discard_changes;

    # can't use cmp_ok - will not engage the M::BI overload of $many_bits
    ok (
      $row->bigint

      ==

      (DBIx::Class::_ENV_::IV_SIZE < 8 and ! modver_gt_or_eq('DBD::SQLite', '1.37')) ? $expect->bstr + 0 : $expect
    , "simple integer math with@{[ $dtype ? '' : 'out' ]} bindtype in database correct (base $v_desc)")
      or diag sprintf '%s != %s', $row->bigint, $expect;
  }
# end of fixme
}

  is_deeply (\@w, [], "No mismatch warnings on bigint operations ($v_desc)" );

}}

done_testing;

# vim:sts=2 sw=2: