diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/BatchExecuteWithErrorsTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/BatchExecuteWithErrorsTest.java new file mode 100644 index 0000000000..1171364217 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/BatchExecuteWithErrorsTest.java @@ -0,0 +1,466 @@ +/* + * 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.UnitStatement; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +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.sql.BatchUpdateException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +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.DBConnection; +import com.microsoft.sqlserver.testframework.Utils; +import com.microsoft.sqlserver.testframework.util.RandomUtil; + +/** + * Tests batch execution with errors + * + */ +@RunWith(JUnitPlatform.class) +public class BatchExecuteWithErrorsTest extends AbstractTest { + + public static final Logger log = Logger.getLogger("BatchExecuteWithErrors"); + Connection con = null; + String tableN = RandomUtil.getIdentifier("t_Repro47239"); + final String tableName = AbstractSQLGenerator.escapeIdentifier(tableN); + final String insertStmt = "INSERT INTO " + tableName + " VALUES (999, 'HELLO', '4/12/1994')"; + final String error16 = "RAISERROR ('raiserror level 16',16,42)"; + final String select = "SELECT 1"; + final String dateConversionError = "insert into " + tableName + " values (999999, 'Hello again', 'asdfasdf')"; + + /** + * Batch test + * + * @throws SQLException + */ + @Test + @DisplayName("Batch Test") + public void Repro47239() throws SQLException { + String tableN = RandomUtil.getIdentifier("t_Repro47239"); + final String tableName = AbstractSQLGenerator.escapeIdentifier(tableN); + final String insertStmt = "INSERT INTO " + tableName + " VALUES (999, 'HELLO', '4/12/1994')"; + final String error16 = "RAISERROR ('raiserror level 16',16,42)"; + final String select = "SELECT 1"; + final String dateConversionError = "insert into " + tableName + " values (999999, 'Hello again', 'asdfasdf')"; + + String warning; + String error; + String severe; + con = DriverManager.getConnection(connectionString); + if (DBConnection.isSqlAzure(con)) { + // SQL Azure will throw exception for "raiserror WITH LOG", so the following RAISERROR statements have not "with log" option + warning = "RAISERROR ('raiserror level 4',4,1)"; + error = "RAISERROR ('raiserror level 11',11,1)"; + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best test coverage. + severe = "--Not executed when testing against SQL Azure"; // this is a dummy statement that never being executed on SQL Azure + } + else { + warning = "RAISERROR ('raiserror level 4',4,1) WITH LOG"; + error = "RAISERROR ('raiserror level 11',11,1) WITH LOG"; + severe = "RAISERROR ('raiserror level 20',20,1) WITH LOG"; + } + con.close(); + + int[] actualUpdateCounts; + int[] expectedUpdateCounts; + String actualExceptionText; + + // SQL Server 2005 driver + try { + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + } + catch (ClassNotFoundException e1) { + fail(e1.toString()); + } + Connection conn = DriverManager.getConnection(connectionString); + Statement stmt = conn.createStatement(); + try { + stmt.executeUpdate("drop table " + tableName); + } + catch (Exception ignored) { + } + stmt.executeUpdate( + "create table " + tableName + " (c1_int int, c2_varchar varchar(20), c3_date datetime, c4_int int identity(1,1) primary key)"); + + // Regular Statement batch update + expectedUpdateCounts = new int[] {1, -2, 1, -2, 1, -2}; + Statement batchStmt = conn.createStatement(); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + try { + actualUpdateCounts = batchStmt.executeBatch(); + actualExceptionText = ""; + } + catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getUpdateCounts(); + actualExceptionText = bue.getMessage(); + if (log.isLoggable(Level.FINE)) { + log.fine("BatchUpdateException occurred. Message:" + actualExceptionText); + } + } + finally { + batchStmt.close(); + } + if (log.isLoggable(Level.FINE)) { + log.fine("UpdateCounts:"); + } + for (int updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + log.fine(""); + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), "Test interleaved inserts and warnings"); + + expectedUpdateCounts = new int[] {-3, 1, 1, 1}; + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + actualUpdateCounts = stmt.executeBatch(); + actualExceptionText = ""; + } + catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + log.fine("UpdateCounts:"); + for (int updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + log.fine(""); + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), "Test error followed by inserts"); + // 50280 + expectedUpdateCounts = new int[] {1, -3}; + stmt.addBatch(insertStmt); + stmt.addBatch(error16); + try { + actualUpdateCounts = stmt.executeBatch(); + actualExceptionText = ""; + } + catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + for (int updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + log.fine(""); + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), "Test insert followed by non-fatal error (50280)"); + + // Test "soft" errors + conn.setAutoCommit(false); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + try { + stmt.executeBatch(); + assertEquals(true, false, "Soft error test: executeBatch unexpectedly succeeded"); + } + catch (BatchUpdateException bue) { + assertEquals("A result set was generated for update.", bue.getMessage(), "Soft error test: wrong error message in BatchUpdateException"); + assertEquals(Arrays.equals(bue.getUpdateCounts(), new int[] {-3, 1, -3, 1}), true, + "Soft error test: wrong update counts in BatchUpdateException"); + } + conn.rollback(); + + // Defect 128801: Rollback (with conversion error) should throw SQLServerException + stmt.addBatch(dateConversionError); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeBatch(); + } + catch (BatchUpdateException bue) { + assertThat(bue.getMessage(), containsString("Syntax error converting date")); + // CTestLog.CompareStartsWith(bue.getMessage(), "Syntax error converting date", "Transaction rollback with conversion error threw wrong + // BatchUpdateException"); + } + catch (SQLException e) { + assertThat(e.getMessage(), containsString("Conversion failed when converting date")); + // CTestLog.CompareStartsWith(e.getMessage(), "Conversion failed when converting date", "Transaction rollback with conversion error threw + // wrong SQLException"); + } + + conn.setAutoCommit(true); + + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best test coverage. + if (!DBConnection.isSqlAzure(conn)) { + // Test Severe (connection-closing) errors + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(warning); + // TODO Removed until ResultSet refactoring task (45832) is complete. + // stmt.addBatch(select); // error: select not permitted in batch + stmt.addBatch(insertStmt); + stmt.addBatch(severe); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeBatch(); + assertEquals(false, true, "Test fatal errors batch execution succeeded (should have failed)"); + } + catch (BatchUpdateException bue) { + assertEquals(false, true, "Test fatal errors returned BatchUpdateException rather than SQLException"); + } + catch (SQLException e) { + actualExceptionText = e.getMessage(); + + if (actualExceptionText.endsWith("reset")) { + assertTrue(actualExceptionText.equalsIgnoreCase("Connection reset"), "Test fatal errors"); + } + else { + assertTrue(actualExceptionText.equalsIgnoreCase("raiserror level 20"), "Test fatal errors"); + } + } + } + + try { + stmt.executeUpdate("drop table " + tableName); + } + catch (Exception ignored) { + } + stmt.close(); + conn.close(); + } + + /** + * Tests large methods, supported in 42 + * + * @throws Exception + */ + @Test + @DisplayName("Regression test for using 'large' methods") + public void Repro47239_large() throws Exception { + + assumeTrue("JDBC42".equals(Utils.getConfiguredProperty("JDBC_Version")), "Aborting test case as JDBC version is not compatible. "); + // the DBConnection for detecting whether the server is SQL Azure or SQL Server. + con = DriverManager.getConnection(connectionString); + final String warning; + final String error; + final String severe; + if (DBConnection.isSqlAzure(con)) { + // SQL Azure will throw exception for "raiserror WITH LOG", so the following RAISERROR statements have not "with log" option + warning = "RAISERROR ('raiserror level 4',4,1)"; + error = "RAISERROR ('raiserror level 11',11,1)"; + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best test coverage. + severe = "--Not executed when testing against SQL Azure"; // this is a dummy statement that never being executed on SQL Azure + } + else { + warning = "RAISERROR ('raiserror level 4',4,1) WITH LOG"; + error = "RAISERROR ('raiserror level 11',11,1) WITH LOG"; + severe = "RAISERROR ('raiserror level 20',20,1) WITH LOG"; + } + con.close(); + + long[] actualUpdateCounts; + long[] expectedUpdateCounts; + String actualExceptionText; + + // SQL Server 2005 driver + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + Connection conn = DriverManager.getConnection(connectionString); + Statement stmt = conn.createStatement(); + + try { + stmt.executeLargeUpdate("drop table " + tableName); + } + catch (Exception ignored) { + } + try { + stmt.executeLargeUpdate( + "create table " + tableName + " (c1_int int, c2_varchar varchar(20), c3_date datetime, c4_int int identity(1,1) primary key)"); + } + catch (Exception ignored) { + } + // Regular Statement batch update + expectedUpdateCounts = new long[] {1, -2, 1, -2, 1, -2}; + Statement batchStmt = conn.createStatement(); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + batchStmt.addBatch(insertStmt); + batchStmt.addBatch(warning); + try { + actualUpdateCounts = batchStmt.executeLargeBatch(); + actualExceptionText = ""; + } + catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getLargeUpdateCounts(); + actualExceptionText = bue.getMessage(); + log.fine("BatchUpdateException occurred. Message:" + actualExceptionText); + } + finally { + batchStmt.close(); + } + log.fine("UpdateCounts:"); + for (long updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + log.fine(""); + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), "Test interleaved inserts and warnings"); + + expectedUpdateCounts = new long[] {-3, 1, 1, 1}; + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + actualUpdateCounts = stmt.executeLargeBatch(); + actualExceptionText = ""; + } + catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getLargeUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + log.fine("UpdateCounts:"); + for (long updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + log.fine(""); + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), "Test error followed by inserts"); + + // 50280 + expectedUpdateCounts = new long[] {1, -3}; + stmt.addBatch(insertStmt); + stmt.addBatch(error16); + try { + actualUpdateCounts = stmt.executeLargeBatch(); + actualExceptionText = ""; + } + catch (BatchUpdateException bue) { + actualUpdateCounts = bue.getLargeUpdateCounts(); + actualExceptionText = bue.getMessage(); + } + for (long updateCount : actualUpdateCounts) { + log.fine("" + updateCount + ","); + } + log.fine(""); + assertTrue(Arrays.equals(actualUpdateCounts, expectedUpdateCounts), "Test insert followed by non-fatal error (50280)"); + + // Test "soft" errors + conn.setAutoCommit(false); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + stmt.addBatch(select); + stmt.addBatch(insertStmt); + try { + stmt.executeLargeBatch(); + assertEquals(false, true, "Soft error test: executeLargeBatch unexpectedly succeeded"); + } + catch (BatchUpdateException bue) { + assertEquals("A result set was generated for update.", bue.getMessage(), "Soft error test: wrong error message in BatchUpdateException"); + assertEquals(Arrays.equals(bue.getLargeUpdateCounts(), new long[] {-3, 1, -3, 1}), true, + "Soft error test: wrong update counts in BatchUpdateException"); + } + conn.rollback(); + + // Defect 128801: Rollback (with conversion error) should throw SQLServerException + stmt.addBatch(dateConversionError); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeLargeBatch(); + } + catch (BatchUpdateException bue) { + assertThat(bue.getMessage(), containsString("Syntax error converting date")); + } + catch (SQLException e) { + assertThat(e.getMessage(), containsString("Conversion failed when converting date")); + } + + conn.setAutoCommit(true); + + // On SQL Azure, raising FATAL error by RAISERROR() is not supported and there is no way to + // cut the current connection by a statement inside a SQL batch. + // Details: Although one can simulate a fatal error (that cuts the connections) by dropping the database, + // this simulation cannot be written entirely in TSQL (because it needs a new connection), + // and thus it cannot be put into a TSQL batch and it is useless here. + // So we have to skip the last scenario of this test case, i.e. "Test Severe (connection-closing) errors" + // It is worthwhile to still execute the first 5 test scenarios of this test case, in order to have best test coverage. + if (!DBConnection.isSqlAzure(DriverManager.getConnection(connectionString))) { + // Test Severe (connection-closing) errors + stmt.addBatch(error); + stmt.addBatch(insertStmt); + stmt.addBatch(warning); + + stmt.addBatch(insertStmt); + stmt.addBatch(severe); + stmt.addBatch(insertStmt); + stmt.addBatch(insertStmt); + try { + stmt.executeLargeBatch(); + assertEquals(false, true, "Test fatal errors batch execution succeeded (should have failed)"); + } + catch (BatchUpdateException bue) { + assertEquals(false, true, "Test fatal errors returned BatchUpdateException rather than SQLException"); + } + catch (SQLException e) { + actualExceptionText = e.getMessage(); + + if (actualExceptionText.endsWith("reset")) { + assertTrue(actualExceptionText.equalsIgnoreCase("Connection reset"), "Test fatal errors"); + } + else { + assertTrue(actualExceptionText.equalsIgnoreCase("raiserror level 20"), "Test fatal errors"); + + } + } + } + + try { + stmt.executeLargeUpdate("drop table " + tableName); + } + catch (Exception ignored) { + } + stmt.close(); + conn.close(); + } +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/CallableMixedTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/CallableMixedTest.java new file mode 100644 index 0000000000..d2ec17148a --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/CallableMixedTest.java @@ -0,0 +1,109 @@ +/* + * 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.UnitStatement; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.util.RandomUtil; + +/** + * Callable Mix tests using stored procedure with input and output + * + */ +@RunWith(JUnitPlatform.class) +public class CallableMixedTest extends AbstractTest { + Connection connection = null; + Statement statement = null; + String tableN = RandomUtil.getIdentifier("TFOO3"); + String procN = RandomUtil.getIdentifier("SPFOO3"); + String tableName = AbstractSQLGenerator.escapeIdentifier(tableN); + String procName = AbstractSQLGenerator.escapeIdentifier(procN); + + /** + * Tests Callable mix + * @throws SQLException + */ + @Test + @DisplayName("Test CallableMix") + public void datatypesTest() throws SQLException { + connection = DriverManager.getConnection(connectionString); + statement = connection.createStatement(); + + try { + statement.executeUpdate("DROP TABLE " + tableName); + statement.executeUpdate(" DROP PROCEDURE " + procName); + } + catch (Exception e) { + } + + statement.executeUpdate("create table " + tableName + " (c1_int int primary key, col2 int)"); + statement.executeUpdate("Insert into " + tableName + " values(0, 1)"); + statement.close(); + Statement stmt = connection.createStatement(); + stmt.executeUpdate("CREATE PROCEDURE " + procName + + " (@p2_int int, @p2_int_out int OUTPUT, @p4_smallint smallint, @p4_smallint_out smallint OUTPUT) AS begin transaction SELECT * FROM " + + tableName + " ; SELECT @p2_int_out=@p2_int, @p4_smallint_out=@p4_smallint commit transaction RETURN -2147483648"); + stmt.close(); + + CallableStatement callableStatement = connection.prepareCall("{ ? = CALL " + procName + " (?, ?, ?, ?) }"); + callableStatement.registerOutParameter((int) 1, (int) 4); + callableStatement.setObject((int) 2, Integer.valueOf("31"), (int) 4); + callableStatement.registerOutParameter((int) 3, (int) 4); + callableStatement.registerOutParameter((int) 5, java.sql.Types.BINARY); + callableStatement.registerOutParameter((int) 5, (int) 5); + callableStatement.setObject((int) 4, Short.valueOf("-5372"), (int) 5); + + // get results and a value + ResultSet rs = callableStatement.executeQuery(); + rs.next(); + + assertEquals(rs.getInt(1), 0, "Received data not equal to setdata"); + assertEquals(callableStatement.getInt((int) 5), -5372, "Received data not equal to setdata"); + + // do nothing and reexecute + rs = callableStatement.executeQuery(); + // get the param without getting the resultset + rs = callableStatement.executeQuery(); + assertEquals(callableStatement.getInt((int) 1), -2147483648, "Received data not equal to setdata"); + + rs = callableStatement.executeQuery(); + rs.next(); + + assertEquals(rs.getInt(1), 0, "Received data not equal to setdata"); + assertEquals(callableStatement.getInt((int) 1), -2147483648, "Received data not equal to setdata"); + assertEquals(callableStatement.getInt((int) 5), -5372, "Received data not equal to setdata"); + rs = callableStatement.executeQuery(); + callableStatement.close(); + rs.close(); + stmt.close(); + terminateVariation(); + } + + + private void terminateVariation() throws SQLException { + statement = connection.createStatement(); + statement.executeUpdate("DROP TABLE " + tableName); + statement.executeUpdate(" DROP PROCEDURE " + procName); + statement.close(); + connection.close(); + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/LimitEscapeTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/LimitEscapeTest.java new file mode 100644 index 0000000000..58e3dcf71b --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/LimitEscapeTest.java @@ -0,0 +1,803 @@ +/* + * 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.UnitStatement; + +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 java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Vector; +import java.util.logging.Logger; + +import org.junit.Before; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.testframework.AbstractTest; + +/** + * Testing with LimitEscape queries + * + */ +@RunWith(JUnitPlatform.class) +public class LimitEscapeTest extends AbstractTest { + public static final Logger log = Logger.getLogger("LimitEscape"); + private static Vector offsetQuery = new Vector(); + private static Connection conn = null; + + static class Query { + String inputSql, outputSql; + int[] idCols = null; + String[][] stringResultCols = null; + int[][] intResultCols = null; + int rows, columns; + boolean prepared = false; + boolean callable = false; + int preparedCount = 0; + boolean verifyResult = true; + ResultSet resultSet; + int queryID; + int queryId = 0; + static int queryCount = 0; + String exceptionMsg = null; + + /* + * This is used to test different SQL queries. Each SQL query to test is an instance of this class, and is initiated using this constructor. + * This constructor sets the expected results from the query and also verifies the translation by comparing it with manual translation. + * + * @param input The SQL query to test + * + * @param output The manual translation of the query to verify with + * + * @param rows The expected number of rows in ResultSet + * + * @param columns The expected number of columns in ResultSet + * + * @param ids The array of the expected id columns in the ResultSet + * + * @param intCols The array of the expected int columns of each row in the ResultSet + * + * @param stringCols The array of the expected String columns of each row in the ResultSet + */ + Query(String input, + String output, + int rows, + int columns, + int[] ids, + int[][] intCols, + String[][] stringCols) throws Exception { + queryCount++; + + queryID = queryCount; + this.inputSql = input; + this.outputSql = output; + this.rows = rows; + this.columns = columns; + + if (null != ids) { + idCols = ids.clone(); + } + if (null != intCols) { + intResultCols = intCols.clone(); + } + if (null != stringCols) { + stringResultCols = stringCols.clone(); + } + + verifyTranslation(); + } + + public void setExceptionMsg(String errorMessage) { + exceptionMsg = errorMessage; + } + + public void verifyTranslation() throws Exception { + Class[] cArg = new Class[1]; + cArg[0] = String.class; + Class innerClass = Class.forName("com.microsoft.sqlserver.jdbc.JDBCSyntaxTranslator"); + Constructor ctor = innerClass.getDeclaredConstructor(); + if (!ctor.isAccessible()) { + ctor.setAccessible(true); + } + + Object innerInstance = ctor.newInstance(); + Method method = innerClass.getDeclaredMethod("translate", cArg); + + if (!method.isAccessible()) { + method.setAccessible(true); + } + Object str = method.invoke(innerInstance, inputSql); + assertEquals(str, outputSql, "Syntax tyranslation does not match for query: " + queryID); + } + + public void setverifyResult(boolean val) { + this.verifyResult = val; + } + + void executeSpecific(Connection conn) throws Exception { + Statement stmt = conn.createStatement(); + resultSet = stmt.executeQuery(inputSql); + } + + void execute(Connection conn) throws Exception { + try { + executeSpecific(conn); + } + catch (Exception e) { + if (null != exceptionMsg) { + // This query is to verify right exception is thrown for errors in syntax. + assertTrue(e.getMessage().equalsIgnoreCase(exceptionMsg), "Test fatal errors"); + // Exception message matched. Return as there is no result to verify. + return; + } + else + throw e; + } + + if (!verifyResult) { + return; + } + + if (null == resultSet) { + assertEquals(false, true, "ResultSet is null"); + } + + int rowCount = 0; + while (resultSet.next()) { + // The int and string columns should be retrieved in order, for example cannot run a query that retrieves col2 but not col1 + assertEquals(resultSet.getInt(1), idCols[rowCount], "ID value does not match for query: " + queryID + ", row: " + rowCount); + for (int j = 0, colNumber = 1; null != intResultCols && j < intResultCols[rowCount].length; ++j) { + String colName = "col" + colNumber; + assertEquals(resultSet.getInt(colName), intResultCols[rowCount][j], + "Int value does not match for query: " + queryID + ", row: " + rowCount + ", column: " + colName); + colNumber++; + } + for (int j = 0, colNumber = 3; null != stringResultCols && j < stringResultCols[rowCount].length; ++j) { + String colName = "col" + colNumber; + assertEquals(resultSet.getString(colName), stringResultCols[rowCount][j], + "String value does not match for query: " + queryID + ", row: " + rowCount + ", column: " + colName); + colNumber++; + } + rowCount++; + } + assertEquals(rowCount, rows, "Row Count does not match for query"); + assertEquals(resultSet.getMetaData().getColumnCount(), columns, "Column Count does not match"); + } + } + + static class PreparedQuery extends Query { + int placeholderCount = 0; + + PreparedQuery(String input, + String output, + int rows, + int columns, + int[] ids, + int[][] intCols, + String[][] stringCols, + int placeholderCount) throws Exception { + super(input, output, rows, columns, ids, intCols, stringCols); + this.placeholderCount = placeholderCount; + } + + void executeSpecific(Connection conn) throws Exception { + PreparedStatement pstmt = conn.prepareStatement(inputSql); + for (int i = 1; i <= placeholderCount; ++i) { + pstmt.setObject(i, i); + } + resultSet = pstmt.executeQuery(); + } + } + + static class CallableQuery extends PreparedQuery { + CallableQuery(String input, + String output, + int rows, + int columns, + int[] ids, + int[][] intCols, + String[][] stringCols, + int placeholderCount) throws Exception { + super(input, output, rows, columns, ids, intCols, stringCols, placeholderCount); + } + + void execute(Connection conn) throws Exception { + CallableStatement cstmt = conn.prepareCall(inputSql); + for (int i = 1; i <= placeholderCount; ++i) { + cstmt.setObject(i, i); + } + resultSet = cstmt.executeQuery(); + } + } + + public static void createAndPopulateTables(Connection conn) throws Exception { + Statement stmt = conn.createStatement(); + // Instead of table identifiers use some simple table names for this test only, as a lot of string manipulation is done + // around table names. + try { + stmt.executeUpdate("drop table UnitStatement_LimitEscape_t1"); + } + catch (Exception ex) { + } + ; + try { + stmt.executeUpdate("drop table UnitStatement_LimitEscape_t2"); + } + catch (Exception ex) { + } + ; + try { + stmt.executeUpdate("drop table UnitStatement_LimitEscape_t3"); + } + catch (Exception ex) { + } + ; + try { + stmt.executeUpdate("drop table UnitStatement_LimitEscape_t4"); + } + catch (Exception ex) { + } + ; + try { + stmt.executeUpdate("drop procedure UnitStatement_LimitEscape_p1"); + } + catch (Exception ex) { + } + ; + stmt.executeUpdate( + "create table UnitStatement_LimitEscape_t1 (col1 int, col2 int, col3 varchar(100), col4 varchar(100), id int identity(1,1) primary key)"); + stmt.executeUpdate( + "create table UnitStatement_LimitEscape_t2 (col1 int, col2 int, col3 varchar(100), col4 varchar(100), id int identity(1,1) primary key)"); + stmt.executeUpdate( + "create table UnitStatement_LimitEscape_t3 (col1 int, col2 int, col3 varchar(100), col4 varchar(100), id int identity(1,1) primary key)"); + stmt.executeUpdate( + "create table UnitStatement_LimitEscape_t4 (col1 int, col2 int, col3 varchar(100), col4 varchar(100), id int identity(1,1) primary key)"); + + stmt.executeUpdate("Insert into UnitStatement_LimitEscape_t1 values " + "(1, 1, 'col3', 'col4'), " + + "(2, 2, 'row2 '' with '' quote', 'row2 with limit {limit 22} {limit ?}')," + + "(3, 3, 'row3 with subquery (select * from t1)', 'row3 with subquery (select * from (select * from t1) {limit 4})')," + + "(4, 4, 'select * from t1 {limit 4} ''quotes'' (braces)', 'ucase(scalar function)')," + + "(5, 5, 'openquery(''server'', ''query'')', 'openrowset(''server'',''connection string'',''query'')')"); + stmt.executeUpdate("Insert into UnitStatement_LimitEscape_t2 values (11, 11, 'col33', 'col44')"); + stmt.executeUpdate("Insert into UnitStatement_LimitEscape_t3 values (111, 111, 'col333', 'col444')"); + stmt.executeUpdate("Insert into UnitStatement_LimitEscape_t4 values (1111, 1111, 'col4444', 'col4444')"); + String query = "create procedure UnitStatement_LimitEscape_p1 @col3Value varchar(512), @col4Value varchar(512) AS BEGIN SELECT TOP 1 * from UnitStatement_LimitEscape_t1 where col3 = @col3Value and col4 = @col4Value END"; + stmt.execute(query); + } + + /** + * Initialize and verify queries + * @throws Exception + */ + @Test + @DisplayName("initAndVerifyQueries") + public void initAndVerifyQueries() throws Exception { + Query qry; + // 1 + // Test whether queries without limit syntax works + qry = new Query("select TOP 1 * from UnitStatement_LimitEscape_t1", "select TOP 1 * from UnitStatement_LimitEscape_t1", 1, // # of rows + 5, // # of columns + new int[] {1}, // id column values + new int[][] {{1, 1}}, // int column values + new String[][] {{"col3", "col4"}}); // string column values + qry.execute(conn); + + // 2 + // Test parentheses in limit syntax + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit ( ( (2)))}", "select TOP ( ( (2))) * from UnitStatement_LimitEscape_t1", + 2, // # of rows + 5, // # of columns + new int[] {1, 2}, // id column values + new int[][] {{1, 1}, {2, 2}}, // int column values + new String[][] {{"col3", "col4"}, {"row2 ' with ' quote", "row2 with limit {limit 22} {limit ?}"}}); // string column values + qry.execute(conn); + + // 3 + // Test limit syntax in string literal as well as in query, also test subquery syntax in string literal + qry = new Query( + "select ( (col1)), ( ((col2) ) ) from UnitStatement_LimitEscape_t1 where col3 = 'row3 with subquery (select * from t1)' and col4 = 'row3 with subquery (select * from (select * from t1) {limit 4})' {limit (35)}", + "select TOP (35) ( (col1)), ( ((col2) ) ) from UnitStatement_LimitEscape_t1 where col3 = 'row3 with subquery (select * from t1)' and col4 = 'row3 with subquery (select * from (select * from t1) {limit 4})'", + 1, // # of rows + 2, // # of columns + new int[] {3}, // id column values + new int[][] {{3, 3}}, // int column values + null); // string column values + qry.execute(conn); + + // 4 + // Test quotes/limit syntax/scalar function in string literal. Also test real limit syntax in query. + qry = new Query( + "select (col1), (col2) from UnitStatement_LimitEscape_t1 where col3 = 'select * from t1 {limit 4} ''quotes'' (braces)' and col4 = 'ucase(scalar function)' {limit 3543}", + "select TOP 3543 (col1), (col2) from UnitStatement_LimitEscape_t1 where col3 = 'select * from t1 {limit 4} ''quotes'' (braces)' and col4 = 'ucase(scalar function)'", + 1, // # of rows + 2, // # of columns + new int[] {4}, // id column values + new int[][] {{4, 4}}, // int column values + null); // string column values + qry.execute(conn); + + // 5 + // Test openquery/openrowset in string literals + qry = new Query( + "select col1 from UnitStatement_LimitEscape_t1 where col3 = 'openquery(''server'', ''query'')' and col4 = 'openrowset(''server'',''connection string'',''query'')' {limit (((2)))}", + "select TOP (((2))) col1 from UnitStatement_LimitEscape_t1 where col3 = 'openquery(''server'', ''query'')' and col4 = 'openrowset(''server'',''connection string'',''query'')'", + 1, // # of rows + 1, // # of columns + new int[] {5}, // id column values + new int[][] {{5}}, // int column values + null); // string column values + qry.execute(conn); + + // 6 + // Test limit syntax in subquery as well as in outer query + qry = new Query("select id from (select * from UnitStatement_LimitEscape_t1 {limit 10}) t1 {limit ((1) )}", + "select TOP ((1) ) id from (select TOP 10 * from UnitStatement_LimitEscape_t1) t1", 1, // # of rows + 1, // # of columns + new int[] {1}, // id column values + null, // int column values + null); // string column values + qry.execute(conn); + + // 7 + // Test multiple parentheses in limit syntax and in subquery + qry = new Query("select id from (( (select * from UnitStatement_LimitEscape_t1 {limit 10})) ) t1 {limit ((1) )}", + "select TOP ((1) ) id from (( (select TOP 10 * from UnitStatement_LimitEscape_t1)) ) t1", 1, // # of rows + 1, // # of columns + new int[] {1}, // id column values + null, // int column values + null); // string column values + qry.execute(conn); + + // 8 + // Test limit syntax in multiple subqueries, also test arbitrary spaces in limit syntax + qry = new Query( + "select j1.id from (( (select * from UnitStatement_LimitEscape_t1 {limit 10})) ) j1 join (select * from UnitStatement_LimitEscape_t2 {limit 4}) j2 on j1.id = j2.id {limit (1)}", + "select TOP (1) j1.id from (( (select TOP 10 * from UnitStatement_LimitEscape_t1)) ) j1 join (select TOP 4 * from UnitStatement_LimitEscape_t2) j2 on j1.id = j2.id", + 1, // # of rows + 1, // # of columns + new int[] {1}, // id column values + null, // int column values + null); // string column values + qry.execute(conn); + + // 9 + // Test limit syntax in multiple levels of nested subqueries + qry = new Query( + "select j1.id from (select * from (select * from UnitStatement_LimitEscape_t1 {limit 3}) j3 {limit 2}) j1 join (select * from UnitStatement_LimitEscape_t2 {limit 4}) j2 on j1.id = j2.id {limit 1}", + "select TOP 1 j1.id from (select TOP 2 * from (select TOP 3 * from UnitStatement_LimitEscape_t1) j3) j1 join (select TOP 4 * from UnitStatement_LimitEscape_t2) j2 on j1.id = j2.id", + 1, // # of rows + 1, // # of columns + new int[] {1}, // id column values + null, // int column values + null); // string column values + qry.execute(conn); + + // 10 + // Test limit syntax in multiple levels of nested subqueries as well as in outer query + qry = new Query( + "select j1.id from (select * from (select * from UnitStatement_LimitEscape_t1 {limit 3}) j3 {limit 2}) j1 join (select j4.id from (select * from UnitStatement_LimitEscape_t3 {limit 5}) j4 join (select * from UnitStatement_LimitEscape_t4 {limit 6}) j5 on j4.id = j5.id ) j2 on j1.id = j2.id {limit 1}", + "select TOP 1 j1.id from (select TOP 2 * from (select TOP 3 * from UnitStatement_LimitEscape_t1) j3) j1 join (select j4.id from (select TOP 5 * from UnitStatement_LimitEscape_t3) j4 join (select TOP 6 * from UnitStatement_LimitEscape_t4) j5 on j4.id = j5.id ) j2 on j1.id = j2.id", + 1, // # of rows + 1, // # of columns + new int[] {1}, // id column values + null, // int column values + null); // string column values + qry.execute(conn); + + // 11 + // Test multiple parentheses/spaces in limit syntax, also test '[]' in columns + qry = new Query("select [col1], col2, [col3], col4 from [UnitStatement_LimitEscape_t1] {limit ( ( (2)))}", + "select TOP ( ( (2))) [col1], col2, [col3], col4 from [UnitStatement_LimitEscape_t1]", 2, // # of rows + 4, // # of columns + new int[] {1, 2}, // id column values + new int[][] {{1, 1}, {2, 2}}, // int column values + new String[][] {{"col3", "col4"}, {"row2 ' with ' quote", "row2 with limit {limit 22} {limit ?}"}}); // string column values + qry.execute(conn); + + // 12 + // Test complicated query with nested subquery having limit syntax + qry = new Query( + "select j1.id from ( ((select * from (select * from UnitStatement_LimitEscape_t1 {limit 3}) j3 {limit 2}))) j1 join (select j4.id from ((((select * from UnitStatement_LimitEscape_t3 {limit 5})))) j4 join (select * from UnitStatement_LimitEscape_t4 {limit 6}) j5 on j4.id = j5.id ) j2 on j1.id = j2.id {limit 1}", + "select TOP 1 j1.id from ( ((select TOP 2 * from (select TOP 3 * from UnitStatement_LimitEscape_t1) j3))) j1 join (select j4.id from ((((select TOP 5 * from UnitStatement_LimitEscape_t3)))) j4 join (select TOP 6 * from UnitStatement_LimitEscape_t4) j5 on j4.id = j5.id ) j2 on j1.id = j2.id", + 1, // # of rows + 1, // # of columns + new int[] {1}, // id column values + null, // int column values + null); // string column values + qry.execute(conn); + + // 13 + // Test prepared statements with limit syntax with multiple parentheses/spaces + qry = new PreparedQuery("select * from UnitStatement_LimitEscape_t1 {limit ( ( (?)))}", + "select TOP ( ( (?))) * from UnitStatement_LimitEscape_t1", 1, // # of rows + 5, // # of columns + new int[] {1}, // id column values + new int[][] {{1, 1}}, // int column values + new String[][] {{"col3", "col4"}}, 1); + qry.execute(conn); + + // 14 + // Test prepared statements with limit syntax + qry = new PreparedQuery("select * from UnitStatement_LimitEscape_t1 {limit (?)}", "select TOP (?) * from UnitStatement_LimitEscape_t1", 1, // # + // of + // rows + 5, // # of columns + new int[] {1}, // id column values + new int[][] {{1, 1}}, // int column values + new String[][] {{"col3", "col4"}}, 1); + qry.execute(conn); + + // 15 + // Test prepared statements with limit syntax with multiple parentheses/spaces + qry = new PreparedQuery("select * from UnitStatement_LimitEscape_t1 {limit ?}", "select TOP (?) * from UnitStatement_LimitEscape_t1", 1, // # + // of + // rows + 5, // # of columns + new int[] {1}, // id column values + new int[][] {{1, 1}}, // int column values + new String[][] {{"col3", "col4"}}, 1); + qry.execute(conn); + + // 16 + // Test prepared statements with limit syntax with subqueries + qry = new PreparedQuery("select * from (select * from UnitStatement_LimitEscape_t1 {limit ?}) t1 {limit (?)}", + "select TOP (?) * from (select TOP (?) * from UnitStatement_LimitEscape_t1) t1", 1, // # of rows + 5, // # of columns + new int[] {1}, // id column values + new int[][] {{1, 1}}, // int column values + new String[][] {{"col3", "col4"}}, 2); + qry.execute(conn); + + // 17 + // Test callable statements as they are also translated by the driver + qry = new CallableQuery("EXEC UnitStatement_LimitEscape_p1 @col3Value = 'col3', @col4Value = 'col4'", + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'col3', @col4Value = 'col4'", 1, // # of rows + 5, // # of columns + new int[] {1}, // id column values + new int[][] {{1, 1}}, // int column values + new String[][] {{"col3", "col4"}}, 0); + qry.execute(conn); + + // 18 + // Test callable statements with limit syntax in string literals + qry = new CallableQuery( + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'row2 '' with '' quote', @col4Value = 'row2 with limit {limit 22} {limit ?}'", + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'row2 '' with '' quote', @col4Value = 'row2 with limit {limit 22} {limit ?}'", 1, // # + // of + // rows + 5, // # of columns + new int[] {2}, // id column values + new int[][] {{2, 2}}, // int column values + new String[][] {{"row2 ' with ' quote", "row2 with limit {limit 22} {limit ?}"}}, 0); + qry.execute(conn); + + // 19 + // Test callable statements with subquery/limit syntax in string literals + qry = new CallableQuery( + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'row3 with subquery (select * from t1)', @col4Value = 'row3 with subquery (select * from (select * from t1) {limit 4})'", + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'row3 with subquery (select * from t1)', @col4Value = 'row3 with subquery (select * from (select * from t1) {limit 4})'", + 1, // # of rows + 5, // # of columns + new int[] {3}, // id column values + new int[][] {{3, 3}}, // int column values + new String[][] {{"row3 with subquery (select * from t1)", "row3 with subquery (select * from (select * from t1) {limit 4})"}}, 0); + qry.execute(conn); + + // 20 + // Test callable statements with quotes/scalar functions/limit syntax in string literals + qry = new CallableQuery( + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'select * from t1 {limit 4} ''quotes'' (braces)', @col4Value = 'ucase(scalar function)'", + "EXEC UnitStatement_LimitEscape_p1 @col3Value = 'select * from t1 {limit 4} ''quotes'' (braces)', @col4Value = 'ucase(scalar function)'", + 1, // # of rows + 5, // # of columns + new int[] {4}, // id column values + new int[][] {{4, 4}}, // int column value + new String[][] {{"select * from t1 {limit 4} 'quotes' (braces)", "ucase(scalar function)"}}, 0); + qry.execute(conn); + + // 21 + // Test callable statement escape syntax with quotes/scalar functions/limit syntax in string literals + qry = new CallableQuery("{call UnitStatement_LimitEscape_p1 ('select * from t1 {limit 4} ''quotes'' (braces)', 'ucase(scalar function)')}", + "EXEC UnitStatement_LimitEscape_p1 'select * from t1 {limit 4} ''quotes'' (braces)', 'ucase(scalar function)'", 1, // # of rows + 5, // # of columns + new int[] {4}, // id column values + new int[][] {{4, 4}}, // int column value + new String[][] {{"select * from t1 {limit 4} 'quotes' (braces)", "ucase(scalar function)"}}, 0); + qry.execute(conn); + + // 22 + // Test callable statement escape syntax with openrowquery/openrowset/quotes in string literals + qry = new CallableQuery( + "{call UnitStatement_LimitEscape_p1 ('openquery(''server'', ''query'')', 'openrowset(''server'',''connection string'',''query'')')}", + "EXEC UnitStatement_LimitEscape_p1 'openquery(''server'', ''query'')', 'openrowset(''server'',''connection string'',''query'')'", 1, // # + // of + // rows + 5, // # of columns + new int[] {5}, // id column values + new int[][] {{5, 5}}, // int column value + new String[][] {{"openquery('server', 'query')", "openrowset('server','connection string','query')"}}, 0); + qry.execute(conn); + + // Do not execute this query as no lnked_server is setup. Only verify the translation for it. + // 23 + // Test openquery syntax translation with limit syntax + qry = new Query("select * from openquery('linked_server', 'select * from UnitStatement_LimitEscape_t1 {limit 2}') {limit 1}", + "select TOP 1 * from openquery('linked_server', 'select TOP 2 * from UnitStatement_LimitEscape_t1')", 1, // # of rows + 5, // # of columns + new int[] {5}, // id column values + new int[][] {{5, 5}}, // int column value + new String[][] {{"openquery('server', 'query')", "openrowset('server','connection string','query')"}}); + + // Do not execute this query as no lnked_server is setup. Only verify the translation for it. + // 24 + // Test openrowset syntax translation with a complicated query with subqueries and limit syntax + qry = new Query( + "select * from openrowset('provider_name', 'provider_string', 'select j1.id from (select * from (select * from UnitStatement_LimitEscape_t1 {limit 3}) j3 {limit 2}) j1 join (select j4.id from (select * from UnitStatement_LimitEscape_t3 {limit 5}) j4 join (select * from UnitStatement_LimitEscape_t4 {limit 6}) j5 on j4.id = j5.id ) j2 on j1.id = j2.id {limit 1}') {limit 1}", + "select TOP 1 * from openrowset('provider_name', 'provider_string', 'select TOP 1 j1.id from (select TOP 2 * from (select TOP 3 * from UnitStatement_LimitEscape_t1) j3) j1 join (select j4.id from (select TOP 5 * from UnitStatement_LimitEscape_t3) j4 join (select TOP 6 * from UnitStatement_LimitEscape_t4) j5 on j4.id = j5.id ) j2 on j1.id = j2.id')", + 1, // # of rows + 5, // # of columns + new int[] {5}, // id column values + new int[][] {{5, 5}}, // int column value + new String[][] {{"openquery('server', 'query')", "openrowset('server','connection string','query')"}}); + + // 25 + // Test offset syntax in string literals + qry = new Query("select * from UnitStatement_LimitEscape_t1 where col3 = '{limit 1 offset 2}'", + "select * from UnitStatement_LimitEscape_t1 where col3 = '{limit 1 offset 2}'", 0, // # of rows + 5, // # of columns + null, // id column values + null, // int column values + null); + qry.execute(conn); + + // 26 + // Do not execute this query as it is a batch query, needs to be handled differently. + // Only test the syntax translation. + // Test batch query. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit 1}; select * from UnitStatement_LimitEscape_t1 {limit 4}", + "select TOP 1 * from UnitStatement_LimitEscape_t1; select TOP 4 * from UnitStatement_LimitEscape_t1", 0, // # of rows + 5, // # of columns + null, // id column values + null, // int column values + null); + + // 27 + // Execute query, and verify exception for unclosed quotation marks. + qry = new Query("select * from UnitStatement_LimitEscape_t1 where col3 = 'abcd", + "select * from UnitStatement_LimitEscape_t1 where col3 = 'abcd", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Unclosed quotation mark after the character string 'abcd'."); + qry.execute(conn); + + // 28 + // Execute query, and verify exception for unclosed subquery. + qry = new Query("select * from (select * from UnitStatement_LimitEscape_t1 {limit 1}", + "select * from (select TOP 1 * from UnitStatement_LimitEscape_t1", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near 'UnitStatement_LimitEscape_t1'."); + qry.execute(conn); + + // 29 + // Execute query, and verify exception for syntax error in select. + qry = new Query("selectsel * from from UnitStatement_LimitEscape_t1 {limit 1}", + "selectsel * from from UnitStatement_LimitEscape_t1 {limit 1}", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '*'."); + qry.execute(conn); + + // 29 + // Execute query, and verify exception for limit syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit1}", "select * from UnitStatement_LimitEscape_t1 {limit1}", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + // 30 + // Execute query, and verify exception for limit syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit(1}", "select * from UnitStatement_LimitEscape_t1 {limit(1}", 0, // # of + // rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + // 31 + // Execute query, and verify exception for limit syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit 1 offset10}", + "select * from UnitStatement_LimitEscape_t1 {limit 1 offset10}", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + // 32 + // Execute query, and verify exception for limit syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit1 offset 10}", + "select * from UnitStatement_LimitEscape_t1 {limit1 offset 10}", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + // 33 + // Execute query, and verify exception for limit syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit1 offset10}", + "select * from UnitStatement_LimitEscape_t1 {limit1 offset10}", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + // 34 + // Execute query, and verify exception for syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("insert into UnitStatement_LimitEscape_t1(col3) values({limit 1})", + "insert into UnitStatement_LimitEscape_t1(col3) values({limit 1})", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '1'."); + qry.execute(conn); + + // 35 + // Execute query, and verify exception for syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit {limit 5}}", "select TOP 5 * from UnitStatement_LimitEscape_t1 {limit}", 0, // # + // of + // rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + // 36 + // Execute query, and verify exception for syntax error. The translator should leave the query unchanged as limit syntax is not correct. + qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit 1} {limit 2}", + "select TOP 1 * from UnitStatement_LimitEscape_t1 {limit 2}", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + // Verified that SQL Server throws an exception with this message for similar errors. + qry.setExceptionMsg("Incorrect syntax near '{'."); + qry.execute(conn); + + log.fine("Tranlsation verified for " + qry.queryCount + " queries"); + } + + /** + * Verify offset Exception + * @throws Exception + */ + @Test + @DisplayName("verifyOffsetException") + public void verifyOffsetException() throws Exception { + offsetQuery.addElement("select * from UnitStatement_LimitEscape_t1 {limit 2 offset 1}"); + offsetQuery.addElement("select * from UnitStatement_LimitEscape_t1 {limit 2232 offset 1232}"); + offsetQuery.addElement("select * from UnitStatement_LimitEscape_t1 {limit (2) offset (1)}"); + offsetQuery.addElement("select * from UnitStatement_LimitEscape_t1 {limit (265) offset (1972)}"); + offsetQuery.addElement("select * from UnitStatement_LimitEscape_t1 {limit ? offset ?}"); + offsetQuery.addElement("select * from UnitStatement_LimitEscape_t1 {limit (?) offset (?)}"); + + int i; + for (i = 0; i < offsetQuery.size(); ++i) { + try { + // Do not execute query. Exception will be thrown when verifying translation. + Query qry = new Query(offsetQuery.elementAt(i), "", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + } + // Exception was thrown from Java reflection method invocation + catch (InvocationTargetException e) { + assertEquals(e.toString(), "java.lang.reflect.InvocationTargetException"); + } + } + log.fine("Offset exception verified for " + i + " queries"); + // Test the parsing error with unmatched braces in limit clause + try { + // Do not execute query. Exception will be thrown when verifying translation. + Query qry = new Query("select * from UnitStatement_LimitEscape_t1 {limit (2))}", "", 0, // # of rows + 0, // # of columns + null, // id column values + null, // int column values + null); // string column values + } + // Exception was thrown from Java reflection method invocation + catch (InvocationTargetException e) { + assertEquals(e.toString(), "java.lang.reflect.InvocationTargetException"); + } + } + + /** + * clean up + */ + @BeforeAll + public static void beforeAll() { + try { + conn = DriverManager.getConnection(connectionString); + createAndPopulateTables(conn); + } + catch (Exception e) { + fail(e.toString()); + } + } + + /** + * Clean up + * @throws Exception + */ + @AfterAll + public static void afterAll() throws Exception { + + Statement stmt = conn.createStatement(); + try { + stmt.executeUpdate("IF OBJECT_ID (N'UnitStatement_LimitEscape_t1', N'U') IS NOT NULL DROP TABLE UnitStatement_LimitEscape_t1"); + + stmt.executeUpdate("IF OBJECT_ID (N'UnitStatement_LimitEscape_t2', N'U') IS NOT NULL DROP TABLE UnitStatement_LimitEscape_t2"); + + stmt.executeUpdate("IF OBJECT_ID (N'UnitStatement_LimitEscape_t3', N'U') IS NOT NULL DROP TABLE UnitStatement_LimitEscape_t3"); + + stmt.executeUpdate("IF OBJECT_ID (N'UnitStatement_LimitEscape_t4', N'U') IS NOT NULL DROP TABLE UnitStatement_LimitEscape_t4"); + } + catch (Exception ex) { + fail(ex.toString()); + } + finally { + stmt.close(); + conn.close(); + } + + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/MergeTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/MergeTest.java new file mode 100644 index 0000000000..2cd76f7823 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/MergeTest.java @@ -0,0 +1,95 @@ +/* + * 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.UnitStatement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.ResultSet; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.DBConnection; +import com.microsoft.sqlserver.testframework.DBStatement; + +/** + * Testing merge queries + */ +@RunWith(JUnitPlatform.class) +public class MergeTest extends AbstractTest { + private static final String setupTables = "IF OBJECT_ID (N'dbo.CricketTeams', N'U') IS NOT NULL DROP TABLE dbo.CricketTeams;" + + " CREATE TABLE dbo.CricketTeams ( CricketTeamID tinyint NOT NULL PRIMARY KEY, CricketTeamCountry nvarchar(30), CricketTeamContinent nvarchar(50))" + + " INSERT INTO dbo.CricketTeams VALUES (1, 'Australia', 'Australia'), (2, 'India', 'Asia'), (3, 'Pakistan', 'Asia'), (4, 'Srilanka', 'Asia'), (5, 'Bangaladesh', 'Asia'), (6, 'HongKong', 'Asia')," + + " (7, 'U.A.E', 'Asia'), (8, 'England', 'Europe'), (9, 'South Africa', 'Africa'), (10, 'West Indies', 'North America');" + + " SELECT * FROM CricketTeams IF OBJECT_ID (N'dbo.CricketTeams_UpdatedList', N'U') IS NOT NULL DROP TABLE dbo.CricketTeams_UpdatedList;" + + " CREATE TABLE dbo.CricketTeams_UpdatedList ( CricketTeamID tinyint NOT NULL PRIMARY KEY, CricketTeamCountry nvarchar(30), CricketTeamContinent nvarchar(50))" + + "INSERT INTO dbo.CricketTeams_UpdatedList VALUES (1, 'Australia', 'Australia'), (2, 'India', 'Asia'), (3, 'Pakistan', 'Asia'), (4, 'Srilanka', 'Asia'), (5, 'Bangaladesh', 'Asia')," + + " (6, 'Hong Kong', 'Asia'), (8, 'England', 'Europe'), (9, 'South Africa', 'Africa'), (10, 'West Indies', 'North America'), (11, 'Zimbabwe', 'Africa');"; + + private static final String mergeCmd2 = "MERGE dbo.CricketTeams AS TARGET " + "USING dbo.CricketTeams_UpdatedList AS SOURCE " + + "ON (TARGET.CricketTeamID = SOURCE.CricketTeamID) " + "WHEN MATCHED AND TARGET.CricketTeamContinent <> SOURCE.CricketTeamContinent OR " + + "TARGET.CricketTeamCountry <> SOURCE.CricketTeamCountry " + + "THEN UPDATE SET TARGET.CricketTeamContinent = SOURCE.CricketTeamContinent ," + "TARGET.CricketTeamCountry = SOURCE.CricketTeamCountry " + + "WHEN NOT MATCHED THEN " + "INSERT (CricketTeamID, CricketTeamCountry, CricketTeamContinent) " + + "VALUES (SOURCE.CricketTeamID, SOURCE.CricketTeamCountry, SOURCE.CricketTeamContinent) " + + "WHEN NOT MATCHED BY SOURCE THEN DELETE;"; + + + /** + * Merge test + * @throws Exception + */ + @Test + @DisplayName("Merge Test") + public void runTest() throws Exception { + DBConnection conn = new DBConnection(connectionString); + if (conn.getServerVersion() >= 10) { + DBStatement stmt = conn.createStatement(); + stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + stmt.executeUpdate(setupTables); + stmt.executeUpdate(mergeCmd2); + int updateCount = stmt.getUpdateCount(); + assertEquals(updateCount, 3, "Received the wrong update count!"); + + if (null != stmt) { + stmt.close(); + } + if (null != conn) { + conn.close(); + } + } + } + + /** + * Clean up + * @throws Exception + */ + @AfterAll + public static void afterAll() throws Exception { + + DBConnection conn = new DBConnection(connectionString); + DBStatement stmt = conn.createStatement(); + try { + stmt.executeUpdate("IF OBJECT_ID (N'dbo.CricketTeams', N'U') IS NOT NULL DROP TABLE dbo.CricketTeams"); + } + catch (Exception ex) { + fail(ex.toString()); + } + finally { + stmt.close(); + conn.close(); + } + + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/NamedParamMultiPartTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/NamedParamMultiPartTest.java new file mode 100644 index 0000000000..866f6f5ed3 --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/NamedParamMultiPartTest.java @@ -0,0 +1,154 @@ +/* + * 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.UnitStatement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.testframework.AbstractTest; + +/** + * Multipart parameters + * + */ +@RunWith(JUnitPlatform.class) +public class NamedParamMultiPartTest extends AbstractTest { + private static final String dataPut = "eminem "; + private static Connection connection = null; + private static CallableStatement cs = null; + + /** + * setup + * @throws SQLException + */ + @BeforeAll + public static void beforeAll() throws SQLException { + connection = DriverManager.getConnection(connectionString); + Statement statement = connection.createStatement(); + statement.executeUpdate( + "if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[mystoredproc]') and OBJECTPROPERTY(id, N'IsProcedure') = 1) DROP PROCEDURE [mystoredproc]"); + statement.executeUpdate("CREATE PROCEDURE [mystoredproc] (@p_out varchar(255) OUTPUT) AS set @p_out = '" + dataPut + "'"); + statement.close(); + } + /** + * Stored procedure call + * @throws Exception + */ + @Test + public void update1() throws Exception { + cs = connection.prepareCall("{ CALL [mystoredproc] (?) }"); + cs.registerOutParameter("p_out", Types.VARCHAR); + cs.executeUpdate(); + String data = cs.getString("p_out"); + assertEquals(data, dataPut, "Received data not equal to setdata"); + } + + /** + * Stored procedure call + * @throws Exception + */ + @Test + public void update2() throws Exception { + cs = connection.prepareCall("{ CALL [dbo].[mystoredproc] (?) }"); + cs.registerOutParameter("p_out", Types.VARCHAR); + cs.executeUpdate(); + Object data = cs.getObject("p_out"); + assertEquals(data, dataPut, "Received data not equal to setdata"); + } + + /** + * Stored procedure call + * @throws Exception + */ + @Test + public void update3() throws Exception { + String catalog = connection.getCatalog(); + String storedproc = "[" + catalog + "]" + ".[dbo].[mystoredproc]"; + cs = connection.prepareCall("{ CALL " + storedproc + " (?) }"); + cs.registerOutParameter("p_out", Types.VARCHAR); + cs.executeUpdate(); + Object data = cs.getObject("p_out"); + assertEquals(data, dataPut, "Received data not equal to setdata"); + } + + /** + * Stored procedure call + * @throws Exception + */ + @Test + public void update4() throws Exception { + cs = connection.prepareCall("{ CALL mystoredproc (?) }"); + cs.registerOutParameter("p_out", Types.VARCHAR); + cs.executeUpdate(); + Object data = cs.getObject("p_out"); + assertEquals(data, dataPut, "Received data not equal to setdata"); + } + + /** + * Stored procedure call + * @throws Exception + */ + @Test + public void update5() throws Exception { + cs = connection.prepareCall("{ CALL dbo.mystoredproc (?) }"); + cs.registerOutParameter("p_out", Types.VARCHAR); + cs.executeUpdate(); + Object data = cs.getObject("p_out"); + assertEquals(data, dataPut, "Received data not equal to setdata"); + } + + /** + * + * @throws Exception + */ + @Test + public void update6() throws Exception { + String catalog = connection.getCatalog(); + String storedproc = catalog + ".dbo.mystoredproc"; + cs = connection.prepareCall("{ CALL " + storedproc + " (?) }"); + cs.registerOutParameter("p_out", Types.VARCHAR); + cs.executeUpdate(); + Object data = cs.getObject("p_out"); + assertEquals(data, dataPut, "Received data not equal to setdata"); + } + + /** + * Clean up + */ + @AfterAll + public static void afterAll() { + try { + if (null != connection) { + connection.close(); + } + if (null != cs) { + cs.close(); + } + } + catch (SQLException e) { + fail(e.toString()); + } + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/PQImpsTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/PQImpsTest.java new file mode 100644 index 0000000000..1fc1e4b59c --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/PQImpsTest.java @@ -0,0 +1,1140 @@ +/* + * 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.UnitStatement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.DriverManager; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerConnection; +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.util.RandomUtil; + +/** + * Tests different kinds of queries + * + */ +@RunWith(JUnitPlatform.class) +public class PQImpsTest extends AbstractTest { + private static final int SQL_SERVER_2012_VERSION = 11; + + private static SQLServerConnection connection = null; + private static Statement stmt = null; + private static PreparedStatement pstmt = null; + private static ResultSet rs = null; + private static ResultSet versionRS = null; + private static int version = -1; + + private static String nameTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("names_DB")); + private static String phoneNumberTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("phoneNumbers_DB")); + private static String mergeNameDesTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("mergeNameDesTable_DB")); + private static String numericTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("numericTable_DB")); + private static String charTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("charTable_DB")); + private static String binaryTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("binaryTable_DB")); + private static String dateAndTimeTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("dateAndTimeTable_DB")); + private static String multipleTypesTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("multipleTypesTable_DB")); + + /** + * Setup + * @throws SQLException + */ + @BeforeAll + public static void BeforeTests() throws SQLException { + connection = (SQLServerConnection) DriverManager.getConnection(connectionString); + stmt = connection.createStatement(); + version = getSQLServerVersion(); + createMultipleTypesTable(); + createNumericTable(); + createCharTable(); + createBinaryTable(); + createDateAndTimeTable(); + createTablesForCompexQueries(); + populateTablesForCompexQueries(); + } + + /** + * Numeric types test + * @throws SQLException + */ + @Test + @DisplayName("Numeric types") + public void numericTest() throws SQLException { + try { + populateNumericTable(); + testBeforeExcute(); + selectNumeric(); + checkNumericMetaData(); + // old PQ implementation doesn't work with "insert" + if (version >= SQL_SERVER_2012_VERSION) { + insertNumeric(); + checkNumericMetaData(); + updateNumeric(); + checkNumericMetaData(); + } + deleteNumeric(); + checkNumericMetaData(); + } + catch (Exception e) { + fail(e.toString()); + } + } + + /** + * Char types test + * @throws SQLException + */ + @Test + @DisplayName("Char Types") + public void charTests() throws SQLException { + try { + populateCharTable(); + selectChar(); + checkCharMetaData(4); + + if (version >= SQL_SERVER_2012_VERSION) { + insertChar(); + checkCharMetaData(6); + updateChar(); + checkCharMetaData(6); + } + deleteChar(); + checkCharMetaData(4); + } + catch (Exception e) { + fail(e.toString()); + } + + } + + /** + * Binary types test + * @throws SQLException + */ + @Test + @DisplayName("Binary Types") + public void binaryTests() throws SQLException { + try { + + populateBinaryTable(); + selectBinary(); + checkBinaryMetaData(); + + if (version >= SQL_SERVER_2012_VERSION) { + insertBinary(); + checkBinaryMetaData(); + updateBinary(); + checkBinaryMetaData(); + } + deleteBinary(); + checkBinaryMetaData(); + } + catch (Exception e) { + fail(e.toString()); + } + + } + + /** + * Temporal types test + * @throws SQLException + */ + @Test + @DisplayName("Temporal Types") + public void temporalTests() throws SQLException { + + try { + populateDateAndTimeTable(); + selectDateAndTime(); + checkDateAndTimeMetaData(); + + if (version >= SQL_SERVER_2012_VERSION) { + insertDateAndTime(); + checkDateAndTimeMetaData(); + updateDateAndTime(); + checkDateAndTimeMetaData(); + } + deleteDateAndTime(); + checkDateAndTimeMetaData(); + } + catch (Exception e) { + fail(e.toString()); + } + + } + + /** + * Multiple Types table + * @throws Exception + */ + @Test + @DisplayName("Multiple Types Table") + public void MultipleTypesTableTest() throws Exception { + + try { + if (version >= SQL_SERVER_2012_VERSION) { + testInsertMultipleTypes(); + testMixedWithHardcodedValues(); + } + } + catch (SQLServerException e) { + fail(e.toString()); + } + + } + + private static int getSQLServerVersion() throws SQLException { + versionRS = stmt.executeQuery("SELECT CONVERT(varchar(100), SERVERPROPERTY('ProductVersion'))"); + versionRS.next(); + String versionString = versionRS.getString(1); + int dotIndex = versionString.indexOf("."); + + return Integer.parseInt(versionString.substring(0, dotIndex)); + } + + private static void checkNumericMetaData() throws SQLException { + + ParameterMetaData pmd = pstmt.getParameterMetaData(); + + assertEquals(pmd.getParameterCount(), 13, "Not all parameters are recognized by driver."); + + compareParameterMetaData(pmd, 1, "java.math.BigDecimal", 3, "decimal", 18, 0); + compareParameterMetaData(pmd, 2, "java.math.BigDecimal", 3, "decimal", 10, 5); + compareParameterMetaData(pmd, 3, "java.math.BigDecimal", 2, "numeric", 18, 0); + compareParameterMetaData(pmd, 4, "java.math.BigDecimal", 2, "numeric", 8, 4); + compareParameterMetaData(pmd, 5, "java.lang.Double", 8, "float", 15, 0); + compareParameterMetaData(pmd, 6, "java.lang.Float", 7, "real", 7, 0); + compareParameterMetaData(pmd, 7, "java.lang.Float", 7, "real", 7, 0); + compareParameterMetaData(pmd, 8, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 9, "java.lang.Long", -5, "bigint", 19, 0); + compareParameterMetaData(pmd, 10, "java.lang.Short", 5, "smallint", 5, 0); + compareParameterMetaData(pmd, 11, "java.lang.Short", -6, "tinyint", 3, 0); + compareParameterMetaData(pmd, 12, "java.math.BigDecimal", 3, "money", 19, 4); + compareParameterMetaData(pmd, 13, "java.math.BigDecimal", 3, "smallmoney", 10, 4); + } + + private static void checkCharMetaData(int expectedParameterCount) throws SQLException { + + ParameterMetaData pmd = pstmt.getParameterMetaData(); + + assertEquals(pmd.getParameterCount(), expectedParameterCount, "Not all parameters are recognized by driver."); + + compareParameterMetaData(pmd, 1, "java.lang.String", 1, "char", 50, 0); + compareParameterMetaData(pmd, 2, "java.lang.String", 12, "varchar", 20, 0); + compareParameterMetaData(pmd, 3, "java.lang.String", -15, "nchar", 30, 0); + compareParameterMetaData(pmd, 4, "java.lang.String", -9, "nvarchar", 60, 0); + + if (expectedParameterCount > 4) { + compareParameterMetaData(pmd, 5, "java.lang.String", -1, "text", 2147483647, 0); + compareParameterMetaData(pmd, 6, "java.lang.String", -16, "ntext", 1073741823, 0); + } + } + + private static void checkBinaryMetaData() throws SQLException { + + ParameterMetaData pmd = pstmt.getParameterMetaData(); + + assertEquals(pmd.getParameterCount(), 2, "Not all parameters are recognized by driver."); + + compareParameterMetaData(pmd, 1, "[B", -2, "binary", 100, 0); + compareParameterMetaData(pmd, 2, "[B", -3, "varbinary", 200, 0); + } + + private static void checkDateAndTimeMetaData() throws SQLException { + + ParameterMetaData pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 9, "Not all parameters are recognized by driver."); + + compareParameterMetaData(pmd, 1, "java.sql.Date", 91, "date", 10, 0); + compareParameterMetaData(pmd, 2, "java.sql.Timestamp", 93, "datetime", 23, 3); + compareParameterMetaData(pmd, 3, "java.sql.Timestamp", 93, "datetime2", 27, 7); + compareParameterMetaData(pmd, 4, "java.sql.Timestamp", 93, "datetime2", 25, 5); + compareParameterMetaData(pmd, 5, "microsoft.sql.DateTimeOffset", -155, "datetimeoffset", 34, 7); + compareParameterMetaData(pmd, 6, "microsoft.sql.DateTimeOffset", -155, "datetimeoffset", 32, 5); + compareParameterMetaData(pmd, 7, "java.sql.Timestamp", 93, "smalldatetime", 16, 0); + compareParameterMetaData(pmd, 8, "java.sql.Time", 92, "time", 16, 7); + compareParameterMetaData(pmd, 9, "java.sql.Time", 92, "time", 14, 5); + } + + private static void compareParameterMetaData(ParameterMetaData pmd, + int index, + String expectedClassName, + int expectedType, + String expectedTypeName, + int expectedPrecision, + int expectedScale) { + + try { + assertTrue(pmd.getParameterClassName(index).equalsIgnoreCase(expectedClassName), + "Parameter class Name error:\n" + "expected: " + expectedClassName + "\n" + "actual: " + pmd.getParameterClassName(index)); + } + catch (SQLException e) { + fail(e.toString()); + } + try { + assertTrue(pmd.getParameterType(index) == expectedType, + "Parameter Type error:\n" + "expected: " + expectedType + " \n" + "actual: " + pmd.getParameterType(index)); + } + catch (SQLException e) { + fail(e.toString()); + } + + try { + assertTrue(pmd.getParameterTypeName(index).equalsIgnoreCase(expectedTypeName), + "Parameter Type Name error:\n" + "expected: " + expectedTypeName + " \n" + "actual: " + pmd.getParameterTypeName(index)); + } + catch (SQLException e) { + fail(e.toString()); + } + try { + assertTrue(pmd.getPrecision(index) == expectedPrecision, + "Parameter Prcision error:\n" + "expected: " + expectedPrecision + " \n" + "actual: " + pmd.getPrecision(index)); + } + catch (SQLException e) { + fail(e.toString()); + } + + try { + assertTrue(pmd.getScale(index) == expectedScale, + "Parameter Prcision error:\n" + "expected: " + expectedScale + " \n" + "actual: " + pmd.getScale(index)); + } + catch (SQLException e) { + fail(e.toString()); + } + + } + + private static void populateNumericTable() throws SQLException { + stmt.execute("insert into " + numericTable + " values (" + "1.123," + "1.123," + "1.2345," + "1.2345," + "1.543," + "1.543," + "5.1234," + + "104935," + "34323," + "123," + "5," + "1.45," + "1.3" + ")"); + } + + private static void testBeforeExcute() throws SQLException { + if (null != pstmt) { + pstmt.close(); + } + + String sql = "select * from " + numericTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? and " + "c5 = ? and " + + "c6 = ? and " + "c7 = ? and " + "c8 = ? and " + "c9 = ? and " + "c10 = ? and " + "c11 = ? and " + "c12 = ? and " + "c13 = ? "; + + pstmt = connection.prepareStatement(sql); + + checkNumericMetaData(); + + if (null != pstmt) { + pstmt.close(); + } + } + + private static void selectNumeric() throws SQLException { + String sql = "select * from " + numericTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? and " + "c5 = ? and " + + "c6 = ? and " + "c7 = ? and " + "c8 = ? and " + "c9 = ? and " + "c10 = ? and " + "c11 = ? and " + "c12 = ? and " + "c13 = ? "; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 13; i++) { + pstmt.setString(i, "1"); + } + + rs = pstmt.executeQuery(); + } + + private static void insertNumeric() throws SQLException { + + String sql = "insert into " + numericTable + " values( " + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?," + + "?" + ")"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 13; i++) { + pstmt.setString(i, "1"); + } + + pstmt.execute(); + } + + private static void updateNumeric() throws SQLException { + + String sql = "update " + numericTable + " set " + "c1 = ?," + "c2 = ?," + "c3 = ?," + "c4 = ?," + "c5 = ?," + "c6 = ?," + "c7 = ?," + + "c8 = ?," + "c9 = ?," + "c10 = ?," + "c11 = ?," + "c12 = ?," + "c13 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 13; i++) { + pstmt.setString(i, "1"); + } + + pstmt.execute(); + } + + private static void deleteNumeric() throws SQLException { + + String sql = "delete from " + numericTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? and " + "c5 = ? and " + + "c6 = ? and " + "c7 = ? and " + "c8 = ? and " + "c9 = ? and " + "c10 = ? and " + "c11 = ? and " + "c12 = ? and " + "c13 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 13; i++) { + pstmt.setString(i, "1"); + } + + pstmt.execute(); + } + + private static void createNumericTable() throws SQLException { + + stmt.execute("Create table " + numericTable + " (" + "c1 decimal not null," + "c2 decimal(10,5) not null," + "c3 numeric not null," + + "c4 numeric(8,4) not null," + "c5 float not null," + "c6 float(10) not null," + "c7 real not null," + "c8 int not null," + + "c9 bigint not null," + "c10 smallint not null," + "c11 tinyint not null," + "c12 money not null," + "c13 smallmoney not null" + + ")"); + } + + private static void createCharTable() throws SQLException { + + stmt.execute("Create table " + charTable + " (" + "c1 char(50) not null," + "c2 varchar(20) not null," + "c3 nchar(30) not null," + + "c4 nvarchar(60) not null," + "c5 text not null," + "c6 ntext not null" + ")"); + } + + private static void populateCharTable() throws SQLException { + stmt.execute("insert into " + charTable + " values (" + "'Hello'," + "'Hello'," + "N'Hello'," + "N'Hello'," + "'Hello'," + "N'Hello'" + ")"); + } + + private static void selectChar() throws SQLException { + String sql = "select * from " + charTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? "; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 4; i++) { + pstmt.setString(i, "Hello"); + } + + rs = pstmt.executeQuery(); + } + + private static void insertChar() throws SQLException { + + String sql = "insert into " + charTable + " values( " + "?," + "?," + "?," + "?," + "?," + "?" + ")"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 6; i++) { + pstmt.setString(i, "simba tech"); + } + + pstmt.execute(); + } + + private static void updateChar() throws SQLException { + + String sql = "update " + charTable + " set " + "c1 = ?," + "c2 = ?," + "c3 = ?," + "c4 = ?," + "c5 = ?," + "c6 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 6; i++) { + pstmt.setString(i, "Simba!!!"); + } + + pstmt.execute(); + } + + private static void deleteChar() throws SQLException { + + String sql = "delete from " + charTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? "; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 4; i++) { + pstmt.setString(i, "Simba!!!"); + } + + pstmt.execute(); + } + + private static void createBinaryTable() throws SQLException { + + stmt.execute("Create table " + binaryTable + " (" + "c1 binary(100) not null," + "c2 varbinary(200) not null" + ")"); + } + + private static void populateBinaryTable() throws SQLException { + + stmt.execute("insert into " + binaryTable + " values (" + "convert(binary(50), 'Simba tech', 0), " + "convert(varbinary(50), 'Simba tech', 0)" + + ")"); + } + + private static void selectBinary() throws SQLException { + String sql = "select * from " + binaryTable + " where " + "c1 = ? and " + "c2 = ? "; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 2; i++) { + pstmt.setString(i, "1"); + } + + rs = pstmt.executeQuery(); + } + + private static void insertBinary() throws SQLException { + + String sql = "insert into " + binaryTable + " values( " + "?," + "?" + ")"; + + pstmt = connection.prepareStatement(sql); + + String str = "simba tech"; + for (int i = 1; i <= 2; i++) { + pstmt.setBytes(i, str.getBytes()); + } + + pstmt.execute(); + } + + private static void updateBinary() throws SQLException { + + String sql = "update " + binaryTable + " set " + "c1 = ?," + "c2 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + String str = "simbaaaaaaaa"; + for (int i = 1; i <= 2; i++) { + pstmt.setBytes(i, str.getBytes()); + } + + pstmt.execute(); + } + + private static void deleteBinary() throws SQLException { + + String sql = "delete from " + binaryTable + " where " + "c1 = ? and " + "c2 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 2; i++) { + pstmt.setString(i, "1"); + } + + pstmt.execute(); + } + + private static void createDateAndTimeTable() throws SQLException { + + stmt.execute("Create table " + dateAndTimeTable + " (" + "c1 date not null," + "c2 datetime not null," + "c3 datetime2 not null," + + "c4 datetime2(5) not null," + "c5 datetimeoffset not null," + "c6 datetimeoffset(5) not null," + "c7 smalldatetime not null," + + "c8 time not null," + "c9 time(5) not null" + ")"); + } + + private static void populateDateAndTimeTable() throws SQLException { + stmt.execute("insert into " + dateAndTimeTable + " values (" + "'1991-10-23'," + "'1991-10-23 06:20:50'," + "'1991-10-23 07:20:50.123'," + + "'1991-10-23 07:20:50.123'," + "'1991-10-23 08:20:50.123'," + "'1991-10-23 08:20:50.123'," + "'1991-10-23 09:20:50'," + + "'10:20:50'," + "'10:20:50'" + ")"); + } + + private static void insertDateAndTime() throws SQLException { + + String sql = "insert into " + dateAndTimeTable + " values( " + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?," + "?" + ")"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 9; i++) { + pstmt.setString(i, "1991-10-23"); + } + + pstmt.execute(); + } + + private static void updateDateAndTime() throws SQLException { + + String sql = "update " + dateAndTimeTable + " set " + "c1 = ?," + "c2 = ?," + "c3 = ?," + "c4 = ?," + "c5 = ?," + "c6 = ?," + "c7 = ?," + + "c8 = ?," + "c9 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 9; i++) { + pstmt.setString(i, "1991-10-23"); + } + + pstmt.execute(); + } + + private static void deleteDateAndTime() throws SQLException { + + String sql = "delete from " + dateAndTimeTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? and " + "c5 = ? and " + + "c6 = ? and " + "c7 = ? and " + "c8 = ? and " + "c9 = ?" + ";"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 9; i++) { + pstmt.setString(i, "1991-10-23"); + } + + pstmt.execute(); + } + + private static void selectDateAndTime() throws SQLException { + String sql = "select * from " + dateAndTimeTable + " where " + "c1 = ? and " + "c2 = ? and " + "c3 = ? and " + "c4 = ? and " + "c5 = ? and " + + "c6 = ? and " + "c7 = ? and " + "c8 = ? and " + "c9 = ? "; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 9; i++) { + pstmt.setString(i, "1"); + } + + rs = pstmt.executeQuery(); + } + + private static void createTablesForCompexQueries() throws SQLException { + stmt.executeUpdate("if object_id('" + nameTable + "','U') is not null" + " drop table " + nameTable); + + stmt.executeUpdate("if object_id('" + phoneNumberTable + "','U') is not null" + " drop table " + phoneNumberTable); + + stmt.executeUpdate("if object_id('" + mergeNameDesTable + "','U') is not null" + " drop table " + mergeNameDesTable); + + String sql = "create table " + nameTable + " (" + // + "ID int NOT NULL," + + "PlainID int not null," + "ID smallint NOT NULL," + "FirstName varchar(50) NOT NULL," + "LastName nchar(60) NOT NULL" + ");"; + + try { + stmt.execute(sql); + } + catch (SQLException e) { + fail(e.toString()); + } + + sql = "create table " + phoneNumberTable + " (" + "PlainID smallint not null," + "ID int NOT NULL," + "PhoneNumber bigint NOT NULL" + ");"; + + try { + stmt.execute(sql); + } + catch (SQLException e) { + fail(e.toString()); + } + + sql = "create table " + mergeNameDesTable + " (" + // + "ID int NOT NULL," + + "PlainID smallint not null," + "ID int NULL," + "FirstName char(30) NULL," + "LastName varchar(50) NULL" + ");"; + + try { + stmt.execute(sql); + } + catch (SQLException e) { + fail(e.toString()); + } + } + + private static void populateTablesForCompexQueries() throws SQLException { + String sql = "insert into " + nameTable + " values " + "(?,?,?,?)," + "(?,?,?,?)," + "(?,?,?,?)" + ""; + pstmt = connection.prepareStatement(sql); + int id = 1; + for (int i = 0; i < 5; i++) { + pstmt.setInt(1, id); + pstmt.setInt(2, id); + pstmt.setString(3, "QWERTYUIOP"); + pstmt.setString(4, "ASDFGHJKL"); + id++; + + pstmt.setInt(5, id); + pstmt.setInt(6, id); + pstmt.setString(7, "QWE"); + pstmt.setString(8, "ASD"); + id++; + + pstmt.setInt(9, id); + pstmt.setInt(10, id); + pstmt.setString(11, "QAZ"); + pstmt.setString(12, "WSX"); + pstmt.execute(); + id++; + } + pstmt.close(); + + sql = "insert into " + phoneNumberTable + " values " + "(?,?,?)," + "(?,?,?)," + "(?,?,?)" + ""; + pstmt = connection.prepareStatement(sql); + id = 1; + for (int i = 0; i < 5; i++) { + pstmt.setInt(1, id); + pstmt.setInt(2, id); + pstmt.setLong(3, 1234567L); + id++; + + pstmt.setInt(4, id); + pstmt.setInt(5, id); + pstmt.setLong(6, 7654321L); + id++; + + pstmt.setInt(7, id); + pstmt.setInt(8, id); + pstmt.setLong(9, 1231231L); + pstmt.execute(); + id++; + } + pstmt.close(); + + sql = "insert into " + mergeNameDesTable + " (PlainID) values " + "(?)," + "(?)," + "(?)" + ""; + pstmt = connection.prepareStatement(sql); + id = 1; + for (int i = 0; i < 5; i++) { + pstmt.setInt(1, id); + id++; + + pstmt.setInt(2, id); + id++; + + pstmt.setInt(3, id); + pstmt.execute(); + id++; + } + pstmt.close(); + } + + /** + * Test subquery + * @throws SQLException + */ + @Test + @DisplayName("SubQuery") + public void testSubquery() throws SQLException { + if (version >= SQL_SERVER_2012_VERSION) { + String sql = "SELECT FirstName,LastName" + " FROM " + nameTable + " WHERE ID IN " + " (SELECT ID" + " FROM " + phoneNumberTable + + " WHERE PhoneNumber = ? and ID = ? and PlainID = ?" + ")"; + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 3, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.Long", -5, "BIGINT", 19, 0); + compareParameterMetaData(pmd, 2, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 3, "java.lang.Short", 5, "smallint", 5, 0); + } + } + + /** + * Test join + * @throws SQLException + */ + @Test + @DisplayName("Join Queries") + public void testJoin() throws SQLException { + if (version >= SQL_SERVER_2012_VERSION) { + String sql = String.format( + "select %s.FirstName, %s.LastName, %s.PhoneNumber" + " from %s join %s on %s.PlainID = %s.PlainID" + + " where %s.ID = ? and %s.PlainID = ?", + nameTable, nameTable, phoneNumberTable, nameTable, phoneNumberTable, nameTable, phoneNumberTable, phoneNumberTable, + phoneNumberTable); + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 2, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 2, "java.lang.Short", 5, "smallint", 5, 0); + } + } + + /** + * Test merge + * @throws SQLException + */ + @Test + @DisplayName("Merge Queries") + public void testMerge() throws SQLException { + if (version >= SQL_SERVER_2012_VERSION) { + String sql = "merge " + mergeNameDesTable + " as T" + " using " + nameTable + " as S" + " on T.PlainID=S.PlainID" + " when matched" + + " then update set T.firstName = ?, T.lastName = ?;"; + + pstmt = connection.prepareStatement(sql); + + pstmt.setString(1, "hello"); + pstmt.setString(2, "world"); + pstmt.execute(); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 2, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.String", 1, "CHAR", 30, 0); + compareParameterMetaData(pmd, 2, "java.lang.String", 12, "VARCHAR", 50, 0); + } + } + + private static void createMultipleTypesTable() throws SQLException { + + stmt.execute("Create table " + multipleTypesTable + " (" + "c1n decimal not null," + "c2n decimal(10,5) not null," + "c3n numeric not null," + + "c4n numeric(8,4) not null," + "c5n float not null," + "c6n float(10) not null," + "c7n real not null," + "c8n int not null," + + "c9n bigint not null," + "c10n smallint not null," + "c11n tinyint not null," + "c12n money not null," + "c13n smallmoney not null," + + + "c1c char(50) not null," + "c2c varchar(20) not null," + "c3c nchar(30) not null," + "c4c nvarchar(60) not null," + + "c5c text not null," + "c6c ntext not null," + + + "c1 binary(100) not null," + "c2 varbinary(200) not null," + + + "c1d date not null," + "c2d datetime not null," + "c3d datetime2 not null," + "c4d datetime2(5) not null," + + "c5d datetimeoffset not null," + "c6d datetimeoffset(5) not null," + "c7d smalldatetime not null," + "c8d time not null," + + "c9d time(5) not null" + ")"); + } + + private static void testInsertMultipleTypes() throws SQLException { + + String sql = "insert into " + multipleTypesTable + " values( " + "?,?,?,?,?,?,?,?,?,?,?,?,?," + "?,?,?,?,?,?," + "?,?," + "?,?,?,?,?,?,?,?,?" + + ")"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 13; i++) { + pstmt.setString(i, "1"); + } + for (int i = 14; i <= 19; i++) { + pstmt.setString(i, "simba tech"); + } + String str = "simba tech"; + for (int i = 20; i <= 21; i++) { + pstmt.setBytes(i, str.getBytes()); + } + for (int i = 22; i <= 30; i++) { + pstmt.setString(i, "1991-10-23"); + } + + pstmt.execute(); + + ParameterMetaData pmd = null; + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 30, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.math.BigDecimal", 3, "decimal", 18, 0); + compareParameterMetaData(pmd, 2, "java.math.BigDecimal", 3, "decimal", 10, 5); + compareParameterMetaData(pmd, 3, "java.math.BigDecimal", 2, "numeric", 18, 0); + compareParameterMetaData(pmd, 4, "java.math.BigDecimal", 2, "numeric", 8, 4); + compareParameterMetaData(pmd, 5, "java.lang.Double", 8, "float", 15, 0); + compareParameterMetaData(pmd, 6, "java.lang.Float", 7, "real", 7, 0); + compareParameterMetaData(pmd, 7, "java.lang.Float", 7, "real", 7, 0); + if (version >= SQL_SERVER_2012_VERSION) { + compareParameterMetaData(pmd, 8, "java.lang.Integer", 4, "int", 10, 0); + } + else { + compareParameterMetaData(pmd, 8, "java.lang.Integer", 4, "int", 10, 0); + } + compareParameterMetaData(pmd, 9, "java.lang.Long", -5, "bigint", 19, 0); + compareParameterMetaData(pmd, 10, "java.lang.Short", 5, "smallint", 5, 0); + compareParameterMetaData(pmd, 11, "java.lang.Short", -6, "tinyint", 3, 0); + compareParameterMetaData(pmd, 12, "java.math.BigDecimal", 3, "money", 19, 4); + compareParameterMetaData(pmd, 13, "java.math.BigDecimal", 3, "smallmoney", 10, 4); + + compareParameterMetaData(pmd, 14, "java.lang.String", 1, "char", 50, 0); + compareParameterMetaData(pmd, 15, "java.lang.String", 12, "varchar", 20, 0); + compareParameterMetaData(pmd, 16, "java.lang.String", -15, "nchar", 30, 0); + compareParameterMetaData(pmd, 17, "java.lang.String", -9, "nvarchar", 60, 0); + compareParameterMetaData(pmd, 18, "java.lang.String", -1, "text", 2147483647, 0); + compareParameterMetaData(pmd, 19, "java.lang.String", -16, "ntext", 1073741823, 0); + + compareParameterMetaData(pmd, 20, "[B", -2, "binary", 100, 0); + compareParameterMetaData(pmd, 21, "[B", -3, "varbinary", 200, 0); + + compareParameterMetaData(pmd, 22, "java.sql.Date", 91, "date", 10, 0); + compareParameterMetaData(pmd, 23, "java.sql.Timestamp", 93, "datetime", 23, 3); + compareParameterMetaData(pmd, 24, "java.sql.Timestamp", 93, "datetime2", 27, 7); + compareParameterMetaData(pmd, 25, "java.sql.Timestamp", 93, "datetime2", 25, 5); + compareParameterMetaData(pmd, 26, "microsoft.sql.DateTimeOffset", -155, "datetimeoffset", 34, 7); + compareParameterMetaData(pmd, 27, "microsoft.sql.DateTimeOffset", -155, "datetimeoffset", 32, 5); + compareParameterMetaData(pmd, 28, "java.sql.Timestamp", 93, "smalldatetime", 16, 0); + compareParameterMetaData(pmd, 29, "java.sql.Time", 92, "time", 16, 7); + compareParameterMetaData(pmd, 30, "java.sql.Time", 92, "time", 14, 5); + } + + @Test + @DisplayName("testNoParameter") + public void testNoParameter() throws SQLException { + String sql = "select * from " + multipleTypesTable; + + pstmt = connection.prepareStatement(sql); + + rs = pstmt.executeQuery(); + + ParameterMetaData pmd = null; + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 0, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + } + + private static void testMixedWithHardcodedValues() throws SQLException { + + String sql = "insert into " + multipleTypesTable + " values( " + "1,?,?,1,?,?,?,1,?,?,?,1,1," + "?,'simba tech','simba tech',?,?,?," + "?,?," + + "?,'1991-10-23',?,?,?,'1991-10-23',?,?,?" + ")"; + + pstmt = connection.prepareStatement(sql); + + for (int i = 1; i <= 8; i++) { + pstmt.setString(i, "1"); + } + for (int i = 9; i <= 12; i++) { + pstmt.setString(i, "simba tech"); + } + String str = "simba tech"; + for (int i = 13; i <= 14; i++) { + pstmt.setBytes(i, str.getBytes()); + } + for (int i = 15; i <= 21; i++) { + pstmt.setString(i, "1991-10-23"); + } + + pstmt.execute(); + + ParameterMetaData pmd = null; + try { + pmd = pstmt.getParameterMetaData(); + + assertEquals(pmd.getParameterCount(), 21, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.math.BigDecimal", 3, "decimal", 10, 5); + compareParameterMetaData(pmd, 2, "java.math.BigDecimal", 2, "numeric", 18, 0); + compareParameterMetaData(pmd, 3, "java.lang.Double", 8, "float", 15, 0); + compareParameterMetaData(pmd, 4, "java.lang.Float", 7, "real", 7, 0); + compareParameterMetaData(pmd, 5, "java.lang.Float", 7, "real", 7, 0); + compareParameterMetaData(pmd, 6, "java.lang.Long", -5, "bigint", 19, 0); + compareParameterMetaData(pmd, 7, "java.lang.Short", 5, "smallint", 5, 0); + compareParameterMetaData(pmd, 8, "java.lang.Short", -6, "tinyint", 3, 0); + + compareParameterMetaData(pmd, 9, "java.lang.String", 1, "char", 50, 0); + compareParameterMetaData(pmd, 10, "java.lang.String", -9, "nvarchar", 60, 0); + compareParameterMetaData(pmd, 11, "java.lang.String", -1, "text", 2147483647, 0); + compareParameterMetaData(pmd, 12, "java.lang.String", -16, "ntext", 1073741823, 0); + + compareParameterMetaData(pmd, 13, "[B", -2, "binary", 100, 0); + compareParameterMetaData(pmd, 14, "[B", -3, "varbinary", 200, 0); + + compareParameterMetaData(pmd, 15, "java.sql.Date", 91, "date", 10, 0); + compareParameterMetaData(pmd, 16, "java.sql.Timestamp", 93, "datetime2", 27, 7); + compareParameterMetaData(pmd, 17, "java.sql.Timestamp", 93, "datetime2", 25, 5); + compareParameterMetaData(pmd, 18, "microsoft.sql.DateTimeOffset", -155, "datetimeoffset", 34, 7); + compareParameterMetaData(pmd, 19, "java.sql.Timestamp", 93, "smalldatetime", 16, 0); + compareParameterMetaData(pmd, 20, "java.sql.Time", 92, "time", 16, 7); + compareParameterMetaData(pmd, 21, "java.sql.Time", 92, "time", 14, 5); + } + + /** + * Test Orderby + * @throws SQLException + */ + @Test + @DisplayName("Test OrderBy") + public void testOrderBy() throws SQLException { + String sql = "SELECT FirstName,LastName" + " FROM " + nameTable + " WHERE FirstName = ? and LastName = ? and PlainID = ? and ID = ? " + + " ORDER BY ID ASC"; + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 4, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.String", 12, "varchar", 50, 0); + compareParameterMetaData(pmd, 2, "java.lang.String", -15, "nchar", 60, 0); + compareParameterMetaData(pmd, 3, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 4, "java.lang.Short", 5, "smallint", 5, 0); + + } + + /** + * Test Groupby + * @throws SQLException + */ + @Test + @DisplayName("Test GroupBy") + private void testGroupBy() throws SQLException { + String sql = "SELECT FirstName,COUNT(LastName)" + " FROM " + nameTable + " WHERE FirstName = ? and LastName = ? and PlainID = ? and ID = ? " + + " group by Firstname"; + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 4, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.String", 12, "varchar", 50, 0); + compareParameterMetaData(pmd, 2, "java.lang.String", -15, "nchar", 60, 0); + compareParameterMetaData(pmd, 3, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 4, "java.lang.Short", 5, "smallint", 5, 0); + + } + + /** + * Test Lower + * @throws SQLException + */ + @Test + public void testLower() throws SQLException { + String sql = "SELECT FirstName,LOWER(LastName)" + " FROM " + nameTable + " WHERE FirstName = ? and LastName = ? and PlainID = ? and ID = ? "; + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + + assertEquals(pmd.getParameterCount(), 4, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.String", 12, "varchar", 50, 0); + compareParameterMetaData(pmd, 2, "java.lang.String", -15, "nchar", 60, 0); + compareParameterMetaData(pmd, 3, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 4, "java.lang.Short", 5, "smallint", 5, 0); + + } + + /** + * Test Power + * @throws SQLException + */ + @Test + public void testPower() throws SQLException { + String sql = "SELECT POWER(ID,2)" + " FROM " + nameTable + " WHERE FirstName = ? and LastName = ? and PlainID = ? and ID = ? "; + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + assertEquals(pmd.getParameterCount(), 4, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.String", 12, "varchar", 50, 0); + compareParameterMetaData(pmd, 2, "java.lang.String", -15, "nchar", 60, 0); + compareParameterMetaData(pmd, 3, "java.lang.Integer", 4, "int", 10, 0); + compareParameterMetaData(pmd, 4, "java.lang.Short", 5, "smallint", 5, 0); + + } + + /** + * All in one queries + * @throws SQLException + */ + @Test + @DisplayName("All In One Queries") + public void testAllInOneQuery() throws SQLException { + if (version >= SQL_SERVER_2012_VERSION) { + + String sql = "select lower(FirstName), count(lastName) from " + nameTable + "where ID = ? and FirstName in" + "(" + " select " + nameTable + + ".FirstName from " + nameTable + " join " + phoneNumberTable + " on " + nameTable + ".ID = " + phoneNumberTable + ".ID" + + " where " + nameTable + ".ID = ? and " + phoneNumberTable + ".ID = ?" + ")" + " group by FirstName " + + " order by FirstName ASC"; + + pstmt = connection.prepareStatement(sql); + + ParameterMetaData pmd = null; + + try { + pmd = pstmt.getParameterMetaData(); + + assertEquals(pmd.getParameterCount(), 3, "Not all parameters are recognized by driver."); + } + catch (Exception e) { + fail(e.toString()); + } + + compareParameterMetaData(pmd, 1, "java.lang.Short", 5, "smallint", 5, 0); + compareParameterMetaData(pmd, 2, "java.lang.Short", 5, "smallint", 5, 0); + compareParameterMetaData(pmd, 3, "java.lang.Integer", 4, "int", 10, 0); + } + } + + /** + * Cleanup + * @throws SQLException + */ + @AfterAll + public static void dropTables() throws SQLException { + stmt.execute("if object_id('" + nameTable + "','U') is not null" + " drop table " + nameTable); + stmt.execute("if object_id('" + phoneNumberTable + "','U') is not null" + " drop table " + phoneNumberTable); + stmt.execute("if object_id('" + mergeNameDesTable + "','U') is not null" + " drop table " + mergeNameDesTable); + stmt.execute("if object_id('" + numericTable + "','U') is not null" + " drop table " + numericTable); + stmt.execute("if object_id('" + charTable + "','U') is not null" + " drop table " + charTable); + stmt.execute("if object_id('" + binaryTable + "','U') is not null" + " drop table " + binaryTable); + stmt.execute("if object_id('" + dateAndTimeTable + "','U') is not null" + " drop table " + dateAndTimeTable); + stmt.execute("if object_id('" + multipleTypesTable + "','U') is not null" + " drop table " + multipleTypesTable); + + if (null != rs) { + rs.close(); + } + if (null != stmt) { + stmt.close(); + } + if (null != pstmt) { + pstmt.close(); + } + if (null != connection) { + connection.close(); + } + } +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/PoolableTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/PoolableTest.java new file mode 100644 index 0000000000..f7e7fee17e --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/PoolableTest.java @@ -0,0 +1,78 @@ +/* + * 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.UnitStatement; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; +import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.SQLServerStatement; +import com.microsoft.sqlserver.testframework.AbstractTest; + +/** + * Test Poolable statements + * + */ +@RunWith(JUnitPlatform.class) +public class PoolableTest extends AbstractTest { + + /** + * Poolable Test + * @throws SQLException + * @throws ClassNotFoundException + */ + @Test + @DisplayName("Poolable Test") + public void poolableTest() throws SQLException, ClassNotFoundException { + Connection connection = DriverManager.getConnection(connectionString); + Statement statement = connection.createStatement(); + try { + // First get the default values + boolean isPoolable = ((SQLServerStatement) statement).isPoolable(); + assertEquals(isPoolable, false, "SQLServerStatement should not be Poolable by default"); + + PreparedStatement prepStmt = connection.prepareStatement("select 1"); + isPoolable = ((SQLServerPreparedStatement) prepStmt).isPoolable(); + assertEquals(isPoolable, true, "SQLServerPreparedStatement should be Poolable by default"); + + + CallableStatement callableStatement = connection.prepareCall("{ ? = CALL " + "ProcName" + " (?, ?, ?, ?) }"); + isPoolable = ((SQLServerCallableStatement) callableStatement).isPoolable(); + + assertEquals(isPoolable, true, "SQLServerCallableStatement should be Poolable by default"); + + // Now do couple of sets and gets + + ((SQLServerCallableStatement) callableStatement).setPoolable(false); + assertEquals(((SQLServerCallableStatement) callableStatement).isPoolable(), false, "set did not work"); + callableStatement.close(); + + ((SQLServerStatement) statement).setPoolable(true); + assertEquals(((SQLServerStatement) statement).isPoolable(), true, "set did not work"); + statement.close(); + + } + catch (UnsupportedOperationException e) { + assertEquals(System.getProperty("java.specification.version"), "1.5", "PoolableTest should be supported in anything other than 1.5"); + assertEquals(e.getMessage(), "This operation is not supported.", "Wrong exception message"); + } + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/UnitStatement/WrapperTest.java b/src/test/java/com/microsoft/sqlserver/UnitStatement/WrapperTest.java new file mode 100644 index 0000000000..748f206e4c --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/UnitStatement/WrapperTest.java @@ -0,0 +1,153 @@ +/* + * 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.UnitStatement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; + +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.microsoft.sqlserver.jdbc.ISQLServerPreparedStatement; +import com.microsoft.sqlserver.jdbc.SQLServerCallableStatement; +import com.microsoft.sqlserver.jdbc.SQLServerException; +import com.microsoft.sqlserver.jdbc.SQLServerStatement; +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.DBConnection; + +/** + * Tests isWrapperFor methods + * + */ +@RunWith(JUnitPlatform.class) +public class WrapperTest extends AbstractTest { + + /** + * Wrapper tests + * @throws Exception + */ + @Test + public void wrapTest() throws Exception { + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + Connection con = DriverManager.getConnection(connectionString); + Statement stmt = con.createStatement(); + + try { + // First make sure that a statement can be unwrapped + boolean isWrapper = ((SQLServerStatement) stmt).isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.SQLServerStatement")); + assertEquals(isWrapper, true, "SQLServerStatement should be a wrapper for self"); + isWrapper = ((SQLServerStatement) stmt).isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.ISQLServerStatement")); + assertEquals(isWrapper, true, "SQLServerStatement should be a wrapper for ISQLServerStatement"); + + isWrapper = ((SQLServerStatement) stmt).isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.SQLServerConnection")); + assertEquals(isWrapper, false, "SQLServerStatement should not be a wrapper for SQLServerConnection"); + + // Now make sure that we can unwrap a SQLServerCallableStatement to a SQLServerStatement + CallableStatement cs = con.prepareCall("{ ? = CALL " + "ProcName" + " (?, ?, ?, ?) }"); + // Test the class first + isWrapper = ((SQLServerCallableStatement) cs).isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.SQLServerStatement")); + assertEquals(isWrapper, true, "SQLServerCallableStatement should be a wrapper for SQLServerStatement"); + // Now unwrap the Callable to a statement and call a SQLServerStatement specific function and make sure it succeeds. + SQLServerStatement stmt2 = (SQLServerStatement) ((SQLServerCallableStatement) cs) + .unwrap(Class.forName("com.microsoft.sqlserver.jdbc.SQLServerStatement")); + stmt2.setResponseBuffering("adaptive"); + // now test the interface + isWrapper = ((SQLServerCallableStatement) cs).isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.ISQLServerCallableStatement")); + assertEquals(isWrapper, true, "SQLServerCallableStatement should be a wrapper for ISQLServerCallableStatement"); + // Now unwrap the Callable to a statement and call a SQLServerStatement specific function and make sure it succeeds. + ISQLServerPreparedStatement stmt4 = (ISQLServerPreparedStatement) ((SQLServerCallableStatement) cs) + .unwrap(Class.forName("com.microsoft.sqlserver.jdbc.ISQLServerPreparedStatement")); + stmt4.setResponseBuffering("adaptive"); + + if (isKatmaiServer()) + stmt4.setDateTimeOffset(1, null); + + // Try Unwrapping CallableStatement to a callableStatement + isWrapper = ((SQLServerCallableStatement) cs).isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.SQLServerCallableStatement")); + assertEquals(isWrapper, true, "SQLServerCallableStatement should be a wrapper for SQLServerCallableStatement"); + // Now unwrap the Callable to a SQLServerCallableStatement and call a SQLServerStatement specific function and make sure it succeeds. + SQLServerCallableStatement stmt3 = (SQLServerCallableStatement) ((SQLServerCallableStatement) cs) + .unwrap(Class.forName("com.microsoft.sqlserver.jdbc.SQLServerCallableStatement")); + stmt3.setResponseBuffering("adaptive"); + if (isKatmaiServer()) { + stmt3.setDateTimeOffset(1, null); + } + if (null != stmt4) { + stmt4.close(); + } + if (null != cs) { + cs.close(); + } + + } + catch (UnsupportedOperationException e) { + assertEquals(System.getProperty("java.specification.version"), "1.5", "isWrapperFor should be supported in anything other than 1.5"); + assertTrue(e.getMessage().equalsIgnoreCase("This operation is not supported."), "Wrong exception message"); + } + finally { + if (null != stmt) { + stmt.close(); + } + if (null != con) { + con.close(); + } + } + + } + + /** + * Tests expected unwrapper failures + * @throws Exception + */ + @Test + public void unWrapFailureTest() throws Exception { + Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + Connection con = DriverManager.getConnection(connectionString); + SQLServerStatement stmt = (SQLServerStatement) con.createStatement(); + + try { + String str = "java.lang.String"; + boolean isWrapper = stmt.isWrapperFor(Class.forName(str)); + stmt.unwrap(Class.forName(str)); + assertEquals(isWrapper, false, "SQLServerStatement should not be a wrapper for string"); + stmt.unwrap(Class.forName(str)); + assertTrue(false, "An exception should have been thrown. This code should not be reached"); + } + catch (SQLServerException ex) { + Throwable t = ex.getCause(); + Class exceptionClass = Class.forName("java.lang.ClassCastException"); + assertEquals(t.getClass(), exceptionClass, "The cause in the exception class does not match"); + } + catch (UnsupportedOperationException e) { + assertEquals(System.getProperty("java.specification.version"), "1.5", "isWrapperFor should be supported in anything other than 1.5"); + assertEquals(e.getMessage(), "This operation is not supported.", "Wrong exception message"); + } + finally { + if (null != stmt) { + stmt.close(); + } + if (null != con) { + con.close(); + } + } + } + + private static boolean isKatmaiServer() throws Exception { + DBConnection conn = new DBConnection(connectionString); + double version = conn.getServerVersion(); + conn.close(); + return ((version >= 10.0) ? true : false); + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java index b77ec8ede8..8770ed352d 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java @@ -84,6 +84,14 @@ public static void setup() throws Exception { } } + /** + * Get the connection String + * @return + */ + public static String getConnectionString() { + return connectionString; + } + /** * This will take care of all clean ups after running the Test Suite. * diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java b/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java new file mode 100644 index 0000000000..f832f453aa --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBCallableStatement.java @@ -0,0 +1,69 @@ +/** + * + */ +package com.microsoft.sqlserver.testframework; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author v-afrafi + * + */ +public class DBCallableStatement extends AbstractParentWrapper{ + + PreparedStatement cstmt = null; + + /** + * + */ + public DBCallableStatement(DBConnection dbconnection) { + super(dbconnection, null, "preparedStatement"); + } + + /** + * @param parent + * @param internal + * @param name + */ + DBCallableStatement(AbstractParentWrapper parent, + Object internal, + String name) { + super(parent, internal, name); + // TODO Auto-generated constructor stub + } + + DBCallableStatement prepareCall(String query) throws SQLException { + cstmt = ((Connection) parent().product()).prepareCall(query); + setInternal(cstmt); + return this; + } + + /** + * + * @param x + * @param y + * @param z + * @throws SQLException + */ + public void registerOutParameter(String x, int y, int z) throws SQLException + { + //product + ((CallableStatement)product()).registerOutParameter(x, y, z); + } + + /** + * + * @param index + * @param sqltype + * @throws SQLException + */ + public void registerOutParameter(int index, int sqltype) throws SQLException + { + ((CallableStatement)product()).registerOutParameter(index, sqltype); + + } + +} diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java b/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java index 31212b9c3e..1ba90ea34c 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBConnection.java @@ -14,6 +14,7 @@ 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; @@ -22,6 +23,7 @@ * Wrapper class for SQLServerConnection */ public class DBConnection extends AbstractParentWrapper { + private double serverversion = 0; // TODO: add Isolation Level // TODO: add auto commit @@ -174,4 +176,53 @@ public static boolean isSqlAzure(Connection con) throws SQLException { return isSqlAzure; } + /** + * @param string + * @return + * @throws SQLException + */ + public DBCallableStatement prepareCall(String query) throws SQLException { + DBCallableStatement dbcstmt = new DBCallableStatement(this); + return dbcstmt.prepareCall(query); + } + + /** + * Retrieve server version + * @return server version + * @throws Exception + */ + public double getServerVersion() throws Exception { + if (0 == serverversion) { + DBStatement stmt = null; + DBResultSet rs = null; + + try { + stmt = this.createStatement(DBResultSet.TYPE_DIRECT_FORWARDONLY, ResultSet.CONCUR_READ_ONLY); + rs = stmt.executeQuery("SELECT @@VERSION"); + rs.next(); + + String version = rs.getString(1); + // i.e. " - 10.50.1064.0" + int firstDot = version.indexOf('.'); + assert ((firstDot - 2) > 0); + int secondDot = version.indexOf('.', (firstDot + 1)); + try { + serverversion = Double.parseDouble(version.substring((firstDot - 2), secondDot)); + } + catch (NumberFormatException ex) { + // for CTP version parsed as P2.3) - 13 throws number format exception + serverversion = 16; + } + } + catch (Exception e) { + throw new Exception("Unable to get dbms major version", e); + } + finally { + rs.close(); + stmt.close(); + } + } + return serverversion; + } + } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java b/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java index 1cbeb60cf8..1b9705402a 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBResultSet.java @@ -46,6 +46,7 @@ public class DBResultSet extends AbstractParentWrapper { public static final int TYPE_SCROLL_INSENSITIVE = ResultSet.TYPE_SCROLL_INSENSITIVE; public static final int TYPE_SCROLL_SENSITIVE = ResultSet.TYPE_SCROLL_SENSITIVE; public static final int CONCUR_UPDATABLE = ResultSet.CONCUR_UPDATABLE; + public static final int TYPE_DIRECT_FORWARDONLY = ResultSet.TYPE_FORWARD_ONLY + 1000; protected DBTable currentTable; public int _currentrow = -1; // The row this rowset is currently pointing to @@ -157,8 +158,8 @@ public void verifydata(int ordinal, Object retrieved = this.getXXX(ordinal + 1, coercion); // Verify - //TODO: Check the intermittent verification error - //verifydata(ordinal, coercion, expectedData, retrieved); + // TODO: Check the intermittent verification error + // verifydata(ordinal, coercion, expectedData, retrieved); } /** @@ -450,4 +451,13 @@ private static Object roundDatetimeValue(Object value) { } return ts; } + + /** + * @param i + * @return + * @throws SQLException + */ + public int getInt(int index) throws SQLException { + return resultSet.getInt(index); + } } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java b/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java index 00aa34eab0..09b7df316c 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/DBStatement.java @@ -143,7 +143,14 @@ void setInternal(Object internal) { * @throws SQLException */ public int getQueryTimeout() throws SQLException { - int current = ((Statement) product()).getQueryTimeout(); - return current; + return ((Statement) product()).getQueryTimeout(); + } + + /** + * @return + * @throws SQLException + */ + public int getUpdateCount() throws SQLException { + return ((Statement) product()).getUpdateCount(); } } diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java index 7fe98b7838..354123c0ba 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/Utils.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/Utils.java @@ -8,12 +8,47 @@ package com.microsoft.sqlserver.testframework; +import java.util.logging.Level; +import java.util.logging.Logger; + /** * Generic Utility class which we can access by test classes. * * @since 6.1.2 */ public class Utils { + public static final Logger log = Logger.getLogger("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"; + + /** + * Returns serverType + * @return + */ + public static String getServerType() { + String serverType = null; + + String serverTypeProperty = getConfiguredProperty("server.type"); + if (null == serverTypeProperty) { + // default to SQL Server + serverType = SERVER_TYPE_SQL_SERVER; + } + else if (serverTypeProperty.equalsIgnoreCase(SERVER_TYPE_SQL_AZURE)) { + serverType = SERVER_TYPE_SQL_AZURE; + } + else if (serverTypeProperty.equalsIgnoreCase(SERVER_TYPE_SQL_SERVER)) { + serverType = SERVER_TYPE_SQL_SERVER; + } + else { + if (log.isLoggable(Level.FINE)) { + log.fine("Server.type '" + serverTypeProperty + "' is not supported yet. Default to SQL Server"); + } + serverType = SERVER_TYPE_SQL_SERVER; + } + return serverType; + } /** * Read variable from property files if found null try to read from env.