From fec3b3e74e970f3652c566ad1c7d627ac12f6a59 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 3 Mar 2017 15:06:10 -0800 Subject: [PATCH 1/5] added lobs Test and changes to test framework --- .../sqlserver/jdbc/unit/lobs/lobsTest.java | 585 ++++++++++++++++++ .../testframework/DBCallableStatement.java | 10 +- .../sqlserver/testframework/DBCoercion.java | 66 ++ .../sqlserver/testframework/DBCoercions.java | 29 + .../sqlserver/testframework/DBColumn.java | 8 +- .../sqlserver/testframework/DBConnection.java | 24 +- .../testframework/DBInvalidUtil.java | 409 ++++++++++++ .../sqlserver/testframework/DBItems.java | 41 ++ .../testframework/DBPreparedStatement.java | 31 +- .../sqlserver/testframework/DBResultSet.java | 93 ++- .../sqlserver/testframework/DBStatement.java | 19 +- .../sqlserver/testframework/DBTable.java | 12 +- .../sqlserver/testframework/Utils.java | 170 ++++- .../testframework/sqlType/SqlBigInt.java | 5 +- .../testframework/sqlType/SqlBinary.java | 7 +- .../testframework/sqlType/SqlBit.java | 3 +- .../testframework/sqlType/SqlChar.java | 11 +- .../testframework/sqlType/SqlDate.java | 3 +- .../testframework/sqlType/SqlDateTime.java | 2 +- .../sqlType/SqlDateTimeOffset.java | 1 + .../testframework/sqlType/SqlDecimal.java | 2 +- .../testframework/sqlType/SqlFloat.java | 7 +- .../testframework/sqlType/SqlInt.java | 3 +- .../testframework/sqlType/SqlNVarCharMax.java | 32 + .../testframework/sqlType/SqlNumber.java | 8 +- .../testframework/sqlType/SqlReal.java | 2 +- .../testframework/sqlType/SqlSmallInt.java | 3 +- .../testframework/sqlType/SqlTime.java | 1 + .../testframework/sqlType/SqlTinyInt.java | 3 +- .../testframework/sqlType/SqlType.java | 100 ++- .../testframework/sqlType/SqlVarBinary.java | 22 +- .../sqlType/SqlVarBinaryMax.java | 27 + .../testframework/sqlType/SqlVarCharMax.java | 28 + .../sqlType/VariableLengthType.java | 9 +- 34 files changed, 1716 insertions(+), 60 deletions(-) create mode 100644 src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/DBCoercions.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/DBInvalidUtil.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/DBItems.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNVarCharMax.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinaryMax.java create mode 100644 src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarCharMax.java diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java new file mode 100644 index 000000000..b88ac0cde --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java @@ -0,0 +1,585 @@ +/* + * 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.unit.lobs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.Reader; +import java.sql.Blob; +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.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.Executable; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.DBCoercion; +import com.microsoft.sqlserver.testframework.DBColumn; +import com.microsoft.sqlserver.testframework.DBConnection; +import com.microsoft.sqlserver.testframework.DBInvalidUtil; +import com.microsoft.sqlserver.testframework.DBResultSet; +import com.microsoft.sqlserver.testframework.DBStatement; +import com.microsoft.sqlserver.testframework.DBTable; +import com.microsoft.sqlserver.testframework.Utils; +import com.microsoft.sqlserver.testframework.Utils.DBBinaryStream; +import com.microsoft.sqlserver.testframework.Utils.DBCharacterStream; +import com.microsoft.sqlserver.testframework.sqlType.SqlType; +import com.microsoft.sqlserver.testframework.util.RandomUtil; + +/** + * This class tests lobs (Blob, Clob and NClob) and their APIs + * + */ +@RunWith(JUnitPlatform.class) +public class lobsTest extends AbstractTest { + static Connection conn = null; + static Statement stmt = null; + static String tableName; + static String escapedTableName; + int datasize; + int packetSize = 1000; + int precision = 2000; + long streamLength = -1; // Used to verify exceptions + public static final Logger log = Logger.getLogger("lobs"); + Class lobClass = null; + boolean isResultSet = false; + DBTable table = null; + + private static final int clobType = 0; + private static final int nClobType = 1; + private static final int blobType = 2; + + @BeforeAll + public static void init() throws SQLException { + conn = DriverManager.getConnection(connectionString); + stmt = conn.createStatement(); + + tableName = RandomUtil.getIdentifier("LOBS"); + escapedTableName = AbstractSQLGenerator.escapeIdentifier(tableName); + } + + @AfterAll + public static void terminate() throws SQLException { + if (null != conn) + conn.close(); + if (null != stmt) + stmt.close(); + } + + @TestFactory + public Collection executeDynamicTests() { + List classes = new ArrayList<>(Arrays.asList(Blob.class, Clob.class, DBBinaryStream.class, DBCharacterStream.class)); + List isResultSetTypes = new ArrayList<>(Arrays.asList(true, false)); + Collection dynamicTests = new ArrayList<>(); + + for (int i = 0; i < classes.size(); i++) { + for (int j = 0; j < isResultSetTypes.size(); j++) { + Class lobClass = classes.get(i); + boolean isResultSet = isResultSetTypes.get(j); + Executable exec = () -> testInvalidLobs(lobClass, isResultSet); + // create a test display name + String testName = " Test: " + lobClass + (isResultSet ? " isResultSet" : " isPreparedStatement"); + // create dynamic test + DynamicTest dTest = DynamicTest.dynamicTest(testName, exec); + + // add the dynamic test to collection + dynamicTests.add(dTest); + } + } + return dynamicTests; + } + + /** + * Tests invalid lobs + * + * @param lobClass + * @param isResultSet + * @throws SQLException + */ + private void testInvalidLobs(Class lobClass, + boolean isResultSet) throws SQLException { + String clobTypes[] = {"varchar(max)", "nvarchar(max)"}; + String blobTypes[] = {"varbinary(max)"}; + int choose = ThreadLocalRandom.current().nextInt(3); + switch (choose) { + case 0: + datasize = packetSize; + break; + case 1: + datasize = packetSize + ThreadLocalRandom.current().nextInt(packetSize) + 1; + break; + default: + datasize = packetSize - ThreadLocalRandom.current().nextInt(packetSize); + } + + int coercionType = isResultSet ? DBCoercion.UPDATE : DBCoercion.SET; + try { + if (clobType == classType(lobClass) || nClobType == classType(lobClass)) { + table = this.createTable(table, clobTypes, true); + } + else { + table = this.createTable(table, blobTypes, true); + } + Object updater; + for (int i = 0; i < table.getColumns().size(); i++) { + DBColumn col = table.getColumns().get(i); + if (!col.getSqlType().canconvert(lobClass, coercionType, new DBConnection(connectionString))) + continue; + // re-create LOB since it might get closed + Object lob = this.createLob(lobClass); + if (isResultSet) { + Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + updater = stmt.executeQuery( + "Select " + table.getEscapedTableName() + ".[" + col.getColumnName() + "]" + " from " + table.getEscapedTableName()); + ((ResultSet) updater).next(); + } + else + updater = conn.prepareStatement("update " + table.getEscapedTableName() + " set " + ".[" + col.getColumnName() + "]" + "=?"); + try { + this.updateLob(lob, updater, 1); + } + catch (SQLException e) { + boolean verified = false; + + if (lobClass == Clob.class) + streamLength = ((DBInvalidUtil.InvalidClob) lob).length; + else if (lobClass == Blob.class) + streamLength = ((DBInvalidUtil.InvalidBlob) lob).length; + + // Case 1: Invalid length value is passed as LOB length + if (streamLength < 0 || streamLength == Long.MAX_VALUE) { + // Applies to all LOB types ("The length {0} is not valid} + assertTrue(e.getMessage().startsWith("The length"), "Unexpected message thrown : " + e.getMessage()); + assertTrue(e.getMessage().endsWith("is not valid."), "Unexpected message thrown : " + e.getMessage()); + verified = true; + + } + + // Case 2: CharacterStream or Clob.getCharacterStream threw IOException + if (lobClass == DBCharacterStream.class || (lobClass == Clob.class && ((DBInvalidUtil.InvalidClob) lob).stream != null)) { + DBInvalidUtil.InvalidCharacterStream stream = lobClass == DBCharacterStream.class + ? ((DBInvalidUtil.InvalidCharacterStream) lob) : ((DBInvalidUtil.InvalidClob) lob).stream; + if (stream.threwException) { + // CharacterStream threw IOException + String[] args = {"java.io.IOException: " + DBInvalidUtil.InvalidCharacterStream.IOExceptionMsg}; + assertTrue(e.getMessage().contains(args[0])); + verified = true; + + } + } + if (!verified) { + // Odd CharacterStream length will throw this exception + if (!e.getMessage().contains("The stream value is not the specified length. The specified length was")) + + { + if (lobClass == DBCharacterStream.class || lobClass == DBBinaryStream.class) + assertTrue(e.getSQLState() != null, "SQLState should not be null"); + assertTrue(e.getMessage().contains("An error occurred while reading the value from the stream object. Error:")); + } + + } + } + } + } + catch (Exception e) { + this.dropTables(table); + e.printStackTrace(); + } + } + + @Test + @DisplayName("testMultipleCloseCharacterStream") + public void testMultipleCloseCharacterStream() throws Exception { + testMultipleClose(DBCharacterStream.class); + } + + @Test + @DisplayName("MultipleCloseBinaryStream") + public void MultipleCloseBinaryStream() throws Exception { + testMultipleClose(DBBinaryStream.class); + } + + /** + * Tests stream closures + * + * @param streamClass + * @throws Exception + */ + private void testMultipleClose(Class streamClass) throws Exception { + DBConnection conn = new DBConnection(connectionString); + String[] types = {"varchar(max)", "nvarchar(max)", "varbinary(max)"}; + try { + table = this.createTable(table, types, true); + + DBStatement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + String query = "select * from " + table.getEscapedTableName(); + DBResultSet rs = stmt.executeQuery(query); + while (rs.next()) { + for (int i = 0; i < 3; i++) { + DBColumn col = table.getColumns().get(i); + if (!col.getSqlType().canconvert(streamClass, DBCoercion.GET, new DBConnection(connectionString))) + continue; + Object stream = rs.getXXX(i + 1, streamClass); + if (stream == null) { + assertEquals(stream, rs.getObject(i + 1), "Stream is null when data is not"); + } + else { + // close the stream twice + if (streamClass == DBCharacterStream.class) { + ((Reader) stream).close(); + ((Reader) stream).close(); + } + else { + ((InputStream) stream).close(); + ((InputStream) stream).close(); + } + } + } + } + } + finally { + if (null != table) + this.dropTables(table); + if (null != null) + conn.close(); + } + } + + /** + * Tests Insert Retrive on nclob + * @throws Exception + */ + @Test + @DisplayName("testlLobs_InsertRetrive") + public void testNClob() throws Exception { + String types[] = {"nvarchar(max)"}; + testlLobs_InsertRetrive(types, NClob.class); + } + + /** + * Tests Insert Retrive on blob + * @throws Exception + */ + @Test + @DisplayName("testlLobs_InsertRetrive") + public void testBlob() throws Exception { + String types[] = {"varbinary(max)"}; + testlLobs_InsertRetrive(types, Blob.class); + } + + /** + * Tests Insert Retrive on clob + * @throws Exception + */ + @Test + @DisplayName("testlLobs_InsertRetrive") + public void testClob() throws Exception { + String types[] = {"varchar(max)"}; + testlLobs_InsertRetrive(types, Clob.class); + } + + private void testlLobs_InsertRetrive(String types[], + Class lobClass) throws Exception { + table = createTable(table, types, false); // create empty table + int size = 10000; + + byte[] data = new byte[size]; + ThreadLocalRandom.current().nextBytes(data); + + Clob clob = null; + Blob blob = null; + NClob nclob = null; + InputStream stream = null; + PreparedStatement ps = conn.prepareStatement("INSERT INTO " + table.getEscapedTableName() + " VALUES(?)"); + if (clobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + clob = conn.createClob(); + clob.setString(1, stringData); + ps.setClob(1, clob); + } + else if (nClobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + nclob = conn.createNClob(); + nclob.setString(1, stringData); + ps.setNClob(1, nclob); + } + + else { + blob = conn.createBlob(); + blob.setBytes(1, data); + ps.setBlob(1, blob); + } + ps.executeUpdate(); + + byte[] chunk = new byte[size]; + ResultSet rs = stmt.executeQuery("select * from " + table.getEscapedTableName()); + while (rs.next()) { + if (clobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + clob = conn.createClob(); + clob.setString(1, stringData); + rs.getClob(1); + stream = clob.getAsciiStream(); + assertEquals(clob.length(), size); + + } + else if (nClobType == classType(lobClass)) { + nclob = rs.getNClob(1); + stream = nclob.getAsciiStream(); + assertEquals(nclob.length(), size); + BufferedInputStream is = new BufferedInputStream(stream); + is.read(chunk); + assertEquals(chunk.length, size); + } + else { + blob = rs.getBlob(1); + stream = blob.getBinaryStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int read = 0; + while ((read = stream.read(chunk)) > 0) + buffer.write(chunk, 0, read); + assertEquals(chunk.length, size); + + } + + } + + if (null != clob) + clob.free(); + if (null != blob) + blob.free(); + if (null != nclob) + nclob.free(); + dropTables(table); + } + + @Test + @DisplayName("testUpdatorNClob") + public void testUpdatorNClob() throws Exception { + String types[] = {"nvarchar(max)"}; + testUpdateLobs(types, NClob.class); + } + + @Test + @DisplayName("testUpdatorBlob") + public void testUpdatorBlob() throws Exception { + String types[] = {"varbinary(max)"}; + testUpdateLobs(types, Blob.class); + } + + @Test + @DisplayName("testUpdatorClob") + public void testUpdatorClob() throws Exception { + String types[] = {"varchar(max)"}; + testUpdateLobs(types, Clob.class); + } + + private void testUpdateLobs(String types[], + Class lobClass) throws Exception { + table = createTable(table, types, false); // create empty table + int size = 10000; + + byte[] data = new byte[size]; + ThreadLocalRandom.current().nextBytes(data); + + Clob clob = null; + Blob blob = null; + NClob nclob = null; + InputStream stream = null; + PreparedStatement ps = conn.prepareStatement("INSERT INTO " + table.getEscapedTableName() + " VALUES(?)"); + if (clobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + clob = conn.createClob(); + clob.setString(1, stringData); + ps.setClob(1, clob); + } + else if (nClobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + nclob = conn.createNClob(); + nclob.setString(1, stringData); + ps.setNClob(1, nclob); + } + + else { + blob = conn.createBlob(); + blob.setBytes(1, data); + ps.setBlob(1, blob); + } + ps.executeUpdate(); + + Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); + ResultSet rs = stmt.executeQuery("select * from " + table.getEscapedTableName()); + while (rs.next()) { + if (clobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + clob = conn.createClob(); + clob.setString(1, stringData); + rs.updateClob(1, clob); + } + else if (nClobType == classType(lobClass)) { + String stringData = new String(data); + size = stringData.length(); + nclob = conn.createNClob(); + nclob.setString(1, stringData); + rs.updateClob(1, nclob); + } + else { + blob = conn.createBlob(); + rs.updateBlob(1, blob); + + } + rs.updateRow(); + } + if (null != clob) + clob.free(); + if (null != blob) + blob.free(); + if (null != nclob) + nclob.free(); + dropTables(table); + + } + + private int classType(Class type) { + if (Clob.class == type) + return clobType; + else if (NClob.class == type) + return nClobType; + else + return blobType; + } + + private void updateLob(Object lob, + Object updater, + int index) throws Exception { + if (updater instanceof PreparedStatement) + this.updatePreparedStatement((PreparedStatement) updater, lob, index, (int) streamLength); + else + this.updateResultSet((ResultSet) updater, lob, index, (int) streamLength); + } + + private void updatePreparedStatement(PreparedStatement ps, + Object lob, + int index, + int length) throws Exception { + if (lob instanceof DBCharacterStream) + ps.setCharacterStream(index, (DBCharacterStream) lob, length); + else if (lob instanceof DBBinaryStream) + ps.setBinaryStream(index, (InputStream) lob, length); + else if (lob instanceof Clob) + ps.setClob(index, (Clob) lob); + else + ps.setBlob(index, (Blob) lob); + assertEquals(ps.executeUpdate(), 1, "ExecuteUpdate did not return the correct updateCount"); + } + + private void updateResultSet(ResultSet rs, + Object lob, + int index, + int length) throws Exception { + if (lob instanceof DBCharacterStream) { + rs.updateCharacterStream(index, (DBCharacterStream) lob, length); + } + else if (lob instanceof DBBinaryStream) { + rs.updateBinaryStream(index, (InputStream) lob, length); + } + else if (lob instanceof Clob) { + rs.updateClob(index, (Clob) lob); + } + else { + rs.updateBlob(index, (Blob) lob); + } + rs.updateRow(); + } + + private Object createLob(Class lobClass) { + // Randomly indicate negative length + streamLength = ThreadLocalRandom.current().nextInt(3) < 2 ? datasize : -1 - ThreadLocalRandom.current().nextInt(datasize); + // For streams -1 means any length, avoid to ensure that an exception is always thrown + if (streamLength == -1 && (lobClass == DBCharacterStream.class || lobClass == DBBinaryStream.class)) + streamLength = datasize; + log.fine("Length passed into update : " + streamLength); + + byte[] data = new byte[datasize]; + ThreadLocalRandom.current().nextBytes(data); + + if (lobClass == DBCharacterStream.class) + return new DBInvalidUtil().new InvalidCharacterStream(new String(data), streamLength < -1); + else if (lobClass == DBBinaryStream.class) + return new DBInvalidUtil().new InvalidBinaryStream(data, streamLength < -1); + if (lobClass == Clob.class) { + ArrayList types = Utils.types(); + SqlType type = Utils.find(String.class); + Object expected = type.createdata(String.class, data); + return new DBInvalidUtil().new InvalidClob(expected, false); + } + else { + ArrayList types = Utils.types(); + SqlType type = Utils.find(byte[].class); + Object expected = type.createdata(type.getClass(), data); + return new DBInvalidUtil().new InvalidBlob(expected, false); + } + + } + + private static DBTable createTable(DBTable table, + String[] types, + boolean populateTable) throws Exception { + + DBStatement stmt = new DBConnection(connectionString).createStatement(); + table = new DBTable(false); + + for (int i = 0; i < types.length; i++) { + SqlType type = Utils.find(types[i]); + table.addColumn(type); + + } + stmt.createTable(table); + if (populateTable) { + stmt.populateTable(table); + } + stmt.close(); + + return table; + } + + private static void dropTables(DBTable table) throws SQLException { + stmt.executeUpdate("if object_id('" + table.getEscapedTableName() + "','U') is not null" + " drop table " + table.getEscapedTableName()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java b/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java index f832f453a..b17c98563 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java @@ -1,5 +1,9 @@ -/** +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) 2016 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.testframework; @@ -9,7 +13,7 @@ import java.sql.SQLException; /** - * @author v-afrafi + * Wrapper class CallableStatement * */ public class DBCallableStatement extends AbstractParentWrapper{ @@ -66,4 +70,4 @@ public void registerOutParameter(int index, int sqltype) throws SQLException } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java b/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java new file mode 100644 index 000000000..505cf1b25 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBCoercion.java @@ -0,0 +1,66 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) 2016 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.testframework; + +import java.util.BitSet; + +public class DBCoercion { + Class type = null; + protected BitSet flags = new BitSet(); + protected String name = null; + // Flags + + public static final int GET = 1; + public static final int UPDATE = 2; + public static final int SET = 3; + public static final int SETOBJECT = 4; + public static final int REG = 5; + public static final int GETPARAM = 6; + public static final int UPDATEOBJECT = 7; + public static final int ALL = 8; + + public static final int STREAM = 9; + public static final int CHAR = 10; + public static final int NCHAR = 11; + public static final int ASCII = 12; + + /** + * + * @param type + */ + public DBCoercion(Class type) { + this(type, new int[] {GET}); + } + + /** + * + * @param type + * @param tempflags + */ + public DBCoercion(Class type, + int[] tempflags) { + name = type.toString(); + type = type; + for (int i = 0; i < tempflags.length; i++) + flags.set(tempflags[i]); + } + + /** + * @return type + */ + public Class type() { + return type; + } + + /** + * @return + */ + public BitSet flags() { + return flags; + } +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBCoercions.java b/src/test/java/com/microsoft/sqlserver/testframework/DBCoercions.java new file mode 100644 index 000000000..c333d71df --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBCoercions.java @@ -0,0 +1,29 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) 2016 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.testframework; + +import java.util.ArrayList; + +/** + * DBCoercions + * + */ +public class DBCoercions extends DBItems { + + /** + * constructor + */ + public DBCoercions() { + coercionsList = new ArrayList(); + } + + public DBCoercions(DBCoercion coercion) { + this.add(coercion); + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBColumn.java b/src/test/java/com/microsoft/sqlserver/testframework/DBColumn.java index ac6107027..7528e9e5d 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBColumn.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBColumn.java @@ -17,7 +17,7 @@ /** * This class holds data for Column. Think about encrypted columns. createCMK code should not add here. */ -class DBColumn { +public class DBColumn { /* * TODO: add nullable, defaultValue, alwaysEncrypted @@ -35,7 +35,7 @@ class DBColumn { /** * @return the columnName */ - String getColumnName() { + public String getColumnName() { return columnName; } @@ -51,7 +51,7 @@ void setColumnName(String columnName) { * * @return SqlType for the column */ - SqlType getSqlType() { + public SqlType getSqlType() { return sqlType; } @@ -93,4 +93,4 @@ Object getRowValue(int row) { return columnValues.get(row); } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java b/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java index 1ba90ea34..51e0c7aa2 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java @@ -14,7 +14,6 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import com.microsoft.sqlserver.jdbc.SQLServerConnection; import com.microsoft.sqlserver.jdbc.SQLServerException; @@ -116,6 +115,26 @@ public DBPreparedStatement prepareStatement(String query) throws SQLException { return dbpstmt.prepareStatement(query); } + /** + * + * @param query + * @param type + * @param concurrency + * @return + * @throws SQLException + */ + public DBPreparedStatement prepareStatement(String query, + int type, + int concurrency) throws SQLException { + // Static for fast-forward, limited settings + if ((type == ResultSet.TYPE_FORWARD_ONLY || type == ResultSet.TYPE_SCROLL_INSENSITIVE)) + concurrency = ResultSet.CONCUR_READ_ONLY; + + DBPreparedStatement dbpstmt = new DBPreparedStatement(this); + + return dbpstmt.prepareStatement(query, type, concurrency); + } + /** * close connection */ @@ -188,6 +207,7 @@ public DBCallableStatement prepareCall(String query) throws SQLException { /** * Retrieve server version + * * @return server version * @throws Exception */ @@ -225,4 +245,4 @@ public double getServerVersion() throws Exception { return serverversion; } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBInvalidUtil.java b/src/test/java/com/microsoft/sqlserver/testframework/DBInvalidUtil.java new file mode 100644 index 000000000..3642c1ddb --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBInvalidUtil.java @@ -0,0 +1,409 @@ +/* + * 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.testframework; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; + +import com.microsoft.sqlserver.testframework.Utils.DBBinaryStream; +import com.microsoft.sqlserver.testframework.Utils.DBCharacterStream; + +/** + * Prepared invalid stream types + */ +public class DBInvalidUtil { + public static final Logger log = Logger.getLogger("DBInvalidUtils"); + + /** + * + * InvalidClob : stream with invalid behavior 1) getCharacterStream returns InvalidCharacterStream 2) length returns invalid lengths + * + */ + public class InvalidClob implements Clob { + final int diff = 5; + + Object expected = null; + public long length; + private long actualLength; + private long invalidLength = -1; + private boolean returnValid = false; + public InvalidCharacterStream stream = null; // keep a handle on any stream + + /** + * Constructor + * + * @param expected + * @param returnValid + */ + public InvalidClob(Object expected, + boolean returnValid) { + this.expected = expected; + actualLength = this.value().length(); + this.returnValid = returnValid; + } + + protected String value() { + return ((String) expected); + } + + /** + * return length + */ + public long length() throws SQLException { + long ret = actualLength; + long actual = ret; + if (invalidLength == -1) { + int choose = ThreadLocalRandom.current().nextInt(5); + int randomInt = 1 + ThreadLocalRandom.current().nextInt(diff); + + switch (choose) { + case 0: // more than ret + actual = ret + randomInt; + break; + case 1: // less than ret + actual = ret - randomInt; + break; + case 2: // 0 + actual = 0; + break; + case 3: // return > SQL Server Limit + actual = Long.MAX_VALUE; + break; + default: // always < -1 + actual = -1 - randomInt; + } + invalidLength = actual; + length = actual; + returnValid = true; + } + + log.fine("invalidClob.length(): Actual chars=" + actualLength + " Returned chars=" + length); + return length; + } + + public Reader getCharacterStream() throws SQLException { + stream = new InvalidCharacterStream(this.value(), returnValid); + return stream; + } + + @Override + public String getSubString(long pos, + int length) throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + + @Override + public InputStream getAsciiStream() throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + + @Override + public long position(String searchstr, + long start) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public long position(Clob searchstr, + long start) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public int setString(long pos, + String str) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public int setString(long pos, + String str, + int offset, + int len) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public OutputStream setAsciiStream(long pos) throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + + @Override + public Writer setCharacterStream(long pos) throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + + @Override + public void truncate(long len) throws SQLException { + assertTrue(false, "Not implemented"); + + } + + @Override + public void free() throws SQLException { + assertTrue(false, "Not implemented"); + + } + + @Override + public Reader getCharacterStream(long pos, + long length) throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + } + + /** + * + * invalidCharacterStream : stream with invalid behavior 1) Read can throw IOException 2) Read can return data length > or < than actual + */ + public class InvalidCharacterStream extends DBCharacterStream { + final int diff = 5; + + private boolean returnValid = false; // Perfom invalid actions at most once + public boolean threwException = false; + public static final String IOExceptionMsg = "invalidCharacterStream.read() throws IOException"; + + // Constructor + public InvalidCharacterStream(String value, + boolean returnValid) { + super(value); + this.returnValid = returnValid; + } + + public int read(char[] cbuf, + int off, + int len) throws IOException { + int ret = super.read(cbuf, off, len); + int actual = ret; + if (!returnValid) { + int choose = ThreadLocalRandom.current().nextInt(5); + int randomInt = 1 + ThreadLocalRandom.current().nextInt(diff); + + switch (choose) { + case 0: // more than ret + actual = ret + randomInt; + break; + case 1: // less than ret + actual = ret - randomInt; + break; + case 2: // 0 + actual = 0; + break; + case 3: // always < -1 + actual = -1 - randomInt; + break; + default: + log.fine(IOExceptionMsg); + threwException = true; + throw new IOException(IOExceptionMsg); + } + returnValid = true; + } + log.fine("invalidCharacterStream.read(): Actual bytes=" + ret + " Returned bytes=" + actual); + return actual; + } + } + + /** + * InvalidBlob : stream with invalid behavior 1) getBinaryStream returns InvalidBinaryStream 2) Length returns invalid lengths + */ + public class InvalidBlob implements Blob { + final int diff = 5; + + private Object expected = null; + public long length; + private long actualLength; + private long invalidLength = -1; + private boolean returnValid = false; + private InvalidBinaryStream stream = null; // keep a handle on any stream + + // Constructor + public InvalidBlob(Object expected, + boolean returnValid) { + this.expected = expected; + actualLength = this.value().length; + this.returnValid = returnValid; + } + + protected byte[] value() { + return ((byte[]) expected); + } + + @Override + public long length() throws SQLException { + long ret = actualLength; + long actual = ret; + if (invalidLength == -1) { + int choose = ThreadLocalRandom.current().nextInt(5); + int randomInt = 1 + ThreadLocalRandom.current().nextInt(diff); + + switch (choose) { + case 0: // more than ret + actual = ret + randomInt; + break; + case 1: // less than ret + actual = ret - randomInt; + break; + case 2: // 0 + actual = 0; + break; + case 3: // return > SQL Server Limit + actual = Long.MAX_VALUE; + break; + default: // always < -1 + actual = -1 - randomInt; + } + invalidLength = actual; + length = actual; + returnValid = true; + } + + log.fine("invalidBlob.length(): Actual bytes=" + actualLength + " Returned bytes=" + length); + return length; + } + + @Override + public InputStream getBinaryStream() throws SQLException { + stream = new InvalidBinaryStream(this.value(), returnValid); + return stream; + } + + @Override + public byte[] getBytes(long pos, + int length) throws SQLException { + // TODO Auto-generated method stub + return null; + } + + @Override + public long position(byte[] pattern, + long start) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public long position(Blob pattern, + long start) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public int setBytes(long pos, + byte[] bytes) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public int setBytes(long pos, + byte[] bytes, + int offset, + int len) throws SQLException { + assertTrue(false, "Not implemented"); + return 0; + } + + @Override + public OutputStream setBinaryStream(long pos) throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + + @Override + public void truncate(long len) throws SQLException { + assertTrue(false, "Not implemented"); + } + + @Override + public void free() throws SQLException { + assertTrue(false, "Not implemented"); + + } + + @Override + public InputStream getBinaryStream(long pos, + long length) throws SQLException { + assertTrue(false, "Not implemented"); + return null; + } + + } + + /** + * invalidBinaryStream : stream with invalid behavior Read can return data length > or < than actual + * + */ + public class InvalidBinaryStream extends DBBinaryStream { + final int diff = 5; + private boolean _returnValid = false; // Perfom invalid actions at most once + + /** + * Constructor + * + * @param value + * @param returnValid + */ + public InvalidBinaryStream(byte[] value, + boolean returnValid) { + super(value); + _returnValid = returnValid; + } + + @Override + public int read(byte[] bytes, + int off, + int len) { + int ret = super.read(bytes, off, len); + int actual = ret; + if (!_returnValid) { + int choose = ThreadLocalRandom.current().nextInt(4); + int randomInt = 1 + ThreadLocalRandom.current().nextInt(diff); + switch (choose) { + case 0: // greater than ret + actual = ret + randomInt; + break; + case 1: // less than ret + actual = ret - randomInt; + break; + case 2: // always < -1 + actual = -1 - randomInt; + break; + default: // 0 + actual = 0; + } + // Return invalid only once per stream + _returnValid = true; + } + log.fine("invalidBinaryStream.read(): Actual bytes=" + ret + " Returned bytes=" + actual); + return actual; + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBItems.java b/src/test/java/com/microsoft/sqlserver/testframework/DBItems.java new file mode 100644 index 000000000..a64386ac4 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBItems.java @@ -0,0 +1,41 @@ +/* + * 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.testframework; + +import java.util.ArrayList; + +/** + * Each sqlType has a list of coercions associate with it + * + */ +public class DBItems { + protected ArrayList coercionsList; + + public DBItems() { + + } + + public DBCoercion find(Class type) { + for (int i = 0; i < coercionsList.size(); i++) { + DBCoercion item = coercionsList.get(i); + if (item.type() == type) + return item; + } + return null; + } + + /** + * + * @param item + * @return + */ + public boolean add(DBCoercion item) { + return coercionsList.add(item); + } +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBPreparedStatement.java b/src/test/java/com/microsoft/sqlserver/testframework/DBPreparedStatement.java index 7d5369ee3..6ff5fa6f1 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBPreparedStatement.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBPreparedStatement.java @@ -17,7 +17,7 @@ * * Wrapper class PreparedStatement */ -public class DBPreparedStatement extends AbstractParentWrapper { +public class DBPreparedStatement extends DBStatement { PreparedStatement pstmt = null; DBResultSet dbresultSet = null; @@ -26,26 +26,31 @@ public class DBPreparedStatement extends AbstractParentWrapper { * */ public DBPreparedStatement(DBConnection dbconnection) { - super(dbconnection, null, "preparedStatement"); + super(dbconnection); } /** - * @param parent - * @param internal - * @param name + * @throws SQLException + * */ - DBPreparedStatement(AbstractParentWrapper parent, - Object internal, - String name) { - super(parent, internal, name); + DBPreparedStatement prepareStatement(String query) throws SQLException { + pstmt = ((Connection) parent().product()).prepareStatement(query); + setInternal(pstmt); + return this; } /** - * @throws SQLException * + * @param query + * @param resultSetType + * @param resultSetConcurrency + * @return + * @throws SQLException */ - DBPreparedStatement prepareStatement(String query) throws SQLException { - pstmt = ((Connection) parent().product()).prepareStatement(query); + DBPreparedStatement prepareStatement(String query, + int resultSetType, + int resultSetConcurrency) throws SQLException { + pstmt = ((Connection) parent().product()).prepareStatement(query, resultSetType, resultSetConcurrency); setInternal(pstmt); return this; } @@ -79,4 +84,4 @@ public DBResultSet executeQuery() throws SQLException { return dbresultSet; } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java b/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java index 1b9705402..78bccaaf4 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java @@ -11,6 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.io.InputStream; +import java.io.Reader; import java.math.BigDecimal; import java.sql.JDBCType; import java.sql.ResultSet; @@ -24,6 +26,9 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.microsoft.sqlserver.testframework.Utils.DBBinaryStream; +import com.microsoft.sqlserver.testframework.Utils.DBCharacterStream; + /** * wrapper class for ResultSet * @@ -98,6 +103,46 @@ public Object getObject(int index) throws SQLException { return resultSet.getObject(index); } + /** + * + * @param x + * @return + * @throws SQLException + */ + public InputStream getBinaryStream(int x) throws SQLException { + return resultSet.getBinaryStream(x); + } + + /** + * + * @param x + * @return + * @throws SQLException + */ + public InputStream getBinaryStream(String x) throws SQLException { + return resultSet.getBinaryStream(x); + } + + /** + * + * @param x + * @return + * @throws SQLException + */ + public Reader getCharacterStream(int x) throws SQLException { + return resultSet.getCharacterStream(x); + } + + /** + * + * @param x + * @return + * @throws SQLException + */ + public Reader getCharacterStream(String x) throws SQLException { + return resultSet.getCharacterStream(x); + } + /** * * @param index @@ -149,6 +194,7 @@ public void verifyCurrentRow(DBTable table) throws SQLException { * @param ordinal * @param coercion * @throws SQLException + * @throws Exception */ public void verifydata(int ordinal, Class coercion) throws SQLException { @@ -287,10 +333,39 @@ private boolean parseByte(byte[] expectedData, return true; } - private Object getXXX(int idx, + /** + * + * @param idx + * @param coercion + * @return + * @throws SQLException + */ + public Object getXXX(Object idx, Class coercion) throws SQLException { + int intOrdinal = 0; + String strOrdinal = ""; + boolean isInteger = false; + + if (idx == null) { + strOrdinal = null; + } + else if (idx instanceof Integer) { + isInteger = true; + intOrdinal = ((Integer) idx).intValue(); + } + else { + // Otherwise + throw new SQLException("Unhandled ordinal type: " + idx.getClass()); + } + if (coercion == Object.class) { - return this.getObject(idx); + return this.getObject(intOrdinal); + } + else if (coercion == DBBinaryStream.class) { + return isInteger ? this.getBinaryStream(intOrdinal) : this.getBinaryStream(strOrdinal); + } + else if (coercion == DBCharacterStream.class) { + return isInteger ? this.getCharacterStream(intOrdinal) : this.getCharacterStream(strOrdinal); } else { if (log.isLoggable(Level.FINE)) { @@ -460,4 +535,16 @@ private static Object roundDatetimeValue(Object value) { public int getInt(int index) throws SQLException { return resultSet.getInt(index); } -} + + /** + * + * @return + */ + public DBStatement statement() { + if (parent instanceof DBStatement) { + return ((DBStatement) parent); + } + return (null); + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java b/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java index 09b7df316..09c76dad4 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java @@ -26,6 +26,13 @@ public class DBStatement extends AbstractParentWrapper { // TODO: support PreparedStatement and CallableStatement // TODO: add stmt level holdability // TODO: support IDENTITY column and stmt.getGeneratedKeys() + public int cursortype = ResultSet.TYPE_FORWARD_ONLY; + public int concurrency = ResultSet.CONCUR_READ_ONLY; + public int holdability = ResultSet.CLOSE_CURSORS_AT_COMMIT; + public static final int STATEMENT = 0; + public static final int PREPAREDSTATEMENT = 1; + public static final int CALLABLESTATEMENT = 2; + public static final int ALL = 3; Statement statement = null; DBResultSet dbresultSet = null; @@ -153,4 +160,14 @@ public int getQueryTimeout() throws SQLException { public int getUpdateCount() throws SQLException { return ((Statement) product()).getUpdateCount(); } -} + + /** + * + * @return + * @throws SQLException + */ + public DBResultSet getResultSet() throws SQLException { + ResultSet rs = ((Statement) product()).getResultSet(); + return dbresultSet = new DBResultSet(this, rs); + } +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java b/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java index 7a58ebdb2..c041bb507 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBTable.java @@ -136,6 +136,10 @@ private void addColumns(boolean unicode) { public String getTableName() { return tableName; } + + public List getColumns() { + return this.columns; + } /** * gets escaped table name of the {@link DBTable} object @@ -380,7 +384,8 @@ boolean passDataAsString(int colNum) { return (JDBCType.CHAR == getColumn(colNum).getJdbctype() || JDBCType.VARCHAR == getColumn(colNum).getJdbctype() || JDBCType.NCHAR == getColumn(colNum).getJdbctype() || JDBCType.NVARCHAR == getColumn(colNum).getJdbctype() || JDBCType.TIMESTAMP == getColumn(colNum).getJdbctype() || JDBCType.DATE == getColumn(colNum).getJdbctype() - || JDBCType.TIME == getColumn(colNum).getJdbctype()); + || JDBCType.TIME == getColumn(colNum).getJdbctype() || JDBCType.LONGVARCHAR == getColumn(colNum).getJdbctype() + || JDBCType.LONGNVARCHAR == getColumn(colNum).getJdbctype()); } /** @@ -390,6 +395,7 @@ boolean passDataAsString(int colNum) { */ boolean passDataAsHex(int colNum) { - return (JDBCType.BINARY == getColumn(colNum).getJdbctype() || JDBCType.VARBINARY == getColumn(colNum).getJdbctype()); + return (JDBCType.BINARY == getColumn(colNum).getJdbctype() || JDBCType.VARBINARY == getColumn(colNum).getJdbctype() + || JDBCType.LONGVARBINARY == getColumn(colNum).getJdbctype()); } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java index 354123c0b..eb21d2864 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java @@ -8,9 +8,43 @@ package com.microsoft.sqlserver.testframework; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; +import com.microsoft.sqlserver.testframework.sqlType.SqlBigInt; +import com.microsoft.sqlserver.testframework.sqlType.SqlBinary; +import com.microsoft.sqlserver.testframework.sqlType.SqlBit; +import com.microsoft.sqlserver.testframework.sqlType.SqlChar; +import com.microsoft.sqlserver.testframework.sqlType.SqlDate; +import com.microsoft.sqlserver.testframework.sqlType.SqlDateTime; +import com.microsoft.sqlserver.testframework.sqlType.SqlDateTime2; +import com.microsoft.sqlserver.testframework.sqlType.SqlDateTimeOffset; +import com.microsoft.sqlserver.testframework.sqlType.SqlDecimal; +import com.microsoft.sqlserver.testframework.sqlType.SqlFloat; +import com.microsoft.sqlserver.testframework.sqlType.SqlInt; +import com.microsoft.sqlserver.testframework.sqlType.SqlMoney; +import com.microsoft.sqlserver.testframework.sqlType.SqlNChar; +import com.microsoft.sqlserver.testframework.sqlType.SqlNVarChar; +import com.microsoft.sqlserver.testframework.sqlType.SqlNVarCharMax; +import com.microsoft.sqlserver.testframework.sqlType.SqlNumeric; +import com.microsoft.sqlserver.testframework.sqlType.SqlReal; +import com.microsoft.sqlserver.testframework.sqlType.SqlSmallDateTime; +import com.microsoft.sqlserver.testframework.sqlType.SqlSmallInt; +import com.microsoft.sqlserver.testframework.sqlType.SqlSmallMoney; +import com.microsoft.sqlserver.testframework.sqlType.SqlTime; +import com.microsoft.sqlserver.testframework.sqlType.SqlTinyInt; +import com.microsoft.sqlserver.testframework.sqlType.SqlType; +import com.microsoft.sqlserver.testframework.sqlType.SqlVarBinary; +import com.microsoft.sqlserver.testframework.sqlType.SqlVarBinaryMax; +import com.microsoft.sqlserver.testframework.sqlType.SqlVarChar; +import com.microsoft.sqlserver.testframework.sqlType.SqlVarCharMax; + /** * Generic Utility class which we can access by test classes. * @@ -22,9 +56,12 @@ public class Utils { // 'SQL' represents SQL Server, while 'SQLAzure' represents SQL Azure. public static final String SERVER_TYPE_SQL_SERVER = "SQL"; public static final String SERVER_TYPE_SQL_AZURE = "SQLAzure"; + // private static SqlType types = null; + private static ArrayList types = null; /** * Returns serverType + * * @return */ public static String getServerType() { @@ -82,4 +119,135 @@ public static String getConfiguredProperty(String key, return value; } -} + + /** + * + * @param javatype + * @return + */ + public static SqlType find(Class javatype) { + if (null != types) { + types(); + for (int i = 0; i < types.size(); i++) { + SqlType type = types.get(i); + if (type.getType() == javatype) + return type; + } + } + return null; + } + + /** + * + * @param name + * @return + */ + public static SqlType find(String name) { + if (null == types) + types(); + if (null != types) { + for (int i = 0; i < types.size(); i++) { + SqlType type = types.get(i); + if (type.getName().equalsIgnoreCase(name)) + return type; + } + } + return null; + } + + /** + * + * @return + */ + public static ArrayList types() { + if (null == types) { + types = new ArrayList(); + + types.add(new SqlInt()); + types.add(new SqlSmallInt()); + types.add(new SqlTinyInt()); + types.add(new SqlBit()); + types.add(new SqlDateTime()); + types.add(new SqlSmallDateTime()); + types.add(new SqlDecimal()); + types.add(new SqlNumeric()); + types.add(new SqlReal()); + types.add(new SqlFloat()); + types.add(new SqlMoney()); + types.add(new SqlSmallMoney()); + types.add(new SqlVarChar()); + types.add(new SqlChar()); + // types.add(new SqlText()); + types.add(new SqlBinary()); + types.add(new SqlVarBinary()); + // types.add(new SqlImage()); + // types.add(new SqlTimestamp()); + + types.add(new SqlNVarChar()); + types.add(new SqlNChar()); + // types.add(new SqlNText()); + // types.add(new SqlGuid()); + + types.add(new SqlBigInt()); + // types.add(new SqlVariant(this)); + + // 9.0 types + types.add(new SqlVarCharMax()); + types.add(new SqlNVarCharMax()); + types.add(new SqlVarBinaryMax()); + // types.add(new SqlXml()); + + // 10.0 types + types.add(new SqlDate()); + types.add(new SqlDateTime2()); + types.add(new SqlTime()); + types.add(new SqlDateTimeOffset()); + } + return types; + } + + /** + * Wrapper Class for BinaryStream + * + */ + public static class DBBinaryStream extends ByteArrayInputStream { + byte[] data; + + // Constructor + public DBBinaryStream(byte[] value) { + super(value); + data = value; + } + + } + + /** + * Wrapper for CharacterStream + * + */ + public static class DBCharacterStream extends CharArrayReader { + String localValue; + + /** + * Constructor + * + * @param value + */ + public DBCharacterStream(String value) { + super(value.toCharArray()); + localValue = value; + } + + } + + /** + * Wrapper for NCharacterStream + */ + class DBNCharacterStream extends DBCharacterStream { + // Constructor + public DBNCharacterStream(String value) { + super(value); + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBigInt.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBigInt.java index 3ac11cdbc..e8fef48a8 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBigInt.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBigInt.java @@ -14,7 +14,10 @@ public class SqlBigInt extends SqlNumber { public SqlBigInt() { - super("bigint", JDBCType.BIGINT, 19, 0, SqlTypeValue.BIGINT.minValue, SqlTypeValue.BIGINT.maxValue, SqlTypeValue.BIGINT.nullValue); + super("bigint", JDBCType.BIGINT, 19, 0, SqlTypeValue.BIGINT.minValue, SqlTypeValue.BIGINT.maxValue, SqlTypeValue.BIGINT.nullValue, + VariableLengthType.Fixed, Long.class); + flags.set(PRIMITIVE); + } public Object createdata() { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java index 6656c93c7..7de575762 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java @@ -34,7 +34,8 @@ public SqlBinary() { JDBCType jdbctype, int precision) { super(name, jdbctype, precision, 0, SqlTypeValue.BINARY.minValue, SqlTypeValue.BINARY.maxValue, SqlTypeValue.BINARY.nullValue, - VariableLengthType.Precision); + VariableLengthType.Precision, byte[].class); + flags.set(FIXED); generatePrecision(); } @@ -42,9 +43,9 @@ public SqlBinary() { * create random data for binary and varbinary column */ public Object createdata() { - int dataLength = ThreadLocalRandom.current().nextInt(precision); + int dataLength = ThreadLocalRandom.current().nextInt(5);// precision byte[] bytes = new byte[dataLength]; ThreadLocalRandom.current().nextBytes(bytes); return bytes; } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBit.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBit.java index 00779232b..54cf4a6e5 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBit.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBit.java @@ -14,7 +14,8 @@ public class SqlBit extends SqlType { public SqlBit() { - super("bit", JDBCType.BIT, 1, 0, SqlTypeValue.BIT.minValue, SqlTypeValue.BIT.maxValue, SqlTypeValue.BIT.nullValue, VariableLengthType.Fixed); + super("bit", JDBCType.BIT, 1, 0, SqlTypeValue.BIT.minValue, SqlTypeValue.BIT.maxValue, SqlTypeValue.BIT.nullValue, VariableLengthType.Fixed, + Boolean.class); } public Object createdata() { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlChar.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlChar.java index 6db9ec653..6e985e47e 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlChar.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlChar.java @@ -11,6 +11,9 @@ import java.sql.JDBCType; import java.util.concurrent.ThreadLocalRandom; +import com.microsoft.sqlserver.testframework.DBCoercion; +import com.microsoft.sqlserver.testframework.Utils; + /* * Restricting the size of char/binary to 2000 and nchar to 1000 to accommodate SQL Sever limitation of having of having maximum allowable table row * size to 8060 @@ -27,8 +30,14 @@ public SqlChar() { JDBCType jdbctype, int precision) { super(name, jdbctype, precision, 0, SqlTypeValue.CHAR.minValue, SqlTypeValue.CHAR.maxValue, SqlTypeValue.CHAR.nullValue, - VariableLengthType.Precision); + VariableLengthType.Precision, String.class); generatePrecision(); + coercions.add(new DBCoercion(Object.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG})); + coercions.add(new DBCoercion(String.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.CHAR})); + coercions.add(new DBCoercion(Utils.DBCharacterStream.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, + DBCoercion.SET, DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.STREAM, DBCoercion.CHAR})); } public Object createdata() { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDate.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDate.java index b6759defe..cf6755330 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDate.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDate.java @@ -22,6 +22,7 @@ public class SqlDate extends SqlDateTime { public SqlDate() { super("date", JDBCType.DATE, null, null); + type = java.sql.Date.class; try { minvalue = new Date(dateFormat.parse((String) SqlTypeValue.DATE.minValue).getTime()); maxvalue = new Date(dateFormat.parse((String) SqlTypeValue.DATE.maxValue).getTime()); @@ -34,4 +35,4 @@ public SqlDate() { public Object createdata() { return new Date(ThreadLocalRandom.current().nextLong(((Date) minvalue).getTime(), ((Date) maxvalue).getTime())); } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime.java index 56f0944d6..f81bb4c82 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTime.java @@ -35,7 +35,7 @@ public SqlDateTime() { JDBCType jdbctype, Object min, Object max) { - super(name, jdbctype, 0, 0, min, max, SqlTypeValue.DATETIME.nullValue, VariableLengthType.Fixed); + super(name, jdbctype, 0, 0, min, max, SqlTypeValue.DATETIME.nullValue, VariableLengthType.Fixed, java.sql.Timestamp.class); } public Object createdata() { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTimeOffset.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTimeOffset.java index a5348366d..3820da540 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTimeOffset.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDateTimeOffset.java @@ -27,6 +27,7 @@ public class SqlDateTimeOffset extends SqlDateTime { // min/max with offset public SqlDateTimeOffset() { super("datetimeoffset", JDBCType.TIMESTAMP /* microsoft.sql.Types.DATETIMEOFFSET */, null, null); + type = microsoft.sql.DateTimeOffset.class; minvalue = Timestamp.valueOf((String) SqlTypeValue.DATETIMEOFFSET.minValue); maxvalue = Timestamp.valueOf((String) SqlTypeValue.DATETIMEOFFSET.maxValue); this.precision = 7; diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDecimal.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDecimal.java index 428939337..2e66d5690 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDecimal.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlDecimal.java @@ -43,7 +43,7 @@ public SqlDecimal() { Object min, Object max, VariableLengthType variableLengthType) { - super(name, jdbctype, precision, scale, min, max, SqlTypeValue.DECIMAL.nullValue, variableLengthType); + super(name, jdbctype, precision, scale, min, max, SqlTypeValue.DECIMAL.nullValue, variableLengthType, BigDecimal.class); // update random precision and scale generatePrecision(); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java index d2d526fa8..86ee22d53 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlFloat.java @@ -20,14 +20,15 @@ public class SqlFloat extends SqlType { Object min, Object max, Object nullvalue, - VariableLengthType variableLengthType) { - super(name, jdbctype, precision, 0, min, max, nullvalue, variableLengthType); + VariableLengthType variableLengthType, + Class type) { + super(name, jdbctype, precision, 0, min, max, nullvalue, variableLengthType, type); generatePrecision(); } public SqlFloat() { super("float", JDBCType.DOUBLE, 53, 0, SqlTypeValue.FLOAT.minValue, SqlTypeValue.FLOAT.maxValue, SqlTypeValue.FLOAT.nullValue, - VariableLengthType.Precision); + VariableLengthType.Precision, Double.class); generatePrecision(); } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlInt.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlInt.java index 10697a747..e988fc49e 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlInt.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlInt.java @@ -14,7 +14,8 @@ public class SqlInt extends SqlNumber { public SqlInt() { - super("int", JDBCType.INTEGER, 10, 0, SqlTypeValue.INTEGER.minValue, SqlTypeValue.INTEGER.maxValue, SqlTypeValue.INTEGER.nullValue); + super("int", JDBCType.INTEGER, 10, 0, SqlTypeValue.INTEGER.minValue, SqlTypeValue.INTEGER.maxValue, SqlTypeValue.INTEGER.nullValue, + VariableLengthType.Fixed, Integer.class); } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNVarCharMax.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNVarCharMax.java new file mode 100644 index 000000000..6adb456c3 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNVarCharMax.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.testframework.sqlType; + +import java.sql.Clob; +import java.sql.JDBCType; +import java.sql.NClob; + +import com.microsoft.sqlserver.testframework.DBCoercion; + + +public class SqlNVarCharMax extends SqlNVarChar { + + public SqlNVarCharMax() { + super(); + name = "nvarchar(max)"; + jdbctype = JDBCType.LONGNVARCHAR; + variableLengthType = variableLengthType.Variable; + + coercions.add(new DBCoercion(Clob.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.CHAR})); + coercions.add(new DBCoercion(NClob.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.NCHAR})); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNumber.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNumber.java index c6d92dc56..7b9ae494d 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNumber.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlNumber.java @@ -17,7 +17,9 @@ public abstract class SqlNumber extends SqlType { int scale, Object min, Object max, - Object nullvalue) { - super(name, jdbctype, precision, scale, min, max, nullvalue, VariableLengthType.Fixed); + Object nullvalue, + VariableLengthType variableLengthType, + Class type) { + super(name, jdbctype, precision, scale, min, max, nullvalue, VariableLengthType.Fixed, type); } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java index bc8867373..cbdd5db90 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlReal.java @@ -14,6 +14,6 @@ public class SqlReal extends SqlFloat { public SqlReal() { super("real", JDBCType.REAL, 24, SqlTypeValue.REAL.minValue, SqlTypeValue.REAL.maxValue, SqlTypeValue.REAL.nullValue, - VariableLengthType.Fixed); + VariableLengthType.Fixed, Float.class); } } \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallInt.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallInt.java index d2904f443..d821c9d7c 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallInt.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlSmallInt.java @@ -14,7 +14,8 @@ public class SqlSmallInt extends SqlNumber { public SqlSmallInt() { - super("smallint", JDBCType.SMALLINT, 5, 0, SqlTypeValue.SMALLINT.minValue, SqlTypeValue.SMALLINT.maxValue, SqlTypeValue.SMALLINT.nullValue); + super("smallint", JDBCType.SMALLINT, 5, 0, SqlTypeValue.SMALLINT.minValue, SqlTypeValue.SMALLINT.maxValue, SqlTypeValue.SMALLINT.nullValue, + VariableLengthType.Fixed, Short.class); } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java index 74c92d43d..8cde8932e 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTime.java @@ -30,6 +30,7 @@ public class SqlTime extends SqlDateTime { public SqlTime() { super("time", JDBCType.TIME, null, null); + type = java.sql.Time.class; try { minvalue = new Time(dateFormat.parse((String) SqlTypeValue.TIME.minValue).getTime()); maxvalue = new Time(dateFormat.parse((String) SqlTypeValue.TIME.maxValue).getTime()); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTinyInt.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTinyInt.java index 28a21dc02..d8c7750dd 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTinyInt.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlTinyInt.java @@ -14,7 +14,8 @@ public class SqlTinyInt extends SqlNumber { public SqlTinyInt() { - super("tinyint", JDBCType.TINYINT, 3, 0, SqlTypeValue.TINYINT.minValue, SqlTypeValue.TINYINT.maxValue, SqlTypeValue.TINYINT.nullValue); + super("tinyint", JDBCType.TINYINT, 3, 0, SqlTypeValue.TINYINT.minValue, SqlTypeValue.TINYINT.maxValue, SqlTypeValue.TINYINT.nullValue, + VariableLengthType.Fixed, Byte.class); } public Object createdata() { diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java index 4a5f7e851..3a3af6a9a 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java @@ -9,20 +9,52 @@ package com.microsoft.sqlserver.testframework.sqlType; import java.sql.JDBCType; +import java.sql.SQLTimeoutException; +import java.sql.SQLType; +import java.util.ArrayList; +import java.util.BitSet; import java.util.concurrent.ThreadLocalRandom; -public abstract class SqlType { +import com.microsoft.sqlserver.testframework.DBCoercion; +import com.microsoft.sqlserver.testframework.DBCoercions; +import com.microsoft.sqlserver.testframework.DBConnection; +import com.microsoft.sqlserver.testframework.DBItems; +import com.microsoft.sqlserver.testframework.Utils; + +public abstract class SqlType extends DBItems { // TODO: add seed to generate random data -> will help to reproduce the // exact data for debugging - protected String name = null; // type name for creating SQL query + protected String name = null; // type name for creating SQL query protected JDBCType jdbctype = JDBCType.NULL; protected int precision = 0; protected int scale = 0; protected Object minvalue = null; protected Object maxvalue = null; - protected Object nullvalue = null; // Primitives have non-null defaults + protected Object nullvalue = null; // Primitives have non-null defaults protected VariableLengthType variableLengthType; - // protected ThreadLocalRandom r; + protected Class type = null; + protected BitSet flags = new BitSet(); + protected DBCoercions coercions = new DBCoercions(); + + public static final int DEFAULT = 0; + public static final int NULLABLE = 1; + public static final int UPDATABLE = 2; + public static final int NUMERIC = 3; + public static final int FLOATINGPOINT = 4; + public static final int FIXED = 5; + public static final int CREATEPARAMS = 6; + public static final int CHARACTER = 7; + public static final int UNICODE = 8; + public static final int LONG = 9; + public static final int SEARCHABLE = 10; + public static final int XML = 11; + public static final int UDT = 12; + public static final int BINARY = 13; + public static final int TEMPORAL = 14; + public static final int BOOLEAN = 15; + public static final int PRIMITIVE = 16; + public static final int COLLATE = 17; + public static final int GUID = 18; /** * @@ -46,7 +78,8 @@ public abstract class SqlType { Object min, Object max, Object nullvalue, - VariableLengthType variableLengthType) { + VariableLengthType variableLengthType, + Class type) { this.name = name; this.jdbctype = jdbctype; this.precision = precision; @@ -55,6 +88,7 @@ public abstract class SqlType { this.maxvalue = max; this.nullvalue = nullvalue; this.variableLengthType = variableLengthType; + this.type = type; } /** @@ -71,6 +105,13 @@ public Object createdata() { } } + public Object createdata(Class type, + byte[] data) { + if (type == String.class) + return new String(data); + return data; + } + /** * * @return JDBCType of SqlType object @@ -79,6 +120,14 @@ public JDBCType getJdbctype() { return jdbctype; } + /** + * + * @return + */ + public Class getType() { + return type; + } + /** * * @param jdbctype @@ -171,4 +220,43 @@ void generatePrecision() { this.precision = ThreadLocalRandom.current().nextInt(minPrecision, maxPrecision + 1); } -} + /** + * @return + */ + public boolean isString() { + return flags.get(CHARACTER); + } + + /** + * + * @param target + * @param flag + * @param conn + * @return + * @throws Exception + */ + public boolean canconvert(Class target, + int flag, + DBConnection conn) throws Exception { + double serverversion = conn.getServerVersion(); + + if (flag == DBCoercion.SET || flag == DBCoercion.SETOBJECT || flag == DBCoercion.UPDATE || flag == DBCoercion.UPDATEOBJECT + || flag == DBCoercion.REG) { + // SQL 8 does not allow conversion from string to money + if (flag != DBCoercion.SETOBJECT && serverversion < 9.0 && this instanceof SqlMoney && target == String.class) + return false; + if (flag == DBCoercion.SET || flag == DBCoercion.SETOBJECT) { + // setTemporal() on textual columns returns unverifiable format + if (this.isString() && (target == java.sql.Date.class || target == java.sql.Time.class || target == java.sql.Timestamp.class)) + return false; + } + } + + DBCoercion coercion = coercions.find(target); + if (coercion != null) + return coercion.flags().get(flag); + + return false; + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinary.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinary.java index 332129dff..cfaca9ef9 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinary.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinary.java @@ -10,6 +10,11 @@ import java.sql.JDBCType; +import com.microsoft.sqlserver.testframework.DBCoercion; +import com.microsoft.sqlserver.testframework.Utils; +import com.microsoft.sqlserver.testframework.Utils.DBBinaryStream; +import com.microsoft.sqlserver.testframework.Utils.DBCharacterStream; + /** * Contains name, jdbctype, precision, scale for varbinary data type */ @@ -20,5 +25,20 @@ public class SqlVarBinary extends SqlBinary { */ public SqlVarBinary() { super("varbinary", JDBCType.VARBINARY, 4000); + coercions.add(new DBCoercion(String.class, new int[] {DBCoercion.GET, DBCoercion.GETPARAM})); + coercions.add(new DBCoercion(Object.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG})); + coercions.add(new DBCoercion(byte[].class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG})); + + // TODO: Following coercions are not supported by AE. add a check later + // coercions.remove(String.class); + coercions.add(new DBCoercion(String.class, + new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SETOBJECT, DBCoercion.GETPARAM})); + coercions.add(new DBCoercion(DBBinaryStream.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.STREAM})); + coercions.add(new DBCoercion(DBCharacterStream.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.STREAM})); + } -} +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinaryMax.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinaryMax.java new file mode 100644 index 000000000..80f7ba6bb --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarBinaryMax.java @@ -0,0 +1,27 @@ +/* + * 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.testframework.sqlType; + +import java.sql.Blob; +import java.sql.JDBCType; + +import com.microsoft.sqlserver.testframework.DBCoercion; +import com.microsoft.sqlserver.testframework.DBCoercions; + +public class SqlVarBinaryMax extends SqlVarBinary { + + public SqlVarBinaryMax() { + super(); + name = "varbinary(max)"; + jdbctype = JDBCType.LONGVARBINARY; + variableLengthType = variableLengthType.Variable; + coercions.add(new DBCoercion(Blob.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG})); + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarCharMax.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarCharMax.java new file mode 100644 index 000000000..a4e700fd3 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlVarCharMax.java @@ -0,0 +1,28 @@ +/* + * 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.testframework.sqlType; + +import java.sql.Clob; +import java.util.ArrayList; + +import com.microsoft.sqlserver.testframework.DBCoercion; +import com.microsoft.sqlserver.testframework.DBCoercions; + +public class SqlVarCharMax extends SqlVarChar { + + public SqlVarCharMax() { + + super(); + name = "varchar(max)"; + jdbctype = jdbctype.LONGVARCHAR; + variableLengthType = variableLengthType.Variable; + coercions.add(new DBCoercion(Clob.class, new int[] {DBCoercion.GET, DBCoercion.UPDATE, DBCoercion.UPDATEOBJECT, DBCoercion.SET, + DBCoercion.SETOBJECT, DBCoercion.GETPARAM, DBCoercion.REG, DBCoercion.CHAR})); + + } +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java index 44d165d82..26d3de103 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/VariableLengthType.java @@ -8,11 +8,12 @@ package com.microsoft.sqlserver.testframework.sqlType; -/* +/** * Used to identify if the SQL type is of fixed length, or has Precision or Scale */ public enum VariableLengthType { - Fixed, // primitive types with fixed Length - Precision, // variable length type that just has precision char/varchar - Scale // variable length type with scale and precision + Fixed, // primitive types with fixed Length + Precision, // variable length type that just has precision char/varchar + Scale, // variable length type with scale and precision + Variable } From 04fb97409985362dbb4ca30149173c4fa042528e Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 3 Mar 2017 15:44:16 -0800 Subject: [PATCH 2/5] update for using lambda expression --- .../microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java index b88ac0cde..938527b44 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java @@ -105,7 +105,12 @@ public Collection executeDynamicTests() { for (int j = 0; j < isResultSetTypes.size(); j++) { Class lobClass = classes.get(i); boolean isResultSet = isResultSetTypes.get(j); - Executable exec = () -> testInvalidLobs(lobClass, isResultSet); + Executable exec = new Executable() { + @Override + public void execute() throws Throwable { + testInvalidLobs(lobClass, isResultSet); + } + }; // create a test display name String testName = " Test: " + lobClass + (isResultSet ? " isResultSet" : " isPreparedStatement"); // create dynamic test @@ -276,6 +281,7 @@ private void testMultipleClose(Class streamClass) throws Exception { /** * Tests Insert Retrive on nclob + * * @throws Exception */ @Test @@ -287,6 +293,7 @@ public void testNClob() throws Exception { /** * Tests Insert Retrive on blob + * * @throws Exception */ @Test @@ -298,6 +305,7 @@ public void testBlob() throws Exception { /** * Tests Insert Retrive on clob + * * @throws Exception */ @Test @@ -461,7 +469,7 @@ else if (nClobType == classType(lobClass)) { else { blob = conn.createBlob(); rs.updateBlob(1, blob); - + } rs.updateRow(); } From e0a396a980694da4ae6032d684e984df916d4aa4 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 3 Mar 2017 15:59:05 -0800 Subject: [PATCH 3/5] fix travis-ci failure --- .../java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java index 938527b44..764d91af8 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java @@ -97,7 +97,7 @@ public static void terminate() throws SQLException { @TestFactory public Collection executeDynamicTests() { - List classes = new ArrayList<>(Arrays.asList(Blob.class, Clob.class, DBBinaryStream.class, DBCharacterStream.class)); + List classes = new ArrayList(Arrays.asList(Blob.class, Clob.class, DBBinaryStream.class, DBCharacterStream.class)); List isResultSetTypes = new ArrayList<>(Arrays.asList(true, false)); Collection dynamicTests = new ArrayList<>(); From 4b2abde165e019464254842fa85f36dc7c7b12f0 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Fri, 3 Mar 2017 16:04:40 -0800 Subject: [PATCH 4/5] update lambda expressions --- .../java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java index 764d91af8..35b5c4526 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java @@ -103,8 +103,8 @@ public Collection executeDynamicTests() { for (int i = 0; i < classes.size(); i++) { for (int j = 0; j < isResultSetTypes.size(); j++) { - Class lobClass = classes.get(i); - boolean isResultSet = isResultSetTypes.get(j); + final Class lobClass = classes.get(i); + final boolean isResultSet = isResultSetTypes.get(j); Executable exec = new Executable() { @Override public void execute() throws Throwable { From 4d6baa0dcc46bfcd5d3f2299672a30c683c00099 Mon Sep 17 00:00:00 2001 From: v-afrafi Date: Thu, 9 Mar 2017 17:42:01 -0800 Subject: [PATCH 5/5] post review commits --- .../microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java | 12 ++++++------ .../sqlserver/testframework/sqlType/SqlBinary.java | 2 +- .../sqlserver/testframework/sqlType/SqlType.java | 8 +++++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java index 35b5c4526..f0b4c6576 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/lobs/lobsTest.java @@ -157,7 +157,7 @@ private void testInvalidLobs(Class lobClass, Object updater; for (int i = 0; i < table.getColumns().size(); i++) { DBColumn col = table.getColumns().get(i); - if (!col.getSqlType().canconvert(lobClass, coercionType, new DBConnection(connectionString))) + if (!col.getSqlType().canConvert(lobClass, coercionType, new DBConnection(connectionString))) continue; // re-create LOB since it might get closed Object lob = this.createLob(lobClass); @@ -251,7 +251,7 @@ private void testMultipleClose(Class streamClass) throws Exception { while (rs.next()) { for (int i = 0; i < 3; i++) { DBColumn col = table.getColumns().get(i); - if (!col.getSqlType().canconvert(streamClass, DBCoercion.GET, new DBConnection(connectionString))) + if (!col.getSqlType().canConvert(streamClass, DBCoercion.GET, new DBConnection(connectionString))) continue; Object stream = rs.getXXX(i + 1, streamClass); if (stream == null) { @@ -288,7 +288,7 @@ private void testMultipleClose(Class streamClass) throws Exception { @DisplayName("testlLobs_InsertRetrive") public void testNClob() throws Exception { String types[] = {"nvarchar(max)"}; - testlLobs_InsertRetrive(types, NClob.class); + testLobs_InsertRetrive(types, NClob.class); } /** @@ -300,7 +300,7 @@ public void testNClob() throws Exception { @DisplayName("testlLobs_InsertRetrive") public void testBlob() throws Exception { String types[] = {"varbinary(max)"}; - testlLobs_InsertRetrive(types, Blob.class); + testLobs_InsertRetrive(types, Blob.class); } /** @@ -312,10 +312,10 @@ public void testBlob() throws Exception { @DisplayName("testlLobs_InsertRetrive") public void testClob() throws Exception { String types[] = {"varchar(max)"}; - testlLobs_InsertRetrive(types, Clob.class); + testLobs_InsertRetrive(types, Clob.class); } - private void testlLobs_InsertRetrive(String types[], + private void testLobs_InsertRetrive(String types[], Class lobClass) throws Exception { table = createTable(table, types, false); // create empty table int size = 10000; diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java index 7de575762..2041733bb 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlBinary.java @@ -43,7 +43,7 @@ public SqlBinary() { * create random data for binary and varbinary column */ public Object createdata() { - int dataLength = ThreadLocalRandom.current().nextInt(5);// precision + int dataLength = ThreadLocalRandom.current().nextInt(precision); byte[] bytes = new byte[dataLength]; ThreadLocalRandom.current().nextBytes(bytes); return bytes; diff --git a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java index 3a3af6a9a..e70f5fd0b 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/sqlType/SqlType.java @@ -105,6 +105,12 @@ public Object createdata() { } } + /** + * create valid random value for the SQL type + * @param type + * @param data + * @return + */ public Object createdata(Class type, byte[] data) { if (type == String.class) @@ -235,7 +241,7 @@ public boolean isString() { * @return * @throws Exception */ - public boolean canconvert(Class target, + public boolean canConvert(Class target, int flag, DBConnection conn) throws Exception { double serverversion = conn.getServerVersion();