diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java index a39126c7d..80341454f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java @@ -30,6 +30,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.UUID; /** * CallableStatement implements JDBC callable statements. CallableStatement allows the caller to specify the procedure name to call along with input @@ -674,8 +675,84 @@ public Object getObject(int index) throws SQLServerException { public T getObject(int index, Class type) throws SQLException { - // The driver currently does not implement the optional JDBC APIs - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + loggerExternal.entering(getClassNameLogging(), "getObject", index); + checkClosed(); + Object returnValue; + if (type == String.class) { + returnValue = getString(index); + } + else if (type == Byte.class) { + byte byteValue = getByte(index); + returnValue = wasNull() ? null : byteValue; + } + else if (type == Short.class) { + short shortValue = getShort(index); + returnValue = wasNull() ? null : shortValue; + } + else if (type == Integer.class) { + int intValue = getInt(index); + returnValue = wasNull() ? null : intValue; + } + else if (type == Long.class) { + long longValue = getLong(index); + returnValue = wasNull() ? null : longValue; + } + else if (type == BigDecimal.class) { + returnValue = getBigDecimal(index); + } + else if (type == Boolean.class) { + boolean booleanValue = getBoolean(index); + returnValue = wasNull() ? null : booleanValue; + } + else if (type == java.sql.Date.class) { + returnValue = getDate(index); + } + else if (type == java.sql.Time.class) { + returnValue = getTime(index); + } + else if (type == java.sql.Timestamp.class) { + returnValue = getTimestamp(index); + } + else if (type == microsoft.sql.DateTimeOffset.class) { + returnValue = getDateTimeOffset(index); + } + else if (type == UUID.class) { + // read binary, avoid string allocation and parsing + byte[] guid = getBytes(index); + returnValue = guid != null ? Util.readGUIDtoUUID(guid) : null; + } + else if (type == SQLXML.class) { + returnValue = getSQLXML(index); + } + else if (type == Blob.class) { + returnValue = getBlob(index); + } + else if (type == Clob.class) { + returnValue = getClob(index); + } + else if (type == NClob.class) { + returnValue = getNClob(index); + } + else if (type == byte[].class) { + returnValue = getBytes(index); + } + else if (type == Float.class) { + float floatValue = getFloat(index); + returnValue = wasNull() ? null : floatValue; + } + else if (type == Double.class) { + double doubleValue = getDouble(index); + returnValue = wasNull() ? null : doubleValue; + } + else { + // if the type is not supported the specification says the should + // a SQLException instead of SQLFeatureNotSupportedException + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionTo")); + Object[] msgArgs = {type}; + throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null); + } + loggerExternal.exiting(getClassNameLogging(), "getObject", index); + return type.cast(returnValue); } public Object getObject(String sCol) throws SQLServerException { @@ -690,8 +767,12 @@ public Object getObject(String sCol) throws SQLServerException { public T getObject(String sCol, Class type) throws SQLException { - // The driver currently does not implement the optional JDBC APIs - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + loggerExternal.entering(getClassNameLogging(), "getObject", sCol); + checkClosed(); + int parameterIndex = findColumn(sCol); + T value = getObject(parameterIndex, type); + loggerExternal.exiting(getClassNameLogging(), "getObject", value); + return value; } public short getShort(int index) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index d4cc1bb7d..b1606dfdc 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -104,6 +104,7 @@ protected Object[][] getContents() { {"R_invalidConnection", "The connection URL is invalid."}, {"R_cannotTakeArgumentsPreparedOrCallable", "The method {0} cannot take arguments on a PreparedStatement or CallableStatement."}, {"R_unsupportedConversionFromTo", "The conversion from {0} to {1} is unsupported."}, // Invalid conversion (e.g. MONEY to Timestamp) + {"R_unsupportedConversionTo", "The conversion to {0} is unsupported."}, // Invalid conversion to an unknown type {"R_errorConvertingValue","An error occurred while converting the {0} value to JDBC data type {1}."}, // Data-dependent conversion failure (e.g. "foo" vs. "123", to Integer) {"R_streamIsClosed", "The stream is closed."}, {"R_invalidTDS", "The TDS protocol stream is not valid."}, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 98da7f11d..f67367b1d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -27,6 +27,7 @@ import java.sql.SQLXML; import java.text.MessageFormat; import java.util.Calendar; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; @@ -2170,8 +2171,84 @@ public Object getObject(int columnIndex) throws SQLServerException { public T getObject(int columnIndex, Class type) throws SQLException { - // The driver currently does not implement the optional JDBC APIs - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + loggerExternal.entering(getClassNameLogging(), "getObject", columnIndex); + checkClosed(); + Object returnValue; + if (type == String.class) { + returnValue = getString(columnIndex); + } + else if (type == Byte.class) { + byte byteValue = getByte(columnIndex); + returnValue = wasNull() ? null : byteValue; + } + else if (type == Short.class) { + short shortValue = getShort(columnIndex); + returnValue = wasNull() ? null : shortValue; + } + else if (type == Integer.class) { + int intValue = getInt(columnIndex); + returnValue = wasNull() ? null : intValue; + } + else if (type == Long.class) { + long longValue = getLong(columnIndex); + returnValue = wasNull() ? null : longValue; + } + else if (type == BigDecimal.class) { + returnValue = getBigDecimal(columnIndex); + } + else if (type == Boolean.class) { + boolean booleanValue = getBoolean(columnIndex); + returnValue = wasNull() ? null : booleanValue; + } + else if (type == java.sql.Date.class) { + returnValue = getDate(columnIndex); + } + else if (type == java.sql.Time.class) { + returnValue = getTime(columnIndex); + } + else if (type == java.sql.Timestamp.class) { + returnValue = getTimestamp(columnIndex); + } + else if (type == microsoft.sql.DateTimeOffset.class) { + returnValue = getDateTimeOffset(columnIndex); + } + else if (type == UUID.class) { + // read binary, avoid string allocation and parsing + byte[] guid = getBytes(columnIndex); + returnValue = guid != null ? Util.readGUIDtoUUID(guid) : null; + } + else if (type == SQLXML.class) { + returnValue = getSQLXML(columnIndex); + } + else if (type == Blob.class) { + returnValue = getBlob(columnIndex); + } + else if (type == Clob.class) { + returnValue = getClob(columnIndex); + } + else if (type == NClob.class) { + returnValue = getNClob(columnIndex); + } + else if (type == byte[].class) { + returnValue = getBytes(columnIndex); + } + else if (type == Float.class) { + float floatValue = getFloat(columnIndex); + returnValue = wasNull() ? null : floatValue; + } + else if (type == Double.class) { + double doubleValue = getDouble(columnIndex); + returnValue = wasNull() ? null : doubleValue; + } + else { + // if the type is not supported the specification says the should + // a SQLException instead of SQLFeatureNotSupportedException + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionTo")); + Object[] msgArgs = {type}; + throw new SQLServerException(form.format(msgArgs), SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null); + } + loggerExternal.exiting(getClassNameLogging(), "getObject", columnIndex); + return type.cast(returnValue); } public Object getObject(String columnName) throws SQLServerException { @@ -2184,8 +2261,11 @@ public Object getObject(String columnName) throws SQLServerException { public T getObject(String columnName, Class type) throws SQLException { - // The driver currently does not implement the optional JDBC APIs - throw new SQLFeatureNotSupportedException(SQLServerException.getErrString("R_notSupported")); + loggerExternal.entering(getClassNameLogging(), "getObject", columnName); + checkClosed(); + T value = getObject(findColumn(columnName), type); + loggerExternal.exiting(getClassNameLogging(), "getObject", value); + return value; } public short getShort(int columnIndex) throws SQLServerException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java index bac845335..57bf89d5e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java @@ -687,6 +687,46 @@ static final byte[] asGuidByteArray(UUID aId) { return buffer; } + static final UUID readGUIDtoUUID(byte[] inputGUID) throws SQLServerException { + if (inputGUID.length != 16) { + throw new SQLServerException("guid length must be 16", null); + } + + // For the first three fields, UUID uses network byte order, + // Guid uses native byte order. So we need to reverse + // the first three fields before creating a UUID. + + byte tmpByte; + + // Reverse the first 4 bytes + tmpByte = inputGUID[0]; + inputGUID[0] = inputGUID[3]; + inputGUID[3] = tmpByte; + tmpByte = inputGUID[1]; + inputGUID[1] = inputGUID[2]; + inputGUID[2] = tmpByte; + + // Reverse the 5th and the 6th + tmpByte = inputGUID[4]; + inputGUID[4] = inputGUID[5]; + inputGUID[5] = tmpByte; + + // Reverse the 7th and the 8th + tmpByte = inputGUID[6]; + inputGUID[6] = inputGUID[7]; + inputGUID[7] = tmpByte; + + long msb = 0L; + for (int i = 0; i < 8; i++) { + msb = msb << 8 | ((long) inputGUID[i] & 0xFFL); + } + long lsb = 0L; + for (int i = 8; i < 16; i++) { + lsb = lsb << 8 | ((long) inputGUID[i] & 0xFFL); + } + return new UUID(msb, lsb); + } + static final String readGUID(byte[] inputGUID) throws SQLServerException { String guidTemplate = "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN"; byte guid[] = inputGUID; diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java new file mode 100644 index 000000000..85f27900b --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java @@ -0,0 +1,32 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ +package com.microsoft.sqlserver.jdbc; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +/** + * Tests the Util class + * + */ +@RunWith(JUnitPlatform.class) +public class UtilTest { + + @Test + public void readGUIDtoUUID() throws SQLServerException { + UUID expected = UUID.fromString("6F9619FF-8B86-D011-B42D-00C04FC964FF"); + byte[] guid = new byte[] {-1, 25, -106, 111, -122, -117, 17, -48, -76, 45, 0, -64, 79, -55, 100, -1}; + assertEquals(expected, Util.readGUIDtoUUID(guid)); + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java index 08d935703..e33fc2f1e 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/resultset/ResultSetTest.java @@ -7,17 +7,24 @@ */ package com.microsoft.sqlserver.jdbc.resultset; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import java.math.BigDecimal; +import java.sql.Blob; +import java.sql.Clob; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.NClob; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLXML; import java.sql.Statement; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; @@ -35,47 +42,201 @@ public class ResultSetTest extends AbstractTest { /** * Tests proper exception for unsupported operation * - * @throws Exception + * @throws SQLException */ @Test - public void testJdbc41ResultSetMethods() throws Exception { - Connection con = DriverManager.getConnection(connectionString); - Statement stmt = con.createStatement(); - try { - stmt.executeUpdate("create table " + tableName + " (col1 int, col2 text, col3 int identity(1,1) primary key)"); + public void testJdbc41ResultSetMethods() throws SQLException { + try (Connection con = DriverManager.getConnection(connectionString); + Statement stmt = con.createStatement()) { + stmt.executeUpdate("create table " + tableName + " ( " + + "col1 int, " + + "col2 varchar(512), " + + "col3 float, " + + "col4 decimal(10,5), " + + "col5 uniqueidentifier, " + + "col6 xml, " + + "col7 varbinary(max), " + + "col8 text, " + + "col9 ntext, " + + "col10 varbinary(max), " + + "col11 date, " + + "col12 time, " + + "col13 datetime2, " + + "col14 datetimeoffset, " + + "order_column int identity(1,1) primary key)"); + try { + + stmt.executeUpdate("Insert into " + tableName + " values(" + + "1, " // col1 + + "'hello', " // col2 + + "2.0, " // col3 + + "123.45, " // col4 + + "'6F9619FF-8B86-D011-B42D-00C04FC964FF', " // col5 + + "'', " // col6 + + "0x63C34D6BCAD555EB64BF7E848D02C376, " // col7 + + "'text', " // col8 + + "'ntext', " // col9 + + "0x63C34D6BCAD555EB64BF7E848D02C376," // col10 + + "'2017-05-19'," // col11 + + "'10:47:15.1234567'," // col12 + + "'2017-05-19T10:47:15.1234567'," // col13 + + "'2017-05-19T10:47:15.1234567+02:00'" // col14 + + ")"); + + stmt.executeUpdate("Insert into " + tableName + " values(" + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null, " + + "null)"); + + try (ResultSet rs = stmt.executeQuery("select * from " + tableName + " order by order_column")) { + // test non-null values + assertTrue(rs.next()); + assertEquals(Byte.valueOf((byte) 1), rs.getObject(1, Byte.class)); + assertEquals(Byte.valueOf((byte) 1), rs.getObject("col1", Byte.class)); + assertEquals(Short.valueOf((short) 1), rs.getObject(1, Short.class)); + assertEquals(Short.valueOf((short) 1), rs.getObject("col1", Short.class)); + assertEquals(Integer.valueOf(1), rs.getObject(1, Integer.class)); + assertEquals(Integer.valueOf(1), rs.getObject("col1", Integer.class)); + assertEquals(Long.valueOf(1), rs.getObject(1, Long.class)); + assertEquals(Long.valueOf(1), rs.getObject("col1", Long.class)); + assertEquals(Boolean.TRUE, rs.getObject(1, Boolean.class)); + assertEquals(Boolean.TRUE, rs.getObject("col1", Boolean.class)); - stmt.executeUpdate("Insert into " + tableName + " values(0, 'hello')"); + assertEquals("hello", rs.getObject(2, String.class)); + assertEquals("hello", rs.getObject("col2", String.class)); - stmt.executeUpdate("Insert into " + tableName + " values(0, 'yo')"); + assertEquals(2.0f, rs.getObject(3, Float.class), 0.0001f); + assertEquals(2.0f, rs.getObject("col3", Float.class), 0.0001f); + assertEquals(2.0d, rs.getObject(3, Double.class), 0.0001d); + assertEquals(2.0d, rs.getObject("col3", Double.class), 0.0001d); - ResultSet rs = stmt.executeQuery("select * from " + tableName); - rs.next(); - // Both methods throw exceptions - try { + // BigDecimal#equals considers the number of decimal places + assertEquals(0, rs.getObject(4, BigDecimal.class).compareTo(new BigDecimal("123.45"))); + assertEquals(0, rs.getObject("col4", BigDecimal.class).compareTo(new BigDecimal("123.45"))); - int col1 = rs.getObject(1, Integer.class); - } - catch (Exception e) { - // unsupported feature - assertEquals(e.getClass(), SQLFeatureNotSupportedException.class, "Verify exception type: " + e.getMessage()); - } - try { - String col2 = rs.getObject("col2", String.class); - } - catch (Exception e) { - // unsupported feature - assertEquals(e.getClass(), SQLFeatureNotSupportedException.class, "Verify exception type: " + e.getMessage()); - } - try { + assertEquals(UUID.fromString("6F9619FF-8B86-D011-B42D-00C04FC964FF"), rs.getObject(5, UUID.class)); + assertEquals(UUID.fromString("6F9619FF-8B86-D011-B42D-00C04FC964FF"), rs.getObject("col5", UUID.class)); + + SQLXML sqlXml; + sqlXml = rs.getObject(6, SQLXML.class); + try { + assertEquals("", sqlXml.getString()); + } finally { + sqlXml.free(); + } + + Blob blob; + blob = rs.getObject(7, Blob.class); + try { + assertArrayEquals(new byte[] {0x63, (byte) 0xC3, 0x4D, 0x6B, (byte) 0xCA, (byte) 0xD5, 0x55, (byte) 0xEB, 0x64, (byte) 0xBF, 0x7E, (byte) 0x84, (byte) 0x8D, 0x02, (byte) 0xC3, 0x76}, + blob.getBytes(1, 16)); + } finally { + blob.free(); + } + + Clob clob; + clob = rs.getObject(8, Clob.class); + try { + assertEquals("text", clob.getSubString(1, 4)); + } finally { + clob.free(); + } + + NClob nclob; + nclob = rs.getObject(9, NClob.class); + try { + assertEquals("ntext", nclob.getSubString(1, 5)); + } finally { + nclob.free(); + } + + assertArrayEquals(new byte[] {0x63, (byte) 0xC3, 0x4D, 0x6B, (byte) 0xCA, (byte) 0xD5, 0x55, (byte) 0xEB, 0x64, (byte) 0xBF, 0x7E, (byte) 0x84, (byte) 0x8D, 0x02, (byte) 0xC3, 0x76}, + rs.getObject(10, byte[].class)); + + assertEquals(java.sql.Date.valueOf("2017-05-19"), rs.getObject(11, java.sql.Date.class)); + assertEquals(java.sql.Date.valueOf("2017-05-19"), rs.getObject("col11", java.sql.Date.class)); + + java.sql.Time expectedTime = new java.sql.Time(java.sql.Time.valueOf("10:47:15").getTime() + 123L); + assertEquals(expectedTime, rs.getObject(12, java.sql.Time.class)); + assertEquals(expectedTime, rs.getObject("col12", java.sql.Time.class)); + + assertEquals(java.sql.Timestamp.valueOf("2017-05-19 10:47:15.1234567"), rs.getObject(13, java.sql.Timestamp.class)); + assertEquals(java.sql.Timestamp.valueOf("2017-05-19 10:47:15.1234567"), rs.getObject("col13", java.sql.Timestamp.class)); + + assertEquals("2017-05-19 10:47:15.1234567 +02:00", rs.getObject(14, microsoft.sql.DateTimeOffset.class).toString()); + assertEquals("2017-05-19 10:47:15.1234567 +02:00", rs.getObject("col14", microsoft.sql.DateTimeOffset.class).toString()); + + + // test null values, mostly to verify primitive wrappers do not return default values + assertTrue(rs.next()); + assertNull(rs.getObject("col1", Boolean.class)); + assertNull(rs.getObject(1, Boolean.class)); + assertNull(rs.getObject("col1", Byte.class)); + assertNull(rs.getObject(1, Byte.class)); + assertNull(rs.getObject("col1", Short.class)); + assertNull(rs.getObject(1, Short.class)); + assertNull(rs.getObject(1, Integer.class)); + assertNull(rs.getObject("col1", Integer.class)); + assertNull(rs.getObject(1, Long.class)); + assertNull(rs.getObject("col1", Long.class)); + + assertNull(rs.getObject(2, String.class)); + assertNull(rs.getObject("col2", String.class)); + + assertNull(rs.getObject(3, Float.class)); + assertNull(rs.getObject("col3", Float.class)); + assertNull(rs.getObject(3, Double.class)); + assertNull(rs.getObject("col3", Double.class)); + + assertNull(rs.getObject(4, BigDecimal.class)); + assertNull(rs.getObject("col4", BigDecimal.class)); + + assertNull(rs.getObject(5, UUID.class)); + assertNull(rs.getObject("col5", UUID.class)); + + assertNull(rs.getObject(6, SQLXML.class)); + assertNull(rs.getObject("col6", SQLXML.class)); + + assertNull(rs.getObject(7, Blob.class)); + assertNull(rs.getObject("col7", Blob.class)); + + assertNull(rs.getObject(8, Clob.class)); + assertNull(rs.getObject("col8", Clob.class)); + + assertNull(rs.getObject(9, NClob.class)); + assertNull(rs.getObject("col9", NClob.class)); + + assertNull(rs.getObject(10, byte[].class)); + assertNull(rs.getObject("col10", byte[].class)); + + assertNull(rs.getObject(11, java.sql.Date.class)); + assertNull(rs.getObject("col11", java.sql.Date.class)); + + assertNull(rs.getObject(12, java.sql.Time.class)); + assertNull(rs.getObject("col12", java.sql.Time.class)); + + assertNull(rs.getObject(13, java.sql.Timestamp.class)); + assertNull(rs.getObject("col14", java.sql.Timestamp.class)); + + assertNull(rs.getObject(14, microsoft.sql.DateTimeOffset.class)); + assertNull(rs.getObject("col14", microsoft.sql.DateTimeOffset.class)); + + assertFalse(rs.next()); + } + } finally { stmt.executeUpdate("drop table " + tableName); } - catch (Exception ex) { - fail(ex.toString()); - } - } - finally { - stmt.close(); - con.close(); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java index da7e3eab1..d6915079f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java @@ -7,22 +7,29 @@ */ package com.microsoft.sqlserver.jdbc.unit.statement; +import static org.junit.Assert.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.Blob; import java.sql.CallableStatement; +import java.sql.Clob; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLXML; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.Random; +import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -1177,55 +1184,133 @@ public class TCStatementCallable { */ @Test public void testJdbc41CallableStatementMethods() throws Exception { - assumeTrue("JDBC41".equals(Utils.getConfiguredProperty("JDBC_Version")), "Aborting test case as JDBC version is not compatible. "); Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); // Prepare database setup String name = RandomUtil.getIdentifier("p1"); String procName = AbstractSQLGenerator.escapeIdentifier(name); - Connection conn = DriverManager.getConnection(connectionString); - Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); - try { - Utils.dropProcedureIfExists(procName, stmt); - } - catch (Exception ex) { - } - ; - String query = "create procedure " + procName - + " @col1Value varchar(512) OUTPUT, @col2Value varchar(512) OUTPUT AS BEGIN SET @col1Value='hello' SET @col2Value='world' END"; - stmt.execute(query); + try (Connection conn = DriverManager.getConnection(connectionString); + Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) { + String query = "create procedure " + procName + + " @col1Value varchar(512) OUTPUT," + + " @col2Value int OUTPUT," + + " @col3Value float OUTPUT," + + " @col4Value decimal(10,5) OUTPUT," + + " @col5Value uniqueidentifier OUTPUT," + + " @col6Value xml OUTPUT," + + " @col7Value varbinary(max) OUTPUT," + + " @col8Value text OUTPUT," + + " @col9Value ntext OUTPUT," + + " @col10Value varbinary(max) OUTPUT," + + " @col11Value date OUTPUT," + + " @col12Value time OUTPUT," + + " @col13Value datetime2 OUTPUT," + + " @col14Value datetimeoffset OUTPUT" + + " AS BEGIN " + + " SET @col1Value = 'hello'" + + " SET @col2Value = 1" + + " SET @col3Value = 2.0" + + " SET @col4Value = 123.45" + + " SET @col5Value = '6F9619FF-8B86-D011-B42D-00C04FC964FF'" + + " SET @col6Value = ''" + + " SET @col7Value = 0x63C34D6BCAD555EB64BF7E848D02C376" + + " SET @col8Value = 'text'" + + " SET @col9Value = 'ntext'" + + " SET @col10Value = 0x63C34D6BCAD555EB64BF7E848D02C376" + + " SET @col11Value = '2017-05-19'" + + " SET @col12Value = '10:47:15.1234567'" + + " SET @col13Value = '2017-05-19T10:47:15.1234567'" + + " SET @col14Value = '2017-05-19T10:47:15.1234567+02:00'" + + " END"; + stmt.execute(query); + + // Test JDBC 4.1 methods for CallableStatement + try (CallableStatement cstmt = conn.prepareCall("{call " + procName + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}")) { + cstmt.registerOutParameter(1, java.sql.Types.VARCHAR); + cstmt.registerOutParameter(2, java.sql.Types.INTEGER); + cstmt.registerOutParameter(3, java.sql.Types.FLOAT); + cstmt.registerOutParameter(4, java.sql.Types.DECIMAL); + cstmt.registerOutParameter(5, microsoft.sql.Types.GUID); + cstmt.registerOutParameter(6, java.sql.Types.SQLXML); + cstmt.registerOutParameter(7, java.sql.Types.VARBINARY); + cstmt.registerOutParameter(8, java.sql.Types.CLOB); + cstmt.registerOutParameter(9, java.sql.Types.NCLOB); + cstmt.registerOutParameter(10, java.sql.Types.VARBINARY); + cstmt.registerOutParameter(11, java.sql.Types.DATE); + cstmt.registerOutParameter(12, java.sql.Types.TIME); + cstmt.registerOutParameter(13, java.sql.Types.TIMESTAMP); + cstmt.registerOutParameter(14, java.sql.Types.TIMESTAMP_WITH_TIMEZONE); + cstmt.execute(); + + assertEquals("hello", cstmt.getObject(1, String.class)); + assertEquals("hello", cstmt.getObject("col1Value", String.class)); + + assertEquals(Integer.valueOf(1), cstmt.getObject(2, Integer.class)); + assertEquals(Integer.valueOf(1), cstmt.getObject("col2Value", Integer.class)); + + assertEquals(2.0f, cstmt.getObject(3, Float.class), 0.0001f); + assertEquals(2.0f, cstmt.getObject("col3Value", Float.class), 0.0001f); + assertEquals(2.0d, cstmt.getObject(3, Double.class), 0.0001d); + assertEquals(2.0d, cstmt.getObject("col3Value", Double.class), 0.0001d); + + // BigDecimal#equals considers the number of decimal places + assertEquals(0, cstmt.getObject(4, BigDecimal.class).compareTo(new BigDecimal("123.45"))); + assertEquals(0, cstmt.getObject("col4Value", BigDecimal.class).compareTo(new BigDecimal("123.45"))); + + assertEquals(UUID.fromString("6F9619FF-8B86-D011-B42D-00C04FC964FF"), cstmt.getObject(5, UUID.class)); + assertEquals(UUID.fromString("6F9619FF-8B86-D011-B42D-00C04FC964FF"), cstmt.getObject("col5Value", UUID.class)); + + SQLXML sqlXml; + sqlXml = cstmt.getObject(6, SQLXML.class); + try { + assertEquals("", sqlXml.getString()); + } finally { + sqlXml.free(); + } - // Test JDBC 4.1 methods for CallableStatement - CallableStatement cstmt = conn.prepareCall("{call " + procName + "(?, ?)}"); - cstmt.registerOutParameter(1, java.sql.Types.VARCHAR); - cstmt.registerOutParameter(2, java.sql.Types.VARCHAR); - cstmt.execute(); + Blob blob; + blob = cstmt.getObject(7, Blob.class); + try { + assertArrayEquals(new byte[] {0x63, (byte) 0xC3, 0x4D, 0x6B, (byte) 0xCA, (byte) 0xD5, 0x55, (byte) 0xEB, 0x64, (byte) 0xBF, 0x7E, (byte) 0x84, (byte) 0x8D, 0x02, (byte) 0xC3, 0x76}, + blob.getBytes(1, 16)); + } finally { + blob.free(); + } - try { - String out1 = cstmt.getObject(1, String.class); - } - catch (Exception e) { + Clob clob; + clob = cstmt.getObject(8, Clob.class); + try { + assertEquals("text", clob.getSubString(1, 4)); + } finally { + clob.free(); + } - fail(e.toString()); + NClob nclob; + nclob = cstmt.getObject(9, NClob.class); + try { + assertEquals("ntext", nclob.getSubString(1, 5)); + } finally { + nclob.free(); + } - } - try { - String out2 = cstmt.getObject("col2Value", String.class); - } - catch (Exception e) { + assertArrayEquals(new byte[] {0x63, (byte) 0xC3, 0x4D, 0x6B, (byte) 0xCA, (byte) 0xD5, 0x55, (byte) 0xEB, 0x64, (byte) 0xBF, 0x7E, (byte) 0x84, (byte) 0x8D, 0x02, (byte) 0xC3, 0x76}, + cstmt.getObject(10, byte[].class)); + assertEquals(java.sql.Date.valueOf("2017-05-19"), cstmt.getObject(11, java.sql.Date.class)); + assertEquals(java.sql.Date.valueOf("2017-05-19"), cstmt.getObject("col11Value", java.sql.Date.class)); - fail(e.toString()); - } + java.sql.Time expectedTime = new java.sql.Time(java.sql.Time.valueOf("10:47:15").getTime() + 123L); + assertEquals(expectedTime, cstmt.getObject(12, java.sql.Time.class)); + assertEquals(expectedTime, cstmt.getObject("col12Value", java.sql.Time.class)); - try { - Utils.dropProcedureIfExists(procName, stmt); - } - catch (Exception ex) { + assertEquals(java.sql.Timestamp.valueOf("2017-05-19 10:47:15.1234567"), cstmt.getObject(13, java.sql.Timestamp.class)); + assertEquals(java.sql.Timestamp.valueOf("2017-05-19 10:47:15.1234567"), cstmt.getObject("col13Value", java.sql.Timestamp.class)); + + assertEquals("2017-05-19 10:47:15.1234567 +02:00", cstmt.getObject(14, microsoft.sql.DateTimeOffset.class).toString()); + assertEquals("2017-05-19 10:47:15.1234567 +02:00", cstmt.getObject("col14Value", microsoft.sql.DateTimeOffset.class).toString()); + } finally { + Utils.dropProcedureIfExists(procName, stmt); + } } - ; - stmt.close(); - cstmt.close(); - conn.close(); } }