// jTDS JDBC Driver for Microsoft SQL Server and Sybase // Copyright (C) 2004 The jTDS Project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package net.sourceforge.jtds.jdbc; import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.sql.*; import java.util.Properties; import java.util.Enumeration; import junit.framework.Test; import junit.framework.TestSuite; import net.sourceforge.jtds.jdbc.JtdsConnection; import net.sourceforge.jtds.jdbc.DefaultProperties; import net.sourceforge.jtds.jdbc.Messages; /** * Unit test for the {@link JtdsConnection} class. * * @author * David Kilzer, Alin Sinpalean, Holger Rehn */ public class ConnectionTest extends UnitTestBase { /** * <p> Construct a test suite for this class. </p> * * The test suite includes the tests in this class, and adds tests from * {@link DefaultPropertiesTestLibrary} after creating an anonymous * {@link DefaultPropertiesTester} object. * * @return * The test suite to run. */ public static Test suite() { final TestSuite testSuite = new TestSuite( ConnectionTest.class ); testSuite.addTest( ConnectionTest.TestUnpackProperties.suite( "testUnpackProperties" ) ); return testSuite; } /** * <p> Constructor. </p> * * @param name * name of the test */ public ConnectionTest( String name ) { super( name ); } /** * Test that an {@link java.sql.SQLException} is thrown when parsing invalid * integer (and long) properties. */ public void testInvalidIntegerProperty() { assertSQLExceptionForBadWholeNumberProperty( Driver.PORTNUMBER ); assertSQLExceptionForBadWholeNumberProperty( Driver.SERVERTYPE ); assertSQLExceptionForBadWholeNumberProperty( Driver.PREPARESQL ); assertSQLExceptionForBadWholeNumberProperty( Driver.PACKETSIZE ); assertSQLExceptionForBadWholeNumberProperty( Driver.LOGINTIMEOUT ); assertSQLExceptionForBadWholeNumberProperty( Driver.LOBBUFFER ); } /** * Class used to test * <code>net.sourceforge.jtds.jdbc.JtdsConnection.unpackProperties(Properties)</code> * . */ public static class TestUnpackProperties extends DefaultPropertiesTestLibrary { /** * Construct a test suite for this library. * * @param name * The name of the tests. * @return The test suite. */ public static Test suite( String name ) { return new TestSuite( ConnectionTest.TestUnpackProperties.class, name ); } /** * Default constructor. */ public TestUnpackProperties() { setTester( new DefaultPropertiesTester() { @Override public void assertDefaultProperty( String message, String url, Properties properties, String fieldName, String key, String expected ) { // FIXME: Hack for JtdsConnection { if( "sendStringParametersAsUnicode".equals( fieldName ) ) { fieldName = "useUnicode"; } else if( "cacheMetaData".equals( fieldName ) ) { fieldName = "useMetadataCache"; } } Properties parsedProperties = (Properties) invokeStaticMethod( Driver.class, "parseURL", new Class[] { String.class, Properties.class }, new Object[] { url, properties } ); parsedProperties = (Properties) invokeStaticMethod( DefaultProperties.class, "addDefaultProperties", new Class[] { Properties.class }, new Object[] { parsedProperties } ); JtdsConnection instance = (JtdsConnection) invokeConstructor( JtdsConnection.class, new Class[] {}, new Object[] {} ); invokeInstanceMethod( instance, "unpackProperties", new Class[] { Properties.class }, new Object[] { parsedProperties } ); String actual = String.valueOf( invokeInstanceMethod( instance, "get" + ucFirst( fieldName ), new Class[] {}, new Object[] {} ) ); // FIXME: Another hack for JtdsConnection { if( "tdsVersion".equals( fieldName ) ) { expected = String.valueOf( DefaultProperties.getTdsVersion( expected ) ); } } assertEquals( message, expected, actual ); } } ); } } /** * Test correct behavior of the <code>charset</code> property. Values should * be stored and retrieved using the requested charset rather than the * server's as long as Unicode is not used. */ public void testForceCharset1() throws Exception { // Set charset to Cp1251 and Unicode parameters to false Properties props = new Properties(); props.setProperty( Messages.get( Driver.CHARSET ), "Cp1251" ); props.setProperty( Messages.get( Driver.SENDSTRINGPARAMETERSASUNICODE ), "false" ); // Obtain connection Connection con = getConnectionOverrideProperties( props ); try { // Test both sending and retrieving of values String value = "\u0410\u0411\u0412"; PreparedStatement pstmt = con.prepareStatement( "select ?" ); pstmt.setString( 1, value ); ResultSet rs = pstmt.executeQuery(); assertTrue( rs.next() ); assertEquals( value, rs.getString( 1 ) ); assertFalse( rs.next() ); rs.close(); pstmt.close(); } finally { con.close(); } } /** * Test correct behavior of the <code>charset</code> property. Stored * procedure output parameters should be decoded using the specified charset * rather than the server's as long as they are non-Unicode. */ public void testForceCharset2() throws Exception { dropProcedure( "testForceCharset2" ); // Set charset to Cp1251 and Unicode parameters to false Properties props = new Properties(); props.setProperty( Messages.get( Driver.CHARSET ), "Cp1251" ); props.setProperty( Messages.get( Driver.SENDSTRINGPARAMETERSASUNICODE ), "false" ); // Obtain connection Connection con = getConnectionOverrideProperties( props ); try { Statement stmt = con.createStatement(); assertEquals( 0, stmt.executeUpdate( "create procedure testForceCharset2 " + "@inParam varchar(10), @outParam varchar(10) output as " + "set @outParam = @inParam" ) ); stmt.close(); // Test both sending and retrieving of parameters String value = "\u0410\u0411\u0412"; CallableStatement cstmt = con.prepareCall( "{call testForceCharset2(?, ?)}" ); cstmt.setString( 1, value ); cstmt.registerOutParameter( 2, Types.VARCHAR ); assertEquals( 0, cstmt.executeUpdate() ); assertEquals( value, cstmt.getString( 2 ) ); cstmt.close(); } finally { con.close(); } } /** * Test for bug [1296482] setAutoCommit() behaviour. * <p/> * The behaviour of setAutoCommit() on JtdsConnection is inconsistent with * the Sun JDBC 3.0 Specification. JDBC 3.0 Specification, section 10.1.1: * <blockquote>"If the value of auto-commit is changed in the middle of a * transaction, the current transaction is committed."</blockquote> */ public void testAutoCommit() throws Exception { Connection con = getConnectionOverrideProperties( new Properties() ); try { Statement stmt = con.createStatement(); // Create temp table assertEquals( 0, stmt.executeUpdate( "create table #testAutoCommit (i int)" ) ); // Manual commit mode con.setAutoCommit( false ); // Insert one row assertEquals( 1, stmt.executeUpdate( "insert into #testAutoCommit (i) values (0)" ) ); // Set commit mode to manual again; should have no effect con.setAutoCommit( false ); // Rollback the transaction; should roll back the insert con.rollback(); // Insert one more row assertEquals( 1, stmt.executeUpdate( "insert into #testAutoCommit (i) values (1)" ) ); // Set commit mode to automatic; should commit everything con.setAutoCommit( true ); // Go back to manual commit mode con.setAutoCommit( false ); // Rollback transaction; should do nothing con.rollback(); // And back to auto commit mode again con.setAutoCommit( true ); // Now see if the second row is there ResultSet rs = stmt.executeQuery( "select i from #testAutoCommit" ); assertTrue( rs.next() ); assertEquals( 1, rs.getInt( 1 ) ); assertFalse( rs.next() ); // We're done, close everything rs.close(); stmt.close(); } finally { con.close(); } } /** * Regression test for bug #673, function expansion causes buffer overflow. */ public void testBug673() throws Exception { Connection con = getConnection(); Statement stmt = con.createStatement(); stmt.execute( "SELECT {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}, {fn curdate()}" ); } /** * Test that temporary procedures created within transactions with savepoints * which are released are still kept in the procedure cache. * * @test.manual * when testing, prepareSQL will have to be set to 1 to make sure temp procedures are used */ public void testSavepointRelease() throws SQLException { // Manual commit mode con.setAutoCommit( false ); // Create two savepoints Savepoint sp1 = con.setSavepoint(); Savepoint sp2 = con.setSavepoint(); // Create and execute a prepared statement PreparedStatement stmt = con.prepareStatement( "SELECT 1" ); assertTrue( stmt.execute() ); // Release the inner savepoint and rollback the outer con.releaseSavepoint( sp2 ); con.rollback( sp1 ); // Now make sure the temp stored procedure still exists assertTrue( stmt.execute() ); // Release resources stmt.close(); con.close(); } /** * Test for bug [1755448], login failure leaves unclosed sockets. */ public void testUnclosedSocket() { final int count = 100; String url = props.getProperty( "url" ) + ";loginTimeout=600"; Properties p = new Properties( props ); p.put( "PASSWORD", "invalid_password" ); p.put( "loginTimeout", "60" ); for( int i = 0; i < count; i++ ) { try { DriverManager.getConnection( url, p ); assertTrue( false ); } catch( SQLException e ) { assertEquals( 18456, e.getErrorCode() ); } } } /** * Test for bug [2871274], TimerThread prevents classloader from being GCed. */ public void testTimerStop() throws Throwable { // number of load/unload cycles (use large numbers > 1000 for real stress // test) int RELOADS = 10; // counter for GCed class loaders final int[] counter = new int[] { 0 }; try { // run the test RELOADS times to ensure everything is GCed correctly for( int i = 0; i < RELOADS; i++ ) { // create new classloader for loading the actual test ClassLoader cloader = new URLClassLoader( new URL[] { new File( "bin" ).toURI().toURL() }, null ) { @Override protected void finalize() throws Throwable { counter[0]++; super.finalize(); } }; // load the actual test class Class clazz = cloader.loadClass( testTimerStopHelper.class.getName() ); Constructor constructor = clazz.getDeclaredConstructor( (Class[]) null ); // start the test by try { constructor.newInstance( (Object[]) null ); } catch( InvocationTargetException e ) { // extract target exception throw e.getTargetException(); } } // squeeze out any remaining class loaders for( int i = 0; i < 10; i++ ) { System.gc(); System.runFinalization(); } // ensure some of the created classloaders have been GCed at all assertTrue( "jTDS prevented its classloader from being GCed", counter[0] > 0 ); // ensure that any of the created classloaders has been GCed assertEquals( "not all of jTDS' classloaders have been GCed", RELOADS, counter[0] ); } catch( OutOfMemoryError oome ) { fail( "jTDS leaked memory, maybe its classloaders could not be GCed" ); } } /** * Helper class for test for bug [2871274]. */ public static class testTimerStopHelper { /** * Constructor for helper class, simply starts method {@link #test()}. */ public testTimerStopHelper() throws Throwable { test(); } /** * The actual test, creates and closes a number of connections. */ public void test() throws Exception { // load driver Class.forName( "net.sourceforge.jtds.jdbc.Driver" ); // load connection properties Properties p = loadProperties(); Connection[] conns = new Connection[5]; // create a number of connections for( int c = 0; c < conns.length; c++ ) { conns[c] = DriverManager.getConnection( p.getProperty( "url" ), p ); } // close the previously created connections for( int c = 0; c < conns.length; c++ ) { conns[c].close(); } // remove driver from DriverManager Enumeration e = DriverManager.getDrivers(); while( e.hasMoreElements() ) { java.sql.Driver d = (java.sql.Driver) e.nextElement(); if( d.getClass().getName().equals( "net.sourceforge.jtds.jdbc.Driver" ) ) { DriverManager.deregisterDriver( d ); break; } } // the class loader should be ready for GC now } /** * Loads the connection properties from config file. */ private static Properties loadProperties() throws Exception { File propFile = new File( "conf/connection.properties" ); if( !propFile.exists() ) fail( "Connection properties not found (" + propFile + ")." ); Properties props = new Properties(); props.load( new FileInputStream( propFile ) ); props.put( "loginTimeout", "60" ); return props; } } /** * Assert that an SQLException is thrown when * {@link JtdsConnection#unpackProperties(Properties)} is called with an * invalid integer (or long) string set on a property. * <p/> * Note that because Java 1.3 is still supported, the * {@link RuntimeException} that is caught may not contain the original * {@link Throwable} cause, only the original message. * * @param key * The message key used to retrieve the property name. */ private void assertSQLExceptionForBadWholeNumberProperty( final String key ) { final JtdsConnection instance = (JtdsConnection) invokeConstructor( JtdsConnection.class, new Class[] {}, new Object[] {} ); Properties properties = (Properties) invokeStaticMethod( Driver.class, "parseURL", new Class[] { String.class, Properties.class }, new Object[] { "jdbc:jtds:sqlserver://servername", new Properties() } ); properties = (Properties) invokeStaticMethod( DefaultProperties.class, "addDefaultProperties", new Class[] { Properties.class }, new Object[] { properties } ); properties.setProperty( Messages.get( key ), "1.21 Gigawatts" ); try { invokeInstanceMethod( instance, "unpackProperties", new Class[] { Properties.class }, new Object[] { properties } ); fail( "RuntimeException expected" ); } catch( RuntimeException e ) { assertEquals( "Unexpected exception message", Messages.get( "error.connection.badprop", Messages.get( key ) ), e.getMessage() ); } } /** * Creates a <code>Connection</code>, overriding the default properties with * the ones provided. * * @param override * the overriding properties * * @return * a <code>Connection</code> object */ private Connection getConnectionOverrideProperties( Properties override ) throws Exception { // Get properties, override with provided values Properties props = (Properties) TestBase.props.clone(); for( Enumeration e = override.keys(); e.hasMoreElements(); ) { String key = (String) e.nextElement(); props.setProperty( key, override.getProperty( key ) ); } // Obtain connection Class.forName( props.getProperty( "driver" ) ); String url = props.getProperty( "url" ); return DriverManager.getConnection( url, props ); } }