/*
* 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.connection;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import com.microsoft.sqlserver.jdbc.ISQLServerConnection;
import com.microsoft.sqlserver.jdbc.SQLServerException;
import com.microsoft.sqlserver.jdbc.SQLServerXADataSource;
import com.microsoft.sqlserver.testframework.AbstractTest;
import com.microsoft.sqlserver.testframework.DBConnection;
import com.microsoft.sqlserver.testframework.DBTable;
import com.microsoft.sqlserver.testframework.util.RandomUtil;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
/**
* Tests pooled connection
*
*/
@RunWith(JUnitPlatform.class)
public class PoolingTest extends AbstractTest {
@Test
public void testPooling() throws SQLException {
assumeTrue(!DBConnection.isSqlAzure(DriverManager.getConnection(connectionString)), "Skipping test case on Azure SQL.");
String randomTableName = RandomUtil.getIdentifier("table");
// make the table a temporary table (will be created in tempdb database)
String tempTableName = "#" + randomTableName;
SQLServerXADataSource XADataSource1 = new SQLServerXADataSource();
XADataSource1.setURL(connectionString);
XADataSource1.setDatabaseName("tempdb");
PooledConnection pc = XADataSource1.getPooledConnection();
Connection conn = pc.getConnection();
// create table in tempdb database
conn.createStatement().execute("create table [" + tempTableName + "] (myid int)");
conn.createStatement().execute("insert into [" + tempTableName + "] values (1)");
conn.close();
conn = pc.getConnection();
boolean tempTableFileRemoved = false;
try {
conn.createStatement().executeQuery("select * from [" + tempTableName + "]");
}
catch (SQLServerException e) {
// make sure the temporary table is not found.
if (e.getMessage().startsWith("Invalid object name")) {
tempTableFileRemoved = true;
}
}
assertTrue(tempTableFileRemoved, "Temporary table is not removed.");
}
@Test
public void testConnectionPoolReget() throws SQLException {
SQLServerXADataSource ds = new SQLServerXADataSource();
ds.setURL(connectionString);
PooledConnection pc = ds.getPooledConnection();
Connection con = pc.getConnection();
// now reget a connection
Connection con2 = pc.getConnection();
// assert that the first connection is closed.
assertTrue(con.isClosed(), "First connection is not closed");
}
@Test
public void testConnectionPoolConnFunctions() throws SQLException {
String tableName = RandomUtil.getIdentifier("table");
tableName = DBTable.escapeIdentifier(tableName);
String sql1 = "if exists (select * from dbo.sysobjects where name = '" + tableName + "' and type = 'U')\n" + "drop table " + tableName + "\n"
+ "create table " + tableName + "\n" + "(\n" + "wibble_id int primary key not null,\n" + "counter int null\n" + ");";
String sql2 = "if exists (select * from dbo.sysobjects where name = '" + tableName + "' and type = 'U')\n" + "drop table " + tableName + "\n";
SQLServerXADataSource ds = new SQLServerXADataSource();
ds.setURL(connectionString);
PooledConnection pc = ds.getPooledConnection();
Connection con = pc.getConnection();
Statement statement = con.createStatement();
statement.execute(sql1);
statement.execute(sql2);
con.clearWarnings();
pc.close();
}
@Test
public void testConnectionPoolClose() throws SQLException {
SQLServerXADataSource ds = new SQLServerXADataSource();
ds.setURL(connectionString);
PooledConnection pc = ds.getPooledConnection();
Connection con = pc.getConnection();
pc.close();
// assert that the first connection is closed.
assertTrue(con.isClosed(), "Connection is not closed with pool close");
}
@Test
public void testConnectionPoolClientConnectionId() throws SQLException {
SQLServerXADataSource ds = new SQLServerXADataSource();
ds.setURL(connectionString);
PooledConnection pc = ds.getPooledConnection();
ISQLServerConnection con = (ISQLServerConnection) pc.getConnection();
UUID Id1 = con.getClientConnectionId();
assertTrue(Id1 != null, "Unexecepted: ClientConnectionId is null from Pool");
con.close();
// now reget the connection
ISQLServerConnection con2 = (ISQLServerConnection) pc.getConnection();
UUID Id2 = con2.getClientConnectionId();
con2.close();
assertEquals(Id1, Id2, "ClientConnection Ids from pool are not the same.");
}
/**
* test connection pool with HikariCP
*
* @throws SQLException
*/
@Test
public void testHikariCP() throws SQLException {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(connectionString);
HikariDataSource ds = new HikariDataSource(config);
try{
connect(ds);
}
finally{
ds.close();
}
}
/**
* test connection pool with Apache DBCP
*
* @throws SQLException
*/
@Test
public void testApacheDBCP() throws SQLException {
BasicDataSource ds = new BasicDataSource();
ds.setUrl(connectionString);
try{
connect(ds);
}
finally{
ds.close();
}
}
/**
* setup connection, get connection from pool, and test threads
*
* @param ds
* @throws SQLException
*/
private static void connect(DataSource ds) throws SQLException {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
con = ds.getConnection();
pst = con.prepareStatement("SELECT SUSER_SNAME()");
pst.setQueryTimeout(5);
rs = pst.executeQuery();
// TODO : we are commenting this out due to AppVeyor failures. Will investigate later.
// assertTrue(countTimeoutThreads() >= 1, "Timeout timer is missing.");
while (rs.next()) {
rs.getString(1);
}
}
finally {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
}
}
/**
* count number of mssql-jdbc-TimeoutTimer threads
*
* @return
*/
private static int countTimeoutThreads() {
int count = 0;
String threadName = "mssql-jdbc-TimeoutTimer";
ThreadInfo[] tinfos = ManagementFactory.getThreadMXBean().getThreadInfo(ManagementFactory.getThreadMXBean().getAllThreadIds(), 0);
for (ThreadInfo ti : tinfos) {
if ((ti.getThreadName().startsWith(threadName)) && (ti.getThreadState().equals(java.lang.Thread.State.TIMED_WAITING))) {
count++;
}
}
return count;
}
}