#!/usr/bin/perl -w -I./t # $Id: 20SqlServer.t 15564 2013-01-25 09:43:46Z mjevans $ use Test::More; use strict; $| = 1; my $has_test_nowarnings = 1; eval "require Test::NoWarnings"; $has_test_nowarnings = undef if $@; my $tests = 64; $tests += 1 if $has_test_nowarnings; plan tests => $tests; my $dbh; # use_ok('DBI', qw(:sql_types)); # can't seem to get the imports right this way use DBI qw(:sql_types); use_ok('ODBCTEST'); #use_ok('Data::Dumper'); BEGIN { plan skip_all => "DBI_DSN is undefined" if (!defined $ENV{DBI_DSN}); } END { if ($dbh) { local $dbh->{PrintError} = 0; local $dbh->{PrintWarn} = 0; eval { $dbh->do(q/drop procedure PERL_DBD_PROC1/); $dbh->do(q/drop procedure PERL_DBD_PROC2/); }; } Test::NoWarnings::had_no_warnings() if ($has_test_nowarnings); } my $dbms_name; my $dbms_version; my $m_dbmsversion; my $driver_name; sub getinfo { my $dbh = shift; $dbms_name = $dbh->get_info(17); ok($dbms_name, "got DBMS name: $dbms_name"); $dbms_version = $dbh->get_info(18); ok($dbms_version, "got DBMS version: $dbms_version"); $m_dbmsversion = $dbms_version; $m_dbmsversion =~ s/^(\d+).*/$1/; ok($m_dbmsversion, "got DBMS major version: $m_dbmsversion"); $driver_name = $dbh->get_info(6); ok($driver_name, "got Driver Name: $driver_name"); } sub varmax_test { my ($dbh, $coltype) = @_; SKIP: { skip "SQL Server major version $m_dbmsversion too old", 4 if $m_dbmsversion < 9; my $data = 'x' x 1000; my $datalen = length($data); local $dbh->{PrintError} = 0; local $dbh->{RaiseError} = 1; local $dbh->{LongReadLen} = length($data) * 2; eval {$dbh->do(q/drop table PERL_DBD_TABLE1/);}; eval { $dbh->do(qq/create table PERL_DBD_TABLE1 (a int identity, b $coltype(MAX))/); # workaround freeTDS problem: if ($driver_name =~ /tdsodbc/) { $dbh->do( qq/insert into PERL_DBD_TABLE1 (b) values(CAST(? AS $coltype(MAX)))/, undef, $data); } else { $dbh->do(q/insert into PERL_DBD_TABLE1 (b) values(?)/, undef, $data); } }; diag($@) if $@; ok(!$@, "create PERL_DBD_TABLE1 and insert test data"); SKIP: { skip "failed to create test table or insert data", 3 if $@; my $sth = $dbh->prepare(q/select a,b from PERL_DBD_TABLE1/); $sth->execute; my ($a, $b); eval { ($a, $b) = $sth->fetchrow_array; }; diag($@) if $@; ok(!$@, "fetchrow for $coltype(max)"); SKIP: { skip "fetchrow failed", 2 if $@; ok($b, "data received from $coltype(max)"); is(length($b), $datalen, 'all data (' . length($b) . ") received from $coltype(max)"); }; }; }; eval { local $dbh->{PrintError} = 0; local $dbh->{RaiseError} = 0; $dbh->do(q/drop table PERL_DBD_TABLE1/); }; } sub _do_proc { my ($dbh, $proc) = @_; my $sth; eval {$sth = $dbh->prepare($proc, {odbc_exec_direct => 1})}; my $ev = $@; diag($ev) if $ev; ok(!$ev, "prepare for $proc"); SKIP: { skip "prepare for $proc failed", 3 if $ev; SKIP: { eval {$sth->execute}; $ev = $@; diag($ev) if $ev; ok(!$ev, "execute for $proc"); SKIP: { skip "execute for $proc failed", 2 if $ev; my $fields; eval {$fields = $sth->{NUM_OF_FIELDS}}; $ev = $@; diag($ev) if $ev; ok(!$ev, "NUM_OF_FIELDS for $proc"); like($fields, qr|^\d+$|, "numeric fields"); }; $sth->finish; }; }; } sub procs_with_no_results { my $dbh = shift; local $dbh->{PrintError} = 0; eval {$dbh->do(q/drop procedure PERL_DBD_PROC1/)}; eval {$dbh->do(q/drop procedure PERL_DBD_PROC2/)}; my $proc1 = <do($proc1)}; my $ev = $@; diag($ev) if $ev; ok(!$ev, 'create perl_dbd_proc1 procedure'); SKIP: { skip 'failed to create perl_dbd_proc1 procedure', 9 if $ev; SKIP: { eval {$dbh->do($proc2)}; $ev = $@; diag($ev) if $ev; ok(!$ev, 'create perl_dbd_proc2 procedure'); SKIP: { skip 'failed to create perl_dbd_proc2 procedure', 8 if $ev; _do_proc($dbh, 'PERL_DBD_PROC1'); _do_proc($dbh, 'PERL_DBD_PROC2'); }; }; }; } sub Multiple_concurrent_stmts { my ($dbh, $expect) = @_; my $sth = $dbh->prepare("select * from PERL_DBD_TABLE1"); $dbh->{RaiseError} = 1; $sth->execute; my @row; eval { while (@row = $sth->fetchrow_array()) { my $sth2 = $dbh->prepare("select * from $ODBCTEST::table_name"); $sth2->execute; my @row2; while (@row2 = $sth2->fetchrow_array()) { } } }; if ($@) { diag($@) if (defined($expect) && ($expect == 1)); return 0; } diag("Expected fail of MARS and it worked!") if (defined($expect) && ($expect == 0)); return 1; } $dbh = DBI->connect(); unless($dbh) { BAIL_OUT("Unable to connect to the database $DBI::errstr\nTests skipped.\n"); exit 0; } my $sth; my $dbname = $dbh->get_info(17); # DBI::SQL_DBMS_NAME SKIP: { skip "Microsoft SQL Server tests not supported using $dbname", 63 unless ($dbname =~ /Microsoft SQL Server/i); getinfo($dbh); varmax_test($dbh, 'varchar'); varmax_test($dbh, 'varbinary'); varmax_test($dbh, 'nvarchar'); procs_with_no_results($dbh); # the times chosen below are VERY specific to NOT cause rounding errors, # but may cause different errors on different versions of SQL Server. my @data = ( [undef, "z" x 13 ], ["2001-01-01 01:01:01.110", "a" x 12], # "aaaaaaaaaaaa" ["2002-02-02 02:02:02.123", "b" x 114], ["2003-03-03 03:03:03.333", "c" x 251], ["2004-04-04 04:04:04.443", "d" x 282], ["2005-05-05 05:05:05.557", "e" x 131] ); eval { local $dbh->{PrintError} = 0; $dbh->do("DROP TABLE PERL_DBD_TABLE1"); }; $dbh->{RaiseError} = 1; $dbh->{LongReadLen} = 800; my @types = (SQL_TYPE_TIMESTAMP, SQL_TIMESTAMP); my $row = $dbh->type_info(\@types); BAIL_OUT("Unable to find a suitable test type for date field") if !$row; my $datetype = $row->{TYPE_NAME}; $dbh->do("CREATE TABLE PERL_DBD_TABLE1 (i INTEGER, time $datetype, str VARCHAR(4000))"); # Insert records into the database: my $sth1 = $dbh->prepare("INSERT INTO PERL_DBD_TABLE1 (i,time,str) values (?,?,?)"); for (my $i=0; $i<@data; $i++) { my ($time,$str) = @{$data[$i]}; # print "Inserting: $i, "; # print $time if (defined($time)); # print " string length " . length($str) . "\n"; $sth1->bind_param (1, $i, SQL_INTEGER); $sth1->bind_param (2, $time, SQL_TIMESTAMP); $sth1->bind_param (3, $str, SQL_LONGVARCHAR); $sth1->execute or die ($DBI::errstr); } # Retrieve records from the database, and see if they match original data: my $sth2 = $dbh->prepare("SELECT i,time,str FROM PERL_DBD_TABLE1"); $sth2->execute or die ($DBI::errstr); my $iErrCount = 0; while (my ($i,$time,$str) = $sth2->fetchrow_array()) { if (defined($time)) { $time =~ s/0000$//o; } if ((defined($time) && $time ne $data[$i][0]) || defined($time) != defined($data[$i][0])) { diag("Retrieving: $i, $time string length: " . length($str) . "\t!time "); $iErrCount++; } if ($str ne $data[$i][1]) { diag("Retrieving: $i, $time string length: " . length($str) . "\t!string "); $iErrCount++; } # print "\n"; } is($iErrCount, 0, "errors on data comparison"); eval { local $dbh->{RaiseError} = 0; $dbh->do("DROP TABLE PERL_DBD_TABLE1"); }; my $sql = 'CREATE TABLE #PERL_DBD_TABLE1 (id INT PRIMARY KEY, val VARCHAR(4))'; $dbh->do($sql); # doesn't work with prepare, etc...hmmm why not? # $sth = $dbh->prepare($sql); # $sth->execute; # $sth->finish; # See http://technet.microsoft.com/en-US/library/ms131667.aspx # which says # "Prepared statements cannot be used to create temporary objects on SQL # Server 2000 or later..." # $sth = $dbh->prepare("INSERT INTO #PERL_DBD_TABLE1 (id, val) VALUES (?, ?)"); $sth2 = $dbh->prepare("INSERT INTO #PERL_DBD_TABLE1 (id, val) VALUES (?, ?)"); my @data2 = (undef, 'foo', 'bar', 'blet', undef); my $i = 0; my $val; foreach $val (@data2) { $sth2->execute($i++, $val); } $i = 0; $sth = $dbh->prepare("Select id, val from #PERL_DBD_TABLE1"); $sth->execute; $iErrCount = 0; while (my @row = $sth->fetchrow_array) { unless ((!defined($row[1]) && !defined($data2[$i])) || ($row[1] eq $data2[$i])) { $iErrCount++ ; print "$row[1] ne $data2[$i]\n"; } $i++; } is($iErrCount, 0, "temporary table handling"); diag("Please upgrade your ODBC drivers to the latest SQL Server drivers available. For example, 2000.80.194.00 is known to be problematic. Use MDAC 2.7, if possible\n") if ($iErrCount != 0); $dbh->{PrintError} = 0; eval {$dbh->do("DROP TABLE PERL_DBD_TABLE1");}; eval {$dbh->do("CREATE TABLE PERL_DBD_TABLE1 (i INTEGER)");}; eval {$dbh->do("DROP PROCEDURE PERL_DBD_PROC1");}; eval {$dbh->do("CREATE PROCEDURE PERL_DBD_PROC1 \@inputval int AS ". "INSERT INTO PERL_DBD_TABLE1 VALUES (\@inputval); " . " return \@inputval;");}; $sth1 = $dbh->prepare ("{? = call PERL_DBD_PROC1(?) }"); my $output = undef; $i = 1; $iErrCount = 0; while ($i < 4) { $sth1->bind_param_inout(1, \$output, 50, DBI::SQL_INTEGER); $sth1->bind_param(2, $i, DBI::SQL_INTEGER); $sth1->execute(); # print "$output"; if (!defined($output) || ($output !~ /\d+/) || ($output != $i)) { $iErrCount++; diag("output='$output' error, expected $i\n"); } # print "\n"; $i++; } is($iErrCount, 0, "bind param in out with insert result set"); $iErrCount = 0; eval {$dbh->do("DROP PROCEDURE PERL_DBD_PROC1");}; my $proc1 = "CREATE PROCEDURE PERL_DBD_PROC1 (\@i int, \@result int OUTPUT) AS ". "BEGIN ". " SET \@result = \@i+1;". "END "; # print "$proc1\n"; $dbh->do($proc1); # $dbh->{PrintError} = 1; $sth1 = $dbh->prepare ("{call PERL_DBD_PROC1(?, ?)}"); $i = 12; $output = undef; $sth1->bind_param(1, $i, DBI::SQL_INTEGER); $sth1->bind_param_inout(2, \$output, 100, DBI::SQL_INTEGER); $sth1->execute; is($i, $output-1, "test output params accurate"); $iErrCount = 0; $sth = $dbh->prepare("select * from PERL_DBD_TABLE1 order by i"); $sth->execute; $i = 1; while (my @row = $sth->fetchrow_array) { if ($i != $row[0]) { diag(join(', ', @row), " ERROR!\n"); $iErrCount++; } $i++; } is($iErrCount, 0, "verify select data"); eval {$dbh->do("DROP TABLE PERL_DBD_TABLE1");}; eval {$dbh->do("CREATE TABLE PERL_DBD_TABLE1 (d DATETIME)");}; $sth = $dbh->prepare ("INSERT INTO PERL_DBD_TABLE1 (d) VALUES (?)"); $sth->bind_param (1, undef, SQL_TYPE_TIMESTAMP); $sth->execute(); $sth->bind_param (1, "2002-07-12 05:08:37.350", SQL_TYPE_TIMESTAMP); $sth->execute(); $sth->bind_param (1, undef, SQL_TYPE_TIMESTAMP); $sth->execute(); $iErrCount = 0; $sth2 = $dbh->prepare("select * from PERL_DBD_TABLE1 where d is not null"); $sth2->execute; while (my @row = $sth2->fetchrow_array) { if ($row[0] ne "2002-07-12 05:08:37.350") { $iErrCount++ ; diag(join(", ", @row), "\n"); } } is($iErrCount, 0, "timestamp handling"); eval {$dbh->do('DROP TABLE PERL_DBD_TABLE1');}; eval {$dbh->do('DROP PROCEDURE PERL_DBD_PROC1');}; eval {$dbh->do('CREATE TABLE PERL_DBD_TABLE1 (i INTEGER, j integer)')} or diag($@); $proc1 = <{RaiseError} = 0; eval {$dbh->do($proc1)} or diag($@); my $sth = $dbh->prepare ('{call PERL_DBD_PROC1 (?)}'); my $success = -1; $sth->bind_param (1, 99, SQL_INTEGER); $sth->execute(); $success = -1; while (my @data = $sth->fetchrow_array()) {($success) = @data;} is($success, 100, 'procedure outputs results as result set'); $sth->bind_param (1, 10, SQL_INTEGER); $sth->execute(); $success = -1; while (my @data = $sth->fetchrow_array()) {($success) = @data;} is($success,10, 'procedure outputs results as result set2'); $sth->bind_param (1, 111, SQL_INTEGER); $sth->execute(); $success = -1; do { my @data; while (@data = $sth->fetchrow_array()) { if ($#data == 0) { ($success) = @data; } } } while ($sth->{odbc_more_results}); is($success, 111, 'procedure outputs results as result set 3'); # # special tests for even stranger cases... # eval {$dbh->do("DROP PROCEDURE PERL_DBD_PROC1");}; $proc1 = < 100) BEGIN INSERT INTO PERL_DBD_TABLE1 (i, j) VALUES (\@i, \@i); SELECT i, j from PERL_DBD_TABLE1; END; SELECT \@result; END EOT eval {$dbh->do($proc1);}; # set the required attribute and check it. $dbh->{odbc_force_rebind} = 1; is($dbh->{odbc_force_rebind}, 1, "setting force_rebind"); $dbh->{odbc_force_rebind} = 0; is($dbh->{odbc_force_rebind}, 0, "resetting force_rebind"); $sth = $dbh->prepare ("{call PERL_DBD_PROC1 (?)}"); is($sth->{odbc_force_rebind}, 0, "testing force rebind after procedure call"); $success = -1; $sth->bind_param (1, 99, SQL_INTEGER); $sth->execute(); $success = -1; while (my @data = $sth->fetchrow_array()) {($success) = @data;} is($success, 100, "force rebind test part 2"); $sth->bind_param (1, 10, SQL_INTEGER); $sth->execute(); $success = -1; while (my @data = $sth->fetchrow_array()) {($success) = @data;} is($success, 10, "force rebind test part 3"); $sth->bind_param (1, 111, SQL_INTEGER); $sth->execute(); $success = -1; do { my @data; while (@data = $sth->fetchrow_array()) { if ($#data == 0) { ($success) = @data; } else { # diag("Data: ", join(',', @data), "\n"); } } } while ($sth->{odbc_more_results}); is($success, 111, "force rebind test part 4"); # ensure the attribute is automatically set. # the multiple result sets will trigger this. is($sth->{odbc_force_rebind}, 1, "forced rebind final"); # # more special tests # make sure output params are being set properly when # multiple result sets are available. Also, ensure fetchrow_hashref # works with multiple statements. # eval {$dbh->do("DROP PROCEDURE PERL_DBD_PROC1");}; $dbh->do("CREATE PROCEDURE PERL_DBD_PROC1 \@parameter1 int = 22 AS /* SET NOCOUNT ON */ select 1 as some_data select isnull(\@parameter1, 0) as parameter1, 3 as some_more_data RETURN(\@parameter1 + 1)"); my $queryInputParameter1 = 2222; my $queryOutputParameter = 0; $sth = $dbh->prepare('{? = call PERL_DBD_PROC1(?) }'); $sth->bind_param_inout(1, \$queryOutputParameter, 30, { TYPE => DBI::SQL_INTEGER }); $sth->bind_param(2, $queryInputParameter1, { TYPE => DBI::SQL_INTEGER }); $sth->execute(); do { for(my $rowRef; $rowRef = $sth->fetchrow_hashref('NAME'); ) { my %outputData = %$rowRef; if (defined($outputData{some_data})) { is($outputData{some_data},1,"Select data available"); ok(!defined($outputData{parameter1}), "output param not yet available"); ok(!defined($outputData{some_more_data}), "output param not yet available2"); } else { is($outputData{parameter1},2222, "Output param data available"); is($outputData{some_more_data},3, "Output param data available 2"); ok(!defined($outputData{some_data}), "select data done"); } # diag('outputData ', Dumper(\%outputData), "\n"); } # print "out of for loop\n"; } while($sth->{odbc_more_results}); # print "out of while loop\n"; is($queryOutputParameter, $queryInputParameter1 + 1, "valid output data"); # test a procedure with no parameters eval {$dbh->do("DROP PROCEDURE PERL_DBD_PROC1");}; eval {$dbh->do("CREATE PROCEDURE PERL_DBD_PROC1 AS return 1;");}; $sth1 = $dbh->prepare ("{ ? = call PERL_DBD_PROC1 }"); $output = undef; $iErrCount = 0; $sth1->bind_param_inout(1, \$output, 50, DBI::SQL_INTEGER); $sth1->execute(); is($output, 1, "test procedure with no input params"); $dbh->{odbc_async_exec} = 1; # print "odbc_async_exec is: $dbh->{odbc_async_exec}\n"; is($dbh->{odbc_async_exec}, 1, "test odbc_async_exec attribute set"); # not sure if this should be a test. May have permissions problems, but # it's the only sample of the error handler stuff I have. my $testpass = 0; my $lastmsg; sub err_handler { my ($state, $msg, $nativeerr) = @_; # Strip out all of the driver ID stuff # normally something like [SQL Server Native Client 10.0][SQL Server] $msg =~ s/^(\[[\w\s:\.]*\])+//; $lastmsg = $msg; #diag "===> state: $state msg: $msg nativeerr: $nativeerr\n"; $testpass++; return 0; } $dbh->{odbc_err_handler} = \&err_handler; $sth = $dbh->prepare("dbcc TRACESTATUS(0)"); $sth->execute; cmp_ok($testpass, '>', 0, "dbcc messages being returned"); $testpass = 0; $dbh->{odbc_async_exec} = 0; is($dbh->{odbc_async_exec}, 0, "reset async exec"); $dbh->{odbc_exec_direct} = 1; is($dbh->{odbc_exec_direct}, 1, "test setting odbc_exec_direct"); $sth2 = $dbh->prepare("print 'START' select count(*) from PERL_DBD_TABLE1 print 'END'"); $sth2->execute; do { while (my @row = $sth2->fetchrow_array) { is($row[0], 1, "Valid select results with print statements"); } } while ($sth2->{odbc_more_results}); is($testpass,2, "ensure 2 error messages from two print statements"); is($lastmsg, 'END', "validate error messages being retrieved"); # need the finish if there are print statements (for now) #$sth2->finish; $dbh->{odbc_err_handler} = undef; # We need to make sure there is sufficient data returned to # overflow the TDS buffer. If all the results fit into one buffer # the tests checking for MAS not working work succeed. for (my $i = 1; $i < 1000; $i += 2) { $dbh->do('insert into PERL_DBD_TABLE1 (i, j) values (?, ?)', undef, $i, $i+1); } #$dbh->do("insert into PERL_DBD_TABLE1 (i, j) values (1, 2)"); #$dbh->do("insert into PERL_DBD_TABLE1 (i, j) values (3, 4)"); $dbh->disconnect; my $dsn = $ENV{DBI_DSN}; if ($dsn !~ /^dbi:ODBC:DSN=/ && $dsn !~ /DRIVER=/i) { my @a = split(q/:/, $ENV{DBI_DSN}); $dsn = join(q/:/, @a[0..($#a - 1)]) . ":DSN=" . $a[-1]; } my $base_dsn = $dsn; $dsn .= ";MARS_Connection=no"; $dbh = DBI->connect($dsn, $ENV{DBI_USER}, $ENV{DBI_PASS}, {PrintError => 0}); ok($dbh, "Connected with MARS_Connection"); diag("$DBI::errstr\n$dsn\n") if !$dbh; SKIP: { skip "could not connect with MARS_Connection attribute", 1 if !$dbh; ok(!&Multiple_concurrent_stmts($dbh, 0), "Multiple concurrent statements should fail"); $dbh->disconnect; }; $dbh = DBI->connect($dsn, $ENV{DBI_USER}, $ENV{DBI_PASS}, { odbc_cursortype => 2, PrintError => 0 }); # $dbh->{odbc_err_handler} = \&err_handler; ok(&Multiple_concurrent_stmts($dbh, 1), "Multiple concurrent statements succeed (odbc_cursortype set)"); SKIP: { skip "MS SQL Server version < 9", 1 if ($m_dbmsversion < 9); $dbh->disconnect; # throw away non-mars connection $dsn = "$base_dsn;MARS_Connection=yes;"; $dbh = DBI->connect($dsn, $ENV{DBI_USER}, $ENV{DBI_PASS}, {PrintError => 0}); my $tst = "Multiple concurrent statements succeed with MARS"; if (&Multiple_concurrent_stmts($dbh,1)) { pass($tst); } else { diag("DSN=$dsn\n"); diag("\nNOTE: You failed this test because your SQL Server driver\nis too old to handle the MARS_Connection attribute. This test cannot\neasily skip this test for old drivers as there is no definite SQL Server\ndriver version it can check.\n\n"); skip 'WARNING: driver does NOT support MARS_Connection', 1; } $dbh->disconnect; # throw away mars connection $dbh = DBI->connect; } # clean up test table and procedure # reset err handler # $dbh->{odbc_err_handler} = undef; eval {$dbh->do("DROP TABLE PERL_DBD_TABLE1");}; eval {$dbh->do("DROP PROCEDURE PERL_DBD_PROC1");}; eval { local $dbh->{PrintError} = 0; $dbh->do("drop table perl_dbd_test1"); }; $dbh->do("create table perl_dbd_test1 (i integer primary key, t varchar(30))"); $dbh->{AutoCommit} = 0; $dbh->do("insert into perl_dbd_test1 (i, t) values (1, 'initial')"); $dbh->commit; $dbh->do("update perl_dbd_test1 set t = 'second' where i = 1"); my $dbh2 = DBI->connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS}, {odbc_query_timeout => 2, PrintError=>0}); # $dbh2->{odbc_query_timeout} = 5; $dbh2->{AutoCommit} = 0; $dbh2->do("update perl_dbd_test1 set t = 'bad' where i = ?",undef,1); $dbh2->rollback; # should timeout and get to here. if so, test will pass pass("passed timeout on query using odbc_query_timeout using do with bind params"); $dbh2->do("update perl_dbd_test1 set t = 'bad' where i = 1"); $dbh2->rollback; $dbh2->disconnect; pass("passed timeout on query using odbc_query_timeout using do without bind params"); $dbh->commit; $dbh->do("drop table perl_dbd_test1"); $dbh->commit; }; $dbh->disconnect; exit 0; # get rid of use once warnings print $DBI::errstr; print $ODBCTEST::table_name;