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:
|