/*
* Microsoft JDBC Driver for SQL Server
*
* Copyright(c) Microsoft Corporation All rights reserved.
*
* This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/
package com.microsoft.sqlserver.jdbc.tvp;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Properties;
import java.util.TimeZone;
import org.junit.jupiter.api.AfterEach;
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.SQLServerException;
import com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement;
import com.microsoft.sqlserver.testframework.AbstractTest;
import com.microsoft.sqlserver.testframework.Utils;
@RunWith(JUnitPlatform.class)
public class TVPResultSetCursorTest extends AbstractTest {
private static Connection conn = null;
static Statement stmt = null;
static BigDecimal[] expectedBigDecimals = {new BigDecimal("12345.12345"), new BigDecimal("125.123"), new BigDecimal("45.12345")};
static String[] expectedBigDecimalStrings = {"12345.12345", "125.12300", "45.12345"};
static String[] expectedStrings = {"hello", "world", "!!!"};
static Timestamp[] expectedTimestamps = {new Timestamp(1433338533461L), new Timestamp(14917485583999L), new Timestamp(1491123533000L)};
static String[] expectedTimestampStrings = {"2015-06-03 13:35:33.4610000", "2442-09-19 01:59:43.9990000", "2017-04-02 08:58:53.0000000"};
private static String tvpName = "TVPResultSetCursorTest_TVP";
private static String procedureName = "TVPResultSetCursorTest_SP";
private static String srcTable = "TVPResultSetCursorTest_SourceTable";
private static String desTable = "TVPResultSetCursorTest_DestinationTable";
/**
* Test a previous failure when using server cursor and using the same connection to create TVP and result set.
*
* @throws SQLException
*/
@Test
public void testServerCursors() throws SQLException {
serverCursorsTest(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
serverCursorsTest(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
serverCursorsTest(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
serverCursorsTest(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
}
private void serverCursorsTest(int resultSetType,
int resultSetConcurrency) throws SQLException {
conn = DriverManager.getConnection(connectionString);
stmt = conn.createStatement();
dropTVPS();
dropTables();
createTVPS();
createTables();
populateSourceTable();
ResultSet rs = conn.createStatement(resultSetType, resultSetConcurrency).executeQuery("select * from " + srcTable);
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt.setStructured(1, tvpName, rs);
pstmt.execute();
verifyDestinationTableData(expectedBigDecimals.length);
if (null != pstmt) {
pstmt.close();
}
if (null != rs) {
rs.close();
}
}
/**
* Test a previous failure when setting SelectMethod to cursor and using the same connection to create TVP and result set.
*
* @throws SQLException
*/
@Test
public void testSelectMethodSetToCursor() throws SQLException {
Properties info = new Properties();
info.setProperty("SelectMethod", "cursor");
conn = DriverManager.getConnection(connectionString, info);
stmt = conn.createStatement();
dropTVPS();
dropTables();
createTVPS();
createTables();
populateSourceTable();
ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable);
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt.setStructured(1, tvpName, rs);
pstmt.execute();
verifyDestinationTableData(expectedBigDecimals.length);
if (null != pstmt) {
pstmt.close();
}
if (null != rs) {
rs.close();
}
}
/**
* Test a previous failure when setting SelectMethod to cursor and using the same connection to create TVP, SP and result set.
*
* @throws SQLException
*/
@Test
public void testSelectMethodSetToCursorWithSP() throws SQLException {
Properties info = new Properties();
info.setProperty("SelectMethod", "cursor");
conn = DriverManager.getConnection(connectionString, info);
stmt = conn.createStatement();
dropProcedure();
dropTVPS();
dropTables();
createTVPS();
createTables();
createPreocedure();
populateSourceTable();
ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable);
final String sql = "{call " + procedureName + "(?)}";
SQLServerCallableStatement pstmt = (SQLServerCallableStatement) conn.prepareCall(sql);
pstmt.setStructured(1, tvpName, rs);
try {
pstmt.execute();
verifyDestinationTableData(expectedBigDecimals.length);
}
finally {
if (null != pstmt) {
pstmt.close();
}
if (null != rs) {
rs.close();
}
dropProcedure();
}
}
/**
* Test exception when giving invalid TVP name
*
* @throws SQLException
*/
@Test
public void testInvalidTVPName() throws SQLException {
Properties info = new Properties();
info.setProperty("SelectMethod", "cursor");
conn = DriverManager.getConnection(connectionString, info);
stmt = conn.createStatement();
dropTVPS();
dropTables();
createTVPS();
createTables();
populateSourceTable();
ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable);
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt.setStructured(1, "invalid" + tvpName, rs);
try {
pstmt.execute();
}
catch (SQLServerException e) {
if (!e.getMessage().contains("Cannot find data type")) {
throw e;
}
}
finally {
if (null != pstmt) {
pstmt.close();
}
if (null != rs) {
rs.close();
}
}
}
/**
* Test exception when giving invalid stored procedure name
*
* @throws SQLException
*/
@Test
public void testInvalidStoredProcedureName() throws SQLException {
Properties info = new Properties();
info.setProperty("SelectMethod", "cursor");
conn = DriverManager.getConnection(connectionString, info);
stmt = conn.createStatement();
dropProcedure();
dropTVPS();
dropTables();
createTVPS();
createTables();
createPreocedure();
populateSourceTable();
ResultSet rs = conn.createStatement().executeQuery("select * from " + srcTable);
final String sql = "{call invalid" + procedureName + "(?)}";
SQLServerCallableStatement pstmt = (SQLServerCallableStatement) conn.prepareCall(sql);
pstmt.setStructured(1, tvpName, rs);
try {
pstmt.execute();
}
catch (SQLServerException e) {
if (!e.getMessage().contains("Could not find stored procedure")) {
throw e;
}
}
finally {
if (null != pstmt) {
pstmt.close();
}
if (null != rs) {
rs.close();
}
dropProcedure();
}
}
/**
* test with multiple prepared statements and result sets
*
* @throws SQLException
*/
@Test
public void testMultiplePreparedStatementAndResultSet() throws SQLException {
conn = DriverManager.getConnection(connectionString);
stmt = conn.createStatement();
dropTVPS();
dropTables();
createTVPS();
createTables();
populateSourceTable();
ResultSet rs = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE).executeQuery("select * from " + srcTable);
SQLServerPreparedStatement pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt1.setStructured(1, tvpName, rs);
pstmt1.execute();
verifyDestinationTableData(expectedBigDecimals.length);
rs.beforeFirst();
pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt1.setStructured(1, tvpName, rs);
pstmt1.execute();
verifyDestinationTableData(expectedBigDecimals.length * 2);
rs.beforeFirst();
SQLServerPreparedStatement pstmt2 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt2.setStructured(1, tvpName, rs);
pstmt2.execute();
verifyDestinationTableData(expectedBigDecimals.length * 3);
String sql = "insert into " + desTable + " values (?,?,?,?)";
Calendar calGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement(sql);
for (int i = 0; i < expectedBigDecimals.length; i++) {
pstmt1.setBigDecimal(1, expectedBigDecimals[i]);
pstmt1.setString(2, expectedStrings[i]);
pstmt1.setTimestamp(3, expectedTimestamps[i], calGMT);
pstmt1.setString(4, expectedStrings[i]);
pstmt1.execute();
}
verifyDestinationTableData(expectedBigDecimals.length * 4);
ResultSet rs2 = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE).executeQuery("select * from " + srcTable);
pstmt1 = (SQLServerPreparedStatement) conn.prepareStatement("INSERT INTO " + desTable + " select * from ? ;");
pstmt1.setStructured(1, tvpName, rs2);
pstmt1.execute();
verifyDestinationTableData(expectedBigDecimals.length * 5);
if (null != pstmt1) {
pstmt1.close();
}
if (null != pstmt2) {
pstmt2.close();
}
if (null != rs) {
rs.close();
}
if (null != rs2) {
rs2.close();
}
}
private static void verifyDestinationTableData(int expectedNumberOfRows) throws SQLException {
ResultSet rs = conn.createStatement().executeQuery("select * from " + desTable);
int expectedArrayLength = expectedBigDecimals.length;
int i = 0;
while (rs.next()) {
assertTrue(rs.getString(1).equals(expectedBigDecimalStrings[i % expectedArrayLength]),
"Expected Value:" + expectedBigDecimalStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(1));
assertTrue(rs.getString(2).trim().equals(expectedStrings[i % expectedArrayLength]),
"Expected Value:" + expectedStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(2));
assertTrue(rs.getString(3).equals(expectedTimestampStrings[i % expectedArrayLength]),
"Expected Value:" + expectedTimestampStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(3));
assertTrue(rs.getString(4).trim().equals(expectedStrings[i % expectedArrayLength]),
"Expected Value:" + expectedStrings[i % expectedArrayLength] + ", Actual Value: " + rs.getString(4));
i++;
}
assertTrue(i == expectedNumberOfRows);
}
private static void populateSourceTable() throws SQLException {
String sql = "insert into " + srcTable + " values (?,?,?,?)";
Calendar calGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) conn.prepareStatement(sql);
for (int i = 0; i < expectedBigDecimals.length; i++) {
pstmt.setBigDecimal(1, expectedBigDecimals[i]);
pstmt.setString(2, expectedStrings[i]);
pstmt.setTimestamp(3, expectedTimestamps[i], calGMT);
pstmt.setString(4, expectedStrings[i]);
pstmt.execute();
}
}
private static void dropTables() throws SQLException {
Utils.dropTableIfExists(srcTable, stmt);
Utils.dropTableIfExists(desTable, stmt);
}
private static void createTables() throws SQLException {
String sql = "create table " + srcTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null, c4 char(7000));";
stmt.execute(sql);
sql = "create table " + desTable + " (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null, c4 char(7000));";
stmt.execute(sql);
}
private static void createTVPS() throws SQLException {
String TVPCreateCmd = "CREATE TYPE " + tvpName
+ " as table (c1 decimal(10,5) null, c2 nchar(50) null, c3 datetime2(7) null, c4 char(7000) null)";
stmt.execute(TVPCreateCmd);
}
private static void dropTVPS() throws SQLException {
stmt.execute("IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '" + tvpName + "') " + " drop type " + tvpName);
}
private static void dropProcedure() throws SQLException {
Utils.dropProcedureIfExists(procedureName, stmt);
}
private static void createPreocedure() throws SQLException {
String sql = "CREATE PROCEDURE " + procedureName + " @InputData " + tvpName + " READONLY " + " AS " + " BEGIN " + " INSERT INTO " + desTable
+ " SELECT * FROM @InputData" + " END";
stmt.execute(sql);
}
@AfterEach
private void terminateVariation() throws SQLException {
if (null != conn) {
conn.close();
}
if (null != stmt) {
stmt.close();
}
}
}