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