Add all database connections via DBICTest::Schema to the leaktrace pool
[dbsrgits/DBIx-Class.git] / t / 746mssql.t
CommitLineData
c1cac633 1use strict;
b9a2c3a5 2use warnings;
c1cac633 3
4use Test::More;
893403c8 5use Test::Exception;
199fbc45 6use Try::Tiny;
7use DBIx::Class::SQLMaker::LimitDialects;
8use DBIx::Class::Optional::Dependencies ();
c1cac633 9use lib qw(t/lib);
10use DBICTest;
b2d16f1f 11use DBIC::SqlMakerTest;
c1cac633 12
199fbc45 13plan skip_all => 'Test needs ' . DBIx::Class::Optional::Dependencies->req_missing_for ('test_rdbms_mssql_odbc')
14 unless DBIx::Class::Optional::Dependencies->req_ok_for ('test_rdbms_mssql_odbc');
15
fcb7fcbb 16my $OFFSET = DBIx::Class::SQLMaker::LimitDialects->__offset_bindtype;
17my $TOTAL = DBIx::Class::SQLMaker::LimitDialects->__total_bindtype;
18
c1cac633 19my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ODBC_${_}" } qw/DSN USER PASS/};
20
21plan skip_all => 'Set $ENV{DBICTEST_MSSQL_ODBC_DSN}, _USER and _PASS to run this test'
22 unless ($dsn && $user);
23
77c7628c 24{
25 my $srv_ver = DBICTest::Schema->connect($dsn, $user, $pass)->storage->_server_info->{dbms_version};
26 ok ($srv_ver, 'Got a test server version on fresh schema: ' . ($srv_ver||'???') );
27}
28
ca791b95 29DBICTest::Schema->load_classes('ArtistGUID');
42e5b103 30my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
c1cac633 31
8c0104fe 32{
33 no warnings 'redefine';
34 my $connect_count = 0;
35 my $orig_connect = \&DBI::connect;
36 local *DBI::connect = sub { $connect_count++; goto &$orig_connect };
37
38 $schema->storage->ensure_connected;
39
40 is( $connect_count, 1, 'only one connection made');
41}
9b3e916d 42
c1cac633 43isa_ok( $schema->storage, 'DBIx::Class::Storage::DBI::ODBC::Microsoft_SQL_Server' );
44
cf89555e 45{
46 my $schema2 = $schema->connect ($schema->storage->connect_info);
47 ok (! $schema2->storage->connected, 'a re-connected cloned schema starts unconnected');
48}
49
ecdf1ac8 50$schema->storage->_dbh->disconnect;
51
52lives_ok {
53 $schema->storage->dbh_do(sub { $_[1]->do('select 1') })
54} '_ping works';
55
384b8bce 56my %opts = (
57 use_mars =>
9ffaf8a3 58 { opts => { on_connect_call => 'use_mars' } },
384b8bce 59 use_dynamic_cursors =>
25d3127d 60 { opts => { on_connect_call => 'use_dynamic_cursors' },
aca3b4c3 61 required => $schema->storage->_using_freetds ? 0 : 1,
25d3127d 62 },
384b8bce 63 use_server_cursors =>
9ffaf8a3 64 { opts => { on_connect_call => 'use_server_cursors' } },
65 NO_OPTION =>
66 { opts => {}, required => 1 },
384b8bce 67);
68
69for my $opts_name (keys %opts) {
70 SKIP: {
9ffaf8a3 71 my $opts = $opts{$opts_name}{opts};
384b8bce 72 $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opts);
73
74 try {
75 $schema->storage->ensure_connected
76 }
77 catch {
9ffaf8a3 78 if ($opts{$opts_name}{required}) {
79 BAIL_OUT "on_connect_call option '$opts_name' is not functional: $_";
80 }
81 else {
82 skip
83"on_connect_call option '$opts_name' not functional in this configuration: $_",
841;
85 }
384b8bce 86 };
87
e56b1c2d 88 $schema->storage->dbh_do (sub {
89 my ($storage, $dbh) = @_;
90 eval { $dbh->do("DROP TABLE artist") };
91 $dbh->do(<<'SQL');
c1cac633 92CREATE TABLE artist (
93 artistid INT IDENTITY NOT NULL,
a0dd8679 94 name VARCHAR(100),
39da2a2b 95 rank INT NOT NULL DEFAULT '13',
2eebd801 96 charfield CHAR(10) NULL,
c1cac633 97 primary key(artistid)
98)
c5f77f6c 99SQL
e56b1c2d 100 });
c5f77f6c 101
384b8bce 102# test Auto-PK
103 $schema->resultset('Artist')->search({ name => 'foo' })->delete;
c1cac633 104
384b8bce 105 my $new = $schema->resultset('Artist')->create({ name => 'foo' });
7b1b2582 106
384b8bce 107 ok(($new->artistid||0) > 0, "Auto-PK worked for $opts_name");
7b1b2582 108
384b8bce 109# Test multiple active statements
110 SKIP: {
111 skip 'not a multiple active statements configuration', 1
112 if $opts_name eq 'plain';
41dd5d30 113
384b8bce 114 my $artist_rs = $schema->resultset('Artist');
41dd5d30 115
384b8bce 116 $artist_rs->delete;
c1cac633 117
384b8bce 118 $artist_rs->create({ name => "Artist$_" }) for (1..3);
ca791b95 119
384b8bce 120 my $forward = $artist_rs->search({},
121 { order_by => { -asc => 'artistid' } });
122 my $backward = $artist_rs->search({},
123 { order_by => { -desc => 'artistid' } });
124
125 my @map = (
126 [qw/Artist1 Artist3/], [qw/Artist2 Artist2/], [qw/Artist3 Artist1/]
127 );
128 my @result;
129
130 while (my $forward_row = $forward->next) {
131 my $backward_row = $backward->next;
132 push @result, [$forward_row->name, $backward_row->name];
133 }
134
135 is_deeply \@result, \@map, "multiple active statements in $opts_name";
136
137 $artist_rs->delete;
25d3127d 138
139 is($artist_rs->count, 0, '$dbh still viable');
384b8bce 140 }
ca791b95 141
a54bd479 142# Test populate
f6b185e1 143
e56b1c2d 144 {
145 $schema->storage->dbh_do (sub {
146 my ($storage, $dbh) = @_;
147 eval { $dbh->do("DROP TABLE owners") };
148 eval { $dbh->do("DROP TABLE books") };
149 $dbh->do(<<'SQL');
02495deb 150CREATE TABLE books (
b9a2c3a5 151 id INT IDENTITY (1, 1) NOT NULL,
152 source VARCHAR(100),
153 owner INT,
154 title VARCHAR(10),
155 price INT NULL
156)
157
02495deb 158CREATE TABLE owners (
b9a2c3a5 159 id INT IDENTITY (1, 1) NOT NULL,
42e5b103 160 name VARCHAR(100),
b9a2c3a5 161)
b9a2c3a5 162SQL
a54bd479 163 });
164
e56b1c2d 165 lives_ok ( sub {
166 # start a new connection, make sure rebless works
167 my $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opts);
168 $schema->populate ('Owners', [
169 [qw/id name /],
170 [qw/1 wiggle/],
171 [qw/2 woggle/],
172 [qw/3 boggle/],
173 [qw/4 fRIOUX/],
174 [qw/5 fRUE/],
175 [qw/6 fREW/],
176 [qw/7 fROOH/],
177 [qw/8 fISMBoC/],
178 [qw/9 station/],
179 [qw/10 mirror/],
180 [qw/11 dimly/],
181 [qw/12 face_to_face/],
182 [qw/13 icarus/],
183 [qw/14 dream/],
184 [qw/15 dyrstyggyr/],
185 ]);
186 }, 'populate with PKs supplied ok' );
187
188
189 lives_ok (sub {
190 # start a new connection, make sure rebless works
191 # test an insert with a supplied identity, followed by one without
192 my $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opts);
193 for (2, 1) {
194 my $id = $_ * 20 ;
195 $schema->resultset ('Owners')->create ({ id => $id, name => "troglodoogle $id" });
196 $schema->resultset ('Owners')->create ({ name => "troglodoogle " . ($id + 1) });
197 }
198 }, 'create with/without PKs ok' );
199
200 is ($schema->resultset ('Owners')->count, 19, 'owner rows really in db' );
201
202 lives_ok ( sub {
203 # start a new connection, make sure rebless works
204 my $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opts);
205 $schema->populate ('BooksInLibrary', [
206 [qw/source owner title /],
207 [qw/Library 1 secrets0/],
208 [qw/Library 1 secrets1/],
209 [qw/Eatery 1 secrets2/],
210 [qw/Library 2 secrets3/],
211 [qw/Library 3 secrets4/],
212 [qw/Eatery 3 secrets5/],
213 [qw/Library 4 secrets6/],
214 [qw/Library 5 secrets7/],
215 [qw/Eatery 5 secrets8/],
216 [qw/Library 6 secrets9/],
217 [qw/Library 7 secrets10/],
218 [qw/Eatery 7 secrets11/],
219 [qw/Library 8 secrets12/],
220 ]);
221 }, 'populate without PKs supplied ok' );
a54bd479 222 }
fb7cd45f 223
e56b1c2d 224# test simple, complex LIMIT and limited prefetch support, with both dialects and quote combinations (if possible)
225 for my $dialect (
226 'Top',
227 ($schema->storage->_server_info->{normalized_dbms_version} || 0 ) >= 9
228 ? ('RowNumberOver')
229 : ()
230 ,
231 ) {
232 for my $quoted (0, 1) {
233
234 $schema = DBICTest::Schema->connect($dsn, $user, $pass, {
235 limit_dialect => $dialect,
236 %$opts,
237 $quoted
238 ? ( quote_char => [ qw/ [ ] / ], name_sep => '.' )
239 : ()
240 ,
241 });
242
243 my $test_type = "Dialect:$dialect Quoted:$quoted";
244
245 # basic limit support
246 TODO: {
247 my $art_rs = $schema->resultset ('Artist');
248 $art_rs->delete;
249 $art_rs->create({ name => 'Artist ' . $_ }) for (1..6);
250
251 my $it = $schema->resultset('Artist')->search( {}, {
252 rows => 4,
253 offset => 3,
254 order_by => 'artistid',
255 });
256
257 is( $it->count, 3, "$test_type: LIMIT count ok" );
258
259 local $TODO = "Top-limit does not work when your limit ends up past the resultset"
260 if $dialect eq 'Top';
261
262 is( $it->next->name, 'Artist 4', "$test_type: iterator->next ok" );
263 $it->next;
264 is( $it->next->name, 'Artist 6', "$test_type: iterator->next ok" );
265 is( $it->next, undef, "$test_type: next past end of resultset ok" );
266 }
f0bd60fc 267
e56b1c2d 268 # plain ordered subqueries throw
269 throws_ok (sub {
270 $schema->resultset('Owners')->search ({}, { order_by => 'name' })->as_query
271 }, qr/ordered subselect encountered/, "$test_type: Ordered Subselect detection throws ok");
a54bd479 272
e56b1c2d 273 # make sure ordered subselects *somewhat* work
274 {
275 my $owners = $schema->resultset ('Owners')->search ({}, { order_by => 'name', offset => 2, rows => 3, unsafe_subselect_ok => 1 });
276 my $sealed_owners = $owners->as_subselect_rs;
277
278 is_deeply (
279 [ map { $_->name } ($sealed_owners->all) ],
280 [ map { $_->name } ($owners->all) ],
281 "$test_type: Sort preserved from within a subquery",
282 );
283 }
a54bd479 284
e56b1c2d 285 # still even with lost order of IN, we should be getting correct
286 # sets
287 {
288 my $owners = $schema->resultset ('Owners')->search ({}, { order_by => 'name', offset => 2, rows => 3, unsafe_subselect_ok => 1 });
289 my $corelated_owners = $owners->result_source->resultset->search (
290 {
291 id => { -in => $owners->get_column('id')->as_query },
292 },
293 {
294 order_by => 'name' #reorder because of what is shown above
295 },
296 );
297
298 is (
299 join ("\x00", map { $_->name } ($corelated_owners->all) ),
300 join ("\x00", map { $_->name } ($owners->all) ),
301 "$test_type: With an outer order_by, everything still matches",
302 );
303 }
a54bd479 304
e56b1c2d 305 # make sure right-join-side single-prefetch ordering limit works
306 {
307 my $rs = $schema->resultset ('BooksInLibrary')->search (
308 {
309 'owner.name' => { '!=', 'woggle' },
310 },
311 {
312 prefetch => 'owner',
313 order_by => 'owner.name',
314 }
315 );
316 # this is the order in which they should come from the above query
317 my @owner_names = qw/boggle fISMBoC fREW fRIOUX fROOH fRUE wiggle wiggle/;
318
319 is ($rs->all, 8, "$test_type: Correct amount of objects from right-sorted joined resultset");
320 is_deeply (
321 [map { $_->owner->name } ($rs->all) ],
322 \@owner_names,
323 "$test_type: Prefetched rows were properly ordered"
324 );
325
326 my $limited_rs = $rs->search ({}, {rows => 6, offset => 2, unsafe_subselect_ok => 1});
327 is ($limited_rs->count, 6, "$test_type: Correct count of limited right-sorted joined resultset");
328 is ($limited_rs->count_rs->next, 6, "$test_type: Correct count_rs of limited right-sorted joined resultset");
329
330 my $queries;
331 my $orig_debug = $schema->storage->debug;
332 $schema->storage->debugcb(sub { $queries++; });
333 $schema->storage->debug(1);
334
335 is_deeply (
336 [map { $_->owner->name } ($limited_rs->all) ],
337 [@owner_names[2 .. 7]],
338 "$test_type: Prefetch-limited rows were properly ordered"
339 );
340 is ($queries, 1, "$test_type: Only one query with prefetch");
341
342 $schema->storage->debugcb(undef);
343 $schema->storage->debug($orig_debug);
344
345 is_deeply (
346 [map { $_->name } ($limited_rs->search_related ('owner')->all) ],
347 [@owner_names[2 .. 7]],
348 "$test_type: Rows are still properly ordered after search_related",
349 );
350 }
a54bd479 351
e56b1c2d 352 # try a ->has_many direction with duplicates
353 my $owners = $schema->resultset ('Owners')->search (
354 {
355 'books.id' => { '!=', undef },
356 'me.name' => { '!=', 'somebogusstring' },
357 },
358 {
359 prefetch => 'books',
86bb5a27 360 order_by => [ { -asc => \['name + ?', [ test => 'xxx' ]] }, 'me.id' ], # test bindvar propagation
e56b1c2d 361 group_by => [ map { "me.$_" } $schema->source('Owners')->columns ], # the literal order_by requires an explicit group_by
362 rows => 3, # 8 results total
363 unsafe_subselect_ok => 1,
364 },
365 );
a54bd479 366
e56b1c2d 367 my ($sql, @bind) = @${$owners->page(3)->as_query};
0e773352 368 is_same_bind (
e56b1c2d 369 \@bind,
370 [
fcb7fcbb 371 ($dialect eq 'Top' ? [ { dbic_colname => 'test' } => 'xxx' ] : ()), # the extra re-order bind
372 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.name' }
373 => 'somebogusstring' ],
374 [ { dbic_colname => 'test' }
375 => 'xxx' ],
376 ($dialect ne 'Top' ? ( [ $OFFSET => 7 ], [ $TOTAL => 9 ] ) : ()), # parameterised RNO
377 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.name' }
378 => 'somebogusstring' ],
379 [ { dbic_colname => 'test' }
380 => 'xxx' ],
e56b1c2d 381 ],
a54bd479 382 );
fb7cd45f 383
e56b1c2d 384 is ($owners->page(1)->all, 3, "$test_type: has_many prefetch returns correct number of rows");
385 is ($owners->page(1)->count, 3, "$test_type: has-many prefetch returns correct count");
a54bd479 386
e56b1c2d 387 is ($owners->page(3)->count, 2, "$test_type: has-many prefetch returns correct count");
388 TODO: {
389 local $TODO = "Top-limit does not work when your limit ends up past the resultset"
390 if $dialect eq 'Top';
391 is ($owners->page(3)->all, 2, "$test_type: has_many prefetch returns correct number of rows");
392 is ($owners->page(3)->count_rs->next, 2, "$test_type: has-many prefetch returns correct count_rs");
a54bd479 393 }
a54bd479 394
a54bd479 395
e56b1c2d 396 # try a ->belongs_to direction (no select collapse, group_by should work)
397 my $books = $schema->resultset ('BooksInLibrary')->search (
398 {
399 'owner.name' => [qw/wiggle woggle/],
400 },
401 {
402 distinct => 1,
403 having => \['1 = ?', [ test => 1 ] ], #test having propagation
404 prefetch => 'owner',
405 rows => 2, # 3 results total
86bb5a27 406 order_by => [{ -desc => 'me.owner' }, 'me.id'],
e56b1c2d 407 unsafe_subselect_ok => 1,
408 },
409 );
6de07ea3 410
e56b1c2d 411 ($sql, @bind) = @${$books->page(3)->as_query};
0e773352 412 is_same_bind (
e56b1c2d 413 \@bind,
414 [
415 # inner
0e773352 416 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'owner.name' }
417 => 'wiggle' ],
418 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'owner.name' }
419 => 'woggle' ],
420 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'source' }
421 => 'Library' ],
422 [ { dbic_colname => 'test' }
423 => '1' ],
424
fcb7fcbb 425 # rno(?)
426 $dialect ne 'Top' ? ( [ $OFFSET => 5 ], [ $TOTAL => 6 ] ) : (),
e56b1c2d 427 # outer
0e773352 428 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'owner.name' }
429 => 'wiggle' ],
430 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'owner.name' }
431 => 'woggle' ],
432 [ { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'source' }
433 => 'Library' ],
e56b1c2d 434 ],
435 );
4bb438ca 436
e56b1c2d 437 is ($books->page(1)->all, 2, "$test_type: Prefetched grouped search returns correct number of rows");
438 is ($books->page(1)->count, 2, "$test_type: Prefetched grouped search returns correct count");
a54bd479 439
e56b1c2d 440 is ($books->page(2)->count, 1, "$test_type: Prefetched grouped search returns correct count");
441 TODO: {
442 local $TODO = "Top-limit does not work when your limit ends up past the resultset"
443 if $dialect eq 'Top';
444 is ($books->page(2)->all, 1, "$test_type: Prefetched grouped search returns correct number of rows");
445 is ($books->page(2)->count_rs->next, 1, "$test_type: Prefetched grouped search returns correct count_rs");
446 }
447 }
a54bd479 448 }
6de07ea3 449
450
a54bd479 451# test GUID columns
e56b1c2d 452 {
453 $schema->storage->dbh_do (sub {
454 my ($storage, $dbh) = @_;
455 eval { $dbh->do("DROP TABLE artist_guid") };
456 $dbh->do(<<'SQL');
b1bdb76d 457CREATE TABLE artist_guid (
a54bd479 458 artistid UNIQUEIDENTIFIER NOT NULL,
459 name VARCHAR(100),
460 rank INT NOT NULL DEFAULT '13',
461 charfield CHAR(10) NULL,
462 a_guid UNIQUEIDENTIFIER,
463 primary key(artistid)
464)
465SQL
e56b1c2d 466 });
8ff60918 467
e56b1c2d 468 # start disconnected to make sure insert works on an un-reblessed storage
469 $schema = DBICTest::Schema->connect($dsn, $user, $pass, $opts);
8ff60918 470
e56b1c2d 471 my $row;
472 lives_ok {
473 $row = $schema->resultset('ArtistGUID')->create({ name => 'mtfnpy' })
474 } 'created a row with a GUID';
8ff60918 475
e56b1c2d 476 ok(
477 eval { $row->artistid },
478 'row has GUID PK col populated',
479 );
480 diag $@ if $@;
8ff60918 481
e56b1c2d 482 ok(
483 eval { $row->a_guid },
484 'row has a GUID col with auto_nextval populated',
485 );
486 diag $@ if $@;
8ff60918 487
e56b1c2d 488 my $row_from_db = $schema->resultset('ArtistGUID')
489 ->search({ name => 'mtfnpy' })->first;
f0bd60fc 490
e56b1c2d 491 is $row_from_db->artistid, $row->artistid,
492 'PK GUID round trip';
b9a2c3a5 493
e56b1c2d 494 is $row_from_db->a_guid, $row->a_guid,
495 'NON-PK GUID round trip';
496 }
02d133f0 497
a54bd479 498# test MONEY type
e56b1c2d 499 {
500 $schema->storage->dbh_do (sub {
501 my ($storage, $dbh) = @_;
502 eval { $dbh->do("DROP TABLE money_test") };
503 $dbh->do(<<'SQL');
a54bd479 504CREATE TABLE money_test (
505 id INT IDENTITY PRIMARY KEY,
506 amount MONEY NULL
507)
508SQL
e56b1c2d 509 });
9010bab8 510
9ffaf8a3 511 TODO: {
35af31a1 512 my $freetds_and_dynamic_cursors = 1
513 if $opts_name eq 'use_dynamic_cursors' &&
aca3b4c3 514 $schema->storage->_using_freetds;
35af31a1 515
8273e845 516 local $TODO =
9ffaf8a3 517'these tests fail on freetds with dynamic cursors for some reason'
35af31a1 518 if $freetds_and_dynamic_cursors;
519 local $ENV{DBIC_NULLABLE_KEY_NOWARN} = 1
520 if $freetds_and_dynamic_cursors;
56d2561e 521
9ffaf8a3 522 my $rs = $schema->resultset('Money');
523 my $row;
6bc666a5 524
9ffaf8a3 525 lives_ok {
526 $row = $rs->create({ amount => 100 });
527 } 'inserted a money value';
b9a2c3a5 528
9ffaf8a3 529 cmp_ok ((try { $rs->find($row->id)->amount })||0, '==', 100,
530 'money value round-trip');
fc85215b 531
9ffaf8a3 532 lives_ok {
533 $row->update({ amount => 200 });
534 } 'updated a money value';
b1e1d073 535
9ffaf8a3 536 cmp_ok ((try { $rs->find($row->id)->amount })||0, '==', 200,
537 'updated money value round-trip');
b1e1d073 538
9ffaf8a3 539 lives_ok {
540 $row->update({ amount => undef });
541 } 'updated a money value to NULL';
542
543 is try { $rs->find($row->id)->amount }, undef,
544 'updated money value to NULL round-trip';
545 }
e56b1c2d 546 }
547 }
384b8bce 548}
c1cac633 549
afcfff01 550done_testing;
551
c1cac633 552# clean up our mess
553END {
ca791b95 554 if (my $dbh = eval { $schema->storage->_dbh }) {
555 eval { $dbh->do("DROP TABLE $_") }
b1bdb76d 556 for qw/artist artist_guid money_test books owners/;
ca791b95 557 }
65d35121 558 undef $schema;
c1cac633 559}
fc85215b 560# vim:sw=2 sts=2