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