Revert 2c2bc4e5 - it is entirely superseded by cb551b07, 2baba3d9 and 83eef562
[dbsrgits/DBIx-Class.git] / t / 746sybase.t
CommitLineData
a964a928 1use strict;
68de9438 2use warnings;
d867eeda 3no warnings 'uninitialized';
a964a928 4
5use Test::More;
e0b2344f 6use Test::Exception;
199fbc45 7use DBIx::Class::Optional::Dependencies ();
a964a928 8use lib qw(t/lib);
9use DBICTest;
2baff5da 10
6d5679b2 11my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_SYBASE_${_}" } qw/DSN USER PASS/};
12if (not ($dsn && $user)) {
13 plan skip_all => join ' ',
14 'Set $ENV{DBICTEST_SYBASE_DSN}, _USER and _PASS to run this test.',
15 'Warning: This test drops and creates the tables:',
16 "'artist', 'money_test' and 'bindtype_test'",
17 ;
18};
19
199fbc45 20plan skip_all => 'Test needs ' . DBIx::Class::Optional::Dependencies->req_missing_for ('test_rdbms_ase')
21 unless DBIx::Class::Optional::Dependencies->req_ok_for ('test_rdbms_ase');
22
d867eeda 23my @storage_types = (
95787afe 24 'DBI::Sybase::ASE',
25 'DBI::Sybase::ASE::NoBindVars',
d867eeda 26);
5ce107ad 27eval "require DBIx::Class::Storage::$_;" for @storage_types;
95787afe 28
d867eeda 29my $schema;
30my $storage_idx = -1;
31
32sub get_schema {
33 DBICTest::Schema->connect($dsn, $user, $pass, {
34 on_connect_call => [
35 [ blob_setup => log_on_update => 1 ], # this is a safer option
36 ],
37 });
38}
39
40my $ping_count = 0;
41{
95787afe 42 my $ping = DBIx::Class::Storage::DBI::Sybase::ASE->can('_ping');
43 *DBIx::Class::Storage::DBI::Sybase::ASE::_ping = sub {
d867eeda 44 $ping_count++;
45 goto $ping;
46 };
47}
48
49for my $storage_type (@storage_types) {
50 $storage_idx++;
51
95787afe 52 unless ($storage_type eq 'DBI::Sybase::ASE') { # autodetect
d867eeda 53 DBICTest::Schema->storage_type("::$storage_type");
54 }
61cfaef7 55
d867eeda 56 $schema = get_schema();
a964a928 57
d867eeda 58 $schema->storage->ensure_connected;
a964a928 59
d867eeda 60 if ($storage_idx == 0 &&
95787afe 61 $schema->storage->isa('DBIx::Class::Storage::DBI::Sybase::ASE::NoBindVars')) {
6d5679b2 62 # no placeholders in this version of Sybase or DBD::Sybase (or using FreeTDS)
63 skip "Skipping entire test for $storage_type - no placeholder support", 1;
d867eeda 64 next;
65 }
5703eb14 66
d867eeda 67 isa_ok( $schema->storage, "DBIx::Class::Storage::$storage_type" );
6b1f5ef7 68
d867eeda 69 $schema->storage->_dbh->disconnect;
70 lives_ok (sub { $schema->storage->dbh }, 'reconnect works');
64f4e691 71
d867eeda 72 $schema->storage->dbh_do (sub {
73 my ($storage, $dbh) = @_;
74 eval { $dbh->do("DROP TABLE artist") };
75 $dbh->do(<<'SQL');
a964a928 76CREATE TABLE artist (
d867eeda 77 artistid INT IDENTITY PRIMARY KEY,
a964a928 78 name VARCHAR(100),
79 rank INT DEFAULT 13 NOT NULL,
d867eeda 80 charfield CHAR(10) NULL
a964a928 81)
26283ee3 82SQL
d867eeda 83 });
a964a928 84
d867eeda 85 my %seen_id;
a964a928 86
d867eeda 87# so we start unconnected
88 $schema->storage->disconnect;
fcc2ec11 89
26283ee3 90# test primary key handling
d867eeda 91 my $new = $schema->resultset('Artist')->create({ name => 'foo' });
044f5b3e 92 like $new->artistid, qr/^\d+\z/, 'Auto-PK returned a number';
d867eeda 93 ok($new->artistid > 0, "Auto-PK worked");
fcc2ec11 94
d867eeda 95 $seen_id{$new->artistid}++;
fcc2ec11 96
d867eeda 97# check redispatch to storage-specific insert when auto-detected storage
95787afe 98 if ($storage_type eq 'DBI::Sybase::ASE') {
d867eeda 99 DBICTest::Schema->storage_type('::DBI');
100 $schema = get_schema();
101 }
102
103 $new = $schema->resultset('Artist')->create({ name => 'Artist 1' });
104 is ( $seen_id{$new->artistid}, undef, 'id for Artist 1 is unique' );
105 $seen_id{$new->artistid}++;
106
107# inserts happen in a txn, so we make sure it still works inside a txn too
108 $schema->txn_begin;
109
110 for (2..6) {
a964a928 111 $new = $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
112 is ( $seen_id{$new->artistid}, undef, "id for Artist $_ is unique" );
113 $seen_id{$new->artistid}++;
d867eeda 114 }
a964a928 115
d867eeda 116 $schema->txn_commit;
a964a928 117
d867eeda 118# test simple count
119 is ($schema->resultset('Artist')->count, 7, 'count(*) of whole table ok');
120
121# test LIMIT support
122 my $it = $schema->resultset('Artist')->search({
123 artistid => { '>' => 0 }
124 }, {
a0348159 125 rows => 3,
a0348159 126 order_by => 'artistid',
d867eeda 127 });
af9e4a5e 128
d867eeda 129 is( $it->count, 3, "LIMIT count ok" );
e19677ad 130
d867eeda 131 is( $it->next->name, "foo", "iterator->next ok" );
132 $it->next;
133 is( $it->next->name, "Artist 2", "iterator->next ok" );
134 is( $it->next, undef, "next past end of resultset ok" );
135
136# now try with offset
137 $it = $schema->resultset('Artist')->search({}, {
138 rows => 3,
139 offset => 3,
140 order_by => 'artistid',
141 });
142
143 is( $it->count, 3, "LIMIT with offset count ok" );
144
145 is( $it->next->name, "Artist 3", "iterator->next ok" );
146 $it->next;
147 is( $it->next->name, "Artist 5", "iterator->next ok" );
148 is( $it->next, undef, "next past end of resultset ok" );
149
150# now try a grouped count
151 $schema->resultset('Artist')->create({ name => 'Artist 6' })
152 for (1..6);
153
154 $it = $schema->resultset('Artist')->search({}, {
155 group_by => 'name'
156 });
157
158 is( $it->count, 7, 'COUNT of GROUP_BY ok' );
159
2baff5da 160# do an IDENTITY_INSERT
d867eeda 161 {
162 no warnings 'redefine';
163
164 my @debug_out;
165 local $schema->storage->{debug} = 1;
166 local $schema->storage->debugobj->{callback} = sub {
167 push @debug_out, $_[1];
168 };
169
170 my $txn_used = 0;
171 my $txn_commit = \&DBIx::Class::Storage::DBI::txn_commit;
172 local *DBIx::Class::Storage::DBI::txn_commit = sub {
173 $txn_used = 1;
174 goto &$txn_commit;
175 };
176
177 $schema->resultset('Artist')
178 ->create({ artistid => 999, name => 'mtfnpy' });
179
2baff5da 180 ok((grep /IDENTITY_INSERT/i, @debug_out), 'IDENTITY_INSERT used');
d867eeda 181
182 SKIP: {
183 skip 'not testing lack of txn on IDENTITY_INSERT with NoBindVars', 1
184 if $storage_type =~ /NoBindVars/i;
185
186 is $txn_used, 0, 'no txn on insert with IDENTITY_INSERT';
187 }
188 }
189
2baff5da 190# do an IDENTITY_UPDATE
191 {
192 my @debug_out;
193 local $schema->storage->{debug} = 1;
194 local $schema->storage->debugobj->{callback} = sub {
195 push @debug_out, $_[1];
196 };
197
198 lives_and {
199 $schema->resultset('Artist')
200 ->find(999)->update({ artistid => 555 });
201 ok((grep /IDENTITY_UPDATE/i, @debug_out));
202 } 'IDENTITY_UPDATE used';
203 $ping_count-- if $@;
204 }
d867eeda 205
206 my $bulk_rs = $schema->resultset('Artist')->search({
207 name => { -like => 'bulk artist %' }
208 });
209
2a6dda4b 210# test _insert_bulk using populate.
2baff5da 211 SKIP: {
2a6dda4b 212 skip '_insert_bulk not supported', 4
d390bd3c 213 unless $storage_type !~ /NoBindVars/i;
e06ad5d5 214
2baff5da 215 lives_ok {
216 $schema->resultset('Artist')->populate([
217 {
218 name => 'bulk artist 1',
219 charfield => 'foo',
220 },
221 {
222 name => 'bulk artist 2',
223 charfield => 'foo',
224 },
225 {
226 name => 'bulk artist 3',
227 charfield => 'foo',
228 },
229 ]);
2a6dda4b 230 } '_insert_bulk via populate';
2baff5da 231
2a6dda4b 232 is $bulk_rs->count, 3, 'correct number inserted via _insert_bulk';
2baff5da 233
234 is ((grep $_->charfield eq 'foo', $bulk_rs->all), 3,
2a6dda4b 235 'column set correctly via _insert_bulk');
2baff5da 236
237 my %bulk_ids;
238 @bulk_ids{map $_->artistid, $bulk_rs->all} = ();
239
240 is ((scalar keys %bulk_ids), 3,
2a6dda4b 241 'identities generated correctly in _insert_bulk');
2baff5da 242
243 $bulk_rs->delete;
244 }
e06ad5d5 245
2a6dda4b 246# make sure _insert_bulk works a second time on the same connection
0a9a9955 247 SKIP: {
2a6dda4b 248 skip '_insert_bulk not supported', 3
d390bd3c 249 unless $storage_type !~ /NoBindVars/i;
0a9a9955 250
251 lives_ok {
252 $schema->resultset('Artist')->populate([
253 {
254 name => 'bulk artist 1',
255 charfield => 'bar',
256 },
257 {
258 name => 'bulk artist 2',
259 charfield => 'bar',
260 },
261 {
262 name => 'bulk artist 3',
263 charfield => 'bar',
264 },
265 ]);
2a6dda4b 266 } '_insert_bulk via populate called a second time';
0a9a9955 267
268 is $bulk_rs->count, 3,
2a6dda4b 269 'correct number inserted via _insert_bulk';
0a9a9955 270
271 is ((grep $_->charfield eq 'bar', $bulk_rs->all), 3,
2a6dda4b 272 'column set correctly via _insert_bulk');
0a9a9955 273
274 $bulk_rs->delete;
275 }
276
2a6dda4b 277# test invalid _insert_bulk (missing required column)
0a9a9955 278#
2a6dda4b 279# There should be a rollback, reconnect and the next valid _insert_bulk should
0a9a9955 280# succeed.
281 throws_ok {
282 $schema->resultset('Artist')->populate([
283 {
284 charfield => 'foo',
285 }
286 ]);
287 } qr/no value or default|does not allow null|placeholders/i,
288# The second pattern is the error from fallback to regular array insert on
289# incompatible charset.
290# The third is for ::NoBindVars with no syb_has_blk.
2a6dda4b 291 '_insert_bulk with missing required column throws error';
0a9a9955 292
2a6dda4b 293# now test _insert_bulk with IDENTITY_INSERT
2baff5da 294 SKIP: {
2a6dda4b 295 skip '_insert_bulk not supported', 3
d390bd3c 296 unless $storage_type !~ /NoBindVars/i;
e19677ad 297
2baff5da 298 lives_ok {
299 $schema->resultset('Artist')->populate([
300 {
301 artistid => 2001,
302 name => 'bulk artist 1',
303 charfield => 'foo',
304 },
305 {
306 artistid => 2002,
307 name => 'bulk artist 2',
308 charfield => 'foo',
309 },
310 {
311 artistid => 2003,
312 name => 'bulk artist 3',
313 charfield => 'foo',
314 },
315 ]);
2a6dda4b 316 } '_insert_bulk with IDENTITY_INSERT via populate';
2baff5da 317
318 is $bulk_rs->count, 3,
2a6dda4b 319 'correct number inserted via _insert_bulk with IDENTITY_INSERT';
2baff5da 320
321 is ((grep $_->charfield eq 'foo', $bulk_rs->all), 3,
2a6dda4b 322 'column set correctly via _insert_bulk with IDENTITY_INSERT');
2baff5da 323
324 $bulk_rs->delete;
325 }
d867eeda 326
327# test correlated subquery
328 my $subq = $schema->resultset('Artist')->search({ artistid => { '>' => 3 } })
329 ->get_column('artistid')
330 ->as_query;
331 my $subq_rs = $schema->resultset('Artist')->search({
332 artistid => { -in => $subq }
333 });
334 is $subq_rs->count, 11, 'correlated subquery';
335
336# mostly stolen from the blob stuff Nniuq wrote for t/73oracle.t
337 SKIP: {
587daa97 338 skip 'TEXT/IMAGE support does not work with FreeTDS', 22
aca3b4c3 339 if $schema->storage->_using_freetds;
d867eeda 340
341 my $dbh = $schema->storage->_dbh;
342 {
343 local $SIG{__WARN__} = sub {};
344 eval { $dbh->do('DROP TABLE bindtype_test') };
345
346 $dbh->do(qq[
8273e845 347 CREATE TABLE bindtype_test
d867eeda 348 (
f3a9ea3d 349 id INT IDENTITY PRIMARY KEY,
350 bytea IMAGE NULL,
351 blob IMAGE NULL,
74b5397c 352 blob2 IMAGE NULL,
f3a9ea3d 353 clob TEXT NULL,
74b5397c 354 clob2 TEXT NULL,
f3a9ea3d 355 a_memo IMAGE NULL
d867eeda 356 )
357 ],{ RaiseError => 1, PrintError => 0 });
358 }
359
360 my %binstr = ( 'small' => join('', map { chr($_) } ( 1 .. 127 )) );
361 $binstr{'large'} = $binstr{'small'} x 1024;
362
363 my $maxloblen = length $binstr{'large'};
8273e845 364
aca3b4c3 365 if (not $schema->storage->_using_freetds) {
d867eeda 366 $dbh->{'LongReadLen'} = $maxloblen * 2;
367 } else {
368 $dbh->do("set textsize ".($maxloblen * 2));
369 }
370
371 my $rs = $schema->resultset('BindType');
372 my $last_id;
373
374 foreach my $type (qw(blob clob)) {
375 foreach my $size (qw(small large)) {
376 no warnings 'uninitialized';
377
0a9a9955 378 my $created;
379 lives_ok {
380 $created = $rs->create( { $type => $binstr{$size} } )
381 } "inserted $size $type without dying";
d867eeda 382
383 $last_id = $created->id if $created;
384
0a9a9955 385 lives_and {
386 ok($rs->find($last_id)->$type eq $binstr{$size})
387 } "verified inserted $size $type";
d867eeda 388 }
389 }
390
689819e1 391 $rs->delete;
392
0a9a9955 393 # blob insert with explicit PK
394 # also a good opportunity to test IDENTITY_INSERT
395 lives_ok {
396 $rs->create( { id => 1, blob => $binstr{large} } )
397 } 'inserted large blob without dying with manual PK';
d867eeda 398
0a9a9955 399 lives_and {
400 ok($rs->find(1)->blob eq $binstr{large})
401 } 'verified inserted large blob with manual PK';
d867eeda 402
403 # try a blob update
404 my $new_str = $binstr{large} . 'mtfnpy';
405
406 # check redispatch to storage-specific update when auto-detected storage
95787afe 407 if ($storage_type eq 'DBI::Sybase::ASE') {
d867eeda 408 DBICTest::Schema->storage_type('::DBI');
409 $schema = get_schema();
410 }
411
0a9a9955 412 lives_ok {
413 $rs->search({ id => 1 })->update({ blob => $new_str })
414 } 'updated blob successfully';
415
416 lives_and {
417 ok($rs->find(1)->blob eq $new_str)
418 } 'verified updated blob';
d867eeda 419
2baff5da 420 # try a blob update with IDENTITY_UPDATE
421 lives_and {
422 $new_str = $binstr{large} . 'hlagh';
423 $rs->find(1)->update({ id => 999, blob => $new_str });
424 ok($rs->find(999)->blob eq $new_str);
425 } 'verified updated blob with IDENTITY_UPDATE';
7ef97d26 426
d867eeda 427 ## try multi-row blob update
428 # first insert some blobs
d867eeda 429 $new_str = $binstr{large} . 'foo';
0a9a9955 430 lives_and {
431 $rs->delete;
432 $rs->create({ blob => $binstr{large} }) for (1..2);
433 $rs->update({ blob => $new_str });
434 is((grep $_->blob eq $new_str, $rs->all), 2);
435 } 'multi-row blob update';
436
437 $rs->delete;
438
2a6dda4b 439 # now try _insert_bulk with blobs and only blobs
0a9a9955 440 $new_str = $binstr{large} . 'bar';
441 lives_ok {
442 $rs->populate([
443 {
0a9a9955 444 blob => $binstr{large},
445 clob => $new_str,
446 },
447 {
0a9a9955 448 blob => $binstr{large},
449 clob => $new_str,
450 },
451 ]);
2a6dda4b 452 } '_insert_bulk with blobs does not die';
0a9a9955 453
454 is((grep $_->blob eq $binstr{large}, $rs->all), 2,
2a6dda4b 455 'IMAGE column set correctly via _insert_bulk');
0a9a9955 456
457 is((grep $_->clob eq $new_str, $rs->all), 2,
2a6dda4b 458 'TEXT column set correctly via _insert_bulk');
2baff5da 459
2a6dda4b 460 # now try _insert_bulk with blobs and a non-blob which also happens to be an
587daa97 461 # identity column
462 SKIP: {
2a6dda4b 463 skip 'no _insert_bulk without placeholders', 4
587daa97 464 if $storage_type =~ /NoBindVars/i;
465
466 $rs->delete;
467 $new_str = $binstr{large} . 'bar';
468 lives_ok {
469 $rs->populate([
470 {
471 id => 1,
472 bytea => 1,
473 blob => $binstr{large},
474 clob => $new_str,
f3a9ea3d 475 a_memo => 2,
587daa97 476 },
477 {
478 id => 2,
479 bytea => 1,
480 blob => $binstr{large},
481 clob => $new_str,
f3a9ea3d 482 a_memo => 2,
587daa97 483 },
484 ]);
2a6dda4b 485 } '_insert_bulk with blobs and explicit identity does NOT die';
587daa97 486
487 is((grep $_->blob eq $binstr{large}, $rs->all), 2,
2a6dda4b 488 'IMAGE column set correctly via _insert_bulk with identity');
587daa97 489
490 is((grep $_->clob eq $new_str, $rs->all), 2,
2a6dda4b 491 'TEXT column set correctly via _insert_bulk with identity');
587daa97 492
493 is_deeply [ map $_->id, $rs->all ], [ 1,2 ],
2a6dda4b 494 'explicit identities set correctly via _insert_bulk with blobs';
587daa97 495 }
496
d390bd3c 497 lives_and {
498 $rs->delete;
499 $rs->create({ blob => $binstr{large} }) for (1..2);
500 $rs->update({ blob => undef });
501 is((grep !defined($_->blob), $rs->all), 2);
502 } 'blob update to NULL';
d867eeda 503 }
504
cd048330 505# test MONEY column support (and some other misc. stuff)
d867eeda 506 $schema->storage->dbh_do (sub {
507 my ($storage, $dbh) = @_;
508 eval { $dbh->do("DROP TABLE money_test") };
509 $dbh->do(<<'SQL');
510CREATE TABLE money_test (
511 id INT IDENTITY PRIMARY KEY,
cd048330 512 amount MONEY DEFAULT $999.99 NULL
d867eeda 513)
514SQL
515 });
516
cd048330 517 my $rs = $schema->resultset('Money');
518
519# test insert with defaults
520 lives_and {
521 $rs->create({});
522 is((grep $_->amount == 999.99, $rs->all), 1);
523 } 'insert with all defaults works';
524 $rs->delete;
525
d867eeda 526# test insert transaction when there's an active cursor
2baff5da 527 {
d867eeda 528 my $artist_rs = $schema->resultset('Artist');
529 $artist_rs->first;
530 lives_ok {
531 my $row = $schema->resultset('Money')->create({ amount => 100 });
532 $row->delete;
533 } 'inserted a row with an active cursor';
534 $ping_count-- if $@; # dbh_do calls ->connected
535 }
536
537# test insert in an outer transaction when there's an active cursor
4ca1fd6f 538 {
d867eeda 539 local $TODO = 'this should work once we have eager cursors';
540
541# clear state, or we get a deadlock on $row->delete
542# XXX figure out why this happens
543 $schema->storage->disconnect;
544
545 lives_ok {
546 $schema->txn_do(sub {
547 my $artist_rs = $schema->resultset('Artist');
548 $artist_rs->first;
549 my $row = $schema->resultset('Money')->create({ amount => 100 });
550 $row->delete;
551 });
552 } 'inserted a row with an active cursor in outer txn';
553 $ping_count-- if $@; # dbh_do calls ->connected
554 }
555
556# Now test money values.
d867eeda 557 my $row;
558 lives_ok {
559 $row = $rs->create({ amount => 100 });
560 } 'inserted a money value';
561
cdf7f026 562 cmp_ok eval { $rs->find($row->id)->amount }, '==', 100,
563 'money value round-trip';
d867eeda 564
565 lives_ok {
566 $row->update({ amount => 200 });
567 } 'updated a money value';
568
cdf7f026 569 cmp_ok eval { $rs->find($row->id)->amount }, '==', 200,
570 'updated money value round-trip';
d867eeda 571
572 lives_ok {
573 $row->update({ amount => undef });
574 } 'updated a money value to NULL';
575
46891041 576 lives_and {
ca507a2f 577 my $null_amount = $rs->find($row->id)->amount;
46891041 578 is $null_amount, undef;
579 } 'updated money value to NULL round-trip';
6469dabf 580
581# Test computed columns and timestamps
582 $schema->storage->dbh_do (sub {
583 my ($storage, $dbh) = @_;
584 eval { $dbh->do("DROP TABLE computed_column_test") };
585 $dbh->do(<<'SQL');
586CREATE TABLE computed_column_test (
587 id INT IDENTITY PRIMARY KEY,
588 a_computed_column AS getdate(),
589 a_timestamp timestamp,
8273e845 590 charfield VARCHAR(20) DEFAULT 'foo'
6469dabf 591)
592SQL
593 });
594
595 require DBICTest::Schema::ComputedColumn;
596 $schema->register_class(
597 ComputedColumn => 'DBICTest::Schema::ComputedColumn'
598 );
599
600 ok (($rs = $schema->resultset('ComputedColumn')),
601 'got rs for ComputedColumn');
602
603 lives_ok { $row = $rs->create({}) }
604 'empty insert for a table with computed columns survived';
605
606 lives_ok {
607 $row->update({ charfield => 'bar' })
608 } 'update of a table with computed columns survived';
d867eeda 609}
610
611is $ping_count, 0, 'no pings';
f1358489 612
72933b15 613# if tests passed and did so under a non-C lang - let's rerun the test
614if (Test::Builder->new->is_passing and $ENV{LANG} and $ENV{LANG} ne 'C') {
615 my $oldlang = $ENV{LANG};
616 local $ENV{LANG} = 'C';
617
618 pass ("Your lang is set to $oldlang - retesting with C");
619
f3ec358e 620 local $ENV{PATH};
621 my @cmd = map { $_ =~ /(.+)/ } ($^X, __FILE__);
72933b15 622
623 # this is cheating, and may even hang here and there (testing on windows passed fine)
624 # will be replaced with Test::SubExec::Noninteractive in due course
625 require IPC::Open2;
626 IPC::Open2::open2(my $out, undef, @cmd);
627 while (my $ln = <$out>) {
628 print " $ln";
629 }
630
631 wait;
632 ok (! $?, "Wstat $? from: @cmd");
633}
634
6d5679b2 635done_testing;
636
a964a928 637# clean up our mess
638END {
d867eeda 639 if (my $dbh = eval { $schema->storage->_dbh }) {
640 eval { $dbh->do("DROP TABLE $_") }
6469dabf 641 for qw/artist bindtype_test money_test computed_column_test/;
d867eeda 642 }
65d35121 643
644 undef $schema;
a964a928 645}