#--------------------------------------------------------------------- # $Header: /Perl/OlleDB/t/7_objectnames.t 13 15-05-24 22:27 Sommar $ # # This test suite tests that we interpret object names passed to sql_sp # and sql_insert correctly. # # $History: 7_objectnames.t $ # # ***************** Version 13 ***************** # User: Sommar Date: 15-05-24 Time: 22:27 # Updated in $/Perl/OlleDB/t # Ripped out code specific for SQL 6.5. # # ***************** Version 12 ***************** # User: Sommar Date: 12-08-19 Time: 14:53 # Updated in $/Perl/OlleDB/t # Corrected for restrictions on SQL 6.5. # # ***************** Version 11 ***************** # User: Sommar Date: 12-08-08 Time: 23:16 # Updated in $/Perl/OlleDB/t # Original intent was to add tests for alias types with parameterised # SQL, but a bug was revealed so that failed tests for checks of error # messages were not registered. This lead to some restructuring and also # some "dummy" tests to make it easier to compute the total number of # tests. (The previous hard-coding masked the bug.) # # ***************** Version 10 ***************** # User: Sommar Date: 08-05-04 Time: 23:14 # Updated in $/Perl/OlleDB/t # In correct no of tests for SQL 2008 and SQLNCLI. # # ***************** Version 9 ***************** # User: Sommar Date: 08-05-04 Time: 21:40 # Updated in $/Perl/OlleDB/t # Careful with that N, Eugene! # # ***************** Version 8 ***************** # User: Sommar Date: 08-03-23 Time: 23:29 # Updated in $/Perl/OlleDB/t # A little fix with the REVERT command, to avoid that SQLOLEDB adds # "exec" in front. # # ***************** Version 7 ***************** # User: Sommar Date: 08-03-09 Time: 22:48 # Updated in $/Perl/OlleDB/t # Added tests for table-valued parameters and table types. # # ***************** Version 6 ***************** # User: Sommar Date: 07-09-08 Time: 23:22 # Updated in $/Perl/OlleDB/t # Corrected the test on which provider we use. # # ***************** Version 5 ***************** # User: Sommar Date: 07-06-10 Time: 21:32 # Updated in $/Perl/OlleDB/t # Don't use sp_addgroup to create a schema on SQL 2005 or higher, since # there is CREATE SCHEMA - and in Katmai there is no sp_addgroup. # # ***************** Version 4 ***************** # User: Sommar Date: 05-11-26 Time: 23:47 # Updated in $/Perl/OlleDB/t # Renamed the module from MSSQL::OlleDB to Win32::SqlServer. # # ***************** Version 3 ***************** # User: Sommar Date: 05-10-30 Time: 22:34 # Updated in $/Perl/OlleDB/t # # ***************** Version 2 ***************** # User: Sommar Date: 05-03-28 Time: 20:01 # Updated in $/Perl/OlleDB/t # # ***************** Version 1 ***************** # User: Sommar Date: 05-03-28 Time: 19:03 # Created in $/Perl/OlleDB/t #--------------------------------------------------------------------- use strict; use Win32::SqlServer qw(:DEFAULT :consts); use File::Basename qw(dirname); require &dirname($0) . '\testsqllogin.pl'; use vars qw(@testres $verbose $no_of_tests); sub blurb{ push (@testres, "#------ Testing @_ ------\n"); print "#------ Testing @_ ------\n" if $verbose; } $verbose = shift @ARGV; $^W = 1; $| = 1; my $X = testsqllogin(); my ($sqlver) = split(/\./, $X->{SQL_version}); my ($sqlncli) = ($X->{Provider} >= PROVIDER_SQLNCLI); my ($sqlncli10) = ($X->{Provider} >= PROVIDER_SQLNCLI10); # Suppress informatiomal messages for our coming creation craze. $X->{errInfo}{printText} = 1; # Permit us to continue on errors. $X->{ErrInfo}{MaxSeverity} = 17; # The out data from the test procedures is a return value, so turn off that # test. $X->{ErrInfo}{CheckRetStat} = 0; # But when we test for error messages, we want different settings. sub setup_for_error_test { delete $X->{ErrInfo}{Messages}; $X->{ErrInfo}{PrintMsg} = 17; $X->{ErrInfo}{PrintLines} = 17; $X->{ErrInfo}{PrintText} = 17; $X->{ErrInfo}{CarpLevel} = 17; $X->{ErrInfo}{SaveMessages} = 1; } sub reset_after_error_test { $X->{ErrInfo}{PrintMsg} = 1; $X->{ErrInfo}{PrintLines} = 11; $X->{ErrInfo}{PrintText} = 1; $X->{ErrInfo}{CarpLevel} = 10; $X->{ErrInfo}{SaveMessages} = 0; } # This becomes "räksmörgås" - but in Greek script. my $shrimp = "\x{03A1}\x{03B5}\x{03BA}\x{03C3}\x{03BC}\x{03BF}\x{03B5}\x{03C1}\x{03BD}\x{03B3}\x{03C9}\x{03C2}"; # Database names we use. They are some absymal to avoid collisions with existing # databases. Names with embedded dots does not work on 6.5, although in theory # they should. my @dbs = ('Olle$DB', '"Olle$DB test"', '"OlleDB.test"', '[Olle$DB.test]', $shrimp); # Schema names that we use. On 6.5, we only test the dbo schema, in SQL 6.5 # groups does not have schemas, and we don't want to create logins to create users. # Also, users cannot have "funny" characters in them on 6.5. my @schemas = ('dbo', 'guest', '"OlleDB$ test"', '"."', '"OlleDB.."""', '[Olle$DB.test]', '[".]', $shrimp); # And procedure names. my @procnames = ('plain_sp', '"space sp"', '"dot.sp"', '"dot.dot.sp"', '[bracket sp.]', '[bracket]]sp]', $shrimp); # And add some really crazy names that SQLOLEDB cannot handle. push(@procnames, '"""quote_sp"', '[]]"]]]') if $sqlncli; # Drop existing databases. This is commented out normally as a safety # precaution, so that we don't drop existing databases. $X->sql("USE master"); #foreach my $db (@dbs) { # $X->sql("IF object_id(N'$db.dbo.sysobjects') IS NOT NULL DROP DATABASE $db"); #} # Go on and create databases, schemas and procedures. Note that we don't drop # existing databases. If the script fails, you may have drop to the databases # manually. my (%procmap, $n); foreach my $db (@dbs) { $X->sql("USE master"); $X->sql("CREATE DATABASE $db"); $X->sql("USE $db"); # Add as user to impersonate. We use guest on SQL 2000 and earlier, # else our own user. if ($sqlver >= 9) { $X->sql('CREATE USER olle WITHOUT LOGIN WITH DEFAULT_SCHEMA = guest'); } else { $X->sql("EXEC sp_adduser guest"); } # And create the schemas as groups (so logins are not required). foreach my $sch (@schemas) { unless ($sch =~ /^(dbo|guest)$/) { if ($sqlver >= 9) { $X->sql("CREATE SCHEMA $sch"); } else { # No direct CREATE SCHEMA in previous version, but creating a # group will do. $X->sql("EXEC sp_addgroup $sch"); } } if ($sqlver >= 9) { $X->sql("GRANT VIEW DEFINITION ON SCHEMA::$sch TO public"); } # And so the procedures and type. Each procedure and type has a # unique signature with the parameter name, and we save this in %procmap. foreach my $proc (@procnames) { $n++; $X->sql ("CREATE PROCEDURE $sch.$proc \@a$n int AS RETURN \@a$n + $n"); $X->sql ("GRANT EXECUTE ON $sch.$proc TO public"); $procmap{$db}{$sch}{$proc} = $n; # We also create types. Exactly how depends on version etc. # and on the latter, types does not have a schema. if ($sqlver >= 10 and $sqlncli10) { # On SQL 2008 and SQLNCLI10 we do table types, so we can # test both type names and typeinfo. $X->sql("CREATE TYPE $sch.$proc AS TABLE (Olle$n int NOT NULL)"); $X->sql(<<SQLEND); CREATE PROCEDURE $sch.Olletbl$n \@t $sch.$proc READONLY AS SELECT * FROM \@t SQLEND $X->sql("GRANT EXECUTE ON TYPE::$sch.$proc TO public"); $X->sql("GRANT EXECUTE ON $sch.Olletbl$n TO public"); } elsif ($sqlver >= 9) { # For other versions we use a plain type. $X->sql("CREATE TYPE $sch.$proc FROM char($n)"); } elsif ($sch eq 'dbo') { # On SQL 2000 and earlier, type does not have schema. # Furthermore, names are entered as is, that is quotedid are # not handled. my $type = $proc; if ($type =~ /^".+"$/) { $type =~ s/""/\"/g; $type = substr($type, 1, length($type) - 2); } elsif ($type =~ /^\[.+\]$/) { $type =~ s/\]\]/\]/g; $type = substr($type, 1, length($type) - 2); } # And on SQL 6.5, there can be no specials at all, so we skip # such type, and we will skip it below as well. $X->sql_sp("sp_addtype", [$type, "char($n)"]); } # On SQL 2005 and later, also create schema collections to test # handling of typeinfo if we have SQL Native client. if ($sqlver >= 9 and $sqlncli) { $X->sql(<<SQLEND); CREATE XML SCHEMA COLLECTION $sch.$proc AS ' <schema xmlns="http://www.w3.org/2001/XMLSchema"> <element name="Olle$n" type="string"/> </schema>' SQLEND $X->sql ("GRANT EXECUTE ON XML SCHEMA COLLECTION::$sch.$proc TO public"); } } } } # Also create a temporary stored procedure and other objects starting # with a hash mark $X->sql("USE $dbs[0]"); $n = 4711; $X->sql("CREATE PROCEDURE #temp_sp \@a$n int AS RETURN 10000 + $n"); if ($sqlver >= 9 and $sqlncli) { $X->sql(<<SQLEND); CREATE XML SCHEMA COLLECTION #temp_sp AS '<schema xmlns="http://www.w3.org/2001/XMLSchema"> <element name="Olle$n" type="string"/> </schema>' SQLEND } if ($sqlver >= 10 and $sqlncli10) { # SQL Server does not accept table types starting with #, so we # cannot create a type. But we create a stored procedure to fake # success in the SP call test. $X->sql("CREATE PROCEDURE Olletbl$n \@x char(1) AS SELECT Olle$n = 100000"); } elsif ($sqlver >= 9) { $X->sql("CREATE TYPE #temp_sp FROM char($n)"); } else { $X->sql("EXEC sp_addtype '#temp_sp', 'char($n)'"); } # First try all SP without schema qualification in the first database. my $db = $dbs[0]; $X->sql("USE $db"); my $sch = 'dbo'; foreach my $proc (@procnames) { my $expect = $procmap{$db}{$sch}{$proc}; do_test($db, $sch, $proc, $expect); do_test($db, $sch, ".$proc", $expect); do_test($db, $sch, "..$proc", $expect); do_test($db, $sch, "...$proc", $expect); do_test($db, $sch, "$db. .$proc", $expect, 'db'); do_test($db, $sch, "....$proc", 'TOOMANY'); do_test($db, $sch, ".$db.$sch.$proc", $expect, 'db'); do_test($db, $sch, ". $db . $sch . $proc", $expect, 'db'); do_test($db, $sch, ". $db . $sch . $proc", $expect, 'db'); # Do it twice to test look-up. do_test($db, $sch, "...$sch.$proc", 'TOOMANY'); do_test($db, $sch, "server.$db.$sch.$proc", 'SERVER'); do_test($db, $sch, "a.b.$db.$sch.$proc", 'TOOMANY'); } my $portioncombos = 12; # Test bad quoting do_test($db, $sch, '[plain_sp', 'UNTERM'); do_test(undef, undef, 'db.[sch]].plain_sp', 'UNTERM'); do_test(undef, undef, '"db.sch.plain_sp', 'UNTERM'); do_test(undef, undef, '[]]"]]', 'UNTERM'); do_test(undef, undef, 'db."sch"s.plain_sp', 'ILLQUOTE'); do_test(undef, undef, 'db. "sch" s.plain_sp', 'ILLQUOTE'); my $badquoting = 6; # Redo for the guest schema. We must flush the proc cache here. if ($sqlver >= 9) { $X->sql("EXECUTE AS USER = 'olle'"); } else { $X->sql("SETUSER 'guest'"); } $X->{'procs'} = {}; $X->{'tabletypes'} = {}; $X->{'usertypes'} = {}; $sch = 'guest'; foreach my $proc (@procnames) { # When running this test, there is a special case: types in SQL 2000 # and earlier are always in dbo. my $expect = $procmap{$db}{$sch}{$proc}; my $expect_dbo = $procmap{$db}{'dbo'}{$proc}; do_test($db, $sch, $proc, $expect, undef, 'dbo', $expect_dbo); do_test($db, $sch, "guest.$proc", $expect); do_test($db, $sch, "..$proc", $expect, undef, 'dbo', $expect_dbo); do_test($db, $sch, ". ..$proc", $expect, undef, 'dbo', $expect_dbo); } # The semi-colon is needed, because else SQLOLEDB adds "exec" before REVERT. $X->sql(($sqlver >= 9 ? "; REVERT" : "SETUSER")); my $testsasguest = 4; # Now try all combinations of schema and procedure. $X->{'procs'} = {}; $X->{'tabletypes'} = {}; $X->{'usertypes'} = {}; foreach $sch (@schemas) { foreach my $proc (@procnames) { my $expect = $procmap{$db}{$sch}{$proc}; do_test($db, $sch, " $sch.$proc ", $expect); do_test($db, $sch, ".$sch.$proc", $expect); do_test($db, $sch, "..$sch.$proc", $expect); } } my $allschproccombos = 3; # And now all combinations of databases, schemas and procedeurs. $X->sql("USE master"); foreach $db (@dbs) { foreach $sch (@schemas) { foreach my $proc (@procnames) { my $expect = $procmap{$db}{$sch}{$proc}; do_test($db, $sch, "$db.$sch.$proc", $expect, 'db'); } } } my $alldbcombos = 1; # Test the temporary stored procedure. $db = $dbs[0]; $X->sql("USE $db"); do_test($db, 'dbo', "#temp_sp", 4711); my $temptest = 1; # Finnaly test system stored procedures. my $resset = 1; $X->sql("USE $dbs[0]"); blurb("sp_help plain_sp"); my @result = sql_sp('sp_help', ['plain_sp']); push(@testres, $result[$resset]{'Parameter_name'} eq '@a' . $procmap{$dbs[0]}{'dbo'}{'plain_sp'}); foreach $db (@dbs) { blurb("$db..sp_help plain_sp"); @result = sql_sp("$db..sp_help", ['plain_sp']); push(@testres, $result[$resset]{'Parameter_name'} eq '@a' . $procmap{$db}{'dbo'}{'plain_sp'}); } $X->sql("USE master"); foreach my $db (@dbs) { $X->sql("DROP DATABASE $db"); } # Now computer the number of tests for each configuration. my $tests_per_objref; if ($sqlver >= 10 and $sqlncli10) { $tests_per_objref = 5; } elsif ($sqlver >= 9 and $sqlncli) { $tests_per_objref = 3; } else { $tests_per_objref = 2; } $no_of_tests = $tests_per_objref * ( $portioncombos * scalar(@procnames) + $badquoting + $testsasguest * scalar(@procnames) + $allschproccombos * scalar(@procnames) * scalar(@schemas) + $alldbcombos * scalar(@procnames) * scalar(@schemas) * scalar(@dbs) + $temptest) + scalar(@dbs) + 1; # System procedures. finally: my $ix = 1; my $blurb = ""; print "1..$no_of_tests\n"; foreach my $result (@testres) { if ($result =~ /^#--/) { print $result if $verbose; $blurb = $result; } elsif ($result == 1) { printf "ok %d\n", $ix++; } else { printf "not ok %d\n$blurb", $ix++; } } exit; sub do_test { my($db, $sch, $objref, $mapvalue, $hasdbspec, $ss2000_typeschema, $ss2000_expect) = @_; $ss2000_typeschema = $sch unless $ss2000_typeschema; $ss2000_expect = $mapvalue unless $ss2000_expect; if ($mapvalue =~ /^\d+$/) { # First test call to stored procedure. my $retvalue; my $params; $$params{"a$mapvalue"} = 10000; my $expect = 10000 + $mapvalue; blurb("SP Call $objref"); $X->sql_sp($objref, \$retvalue, $params); push(@testres, $retvalue == $expect); # XML schema collections. if ($sqlver >= 9 and $sqlncli) { blurb($objref . ' XML '); my $errorexpect; $expect = "<Olle$mapvalue>$mapvalue</Olle$mapvalue>"; if ($objref =~ /^\./) { setup_for_error_test(); $errorexpect = qr/Incorrect syntax near/; } my $sqlparams = ['xml', '<?xml version="1.0"?>' . $expect, $objref]; $retvalue = $X->sql('SELECT convert(nvarchar(MAX), ?)', [$sqlparams], SCALAR, SINGLEROW); if ($errorexpect) { my $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; push(@testres, scalar($errmsg =~ $errorexpect)); } else { push(@testres, $retvalue eq $expect); } reset_after_error_test(); } # Test types. This is done different depending on SQL Server # version. if ($sqlver >= 10 and $sqlncli10) { # For "modern" platforms we test table types, for which # there are restrictions for which syntaxes that are legal. # Some of the test case will result in error. # First SP call with TVP. These tests always passes. $expect = "Olle$mapvalue"; blurb("Table type $objref SP call"); $retvalue = $X->sql_sp("$db.$sch.Olletbl$mapvalue", [[]], COLINFO_NAMES, LIST); push(@testres, (ref $retvalue eq 'ARRAY' and $$retvalue[0][0] eq $expect)); # The adhoc stuff is worse, here errors may occur: my ($errorexpect1, $errorexpect2); if ($hasdbspec) { $errorexpect1 = $errorexpect2 = qr/\Q'$objref'\E.*database portion/; } elsif ($objref =~ /^\./) { $errorexpect1 = $errorexpect2 = qr/Incorrect syntax near/; } elsif ($objref =~ /^\#/) { $errorexpect1 = qr/Unable to find.*\'\Q$objref\E\'/; $errorexpect2 = qr/Unknown data type '\Q$objref\E\'/; } if ($errorexpect1) { setup_for_error_test(); } blurb("Table type $objref param sql"); $retvalue = $X->sql('SELECT * FROM ?', [['table', [], $objref]], COLINFO_NAMES, LIST); if ($errorexpect1) { my $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; push(@testres, scalar($errmsg =~ $errorexpect1)); } else { push(@testres, $$retvalue[0][0] eq $expect); } delete $X->{ErrInfo}{Messages}; blurb("Table type $objref param sql, with type name"); $retvalue = $X->sql('SELECT * FROM ?', [[$objref, []]], COLINFO_NAMES, LIST); if ($errorexpect2) { my $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; push(@testres, scalar($errmsg =~ $errorexpect2)); } else { push(@testres, $$retvalue[0][0] eq $expect); } reset_after_error_test(); } elsif ($sqlver >= 9 or $ss2000_typeschema eq 'dbo') { # With plain types we can test it all. No errors are expected. blurb("Type $objref"); $expect = ($sqlver >= 9 ? $mapvalue : $ss2000_expect); $retvalue = $X->sql_one('SELECT datalength(?)', [[$objref, ' ']], SCALAR); push(@testres, $retvalue eq $expect); } else { # On SQL 2000 and we don't accept any other schema than dbo. setup_for_error_test(); blurb("Type $objref (error expected in SQL 2000)"); $X->sql('SELECT ?', [[$objref, undef]]); my $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; push(@testres, scalar ($errmsg =~ /has a schema different from/)); reset_after_error_test(); } } else { setup_for_error_test(); my $expect; if ($mapvalue eq 'TOOMANY') { $expect = qr/'\Q$objref\E'.*includes more than four/; } elsif ($mapvalue eq 'SERVER') { $expect = qr/'\Q$objref\E'.*server (portion|component)/; } elsif ($mapvalue eq 'UNTERM') { $expect = qr/'\Q$objref\E'.*unterminated/; } elsif ($mapvalue eq 'ILLQUOTE') { $expect = qr/'\Q$objref\E'.*incorrectly quoted/; } else { die "Mapvalue has an unexpected value: '$mapvalue'."; } $X->sql_sp($objref); my $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; blurb("SP call $objref (expected $expect, got '$errmsg')"); push(@testres, scalar($errmsg =~ $expect)); delete $X->{ErrInfo}{Messages}; $X->sql('SELECT ?', [[$objref, undef]]); $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; blurb("Type $objref (expected $expect, got '$errmsg')"); push(@testres, scalar($errmsg =~ $expect)); if ($sqlver >= 9 and $sqlncli) { delete $X->{ErrInfo}{Messages}; $X->sql('SELECT ?', [['xml', undef, $objref]]); $errmsg = $X->{ErrInfo}{Messages}[0]{'text'}; blurb("XML $objref (expected $expect, got '$errmsg')"); push(@testres, scalar($errmsg =~ $expect)); } if ($sqlver >= 10 and $sqlncli10) { delete $X->{ErrInfo}{Messages}; $X->sql('SELECT * FROM ?', [['table', [], $objref]], COLINFO_NAMES, LIST); blurb("Table type $objref param sql (expected $expect, got '$errmsg')"); push(@testres, scalar($errmsg =~ $expect)); # This is a dummy "test" to have equally many tests for # errors and success. This makes it easier to compute the # total. blurb("Dummy test"); push(@testres, 1); } reset_after_error_test(); } }