fix export of org.xbib.jdbc.pool.api.configuration.supplier
This commit is contained in:
parent
757805851a
commit
54dcdc02d7
35 changed files with 8126 additions and 1 deletions
|
@ -1,3 +1,3 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = database
|
name = database
|
||||||
version = 2.3.0
|
version = 2.3.1
|
||||||
|
|
|
@ -6,6 +6,7 @@ module org.xbib.jdbc.pool {
|
||||||
exports org.xbib.jdbc.pool.api;
|
exports org.xbib.jdbc.pool.api;
|
||||||
exports org.xbib.jdbc.pool.api.cache;
|
exports org.xbib.jdbc.pool.api.cache;
|
||||||
exports org.xbib.jdbc.pool.api.configuration;
|
exports org.xbib.jdbc.pool.api.configuration;
|
||||||
|
exports org.xbib.jdbc.pool.api.configuration.supplier;
|
||||||
exports org.xbib.jdbc.pool.api.exceptionsorter;
|
exports org.xbib.jdbc.pool.api.exceptionsorter;
|
||||||
exports org.xbib.jdbc.pool.api.security;
|
exports org.xbib.jdbc.pool.api.security;
|
||||||
exports org.xbib.jdbc.pool.api.transaction;
|
exports org.xbib.jdbc.pool.api.transaction;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
@SuppressWarnings( "UtilityClass" )
|
||||||
|
public final class AgroalTestGroup {
|
||||||
|
|
||||||
|
public static final String FUNCTIONAL = "functional";
|
||||||
|
public static final String TRANSACTION = "transaction";
|
||||||
|
public static final String CONCURRENCY = "concurrency";
|
||||||
|
public static final String OSGI = "osgi";
|
||||||
|
public static final String SPRING = "spring";
|
||||||
|
|
||||||
|
private AgroalTestGroup() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,339 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.CONCURRENCY;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.time.Duration.ofSeconds;
|
||||||
|
import static java.util.concurrent.Executors.newFixedThreadPool;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
@Tag( CONCURRENCY )
|
||||||
|
public class BasicConcurrencyTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( BasicConcurrencyTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() {
|
||||||
|
registerMockDriver();
|
||||||
|
if ( Utils.isWindowsOS() ) {
|
||||||
|
Utils.windowsTimerHack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Multiple threads" )
|
||||||
|
@SuppressWarnings( "ObjectAllocationInLoop" )
|
||||||
|
void basicConnectionAcquireTest() throws SQLException {
|
||||||
|
int MAX_POOL_SIZE = 10, THREAD_POOL_SIZE = 32, CALLS = 50000, SLEEP_TIME = 1, OVERHEAD = 1;
|
||||||
|
|
||||||
|
ExecutorService executor = newFixedThreadPool( THREAD_POOL_SIZE );
|
||||||
|
CountDownLatch latch = new CountDownLatch( CALLS );
|
||||||
|
BasicConcurrencyTestsListener listener = new BasicConcurrencyTestsListener();
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
executor.submit( () -> {
|
||||||
|
try {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
// logger.info( format( "{0} got {1}", Thread.currentThread().getName(), connection ) );
|
||||||
|
LockSupport.parkNanos( ofMillis( SLEEP_TIME ).toNanos() );
|
||||||
|
connection.close();
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
fail( "Unexpected SQLException " + e.getMessage() );
|
||||||
|
} finally {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long waitTime = ( SLEEP_TIME + OVERHEAD ) * CALLS / MAX_POOL_SIZE;
|
||||||
|
logger.info( format( "Main thread waiting for {0}ms", waitTime ) );
|
||||||
|
if ( !latch.await( waitTime, MILLISECONDS ) ) {
|
||||||
|
fail( "Did not execute within the required amount of time" );
|
||||||
|
}
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
logger.info( format( "Closing DataSource" ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Main thread proceeding with assertions" ) );
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getCreationCount().longValue() );
|
||||||
|
assertEquals( CALLS, listener.getAcquireCount().longValue() );
|
||||||
|
assertEquals( CALLS, listener.getReturnCount().longValue() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Concurrent DataSource in closed state" )
|
||||||
|
@SuppressWarnings( {"BusyWait", "JDBCResourceOpenedButNotSafelyClosed", "MethodCallInLoopCondition"} )
|
||||||
|
void concurrentDataSourceCloseTest() throws SQLException, InterruptedException {
|
||||||
|
int MAX_POOL_SIZE = 10, THREAD_POOL_SIZE = 2, ACQUISITION_TIMEOUT_MS = 2000;
|
||||||
|
|
||||||
|
BasicConcurrencyTestsListener listener = new BasicConcurrencyTestsListener();
|
||||||
|
ExecutorService executor = newFixedThreadPool( THREAD_POOL_SIZE );
|
||||||
|
CountDownLatch latch = new CountDownLatch( 1 );
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.acquisitionTimeout( ofMillis( ACQUISITION_TIMEOUT_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener );
|
||||||
|
|
||||||
|
executor.submit( () -> {
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
try {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection, "Expected non null connection" );
|
||||||
|
//connection.close();
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
fail( "SQLException", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertEquals( 0, dataSource.getMetrics().availableCount(), "Should not be any available connections" );
|
||||||
|
|
||||||
|
logger.info( "Blocked waiting for a connection" );
|
||||||
|
dataSource.getConnection();
|
||||||
|
|
||||||
|
fail( "Expected SQLException" );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
// SQLException should not be because of acquisition timeout
|
||||||
|
assertTrue( e.getCause() instanceof RejectedExecutionException || e.getCause() instanceof CancellationException, "Cause for SQLException should be either RejectedExecutionException or CancellationException" );
|
||||||
|
latch.countDown();
|
||||||
|
|
||||||
|
logger.info( "Unblocked after datasource close" );
|
||||||
|
} catch ( Throwable t ) {
|
||||||
|
fail( "Unexpected throwable", t );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
do {
|
||||||
|
Thread.sleep( ACQUISITION_TIMEOUT_MS / 10 );
|
||||||
|
} while ( dataSource.getMetrics().awaitingCount() == 0 );
|
||||||
|
|
||||||
|
logger.info( "Closing the datasource" );
|
||||||
|
dataSource.close();
|
||||||
|
|
||||||
|
if ( !latch.await( ACQUISITION_TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( "Did not execute within the required amount of time" );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertThrows( SQLException.class, dataSource::getConnection );
|
||||||
|
assertFalse( listener.getWarning().get(), "Unexpected warning" );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getCreationCount().longValue() );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getAcquireCount().longValue() );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getDestroyCount().longValue() );
|
||||||
|
assertEquals( MAX_POOL_SIZE, dataSource.getMetrics().destroyCount(), "Destroy count" );
|
||||||
|
assertEquals( 0, listener.getReturnCount().longValue() );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Active connections" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().availableCount(), "Should not be any available connections" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Subsequent calls to dataSource.close() should not throw any exception
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "DataSource close" )
|
||||||
|
void dataSourceCloseTest() throws SQLException, InterruptedException {
|
||||||
|
int MAX_POOL_SIZE = 10, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
ShutdownListener listener = new ShutdownListener( MAX_POOL_SIZE );
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
// Add periodic tasks that should be cancelled on close
|
||||||
|
.reapTimeout( ofSeconds( 10 ) )
|
||||||
|
.validationTimeout( ofSeconds( 2 ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener );
|
||||||
|
|
||||||
|
if ( !listener.getStartupLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( "Did not execute within the required amount of time" );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( MAX_POOL_SIZE, dataSource.getMetrics().availableCount() );
|
||||||
|
|
||||||
|
Connection c = dataSource.getConnection();
|
||||||
|
assertEquals( 1, dataSource.getMetrics().activeCount() );
|
||||||
|
assertFalse( c.isClosed() );
|
||||||
|
|
||||||
|
dataSource.close();
|
||||||
|
|
||||||
|
// Connections take a while to be destroyed because the executor has to wait on the listener.
|
||||||
|
// We check right after close() to make sure all were destroyed when the method returns.
|
||||||
|
assertAll( () -> {
|
||||||
|
assertFalse( listener.getWarning(), "Datasource closed but there are tasks to run" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().availableCount() );
|
||||||
|
assertEquals( MAX_POOL_SIZE, dataSource.getMetrics().destroyCount() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class BasicConcurrencyTestsListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final LongAdder creationCount = new LongAdder(), acquireCount = new LongAdder(), returnCount = new LongAdder(), destroyCount = new LongAdder();
|
||||||
|
|
||||||
|
private final AtomicBoolean warning = new AtomicBoolean( false );
|
||||||
|
|
||||||
|
BasicConcurrencyTestsListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
creationCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionAcquire(Connection connection) {
|
||||||
|
acquireCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionReturn(Connection connection) {
|
||||||
|
returnCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionDestroy(Connection connection) {
|
||||||
|
destroyCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
LongAdder getCreationCount() {
|
||||||
|
return creationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongAdder getAcquireCount() {
|
||||||
|
return acquireCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongAdder getReturnCount() {
|
||||||
|
return returnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongAdder getDestroyCount() {
|
||||||
|
return destroyCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicBoolean getWarning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class ShutdownListener implements AgroalDataSourceListener {
|
||||||
|
private boolean warning;
|
||||||
|
private final CountDownLatch startupLatch;
|
||||||
|
|
||||||
|
ShutdownListener(int poolSize) {
|
||||||
|
startupLatch = new CountDownLatch( poolSize );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
startupLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
try {
|
||||||
|
// sleep for 1 ms
|
||||||
|
Thread.sleep( 1 );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Interrupted" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch getStartupLatch() {
|
||||||
|
return startupLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean getWarning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import io.agroal.test.MockDriver;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Driver;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.api.configuration.AgroalDataSourceConfiguration.DataSourceImplementation.HIKARI;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static java.lang.System.nanoTime;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class BasicHikariTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( BasicHikariTests.class.getName() );
|
||||||
|
|
||||||
|
private static final Driver fakeDriver = new FakeDriver();
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() throws SQLException {
|
||||||
|
DriverManager.registerDriver( fakeDriver );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() throws SQLException {
|
||||||
|
DriverManager.deregisterDriver( fakeDriver );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Mock driver providing fake connections" )
|
||||||
|
void basicConnectionAcquireTest() throws SQLException {
|
||||||
|
int VALIDATION_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( HIKARI )
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.validationTimeout( ofMillis( VALIDATION_MS ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClassName( fakeDriver.getClass().getName() )
|
||||||
|
.jdbcUrl( "jdbc://" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getSchema(), FakeDriver.FakeConnection.FAKE_SCHEMA );
|
||||||
|
logger.info( format( "Got schema \"{0}\" from {1}", connection.getSchema(), connection ) );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection wrapper in closed state" )
|
||||||
|
void basicConnectionCloseTest() throws SQLException {
|
||||||
|
int VALIDATION_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( HIKARI )
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.validationTimeout( ofMillis( VALIDATION_MS ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClassName( fakeDriver.getClass().getName() )
|
||||||
|
.jdbcUrl( "jdbc://" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertFalse( connection.isClosed(), "Expected open connection, but it's closed" );
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertThrows( SQLException.class, connection::getSchema );
|
||||||
|
assertTrue( connection.isClosed(), "Expected closed connection, but it's open" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Acquisition timeout" )
|
||||||
|
@SuppressWarnings( "JDBCResourceOpenedButNotSafelyClosed" )
|
||||||
|
void basicAcquisitionTimeoutTest() throws SQLException {
|
||||||
|
int MAX_POOL_SIZE = 100, ACQUISITION_TIMEOUT_MS = 1000, VALIDATION_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( HIKARI )
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.acquisitionTimeout( ofMillis( ACQUISITION_TIMEOUT_MS ) )
|
||||||
|
.validationTimeout( ofMillis( VALIDATION_MS ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClassName( fakeDriver.getClass().getName() )
|
||||||
|
.jdbcUrl( "jdbc://" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
//connection.close();
|
||||||
|
}
|
||||||
|
logger.info( format( "Holding all {0} connections from the pool and requesting a new one", MAX_POOL_SIZE ) );
|
||||||
|
|
||||||
|
long start = nanoTime(), timeoutBound = (long) ( ACQUISITION_TIMEOUT_MS * 1.1 );
|
||||||
|
assertTimeoutPreemptively( ofMillis( timeoutBound ), () -> assertThrows( SQLException.class, dataSource::getConnection ), "Expecting acquisition timeout" );
|
||||||
|
|
||||||
|
long elapsed = NANOSECONDS.toMillis( nanoTime() - start );
|
||||||
|
logger.info( format( "Acquisition timeout after {0}ms - Configuration is {1}ms", elapsed, ACQUISITION_TIMEOUT_MS ) );
|
||||||
|
assertTrue( elapsed > ACQUISITION_TIMEOUT_MS, "Acquisition timeout before time" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class FakeDriver implements MockDriver {
|
||||||
|
@Override
|
||||||
|
public Connection connect(String url, Properties info) {
|
||||||
|
return new FakeConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FakeConnection implements MockConnection {
|
||||||
|
|
||||||
|
private static final String FAKE_SCHEMA = "skeema";
|
||||||
|
|
||||||
|
FakeConnection() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() {
|
||||||
|
return FAKE_SCHEMA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.narayana.NarayanaTransactionIntegration;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.transaction.HeuristicMixedException;
|
||||||
|
import jakarta.transaction.HeuristicRollbackException;
|
||||||
|
import jakarta.transaction.NotSupportedException;
|
||||||
|
import jakarta.transaction.RollbackException;
|
||||||
|
import jakarta.transaction.SystemException;
|
||||||
|
import jakarta.transaction.TransactionManager;
|
||||||
|
import jakarta.transaction.TransactionSynchronizationRegistry;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionPoolConfiguration.TransactionRequirement.STRICT;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionPoolConfiguration.TransactionRequirement.WARN;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.TRANSACTION;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
@Tag( TRANSACTION )
|
||||||
|
public class BasicNarayanaTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( BasicNarayanaTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() {
|
||||||
|
registerMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection acquire test" )
|
||||||
|
@SuppressWarnings( "JDBCResourceOpenedButNotSafelyClosed" )
|
||||||
|
void basicConnectionAcquireTest() throws SQLException {
|
||||||
|
TransactionManager txManager = com.arjuna.ats.jta.TransactionManager.transactionManager();
|
||||||
|
TransactionSynchronizationRegistry txSyncRegistry = new com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple();
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.transactionIntegration( new NarayanaTransactionIntegration( txManager, txSyncRegistry ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf.autoCommit( true ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
txManager.begin();
|
||||||
|
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
logger.info( format( "Got connection {0}", connection ) );
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertThrows( SQLException.class, () -> connection.setAutoCommit( true ) );
|
||||||
|
assertFalse( connection.getAutoCommit(), "Expect connection to have autocommit not set" );
|
||||||
|
// TODO: comparing toString is brittle. Find a better way to make sure the underlying physical connection is the same.
|
||||||
|
assertEquals( connection.toString(), dataSource.getConnection().toString(), "Expect the same connection under the same transaction" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
txManager.commit();
|
||||||
|
|
||||||
|
assertTrue( connection.isClosed() );
|
||||||
|
} catch ( NotSupportedException | SystemException | RollbackException | HeuristicMixedException | HeuristicRollbackException e ) {
|
||||||
|
fail( "Exception: " + e.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Basic rollback test" )
|
||||||
|
void basicRollbackTest() throws SQLException {
|
||||||
|
TransactionManager txManager = com.arjuna.ats.jta.TransactionManager.transactionManager();
|
||||||
|
TransactionSynchronizationRegistry txSyncRegistry = new com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple();
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.transactionIntegration( new NarayanaTransactionIntegration( txManager, txSyncRegistry ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
txManager.begin();
|
||||||
|
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
logger.info( format( "Got connection {0}", connection ) );
|
||||||
|
|
||||||
|
txManager.rollback();
|
||||||
|
|
||||||
|
assertTrue( connection.isClosed() );
|
||||||
|
} catch ( NotSupportedException | SystemException e ) {
|
||||||
|
fail( "Exception: " + e.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Multiple close test" )
|
||||||
|
void multipleCloseTest() throws SQLException {
|
||||||
|
TransactionManager txManager = com.arjuna.ats.jta.TransactionManager.transactionManager();
|
||||||
|
TransactionSynchronizationRegistry txSyncRegistry = new com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple();
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.transactionIntegration( new NarayanaTransactionIntegration( txManager, txSyncRegistry ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
|
||||||
|
// there is a call to connection#close in the try-with-resources block and another on the callback from the transaction#commit()
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
logger.info( format( "Got connection {0}", connection ) );
|
||||||
|
try {
|
||||||
|
txManager.begin();
|
||||||
|
txManager.commit();
|
||||||
|
} catch ( NotSupportedException | SystemException | RollbackException | HeuristicMixedException | HeuristicRollbackException e ) {
|
||||||
|
fail( "Exception: " + e.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Transaction required tests" )
|
||||||
|
void transactionRequiredTests() throws SQLException {
|
||||||
|
TransactionManager txManager = com.arjuna.ats.jta.TransactionManager.transactionManager();
|
||||||
|
TransactionSynchronizationRegistry txSyncRegistry = new com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple();
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.transactionIntegration( new NarayanaTransactionIntegration( txManager, txSyncRegistry ) )
|
||||||
|
), new NoWarningsListener() ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
logger.info( "Got connection " + c );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WarningListener warningListener = new WarningListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.transactionIntegration( new NarayanaTransactionIntegration( txManager, txSyncRegistry ) )
|
||||||
|
.transactionRequirement( WARN )
|
||||||
|
), warningListener ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 1, warningListener.getWarnings(), "Expected a warning message" );
|
||||||
|
logger.info( "Got connection with warning :" + c );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.transactionIntegration( new NarayanaTransactionIntegration( txManager, txSyncRegistry ) )
|
||||||
|
.transactionRequirement( STRICT )
|
||||||
|
) ) ) {
|
||||||
|
assertThrows( SQLException.class, dataSource::getConnection );
|
||||||
|
|
||||||
|
// Make sure connection is available after getConnection() throws
|
||||||
|
txManager.begin();
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
logger.info( "Got connection with tx :" + c );
|
||||||
|
}
|
||||||
|
txManager.rollback();
|
||||||
|
} catch ( SystemException | NotSupportedException e ) {
|
||||||
|
fail( "Exception: " + e.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
private static class NoWarningsListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
NoWarningsListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
fail( "Got warning: " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
fail( "Got warning: " + throwable.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class WarningListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private int warnings;
|
||||||
|
|
||||||
|
WarningListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
logger.warning( throwable.getMessage() );
|
||||||
|
warnings++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWarnings() {
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
567
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/BasicTests.java
Normal file
567
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/BasicTests.java
Normal file
|
@ -0,0 +1,567 @@
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSource;
|
||||||
|
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 java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.pool.api.XbibDataSourceListener;
|
||||||
|
import org.xbib.jdbc.pool.api.configuration.supplier.XbibDataSourceConfigurationSupplier;
|
||||||
|
|
||||||
|
import static java.lang.Thread.currentThread;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.MultipleAcquisitionAction.STRICT;
|
||||||
|
import static org.xbib.jdbc.pool.api.configuration.XbibConnectionPoolConfiguration.MultipleAcquisitionAction.WARN;
|
||||||
|
import static org.xbib.jdbc.pool.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static org.xbib.jdbc.pool.test.MockDriver.registerMockDriver;
|
||||||
|
|
||||||
|
public class BasicTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( BasicTests.class.getName() );
|
||||||
|
|
||||||
|
private static final String FAKE_SCHEMA = "skeema";
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeSchemaConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Mock driver providing fake connections" )
|
||||||
|
void basicConnectionAcquireTest() throws SQLException, IOException {
|
||||||
|
try (XbibDataSource dataSource = XbibDataSource.from( new XbibDataSourceConfigurationSupplier().connectionPoolConfiguration(cp -> cp.maxSize( 1 ) ) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertEquals(FAKE_SCHEMA, connection.getSchema());
|
||||||
|
logger.info( format( "Got schema \"{0}\" from {1}", connection.getSchema(), connection ) );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "DataSource in closed state" )
|
||||||
|
@SuppressWarnings( "AnonymousInnerClassMayBeStatic" )
|
||||||
|
void basicDataSourceCloseTest() throws SQLException, IOException {
|
||||||
|
AtomicBoolean warning = new AtomicBoolean( false );
|
||||||
|
XbibDataSource dataSource = XbibDataSource.from( new XbibDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 2 ) ), new XbibDataSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
Connection leaked = dataSource.getConnection();
|
||||||
|
assertAll( () -> {
|
||||||
|
assertFalse( connection.isClosed(), "Expected open connection, but it's closed" );
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
} );
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
dataSource.close();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertThrows( SQLException.class, dataSource::getConnection );
|
||||||
|
assertTrue( leaked.isClosed(), "Expected closed connection, but it's open" );
|
||||||
|
assertFalse( warning.get(), "Unexpected warning" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Leak detection" )
|
||||||
|
@SuppressWarnings( "JDBCResourceOpenedButNotSafelyClosed" )
|
||||||
|
void basicLeakDetectionTest() throws SQLException, IOException {
|
||||||
|
int MAX_POOL_SIZE = 100, LEAK_DETECTION_MS = 1000;
|
||||||
|
Thread leakingThread = currentThread();
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.leakTimeout( ofMillis( LEAK_DETECTION_MS ) )
|
||||||
|
.acquisitionTimeout( ofMillis( LEAK_DETECTION_MS ) )
|
||||||
|
);
|
||||||
|
CountDownLatch latch = new CountDownLatch( MAX_POOL_SIZE );
|
||||||
|
|
||||||
|
XbibDataSourceListener listener = new LeakDetectionListener( leakingThread, latch );
|
||||||
|
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
//connection.close();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.info( format( "Holding all {0} connections from the pool and waiting for leak notifications", MAX_POOL_SIZE ) );
|
||||||
|
if ( !latch.await( 3L * LEAK_DETECTION_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Missed detection of {0} leaks", latch.getCount() ) );
|
||||||
|
}
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Borrow Connection Validation" )
|
||||||
|
void basicBorrowValidationTest() throws SQLException, IOException {
|
||||||
|
int CALLS = 10;
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 2 )
|
||||||
|
.validateOnBorrow(true)
|
||||||
|
);
|
||||||
|
ValidationCountListener listener = new ValidationCountListener();
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( CALLS, listener.getValidationCount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection Validation" )
|
||||||
|
void basicValidationTest() throws SQLException, IOException {
|
||||||
|
int MAX_POOL_SIZE = 100, CALLS = 1000, VALIDATION_MS = 1000;
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.validationTimeout( ofMillis( VALIDATION_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch( MAX_POOL_SIZE );
|
||||||
|
|
||||||
|
XbibDataSourceListener listener = new ValidationListener( latch );
|
||||||
|
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.info( format( "Awaiting for validation of all the {0} connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !latch.await( 3L * VALIDATION_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Validation of {0} connections", latch.getCount() ) );
|
||||||
|
}
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Idle Connection Validation" )
|
||||||
|
void basicIdleValidationTest() throws SQLException, IOException {
|
||||||
|
int CALLS = 10, IDLE_VALIDATION_MS = 1000;
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 2 )
|
||||||
|
.idleValidationTimeout( ofMillis( IDLE_VALIDATION_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch( 1 );
|
||||||
|
|
||||||
|
XbibDataSourceListener listener = new ValidationListener( latch );
|
||||||
|
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
assertEquals( 1, latch.getCount(), "Not expected validation to occur before " + IDLE_VALIDATION_MS );
|
||||||
|
Executors.newSingleThreadScheduledExecutor().schedule( () -> {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
fail( e );
|
||||||
|
}
|
||||||
|
}, IDLE_VALIDATION_MS, MILLISECONDS );
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info( format( "Awaiting validation of idle connection" ) );
|
||||||
|
if ( !latch.await( 3L * IDLE_VALIDATION_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Did not validate idle connection", latch.getCount() ) );
|
||||||
|
}
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection Reap" )
|
||||||
|
void basicReapTest() throws SQLException, IOException {
|
||||||
|
int MIN_POOL_SIZE = 40, MAX_POOL_SIZE = 100, CALLS = 1000, REAP_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.reapTimeout( ofMillis( REAP_TIMEOUT_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch allLatch = new CountDownLatch( MAX_POOL_SIZE );
|
||||||
|
CountDownLatch destroyLatch = new CountDownLatch( MAX_POOL_SIZE - MIN_POOL_SIZE );
|
||||||
|
LongAdder reapCount = new LongAdder();
|
||||||
|
|
||||||
|
XbibDataSourceListener listener = new ReapListener( allLatch, reapCount, destroyLatch );
|
||||||
|
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.info( format( "Awaiting test of all the {0} connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !allLatch.await( 3L * REAP_TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not tested for reap", allLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Waiting for reaping of {0} connections ", MAX_POOL_SIZE - MIN_POOL_SIZE ) );
|
||||||
|
if ( !destroyLatch.await( 2L * REAP_TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} idle connections not sent for destruction", destroyLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( MAX_POOL_SIZE - MIN_POOL_SIZE, reapCount.longValue(), "Unexpected number of idle connections " );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Enhanced leak report" )
|
||||||
|
void enhancedLeakReportTest() throws SQLException, IOException {
|
||||||
|
int LEAK_DETECTION_MS = 1000;
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 10 )
|
||||||
|
.leakTimeout( ofMillis( LEAK_DETECTION_MS ) )
|
||||||
|
.acquisitionTimeout( ofMillis( LEAK_DETECTION_MS ) )
|
||||||
|
.enhancedLeakReport()
|
||||||
|
);
|
||||||
|
CountDownLatch latch = new CountDownLatch( 1 );
|
||||||
|
|
||||||
|
LeakDetectionListener listener = new LeakDetectionListener( currentThread(), latch );
|
||||||
|
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
connection.unwrap( Connection.class ).close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info( format( "Holding connection from the pool and waiting for leak notification" ) );
|
||||||
|
if ( !latch.await( 3L * LEAK_DETECTION_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Missed detection of {0} leaks", latch.getCount() ) );
|
||||||
|
}
|
||||||
|
Thread.sleep( 100 ); // hold for a bit to allow for enhanced info
|
||||||
|
assertEquals( 3 + 1, listener.getInfoCount(), "Not enough info on extended leak report" );
|
||||||
|
assertEquals( 1, listener.getWarningCount(), "Not enough info on extended leak report" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings( "JDBCResourceOpenedButNotSafelyClosed" )
|
||||||
|
@DisplayName( "Initial SQL test" )
|
||||||
|
void initialSQLTest() throws SQLException, IOException {
|
||||||
|
int MAX_POOL_SIZE = 10;
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier configurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf.initialSql( "Initial SQL" ) )
|
||||||
|
);
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( configurationSupplier ) ) {
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
assertEquals( "Initial SQL", dataSource.getConnection().unwrap( FakeSchemaConnection.class ).initialSQL(), "Connection not initialized" );
|
||||||
|
//connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Single acquisition" )
|
||||||
|
@SuppressWarnings( {"JDBCResourceOpenedButNotSafelyClosed", "ObjectAllocationInLoop"} )
|
||||||
|
void basicSingleAcquisitionTest() throws SQLException, IOException {
|
||||||
|
int MAX_POOL_SIZE = 10;
|
||||||
|
|
||||||
|
XbibDataSourceConfigurationSupplier offConfigurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
);
|
||||||
|
XbibDataSourceConfigurationSupplier warnConfigurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.multipleAcquisition( WARN ) );
|
||||||
|
XbibDataSourceConfigurationSupplier strictConfigurationSupplier = new XbibDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.multipleAcquisition( STRICT ) );
|
||||||
|
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( offConfigurationSupplier, new NoWarningsAgroalListener() ) ) {
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
//connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WarningsAgroalListener warningsAgroalListener = new WarningsAgroalListener();
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( warnConfigurationSupplier, warningsAgroalListener ) ) {
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
//connection.close();
|
||||||
|
}
|
||||||
|
assertEquals( MAX_POOL_SIZE - 1, warningsAgroalListener.getWarningCount(), "Wrong number of warning messages" );
|
||||||
|
}
|
||||||
|
try ( XbibDataSource dataSource = XbibDataSource.from( strictConfigurationSupplier, new NoWarningsAgroalListener() ) ) {
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
if ( i == 0 ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
//connection.close();
|
||||||
|
} else {
|
||||||
|
assertThrows( SQLException.class, dataSource::getConnection, "Expected exception on multiple acquisition" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().acquireCount() );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().activeCount() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class LeakDetectionListener implements XbibDataSourceListener {
|
||||||
|
private final Thread leakingThread;
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
private int infoCount, warningCount;
|
||||||
|
|
||||||
|
LeakDetectionListener(Thread leakingThread, CountDownLatch latch) {
|
||||||
|
this.leakingThread = leakingThread;
|
||||||
|
this.latch = latch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionLeak(Connection connection, Thread thread) {
|
||||||
|
assertEquals( leakingThread, thread, "Wrong thread reported" );
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warningCount++;
|
||||||
|
logger.warning( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInfo(String message) {
|
||||||
|
infoCount++;
|
||||||
|
logger.info( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
int getInfoCount() {
|
||||||
|
return infoCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWarningCount() {
|
||||||
|
return warningCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ValidationCountListener implements XbibDataSourceListener {
|
||||||
|
private int validationCount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionValidation(Connection connection) {
|
||||||
|
validationCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValidationCount() {
|
||||||
|
return validationCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ValidationListener implements XbibDataSourceListener {
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
ValidationListener(CountDownLatch latch) {
|
||||||
|
this.latch = latch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionValidation(Connection connection) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ReapListener implements XbibDataSourceListener {
|
||||||
|
|
||||||
|
private final CountDownLatch allLatch;
|
||||||
|
private final LongAdder reapCount;
|
||||||
|
private final CountDownLatch destroyLatch;
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
ReapListener(CountDownLatch allLatch, LongAdder reapCount, CountDownLatch destroyLatch) {
|
||||||
|
this.allLatch = allLatch;
|
||||||
|
this.reapCount = reapCount;
|
||||||
|
this.destroyLatch = destroyLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionReap(Connection connection) {
|
||||||
|
allLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionReap(Connection connection) {
|
||||||
|
reapCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionDestroy(Connection connection) {
|
||||||
|
destroyLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NoWarningsAgroalListener implements XbibDataSourceListener {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
NoWarningsAgroalListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
fail( "Unexpected warn message: " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
fail( "Unexpected warn throwable: " + throwable.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class WarningsAgroalListener implements XbibDataSourceListener {
|
||||||
|
private int warningCount;
|
||||||
|
|
||||||
|
WarningsAgroalListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warningCount++;
|
||||||
|
logger.warning( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable t) {
|
||||||
|
warningCount++;
|
||||||
|
logger.warning( t.getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWarningCount() {
|
||||||
|
return warningCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FakeSchemaConnection implements MockConnection {
|
||||||
|
|
||||||
|
private boolean closed;
|
||||||
|
private final RecordingStatement statement = new RecordingStatement();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
return FAKE_SCHEMA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement createStatement() throws SQLException {
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
String initialSQL() {
|
||||||
|
return statement.getInitialSQL();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings( "unchecked" )
|
||||||
|
public <T> T unwrap(Class<T> target) throws SQLException {
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RecordingStatement implements MockStatement {
|
||||||
|
|
||||||
|
private String initialSQL;
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
RecordingStatement() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql) throws SQLException {
|
||||||
|
initialSQL = sql;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getInitialSQL() {
|
||||||
|
return initialSQL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.pool.wrapper.StatementWrapper;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import io.agroal.test.MockStatement;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class ConnectionCloseTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( ConnectionCloseTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeSchemaConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection wrapper in closed state" )
|
||||||
|
void basicConnectionCloseTest() throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 1 ) ) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertFalse( connection.isClosed(), "Expected open connection, but it's closed" );
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertThrows( SQLException.class, connection::getSchema );
|
||||||
|
assertTrue( connection.isClosed(), "Expected closed connection, but it's open" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection closes Statements and ResultSets" )
|
||||||
|
void statementCloseTest() throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 1 ) ) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
logger.info( format( "Creating 2 Statements on Connection {0}", connection ) );
|
||||||
|
|
||||||
|
Statement statementOne = connection.createStatement();
|
||||||
|
Statement statementTwo = connection.createStatement();
|
||||||
|
ResultSet setOne = statementOne.getResultSet();
|
||||||
|
ResultSet setTwo = statementTwo.getResultSet();
|
||||||
|
statementTwo.close();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertNotNull( setOne, "Expected non null value" );
|
||||||
|
assertFalse( statementOne.isClosed(), "Expected open Statement, but it's closed" );
|
||||||
|
assertThrows( SQLException.class, statementTwo::getResultSet, "Expected SQLException on closed Connection" );
|
||||||
|
assertTrue( statementTwo.isClosed(), "Expected closed Statement, but it's open" );
|
||||||
|
assertTrue( setTwo.isClosed(), "Expected closed ResultSet, but it's open" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
statementTwo.close();
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertThrows( SQLException.class, statementOne::getResultSet, "Expected SQLException on closed Connection" );
|
||||||
|
assertTrue( statementOne.isClosed(), "Expected closed Statement, but it's open" );
|
||||||
|
assertTrue( setOne.isClosed(), "Expected closed ResultSet, but it's open" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection closed multiple times" )
|
||||||
|
@SuppressWarnings( "RedundantExplicitClose" )
|
||||||
|
void multipleCloseTest() throws SQLException {
|
||||||
|
ReturnListener returnListener = new ReturnListener();
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().metricsEnabled().connectionPoolConfiguration( cp -> cp.maxSize( 1 ) ), returnListener ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
// Explicit close. This is a try-with-resources so there is another call to connection.close() after this one.
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( 1, returnListener.getReturnCount().longValue(), "Expecting connection to be returned once to the pool" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Expecting 0 active connections" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Flush on close" )
|
||||||
|
void flushOnCloseTest() throws Exception {
|
||||||
|
OnWarningListener warningListener = new OnWarningListener();
|
||||||
|
OnDestroyListener destroyListener = new OnDestroyListener( 1 );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().metricsEnabled().connectionPoolConfiguration( cp -> cp.minSize( 1 ).maxSize( 1 ).flushOnClose() ), warningListener, destroyListener ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertFalse( connection.isClosed() );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue( destroyListener.awaitSeconds( 1 ) );
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertFalse( warningListener.getWarning().get(), "Unexpected warning on close connection" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().destroyCount(), "Expecting 1 destroyed connection" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount(), "Expecting 1 available connection" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Expecting 0 active connections" );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Statement close" )
|
||||||
|
void statementClose() throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 1 ) ) ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
try ( Statement statement = connection.createStatement() ) {
|
||||||
|
Statement underlyingStatement = statement.unwrap( ClosableStatement.class );
|
||||||
|
statement.close();
|
||||||
|
assertTrue( underlyingStatement.isClosed() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "ResultSet leak" )
|
||||||
|
void resultSetLeak() throws SQLException {
|
||||||
|
ResultSet resultSet;
|
||||||
|
OnWarningListener listener = new OnWarningListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 1 ) ), listener ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
resultSet = statement.getResultSet();
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue( resultSet.isClosed(), "Leaked ResultSet not closed" );
|
||||||
|
assertTrue( listener.getWarning().get(), "No warning message on ResultSet leak" );
|
||||||
|
resultSet.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "JDBC resources tracking disabled" )
|
||||||
|
@SuppressWarnings( "InstanceofConcreteClass" )
|
||||||
|
void jdbcResourcesTrackingDisabled() throws SQLException {
|
||||||
|
Statement statement;
|
||||||
|
OnWarningListener listener = new OnWarningListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().metricsEnabled().connectionPoolConfiguration( cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.trackJdbcResources( false ) ) ), listener ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
statement = connection.createStatement();
|
||||||
|
assertTrue( statement instanceof ClosableStatement, "Wrapped Statement when tracking is disabled" );
|
||||||
|
assertThrows( ClassCastException.class, () -> statement.unwrap( StatementWrapper.class ), "Wrapped Statement when tracking is disabled" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse( listener.getWarning().get(), "Leak warning when tracking is disabled " );
|
||||||
|
assertFalse( statement.isClosed(), "Tracking is disabled, but acted to clean leak!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class ReturnListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final LongAdder returnCount = new LongAdder();
|
||||||
|
|
||||||
|
ReturnListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionReturn(Connection connection) {
|
||||||
|
returnCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
LongAdder getReturnCount() {
|
||||||
|
return returnCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class FakeSchemaConnection implements MockConnection {
|
||||||
|
|
||||||
|
private static final String FAKE_SCHEMA = "skeema";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() {
|
||||||
|
return FAKE_SCHEMA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement createStatement() throws SQLException {
|
||||||
|
return new ClosableStatement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClosableStatement implements MockStatement {
|
||||||
|
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return iface.cast( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class OnWarningListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final AtomicBoolean warning = new AtomicBoolean( false );
|
||||||
|
|
||||||
|
OnWarningListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicBoolean getWarning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( {"WeakerAccess", "SameParameterValue"} )
|
||||||
|
private static class OnDestroyListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
|
||||||
|
OnDestroyListener(int count) {
|
||||||
|
latch = new CountDownLatch( count );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean awaitSeconds(int timeout) throws InterruptedException {
|
||||||
|
return latch.await( timeout, TimeUnit.SECONDS );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import io.agroal.test.MockDataSource;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.NONE;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.READ_COMMITTED;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.READ_UNCOMMITTED;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.REPEATABLE_READ;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.SERIALIZABLE;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionPoolConfiguration.ExceptionSorter.emptyExceptionSorter;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionPoolConfiguration.ExceptionSorter.fatalExceptionSorter;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class ConnectionResetTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( ConnectionResetTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection isolation remains the same after being changed" )
|
||||||
|
void isolationTest() throws SQLException {
|
||||||
|
isolation( NONE, Connection.TRANSACTION_NONE );
|
||||||
|
isolation( READ_UNCOMMITTED, Connection.TRANSACTION_READ_UNCOMMITTED );
|
||||||
|
isolation( READ_COMMITTED, Connection.TRANSACTION_READ_COMMITTED );
|
||||||
|
isolation( REPEATABLE_READ, Connection.TRANSACTION_REPEATABLE_READ );
|
||||||
|
isolation( SERIALIZABLE, Connection.TRANSACTION_SERIALIZABLE );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void isolation(AgroalConnectionFactoryConfiguration.TransactionIsolation isolation, int level) throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration(
|
||||||
|
cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.jdbcTransactionIsolation( isolation ) )
|
||||||
|
) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getTransactionIsolation(), level );
|
||||||
|
connection.setTransactionIsolation( Connection.TRANSACTION_NONE );
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getTransactionIsolation(), level );
|
||||||
|
logger.info( format( "Got isolation \"{0}\" from {1}", connection.getTransactionIsolation(), connection ) );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection with custom transaction isolation level" )
|
||||||
|
void customIsolationTest() throws SQLException {
|
||||||
|
int level = 42;
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration(
|
||||||
|
cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.jdbcTransactionIsolation( level ) )
|
||||||
|
) ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertEquals( connection.getTransactionIsolation(), level );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection reset with default (driver) transaction isolation level" )
|
||||||
|
void defaultIsolationResetTest() throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration(
|
||||||
|
cp -> cp.maxSize( 1 )
|
||||||
|
) ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertEquals( connection.getTransactionIsolation(), FakeConnection.DEFAULT_ISOLATION );
|
||||||
|
connection.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE );
|
||||||
|
assertEquals( connection.getTransactionIsolation(), Connection.TRANSACTION_SERIALIZABLE );
|
||||||
|
}
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertEquals( connection.getTransactionIsolation(), FakeConnection.DEFAULT_ISOLATION );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection autoCommit status remains the same after being changed" )
|
||||||
|
void autoCommitTest() throws SQLException {
|
||||||
|
autocommit( false );
|
||||||
|
autocommit( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void autocommit(boolean autoCommit) throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration(
|
||||||
|
cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.autoCommit( autoCommit ) )
|
||||||
|
) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getAutoCommit(), autoCommit );
|
||||||
|
connection.setAutoCommit( !autoCommit );
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getAutoCommit(), autoCommit );
|
||||||
|
logger.info( format( "Got autoCommit \"{0}\" from {1}", connection.getAutoCommit(), connection ) );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection with warnings" )
|
||||||
|
void warningsTest() throws SQLException {
|
||||||
|
warnings( false );
|
||||||
|
warnings( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void warnings(boolean fatal) throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp.maxSize( 1 ).exceptionSorter( fatal ? fatalExceptionSorter() : emptyExceptionSorter() ) )
|
||||||
|
) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertNotNull( connection.getWarnings() );
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
connection = dataSource.getConnection();
|
||||||
|
assertEquals( fatal ? 2 : 1, dataSource.getMetrics().creationCount() );
|
||||||
|
assertEquals( fatal, connection.getWarnings() != null ); // checks if warnings were cleared
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test exception during reset" )
|
||||||
|
void resetExceptionTest() throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().metricsEnabled().connectionPoolConfiguration(
|
||||||
|
cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.connectionProviderClass( SneakyDataSource.class ) ) ) ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertThrows( SQLException.class, c::getWarnings );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class FakeConnection implements MockConnection {
|
||||||
|
|
||||||
|
private static final int DEFAULT_ISOLATION = 99;
|
||||||
|
|
||||||
|
private int isolation = DEFAULT_ISOLATION;
|
||||||
|
private boolean autoCommit;
|
||||||
|
private boolean warnings = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings( "MagicConstant" )
|
||||||
|
public int getTransactionIsolation() {
|
||||||
|
return isolation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTransactionIsolation(int level) {
|
||||||
|
isolation = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getAutoCommit() {
|
||||||
|
return autoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutoCommit(boolean autoCommit) {
|
||||||
|
this.autoCommit = autoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
return warnings ? new SQLWarning( "SQL Warning" ) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
warnings = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SneakyDataSource implements MockDataSource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return new SneakyConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SneakyConnection implements MockConnection {
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
// getWarnings method is called on connection return. Need to make sure the pool is usable in that scenario.
|
||||||
|
throw new SQLException("This one is sneaky!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockDriver;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class DriverTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( DriverTests.class.getName() );
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Driver does not accept the provided URL" )
|
||||||
|
@SuppressWarnings( "JDBCResourceOpenedButNotSafelyClosed" )
|
||||||
|
void basicUnacceptableURL() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configuration = new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration(
|
||||||
|
cp -> cp.maxSize( 1 ).connectionFactoryConfiguration(
|
||||||
|
cf -> cf.connectionProviderClass( UnacceptableURLDriver.class ).jdbcUrl( "jdbc:unacceptableURL" )
|
||||||
|
) );
|
||||||
|
|
||||||
|
DriverAgroalDataSourceListener listener = new DriverAgroalDataSourceListener();
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configuration, listener ) ) {
|
||||||
|
dataSource.getConnection();
|
||||||
|
fail( "Should thrown SQLException" );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
logger.info( "Expected SQLException: " + e.getMessage() );
|
||||||
|
}
|
||||||
|
assertTrue( listener.hasWarning(), "An warning message should be issued" );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class DriverAgroalDataSourceListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private boolean warning;
|
||||||
|
|
||||||
|
DriverAgroalDataSourceListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
logger.info( "EXPECTED WARNING: " + message );
|
||||||
|
warning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
logger.info( "EXPECTED WARNING: " + throwable.getMessage() );
|
||||||
|
warning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasWarning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UnacceptableURLDriver implements MockDriver {
|
||||||
|
@Override
|
||||||
|
public boolean acceptsURL(String url) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
481
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/FlushTests.java
Normal file
481
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/FlushTests.java
Normal file
|
@ -0,0 +1,481 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.AgroalConnectionPoolConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.lang.Integer.max;
|
||||||
|
import static java.lang.Integer.min;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class FlushTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( FlushTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "FlushMode.ALL" )
|
||||||
|
void modeAll() throws SQLException {
|
||||||
|
int MIN_POOL_SIZE = 40, MAX_POOL_SIZE = 100, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
);
|
||||||
|
|
||||||
|
FlushListener listener = new FlushListener( new CountDownLatch( MAX_POOL_SIZE ), new CountDownLatch( MAX_POOL_SIZE ), new CountDownLatch( MAX_POOL_SIZE ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting fill of all the {0} initial connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
listener.resetCreationLatch( MIN_POOL_SIZE );
|
||||||
|
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertFalse( connection.isClosed() );
|
||||||
|
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.ALL );
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting flush of all the {0} connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !listener.getFlushLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not flush", listener.getFlushLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Waiting for destruction of {0} connections ", MAX_POOL_SIZE - MIN_POOL_SIZE ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} flushed connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Awaiting fill of all the {0} min connections on the pool", MIN_POOL_SIZE ) );
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertTrue( connection.isClosed(), "Expecting connection closed after forced flush" );
|
||||||
|
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getFlushCount().longValue(), "Unexpected number of beforeFlushConnection" );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getDestroyCount().longValue(), "Unexpected number of destroyed connections" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + MIN_POOL_SIZE, listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
} );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "FlushMode.GRACEFUL" )
|
||||||
|
void modeGraceful() throws SQLException {
|
||||||
|
int MIN_POOL_SIZE = 10, MAX_POOL_SIZE = 30, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
);
|
||||||
|
|
||||||
|
FlushListener listener = new FlushListener( new CountDownLatch( MAX_POOL_SIZE ), new CountDownLatch( MAX_POOL_SIZE - 1 ), new CountDownLatch( MAX_POOL_SIZE - 1 ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting fill of all the {0} initial connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.resetCreationLatch( MIN_POOL_SIZE - 1 );
|
||||||
|
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertFalse( connection.isClosed() );
|
||||||
|
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.GRACEFUL );
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting flush of the {0} connections on the pool", MAX_POOL_SIZE - 1 ) );
|
||||||
|
if ( !listener.getFlushLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not flush", listener.getFlushLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Waiting for destruction of {0} connections ", MAX_POOL_SIZE - 1 ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} flushed connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created after flush", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.resetCreationLatch( 1 );
|
||||||
|
listener.resetFlushLatch( 1 );
|
||||||
|
listener.resetDestroyLatch( 1 );
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getFlushCount().longValue(), "Unexpected number of beforeFlushConnection" );
|
||||||
|
assertEquals( MAX_POOL_SIZE - 1, listener.getDestroyCount().longValue(), "Unexpected number of destroy connections" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + MIN_POOL_SIZE - 1, listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
|
||||||
|
assertEquals( MIN_POOL_SIZE - 1, dataSource.getMetrics().availableCount(), "Pool not fill to min" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().activeCount(), "Incorrect active count" );
|
||||||
|
|
||||||
|
assertFalse( connection.isClosed(), "Expecting connection open after graceful flush" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting flush of one remaining connection" ) );
|
||||||
|
if ( !listener.getFlushLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not flush", listener.getFlushLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Waiting for destruction of one remaining connection" ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} flushed connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Awaiting creation of one additional connections" ) );
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getDestroyCount().longValue(), "Unexpected number of destroy connections" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + MIN_POOL_SIZE, listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Incorrect active count" );
|
||||||
|
assertEquals( MIN_POOL_SIZE, dataSource.getMetrics().availableCount(), "Pool not fill to min" );
|
||||||
|
} );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "FlushMode.INVALID" )
|
||||||
|
void modeValid() throws SQLException {
|
||||||
|
int MIN_POOL_SIZE = 10, MAX_POOL_SIZE = 30, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.connectionValidator( AgroalConnectionPoolConfiguration.ConnectionValidator.defaultValidator() )
|
||||||
|
);
|
||||||
|
|
||||||
|
FlushListener listener = new FlushListener( new CountDownLatch( MAX_POOL_SIZE ), new CountDownLatch( MAX_POOL_SIZE ), new CountDownLatch( MAX_POOL_SIZE ) );
|
||||||
|
listener.resetValidationLatch( MAX_POOL_SIZE );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting fill of all the {0} initial connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.resetCreationLatch( MIN_POOL_SIZE );
|
||||||
|
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.INVALID );
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting for validation of all the {0} connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !listener.getValidationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Missed validation of {0} connections", listener.getValidationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
logger.info( format( "Waiting for destruction of {0} connections ", MAX_POOL_SIZE - 1 ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} invalid connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created after flush", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getFlushCount().longValue(), "Unexpected number of beforeFlushConnection" );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getDestroyCount().longValue(), "Unexpected number of destroy connections" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + MIN_POOL_SIZE, listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
} );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "FlushMode.IDLE" )
|
||||||
|
@SuppressWarnings( "ConstantConditions" )
|
||||||
|
void modeIdle() throws SQLException {
|
||||||
|
int MIN_POOL_SIZE = 25, MAX_POOL_SIZE = 50, CALLS = 30, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.connectionValidator( AgroalConnectionPoolConfiguration.ConnectionValidator.defaultValidator() )
|
||||||
|
);
|
||||||
|
|
||||||
|
FlushListener listener = new FlushListener(
|
||||||
|
new CountDownLatch( MAX_POOL_SIZE ),
|
||||||
|
new CountDownLatch( MAX_POOL_SIZE ),
|
||||||
|
new CountDownLatch( MAX_POOL_SIZE - max( MIN_POOL_SIZE, CALLS ) ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
Collection<Connection> connections = new ArrayList<>();
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
connections.add( dataSource.getConnection() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush to CALLS
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.IDLE );
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for destruction of {0} connections ", MAX_POOL_SIZE - max( MIN_POOL_SIZE, CALLS ) ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} invalid connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( MAX_POOL_SIZE - max( MIN_POOL_SIZE, CALLS ), listener.getDestroyCount().longValue(), "Unexpected number of destroy connections" );
|
||||||
|
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getFlushCount().longValue(), "Unexpected number of beforeFlushConnection" );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
} );
|
||||||
|
|
||||||
|
for ( Connection connection : connections ) {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int remaining = max( MIN_POOL_SIZE, CALLS ) - min( MIN_POOL_SIZE, CALLS );
|
||||||
|
listener.resetDestroyLatch( remaining );
|
||||||
|
|
||||||
|
// Flush to MIN_SIZE
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.IDLE );
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for destruction of {0} remaining connection", remaining ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} flushed connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( MAX_POOL_SIZE - MIN_POOL_SIZE, listener.getDestroyCount().longValue(), "Unexpected number of destroy connections" );
|
||||||
|
|
||||||
|
assertEquals( MAX_POOL_SIZE + max( MIN_POOL_SIZE, CALLS ), listener.getFlushCount().longValue(), "Unexpected number of beforeFlushConnection" );
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
} );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "FlushMode.LEAK" )
|
||||||
|
@SuppressWarnings( "ConstantConditions" )
|
||||||
|
void modeLeak() throws SQLException {
|
||||||
|
int MIN_POOL_SIZE = 25, MAX_POOL_SIZE = 50, CALLS = 30, LEAK_MS = 100, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.leakTimeout( ofMillis( LEAK_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
FlushListener listener = new FlushListener(
|
||||||
|
new CountDownLatch( MIN_POOL_SIZE + CALLS ),
|
||||||
|
new CountDownLatch( MAX_POOL_SIZE ),
|
||||||
|
new CountDownLatch( min( MAX_POOL_SIZE, CALLS ) ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
Collection<Connection> connections = new ArrayList<>();
|
||||||
|
for ( int i = 0; i < CALLS; i++ ) {
|
||||||
|
connections.add( dataSource.getConnection() );
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep( LEAK_MS << 1 ); // 2 * LEAK_MS
|
||||||
|
|
||||||
|
// Flush to CALLS
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.LEAK );
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for destruction of {0} connections ", MAX_POOL_SIZE ) );
|
||||||
|
if ( !listener.getDestroyLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} leak connections not sent for destruction", listener.getDestroyLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
if ( !listener.getCreationLatch().await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created after flush", listener.getCreationLatch().getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAll( () -> {
|
||||||
|
assertEquals( CALLS, listener.getDestroyCount().longValue(), "Unexpected number of destroy connections" );
|
||||||
|
|
||||||
|
assertEquals( MAX_POOL_SIZE, listener.getFlushCount().longValue(), "Unexpected number of beforeFlushConnection" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + ( CALLS - MIN_POOL_SIZE ), listener.getCreationCount().longValue(), "Unexpected number of created connections" );
|
||||||
|
|
||||||
|
assertTrue( connections.stream().allMatch( c -> {
|
||||||
|
try {
|
||||||
|
return c.isClosed();
|
||||||
|
} catch ( SQLException ignore ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} ) );
|
||||||
|
} );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class FlushListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final LongAdder creationCount = new LongAdder(), flushCount = new LongAdder(), destroyCount = new LongAdder();
|
||||||
|
private CountDownLatch creationLatch, validationLatch, flushLatch, destroyLatch;
|
||||||
|
|
||||||
|
FlushListener(CountDownLatch creationLatch, CountDownLatch flushLatch, CountDownLatch destroyLatch) {
|
||||||
|
this.creationLatch = creationLatch;
|
||||||
|
this.flushLatch = flushLatch;
|
||||||
|
this.destroyLatch = destroyLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionCreation() {
|
||||||
|
creationCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
creationLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionValidation(Connection connection) {
|
||||||
|
validationLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionFlush(Connection connection) {
|
||||||
|
flushCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionFlush(Connection connection) {
|
||||||
|
flushLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionDestroy(Connection connection) {
|
||||||
|
destroyCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
destroyLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
void resetCreationLatch(int count) {
|
||||||
|
creationLatch = new CountDownLatch( count );
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetValidationLatch(int count) {
|
||||||
|
validationLatch = new CountDownLatch( count );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "SameParameterValue" )
|
||||||
|
void resetFlushLatch(int count) {
|
||||||
|
flushLatch = new CountDownLatch( count );
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetDestroyLatch(int count) {
|
||||||
|
destroyLatch = new CountDownLatch( count );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
LongAdder getCreationCount() {
|
||||||
|
return creationCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongAdder getFlushCount() {
|
||||||
|
return flushCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongAdder getDestroyCount() {
|
||||||
|
return destroyCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch getCreationLatch() {
|
||||||
|
return creationLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch getValidationLatch() {
|
||||||
|
return validationLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch getFlushLatch() {
|
||||||
|
return flushLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch getDestroyLatch() {
|
||||||
|
return destroyLatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class FakeConnection implements MockConnection {
|
||||||
|
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
232
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/HealthTests.java
Normal file
232
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/HealthTests.java
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.AgroalDataSourceConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockDataSource;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class HealthTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( HealthTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Perform health checks" )
|
||||||
|
void healthChecksTest() throws SQLException, InterruptedException {
|
||||||
|
ValidationCountListener listener = new ValidationCountListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.acquisitionTimeout( ofMillis( 100 ) )
|
||||||
|
), listener ) ) {
|
||||||
|
|
||||||
|
assertEquals( 0, dataSource.getMetrics().creationCount(), "Expected empty pool" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on empty pool" );
|
||||||
|
assertTrue( dataSource.isHealthy( false ) );
|
||||||
|
|
||||||
|
assertEquals( 1, dataSource.getMetrics().creationCount(), "Expected connection in pool" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount(), "Expected connection in pool" );
|
||||||
|
assertEquals( 1, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
assertEquals( 1, listener.validCount(), "Expected validation to have been successful" );
|
||||||
|
assertEquals( 0, listener.invalidCount(), "Expected validation to have been successful" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on non-empty pool" );
|
||||||
|
assertTrue( dataSource.isHealthy( false ) );
|
||||||
|
|
||||||
|
assertEquals( 1, dataSource.getMetrics().creationCount(), "Expected connection to be re-used" );
|
||||||
|
assertEquals( 2, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on non-empty pool, on a new connection" );
|
||||||
|
assertTrue( dataSource.isHealthy( true ) );
|
||||||
|
|
||||||
|
assertEquals( 2, dataSource.getMetrics().creationCount(), "Expected connection to be created" );
|
||||||
|
assertEquals( 2, dataSource.getMetrics().availableCount(), "Expected extra connection in pool" );
|
||||||
|
assertEquals( 3, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
|
||||||
|
dataSource.flush( AgroalDataSource.FlushMode.ALL );
|
||||||
|
Thread.sleep( 100 );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().availableCount(), "Expected empty pool" );
|
||||||
|
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 3, dataSource.getMetrics().creationCount(), "Expected connection to be created" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on exhausted pool" );
|
||||||
|
assertThrows( SQLException.class, () -> dataSource.isHealthy( false ), "Expected acquisition timeout" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on exhausted pool, on a new connection" );
|
||||||
|
assertTrue( dataSource.isHealthy( true ) );
|
||||||
|
|
||||||
|
assertEquals( 4, dataSource.getMetrics().creationCount(), "Expected connection to be re-used" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount(), "Expected one connection in pool" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().activeCount(), "Expect single connection in use" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().acquireCount(), "Expect acquisition metric to report a single acquisition" );
|
||||||
|
assertEquals( 4, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
|
||||||
|
// use the connection
|
||||||
|
c.getSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount(), "Expected connection flush on close" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Perform health checks on poolless data source" )
|
||||||
|
void polllessTest() throws SQLException, InterruptedException {
|
||||||
|
ValidationCountListener listener = new ValidationCountListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL_POOLLESS )
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionValidator( c -> false ) // connections are always invalid
|
||||||
|
.acquisitionTimeout( ofMillis( 100 ) )
|
||||||
|
), listener ) ) {
|
||||||
|
|
||||||
|
assertEquals( 0, dataSource.getMetrics().creationCount(), "Expected empty pool" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on pool(less)" );
|
||||||
|
assertFalse( dataSource.isHealthy( false ) );
|
||||||
|
|
||||||
|
assertEquals( 1, dataSource.getMetrics().creationCount(), "Expected connection in pool" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Expected no active connection" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount(), "Expected no connection in pool" );
|
||||||
|
assertEquals( 1, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
assertEquals( 0, listener.validCount(), "Expected validation to have been successful" );
|
||||||
|
assertEquals( 1, listener.invalidCount(), "Expected validation to have been successful" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on pool(less), on a new connection" );
|
||||||
|
assertFalse( dataSource.isHealthy( true ) );
|
||||||
|
|
||||||
|
assertEquals( 2, dataSource.getMetrics().creationCount(), "Expected connection to be created" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Expected no active connection" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount(), "Expected no connection in pool" );
|
||||||
|
assertEquals( 2, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 3, dataSource.getMetrics().creationCount(), "Expected connection to be created" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on exhausted pool(less)" );
|
||||||
|
assertThrows( SQLException.class, () -> dataSource.isHealthy( false ), "Expected acquisition timeout" );
|
||||||
|
|
||||||
|
logger.info( "Performing health check on exhausted pool(less), on a new connection" );
|
||||||
|
assertFalse( dataSource.isHealthy( true ) );
|
||||||
|
|
||||||
|
assertEquals( 4, dataSource.getMetrics().creationCount(), "Expected connection to be re-used" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().availableCount(), "Expected no connection in pool" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().activeCount(), "Expect single connection in use" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().acquireCount(), "Expect acquisition metric to report a single acquisition" );
|
||||||
|
assertEquals( 3, listener.beforeCount(), "Expected validation to have been performed" );
|
||||||
|
|
||||||
|
// use the connection
|
||||||
|
c.getSchema();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Perform health checks when connection provider throws" )
|
||||||
|
void bogusFactoryTest() throws SQLException, InterruptedException {
|
||||||
|
ValidationCountListener listener = new ValidationCountListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf.connectionProviderClass( UnhealthyDataSource.class ) )
|
||||||
|
), listener ) ) {
|
||||||
|
|
||||||
|
assertThrows( SQLException.class, () -> dataSource.isHealthy( true ), "Expected exception from connection provider" );
|
||||||
|
|
||||||
|
assertEquals( 0, dataSource.getMetrics().creationCount(), "Expected no connection to be created" );
|
||||||
|
assertEquals( 0, listener.beforeCount(), "Expected no validation to have been performed" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class ValidationCountListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final AtomicInteger before = new AtomicInteger(), valid = new AtomicInteger(), invalid = new AtomicInteger();
|
||||||
|
|
||||||
|
ValidationCountListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionValidation(Connection connection) {
|
||||||
|
before.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionValid(Connection connection) {
|
||||||
|
valid.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionInvalid(Connection connection) {
|
||||||
|
invalid.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int beforeCount() {
|
||||||
|
return before.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int validCount() {
|
||||||
|
return valid.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int invalidCount() {
|
||||||
|
return invalid.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class UnhealthyDataSource implements MockDataSource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
throw new SQLException( "Unobtainable" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,275 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.AgroalPoolInterceptor;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.api.configuration.AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL_POOLLESS;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.List.of;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class InterceptorTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( InterceptorTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeSchemaConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
protected static void setSchema(String value, Connection connection) {
|
||||||
|
try {
|
||||||
|
connection.setSchema( value );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void assertSchema(String expected, Connection connection) {
|
||||||
|
try {
|
||||||
|
assertEquals( expected, connection.getSchema() );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Interceptor basic test" )
|
||||||
|
void basicInterceptorTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, new InterceptorListener() ) ) {
|
||||||
|
dataSource.setPoolInterceptors( asList( new LowPriorityInterceptor(), new MainInterceptor() ) );
|
||||||
|
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertSchema( "during", c );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Negative priority test" )
|
||||||
|
void negativePriorityTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, new InterceptorListener() ) ) {
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> dataSource.setPoolInterceptors( asList( new LowPriorityInterceptor(), new MainInterceptor(), new NegativePriorityInterceptor() ) ),
|
||||||
|
"Interceptors with negative priority throw an IllegalArgumentException as negative priority values are reserved." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Pool interceptor test" )
|
||||||
|
void poolInterceptorTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 ) );
|
||||||
|
|
||||||
|
InvocationCountInterceptor countInterceptor = new InvocationCountInterceptor();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
dataSource.setPoolInterceptors( of( countInterceptor ) );
|
||||||
|
|
||||||
|
assertEquals( 0, countInterceptor.created, "Expected connection not created" );
|
||||||
|
assertEquals( 0, countInterceptor.acquired, "Expected connection not acquired" );
|
||||||
|
|
||||||
|
try ( Connection ignored = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 1, countInterceptor.created, "Expected one connection created" );
|
||||||
|
assertEquals( 1, countInterceptor.acquired, "Expected one connection acquired" );
|
||||||
|
}
|
||||||
|
assertEquals( 1, countInterceptor.returned, "Expected one connection returned" );
|
||||||
|
|
||||||
|
try ( Connection ignored = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 1, countInterceptor.created, "Expected one connection created" );
|
||||||
|
assertEquals( 2, countInterceptor.acquired, "Expected two connection acquired" );
|
||||||
|
}
|
||||||
|
assertEquals( 2, countInterceptor.returned, "Expected two connection returned" );
|
||||||
|
}
|
||||||
|
assertEquals( 1, countInterceptor.destroy, "Expected one connection destroyed" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Pooless interceptor test" )
|
||||||
|
void poolessInterceptorTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( AGROAL_POOLLESS )
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 ) );
|
||||||
|
|
||||||
|
InvocationCountInterceptor countInterceptor = new InvocationCountInterceptor();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
dataSource.setPoolInterceptors( of( countInterceptor ) );
|
||||||
|
|
||||||
|
assertEquals( 0, countInterceptor.created, "Expected connection not created" );
|
||||||
|
assertEquals( 0, countInterceptor.acquired, "Expected connection not acquired" );
|
||||||
|
|
||||||
|
try ( Connection ignored = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 1, countInterceptor.created, "Expected one connection created" );
|
||||||
|
assertEquals( 1, countInterceptor.acquired, "Expected one connection acquired" );
|
||||||
|
}
|
||||||
|
assertEquals( 1, countInterceptor.returned, "Expected one connection returned" );
|
||||||
|
assertEquals( 1, countInterceptor.destroy, "Expected one connection destroyed" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class InterceptorListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
InterceptorListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
assertSchema( "before", connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionDestroy(Connection connection) {
|
||||||
|
assertSchema( "after", connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInfo(String message) {
|
||||||
|
logger.info( message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MainInterceptor implements AgroalPoolInterceptor {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
MainInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionAcquire(Connection connection) {
|
||||||
|
setSchema( "during", connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionReturn(Connection connection) {
|
||||||
|
setSchema( "after", connection );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This interceptor should be "inner" of the main one because has lower priority.
|
||||||
|
private static class LowPriorityInterceptor implements AgroalPoolInterceptor {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
LowPriorityInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionAcquire(Connection connection) {
|
||||||
|
assertSchema( "during", connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionReturn(Connection connection) {
|
||||||
|
assertSchema( "during", connection );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This interceptor is invalid because priority is negative.
|
||||||
|
private static class NegativePriorityInterceptor implements AgroalPoolInterceptor {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
NegativePriorityInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class InvocationCountInterceptor implements AgroalPoolInterceptor {
|
||||||
|
|
||||||
|
private int created, acquired, returned, destroy;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionCreate(Connection connection) {
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionAcquire(Connection connection) {
|
||||||
|
acquired++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionReturn(Connection connection) {
|
||||||
|
returned++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
destroy++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
|
||||||
|
public static class FakeSchemaConnection implements MockConnection {
|
||||||
|
|
||||||
|
private String schema = "before";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSchema(String schema) {
|
||||||
|
this.schema = schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (C) 2023 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import org.junit.platform.launcher.LauncherInterceptor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.lang.Thread.currentThread;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link LauncherInterceptor} that loads test classes, and classes the test relies on from designated packages, in new {@link ClassLoader}.
|
||||||
|
*
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public class IsolatingClassloaderLauncherInterceptor implements LauncherInterceptor {
|
||||||
|
|
||||||
|
private static final String TRANSACTION_TEST_PACKAGE_PREFIX = "io.agroal.test.narayana", NARAYANA_PACKAGE_PREFIX = "com.arjuna";
|
||||||
|
|
||||||
|
private final ClassLoader parentClassloader;
|
||||||
|
|
||||||
|
public IsolatingClassloaderLauncherInterceptor() {
|
||||||
|
parentClassloader = currentThread().getContextClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T intercept(Invocation<T> invocation) {
|
||||||
|
currentThread().setContextClassLoader( new IsolatingClassLoader( parentClassloader ) );
|
||||||
|
return invocation.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
currentThread().setContextClassLoader( parentClassloader );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
// This classloader intercepts calls to loadClass() and redefines the test class in a new classloader
|
||||||
|
private static class IsolatingClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( IsolatingClassLoader.class.getName() );
|
||||||
|
|
||||||
|
public IsolatingClassLoader(ClassLoader parent) {
|
||||||
|
super( parent );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings( {"StringConcatenation", "HardcodedFileSeparator"} )
|
||||||
|
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||||
|
if ( name.startsWith( TRANSACTION_TEST_PACKAGE_PREFIX ) && !name.contains( "$" ) ) {
|
||||||
|
Class<?> existingClass = findLoadedClass( name );
|
||||||
|
URL resourceURL = getResource( existingClass != null ? existingClass.getSimpleName() : name.replace( ".", "/" ) + ".class" );
|
||||||
|
if ( resourceURL != null ) {
|
||||||
|
try ( InputStream in = resourceURL.openStream() ) {
|
||||||
|
logger.info( "New NarayanaRedefiningClassloader for test class " + name );
|
||||||
|
return new NarayanaRedefiningClassloader( getParent() ).defineClass( name, in.readAllBytes() );
|
||||||
|
} catch ( IOException e ) {
|
||||||
|
throw new RuntimeException( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.loadClass( name, resolve );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This classloader re-defines the test case (including inner classes) and all the Narayana classes
|
||||||
|
// Loading of other classes is delegated to the parent classloader
|
||||||
|
private static class NarayanaRedefiningClassloader extends ClassLoader {
|
||||||
|
|
||||||
|
public NarayanaRedefiningClassloader(ClassLoader parent) {
|
||||||
|
super( parent );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings( "HardcodedFileSeparator" )
|
||||||
|
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||||
|
if ( name.startsWith( TRANSACTION_TEST_PACKAGE_PREFIX ) || name.startsWith( NARAYANA_PACKAGE_PREFIX ) ) {
|
||||||
|
Class<?> existingClass = findLoadedClass( name );
|
||||||
|
URL resourceURL = getResource( ( existingClass != null ) ? existingClass.getSimpleName() : name.replace( ".", "/" ) + ".class" );
|
||||||
|
if ( resourceURL != null ) {
|
||||||
|
try ( InputStream in = resourceURL.openStream() ) {
|
||||||
|
defineClass( name, in.readAllBytes() );
|
||||||
|
} catch ( IOException e ) {
|
||||||
|
throw new RuntimeException( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.loadClass( name, resolve );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> defineClass(String name, byte[] data) {
|
||||||
|
return defineClass( name, data, 0, data.length );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,237 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class LifetimeTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( LifetimeTests.class.getName() );
|
||||||
|
|
||||||
|
private static final String FAKE_SCHEMA = "skeema";
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeSchemaConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Lifetime Test" )
|
||||||
|
void basicLifetimeTest() throws SQLException {
|
||||||
|
int MIN_POOL_SIZE = 40, MAX_POOL_SIZE = 100, MAX_LIFETIME_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.minSize( MIN_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.maxLifetime( ofMillis( MAX_LIFETIME_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch allLatch = new CountDownLatch( MAX_POOL_SIZE );
|
||||||
|
CountDownLatch destroyLatch = new CountDownLatch( MAX_POOL_SIZE );
|
||||||
|
LongAdder flushCount = new LongAdder();
|
||||||
|
|
||||||
|
AgroalDataSourceListener listener = new MaxLifetimeListener( allLatch, flushCount, destroyLatch );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
try {
|
||||||
|
logger.info( format( "Awaiting creation of all the {0} connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
if ( !allLatch.await( MAX_LIFETIME_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created for maxLifetime", allLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( MAX_POOL_SIZE, dataSource.getMetrics().creationCount(), "Unexpected number of connections on the pool" );
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for removal of {0} connections ", MAX_POOL_SIZE ) );
|
||||||
|
if ( !destroyLatch.await( 2L * MAX_LIFETIME_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} old connections not sent for destruction", destroyLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( MAX_POOL_SIZE, flushCount.longValue(), "Unexpected number of old connections" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Connection in use reaches maxLifetime" )
|
||||||
|
void inUseLifetimeTest() throws SQLException {
|
||||||
|
int MAX_LIFETIME_MS = 200;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.maxLifetime( ofMillis( MAX_LIFETIME_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch allLatch = new CountDownLatch( 1 );
|
||||||
|
CountDownLatch destroyLatch = new CountDownLatch( 1 );
|
||||||
|
LongAdder flushCount = new LongAdder();
|
||||||
|
|
||||||
|
AgroalDataSourceListener listener = new MaxLifetimeListener( allLatch, flushCount, destroyLatch );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
connection.getSchema();
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for {0}ms (twice the maxLifetime)", 2 * MAX_LIFETIME_MS ) );
|
||||||
|
LockSupport.parkNanos( ofMillis( 2 * MAX_LIFETIME_MS ).toNanos() );
|
||||||
|
|
||||||
|
assertFalse( connection.isClosed() );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().creationCount(), "Unexpected number of connections on the pool" );
|
||||||
|
assertEquals( 0, flushCount.longValue(), "Unexpected number of flushed connections" );
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info( format( "Waiting for removal of {0} connections", 1 ) );
|
||||||
|
if ( !destroyLatch.await( MAX_LIFETIME_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} old connections not sent for destruction", destroyLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( 1, flushCount.longValue(), "Unexpected number of old connections" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Min-size after maxLifetime" )
|
||||||
|
void minSizeLifetimeTest() throws SQLException {
|
||||||
|
int MIN_SIZE = 5, MAX_LIFETIME_MS = 200;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( 2 * MIN_SIZE )
|
||||||
|
.minSize( MIN_SIZE )
|
||||||
|
.maxSize( 10 * MIN_SIZE )
|
||||||
|
.maxLifetime( ofMillis( MAX_LIFETIME_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch initialLatch = new CountDownLatch( 2 * MIN_SIZE );
|
||||||
|
CountDownLatch allLatch = new CountDownLatch( 3 * MIN_SIZE );
|
||||||
|
CountDownLatch destroyLatch = new CountDownLatch( 2 * MIN_SIZE );
|
||||||
|
|
||||||
|
MaxLifetimeListener initialListener = new MaxLifetimeListener( initialLatch, new LongAdder(), destroyLatch);
|
||||||
|
MaxLifetimeListener finalListener = new MaxLifetimeListener( allLatch, new LongAdder(), new CountDownLatch( 0 ) );
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, initialListener, finalListener ) ) {
|
||||||
|
logger.info( format( "Waiting at most {0}ms for the creating of {1} connections", MAX_LIFETIME_MS, MIN_SIZE ) );
|
||||||
|
if ( !initialLatch.await( MAX_LIFETIME_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created in time", destroyLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( 2 * MIN_SIZE, dataSource.getMetrics().creationCount(), "Unexpected number of initial connections" );
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for {0}ms (twice the maxLifetime)", 2 * MAX_LIFETIME_MS ) );
|
||||||
|
if ( !destroyLatch.await( 2 * MAX_LIFETIME_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not destroyed in time", destroyLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( 2 * MIN_SIZE, dataSource.getMetrics().destroyCount(), "Unexpected number of flush connections" );
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for {0}ms", MAX_LIFETIME_MS ) );
|
||||||
|
if ( !allLatch.await( MAX_LIFETIME_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created in time", allLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( 3 * MIN_SIZE, dataSource.getMetrics().creationCount(), "Unexpected number of created connections" );
|
||||||
|
assertEquals( MIN_SIZE, dataSource.getMetrics().availableCount(), "Min-size not maintained" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class MaxLifetimeListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final CountDownLatch allLatch;
|
||||||
|
private final LongAdder flushCount;
|
||||||
|
private final CountDownLatch destroyLatch;
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
MaxLifetimeListener(CountDownLatch allLatch, LongAdder flushCount, CountDownLatch destroyLatch) {
|
||||||
|
this.allLatch = allLatch;
|
||||||
|
this.flushCount = flushCount;
|
||||||
|
this.destroyLatch = destroyLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
allLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionFlush(Connection connection) {
|
||||||
|
flushCount.increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
destroyLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class FakeSchemaConnection implements MockConnection {
|
||||||
|
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
return FAKE_SCHEMA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
if ( closed ) {
|
||||||
|
fail( "Double close on connection" );
|
||||||
|
} else {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.Blob;
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.Clob;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.NClob;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLClientInfoException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Savepoint;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Struct;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockConnection extends Connection {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Statement createStatement() throws SQLException {
|
||||||
|
return new MockStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PreparedStatement prepareStatement(String sql) throws SQLException {
|
||||||
|
return new MockPreparedStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default CallableStatement prepareCall(String sql) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String nativeSQL(String sql) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean getAutoCommit() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setAutoCommit(boolean autoCommit) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void commit() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void rollback() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isClosed() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default DatabaseMetaData getMetaData() throws SQLException {
|
||||||
|
return new MockDatabaseMetaData.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isReadOnly() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setReadOnly(boolean readOnly) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getCatalog() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setCatalog(String catalog) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getTransactionIsolation() throws SQLException {
|
||||||
|
return TRANSACTION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setTransactionIsolation(int level) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default SQLWarning getWarnings() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void clearWarnings() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
return new MockStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
return new MockPreparedStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Map<String, Class<?>> getTypeMap() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setTypeMap(Map<String, Class<?>> map) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getHoldability() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setHoldability(int holdability) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Savepoint setSavepoint() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Savepoint setSavepoint(String name) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void rollback(Savepoint savepoint) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void releaseSavepoint(Savepoint savepoint) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
return new MockStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
return new MockPreparedStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return new MockPreparedStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return new MockPreparedStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return new MockPreparedStatement.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Clob createClob() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Blob createBlob() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default NClob createNClob() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default SQLXML createSQLXML() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isValid(int timeout) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setClientInfo(String name, String value) throws SQLClientInfoException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getClientInfo(String name) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Properties getClientInfo() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setClientInfo(Properties properties) throws SQLClientInfoException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Array createArrayOf(String typeName, Object[] elements) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Struct createStruct(String typeName, Object[] attributes) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSchema() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setSchema(String schema) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void abort(Executor executor) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getNetworkTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T unwrap(Class<T> target) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isWrapperFor(Class<?> target) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockConnection {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockConnection@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockDataSource extends DataSource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Connection getConnection() throws SQLException {
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Connection getConnection(String username, String password) throws SQLException {
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PrintWriter getLogWriter() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setLogWriter(PrintWriter out) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getLoginTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockDataSource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockDataSource@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,923 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.RowIdLifetime;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockDatabaseMetaData extends DatabaseMetaData {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean allProceduresAreCallable() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean allTablesAreSelectable() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getURL() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getUserName() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isReadOnly() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean nullsAreSortedHigh() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean nullsAreSortedLow() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean nullsAreSortedAtStart() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean nullsAreSortedAtEnd() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getDatabaseProductName() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getDatabaseProductVersion() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getDriverName() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getDriverVersion() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getDriverMajorVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getDriverMinorVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean usesLocalFiles() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean usesLocalFilePerTable() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsMixedCaseIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean storesUpperCaseIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean storesLowerCaseIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean storesMixedCaseIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getIdentifierQuoteString() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSQLKeywords() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getNumericFunctions() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getStringFunctions() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSystemFunctions() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getTimeDateFunctions() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSearchStringEscape() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getExtraNameCharacters() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsAlterTableWithAddColumn() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsAlterTableWithDropColumn() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsColumnAliasing() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean nullPlusNonNullIsNull() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsConvert() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsConvert(int fromType, int toType) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsTableCorrelationNames() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsDifferentTableCorrelationNames() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsExpressionsInOrderBy() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsOrderByUnrelated() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsGroupBy() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsGroupByUnrelated() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsGroupByBeyondSelect() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsLikeEscapeClause() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsMultipleResultSets() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsMultipleTransactions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsNonNullableColumns() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsMinimumSQLGrammar() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCoreSQLGrammar() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsExtendedSQLGrammar() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsANSI92EntryLevelSQL() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsANSI92IntermediateSQL() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsANSI92FullSQL() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsIntegrityEnhancementFacility() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsOuterJoins() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsFullOuterJoins() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsLimitedOuterJoins() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getSchemaTerm() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getProcedureTerm() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getCatalogTerm() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isCatalogAtStart() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getCatalogSeparator() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSchemasInDataManipulation() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSchemasInProcedureCalls() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSchemasInTableDefinitions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSchemasInIndexDefinitions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCatalogsInDataManipulation() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCatalogsInProcedureCalls() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCatalogsInTableDefinitions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCatalogsInIndexDefinitions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsPositionedDelete() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsPositionedUpdate() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSelectForUpdate() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsStoredProcedures() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSubqueriesInComparisons() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSubqueriesInExists() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSubqueriesInIns() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSubqueriesInQuantifieds() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsCorrelatedSubqueries() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsUnion() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsUnionAll() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsOpenCursorsAcrossCommit() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsOpenCursorsAcrossRollback() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsOpenStatementsAcrossCommit() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsOpenStatementsAcrossRollback() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxBinaryLiteralLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxCharLiteralLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxColumnNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxColumnsInGroupBy() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxColumnsInIndex() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxColumnsInOrderBy() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxColumnsInSelect() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxColumnsInTable() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxConnections() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxCursorNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxIndexLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxSchemaNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxProcedureNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxCatalogNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxRowSize() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxStatementLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxStatements() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxTableNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxTablesInSelect() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxUserNameLength() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getDefaultTransactionIsolation() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsTransactions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsTransactionIsolationLevel(int level) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsDataManipulationTransactionsOnly() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean dataDefinitionCausesTransactionCommit() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean dataDefinitionIgnoredInTransactions() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getSchemas() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getCatalogs() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getTableTypes() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getTypeInfo() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsResultSetType(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean ownUpdatesAreVisible(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean ownDeletesAreVisible(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean ownInsertsAreVisible(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean othersUpdatesAreVisible(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean othersDeletesAreVisible(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean othersInsertsAreVisible(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean updatesAreDetected(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean deletesAreDetected(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean insertsAreDetected(int type) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsBatchUpdates() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Connection getConnection() throws SQLException {
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSavepoints() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsNamedParameters() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsMultipleOpenResults() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsGetGeneratedKeys() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsResultSetHoldability(int holdability) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getResultSetHoldability() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getDatabaseMajorVersion() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getDatabaseMinorVersion() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getJDBCMajorVersion() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getJDBCMinorVersion() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getSQLStateType() throws SQLException {
|
||||||
|
return sqlStateSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean locatorsUpdateCopy() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsStatementPooling() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default RowIdLifetime getRowIdLifetime() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean autoCommitFailureClosesAllResultSets() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getClientInfoProperties() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean generatedKeyAlwaysReturned() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long getMaxLogicalLobSize() throws SQLException {
|
||||||
|
return DatabaseMetaData.super.getMaxLogicalLobSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsRefCursors() throws SQLException {
|
||||||
|
return DatabaseMetaData.super.supportsRefCursors();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean supportsSharding() throws SQLException {
|
||||||
|
return DatabaseMetaData.super.supportsSharding();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockDatabaseMetaData {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockDatabaseMetaDatat@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/MockDriver.java
Normal file
104
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/MockDriver.java
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Driver;
|
||||||
|
import java.sql.DriverPropertyInfo;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
import static java.sql.DriverManager.deregisterDriver;
|
||||||
|
import static java.sql.DriverManager.getDriver;
|
||||||
|
import static java.sql.DriverManager.registerDriver;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
public interface MockDriver extends Driver {
|
||||||
|
|
||||||
|
DriverPropertyInfo[] EMPTY_PROPERTY_INFO = new DriverPropertyInfo[0];
|
||||||
|
|
||||||
|
static void registerMockDriver(Class<? extends Connection> connectionType) {
|
||||||
|
try {
|
||||||
|
registerDriver( new Empty( connectionType ) );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
getLogger( MockDriver.class.getName() ).log( WARNING, "Unable to register MockDriver into Driver Manager", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerMockDriver() {
|
||||||
|
registerMockDriver( MockConnection.Empty.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deregisterMockDriver() {
|
||||||
|
try {
|
||||||
|
deregisterDriver( getDriver( "" ) );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
getLogger( MockDriver.class.getName() ).log( WARNING, "Unable to deregister MockDriver from Driver Manager", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Connection connect(String url, Properties info) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean acceptsURL(String url) throws SQLException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
|
||||||
|
return EMPTY_PROPERTY_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMajorVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMinorVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean jdbcCompliant() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Empty implements MockDriver {
|
||||||
|
|
||||||
|
Class<? extends Connection> connectionType;
|
||||||
|
|
||||||
|
public Empty() {
|
||||||
|
this( MockConnection.Empty.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Empty( Class<? extends Connection> connectionType ) {
|
||||||
|
this.connectionType = connectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection connect( String url, Properties info ) throws SQLException {
|
||||||
|
try {
|
||||||
|
return connectionType.getDeclaredConstructor().newInstance();
|
||||||
|
} catch ( InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) {
|
||||||
|
throw new SQLException( "Cannot create mock connection", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockDriver@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,284 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.Blob;
|
||||||
|
import java.sql.Clob;
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.sql.NClob;
|
||||||
|
import java.sql.ParameterMetaData;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.Ref;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.RowId;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLType;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockPreparedStatement extends MockStatement, PreparedStatement {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet executeQuery() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int executeUpdate() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNull(int parameterIndex, int sqlType) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBoolean(int parameterIndex, boolean x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setByte(int parameterIndex, byte x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setShort(int parameterIndex, short x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setInt(int parameterIndex, int x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setLong(int parameterIndex, long x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setFloat(int parameterIndex, float x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setDouble(int parameterIndex, double x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setString(int parameterIndex, String x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBytes(int parameterIndex, byte[] x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setDate(int parameterIndex, Date x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setTime(int parameterIndex, Time x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void clearParameters() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setObject(int parameterIndex, Object x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean execute() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void addBatch() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setRef(int parameterIndex, Ref x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBlob(int parameterIndex, Blob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setClob(int parameterIndex, Clob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setArray(int parameterIndex, Array x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSetMetaData getMetaData() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setURL(int parameterIndex, URL x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ParameterMetaData getParameterMetaData() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setRowId(int parameterIndex, RowId x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNString(int parameterIndex, String value) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNClob(int parameterIndex, NClob value) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setNClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException {
|
||||||
|
PreparedStatement.super.setObject( parameterIndex, x, targetSqlType, scaleOrLength );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException {
|
||||||
|
PreparedStatement.super.setObject( parameterIndex, x, targetSqlType );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long executeLargeUpdate() throws SQLException {
|
||||||
|
return PreparedStatement.super.executeLargeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockPreparedStatement {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockPreparedStatement@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,919 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.Blob;
|
||||||
|
import java.sql.Clob;
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.sql.NClob;
|
||||||
|
import java.sql.Ref;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.RowId;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "InterfaceWithOnlyOneDirectInheritor" )
|
||||||
|
public interface MockResultSet extends ResultSet {
|
||||||
|
|
||||||
|
byte[] BYTES = new byte[0];
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean next() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean wasNull() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getString(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean getBoolean(int columnIndex) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default byte getByte(int columnIndex) throws SQLException {
|
||||||
|
return (byte) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default short getShort(int columnIndex) throws SQLException {
|
||||||
|
return (short) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getInt(int columnIndex) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long getLong(int columnIndex) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default float getFloat(int columnIndex) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default double getDouble(int columnIndex) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
default BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default byte[] getBytes(int columnIndex) throws SQLException {
|
||||||
|
return BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Date getDate(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Time getTime(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Timestamp getTimestamp(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default InputStream getAsciiStream(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
default InputStream getUnicodeStream(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default InputStream getBinaryStream(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getString(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean getBoolean(String columnLabel) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default byte getByte(String columnLabel) throws SQLException {
|
||||||
|
return (byte) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default short getShort(String columnLabel) throws SQLException {
|
||||||
|
return (short) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getInt(String columnLabel) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long getLong(String columnLabel) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default float getFloat(String columnLabel) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default double getDouble(String columnLabel) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
default BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default byte[] getBytes(String columnLabel) throws SQLException {
|
||||||
|
return BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Date getDate(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Time getTime(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Timestamp getTimestamp(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default InputStream getAsciiStream(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
default InputStream getUnicodeStream(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default InputStream getBinaryStream(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default SQLWarning getWarnings() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void clearWarnings() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getCursorName() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSetMetaData getMetaData() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Object getObject(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Object getObject(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int findColumn(String columnLabel) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Reader getCharacterStream(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Reader getCharacterStream(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default BigDecimal getBigDecimal(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default BigDecimal getBigDecimal(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isBeforeFirst() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isAfterLast() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isFirst() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isLast() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void beforeFirst() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void afterLast() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean first() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean last() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getRow() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean absolute(int row) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean relative(int rows) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean previous() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getFetchDirection() throws SQLException {
|
||||||
|
return FETCH_FORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setFetchDirection(int direction) throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getFetchSize() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setFetchSize(int rows) throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getType() throws SQLException {
|
||||||
|
return TYPE_FORWARD_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getConcurrency() throws SQLException {
|
||||||
|
return CONCUR_READ_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean rowUpdated() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean rowInserted() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean rowDeleted() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNull(int columnIndex) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBoolean(int columnIndex, boolean x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateByte(int columnIndex, byte x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateShort(int columnIndex, short x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateInt(int columnIndex, int x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateLong(int columnIndex, long x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateFloat(int columnIndex, float x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateDouble(int columnIndex, double x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateString(int columnIndex, String x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBytes(int columnIndex, byte[] x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateDate(int columnIndex, Date x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateTime(int columnIndex, Time x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateObject(int columnIndex, Object x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNull(String columnLabel) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBoolean(String columnLabel, boolean x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateByte(String columnLabel, byte x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateShort(String columnLabel, short x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateInt(String columnLabel, int x) throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateLong(String columnLabel, long x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateFloat(String columnLabel, float x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateDouble(String columnLabel, double x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateString(String columnLabel, String x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBytes(String columnLabel, byte[] x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateDate(String columnLabel, Date x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateTime(String columnLabel, Time x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateObject(String columnLabel, Object x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void insertRow() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateRow() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void deleteRow() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void refreshRow() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void cancelRowUpdates() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void moveToInsertRow() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void moveToCurrentRow() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Statement getStatement() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Ref getRef(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Blob getBlob(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Clob getClob(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Array getArray(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Ref getRef(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Blob getBlob(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Clob getClob(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Array getArray(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Date getDate(int columnIndex, Calendar cal) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Date getDate(String columnLabel, Calendar cal) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Time getTime(int columnIndex, Calendar cal) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Time getTime(String columnLabel, Calendar cal) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default URL getURL(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default URL getURL(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateRef(int columnIndex, Ref x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateRef(String columnLabel, Ref x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBlob(int columnIndex, Blob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBlob(String columnLabel, Blob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateClob(int columnIndex, Clob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateClob(String columnLabel, Clob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateArray(int columnIndex, Array x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateArray(String columnLabel, Array x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default RowId getRowId(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default RowId getRowId(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateRowId(int columnIndex, RowId x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateRowId(String columnLabel, RowId x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getHoldability() throws SQLException {
|
||||||
|
return CLOSE_CURSORS_AT_COMMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isClosed() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNString(int columnIndex, String nString) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNString(String columnLabel, String nString) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNClob(int columnIndex, NClob nClob) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNClob(String columnLabel, NClob nClob) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default NClob getNClob(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default NClob getNClob(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default SQLXML getSQLXML(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default SQLXML getSQLXML(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getNString(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String getNString(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Reader getNCharacterStream(int columnIndex) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Reader getNCharacterStream(String columnLabel) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateClob(int columnIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateClob(String columnLabel, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNClob(int columnIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void updateNClob(String columnLabel, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockResultSet {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockResultSet@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockStatement extends Statement {
|
||||||
|
|
||||||
|
long[] LARGE_BATCH = new long[0];
|
||||||
|
int[] BATCH = new int[0];
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long getLargeUpdateCount() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long getLargeMaxRows() throws SQLException {
|
||||||
|
return Statement.super.getLargeMaxRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setLargeMaxRows(long max) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long[] executeLargeBatch() throws SQLException {
|
||||||
|
return LARGE_BATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long executeLargeUpdate(String sql) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet executeQuery(String sql) throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int executeUpdate(String sql) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxFieldSize() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setMaxFieldSize(int max) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getMaxRows() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setMaxRows(int max) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setEscapeProcessing(boolean enable) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getQueryTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setQueryTimeout(int seconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void cancel() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default SQLWarning getWarnings() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void clearWarnings() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setCursorName(String name) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean execute(String sql) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getResultSet() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getUpdateCount() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean getMoreResults() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getFetchDirection() throws SQLException {
|
||||||
|
return ResultSet.FETCH_FORWARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setFetchDirection(int direction) throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getFetchSize() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setFetchSize(int rows) throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getResultSetConcurrency() throws SQLException {
|
||||||
|
return ResultSet.CONCUR_READ_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getResultSetType() throws SQLException {
|
||||||
|
return ResultSet.TYPE_FORWARD_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void addBatch(String sql) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void clearBatch() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int[] executeBatch() throws SQLException {
|
||||||
|
return BATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Connection getConnection() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean getMoreResults(int current) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default ResultSet getGeneratedKeys() throws SQLException {
|
||||||
|
return new MockResultSet.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int executeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean execute(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean execute(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getResultSetHoldability() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isClosed() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isPoolable() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setPoolable(boolean poolable) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void closeOnCompletion() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isCloseOnCompletion() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockStatement {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockStatement@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import javax.sql.ConnectionEventListener;
|
||||||
|
import javax.sql.StatementEventListener;
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockXAConnection extends XAConnection {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default XAResource getXAResource() throws SQLException {
|
||||||
|
return new MockXAResource.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Connection getConnection() throws SQLException {
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void addConnectionEventListener(ConnectionEventListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void removeConnectionEventListener(ConnectionEventListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void addStatementEventListener(StatementEventListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void removeStatementEventListener(StatementEventListener listener) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockXAConnection {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockXAConnection@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import javax.sql.XADataSource;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public interface MockXADataSource extends MockDataSource, XADataSource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default XAConnection getXAConnection() throws SQLException {
|
||||||
|
return new MockXAConnection.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default XAConnection getXAConnection(String user, String password) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default PrintWriter getLogWriter() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setLogWriter(PrintWriter out) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getLoginTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockXADataSource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockXADataSource@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import javax.transaction.xa.XAException;
|
||||||
|
import javax.transaction.xa.XAResource;
|
||||||
|
import javax.transaction.xa.Xid;
|
||||||
|
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "InterfaceWithOnlyOneDirectInheritor" )
|
||||||
|
public interface MockXAResource extends XAResource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void commit(Xid xid, boolean b) throws XAException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void end(Xid xid, int i) throws XAException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void forget(Xid xid) throws XAException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int getTransactionTimeout() throws XAException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isSameRM(XAResource xaResource) throws XAException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int prepare(Xid xid) throws XAException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Xid[] recover(int i) throws XAException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void rollback(Xid xid) throws XAException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean setTransactionTimeout(int i) throws XAException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void start(Xid xid, int i) throws XAException {
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
class Empty implements MockXAResource {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MockXAResource@" + identityHashCode( this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.api.security.AgroalSecurityProvider;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import io.agroal.test.MockDataSource;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.NONE;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.READ_COMMITTED;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.READ_UNCOMMITTED;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.REPEATABLE_READ;
|
||||||
|
import static io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation.SERIALIZABLE;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class NewConnectionTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( NewConnectionTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( FakeConnection.class );
|
||||||
|
if ( Utils.isWindowsOS() ) {
|
||||||
|
Utils.windowsTimerHack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection isolation" )
|
||||||
|
void isolationTest() throws SQLException {
|
||||||
|
isolation( NONE, Connection.TRANSACTION_NONE );
|
||||||
|
isolation( READ_UNCOMMITTED, Connection.TRANSACTION_READ_UNCOMMITTED );
|
||||||
|
isolation( READ_COMMITTED, Connection.TRANSACTION_READ_COMMITTED );
|
||||||
|
isolation( REPEATABLE_READ, Connection.TRANSACTION_REPEATABLE_READ );
|
||||||
|
isolation( SERIALIZABLE, Connection.TRANSACTION_SERIALIZABLE );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void isolation(AgroalConnectionFactoryConfiguration.TransactionIsolation isolation, int level) throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.jdbcTransactionIsolation( isolation ) ) ) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getTransactionIsolation(), level );
|
||||||
|
logger.info( format( "Got isolation \"{0}\" from {1}", connection.getTransactionIsolation(), connection ) );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test connection autoCommit status" )
|
||||||
|
void autoCommitTest() throws SQLException {
|
||||||
|
autocommit( false );
|
||||||
|
autocommit( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void autocommit(boolean autoCommit) throws SQLException {
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( new AgroalDataSourceConfigurationSupplier().connectionPoolConfiguration( cp -> cp.maxSize( 1 ).connectionFactoryConfiguration( cf -> cf.autoCommit( autoCommit ) ) ) ) ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertEquals( connection.getAutoCommit(), autoCommit );
|
||||||
|
logger.info( format( "Got autoCommit \"{0}\" from {1}", connection.getAutoCommit(), connection ) );
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test faulty URL setter" )
|
||||||
|
void faultyUrlTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( FaultyUrlDataSource.class )
|
||||||
|
.jdbcUrl( "the_url" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, new NoWarningsAgroalListener() ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
logger.info( "Got connection " + c + " with some URL" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Properties injection test" )
|
||||||
|
void propertiesInjectionTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( PropertiesDataSource.class )
|
||||||
|
.jdbcProperty( "connectionProperties", "url=some_url;custom=some_custom_prop" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, new NoWarningsAgroalListener() ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
logger.info( "Got connection " + c + " with some URL" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Multiple methods injection test" )
|
||||||
|
void multipleMethodsInjectionTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( MultipleSettersDataSource.class )
|
||||||
|
.jdbcProperty( "someString", "some_value" )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, new NoWarningsAgroalListener() ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
logger.info( "Got connection " + c + " with some someString set" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Exception on new connection" )
|
||||||
|
void newConnectionExceptionTest() throws SQLException {
|
||||||
|
int INITIAL_SIZE = 3, INITIAL_TIMEOUT_MS = 100 * INITIAL_SIZE;
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.initialSize( INITIAL_SIZE )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.credential( new Object() )
|
||||||
|
.addSecurityProvider( new ExceptionSecurityProvider() )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
WarningsAgroalListener warningsListener = new WarningsAgroalListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, warningsListener ) ) {
|
||||||
|
Thread.sleep( INITIAL_TIMEOUT_MS );
|
||||||
|
|
||||||
|
assertEquals( 0, dataSource.getMetrics().creationCount() );
|
||||||
|
assertEquals( INITIAL_SIZE, warningsListener.warningCount(), "Expected warning(s)" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Interrupt " + e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class WarningsAgroalListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final AtomicInteger warnings = new AtomicInteger();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warnings.getAndIncrement();
|
||||||
|
logger.info( "Expected WARN: " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
warnings.getAndIncrement();
|
||||||
|
logger.info( "Expected WARN" + throwable.getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int warningCount() {
|
||||||
|
return warnings.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoWarningsAgroalListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
fail( "Unexpected warning " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
fail( "Unexpected warning " + throwable.getMessage() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FaultyUrlDataSource implements MockDataSource {
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
public void setURL(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
assertNotNull( url, "Expected URL to be set before getConnection()" );
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PropertiesDataSource implements MockDataSource {
|
||||||
|
|
||||||
|
private Properties connectionProperties;
|
||||||
|
|
||||||
|
public void setConnectionProperties(Properties properties) {
|
||||||
|
connectionProperties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
assertEquals( "some_url", connectionProperties.getProperty( "url" ), "Expected URL property to be set before getConnection()" );
|
||||||
|
assertEquals( "some_custom_prop", connectionProperties.getProperty( "custom" ), "Expected Custom property to be set before getConnection()" );
|
||||||
|
assertNull( connectionProperties.getProperty( "connectionProperties" ), "Not expecting property to be set before getConnection()" );
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MultipleSettersDataSource implements MockDataSource {
|
||||||
|
|
||||||
|
private String some = "default";
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void setSomeString(String ignore) {
|
||||||
|
some = "string_method";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSomeString(char[] chars) {
|
||||||
|
some = new String( chars );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
assertEquals( "some_value", some, "Expected property to be set before getConnection()" );
|
||||||
|
return new MockConnection.Empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExceptionSecurityProvider implements AgroalSecurityProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getSecurityProperties(Object securityObject) {
|
||||||
|
throw new RuntimeException( "SecurityProvider throws!" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FakeConnection implements MockConnection {
|
||||||
|
|
||||||
|
private int isolation;
|
||||||
|
private boolean autoCommit;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTransactionIsolation() {
|
||||||
|
return isolation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTransactionIsolation(int level) {
|
||||||
|
isolation = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getAutoCommit() {
|
||||||
|
return autoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutoCommit(boolean autoCommit) {
|
||||||
|
this.autoCommit = autoCommit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,211 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockDataSource;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.api.configuration.AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL_POOLLESS;
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.lang.Thread.currentThread;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
class PoollessTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( PoollessTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Pool-less Test" )
|
||||||
|
@SuppressWarnings( {"AnonymousInnerClassMayBeStatic", "ObjectAllocationInLoop", "JDBCResourceOpenedButNotSafelyClosed"} )
|
||||||
|
void poollessTest() throws SQLException {
|
||||||
|
int TIMEOUT_MS = 100, NUM_THREADS = 4;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( AGROAL_POOLLESS )
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( 1 ) // ignored
|
||||||
|
.minSize( 1 ) // ignored
|
||||||
|
.maxSize( 2 )
|
||||||
|
.acquisitionTimeout( Duration.ofMillis( 5 * TIMEOUT_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch destroyLatch = new CountDownLatch( 1 );
|
||||||
|
|
||||||
|
AgroalDataSourceListener listener = new AgroalDataSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
destroyLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
logger.info( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
fail( throwable );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
assertEquals( 0, dataSource.getMetrics().creationCount() );
|
||||||
|
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertFalse( c.isClosed() );
|
||||||
|
|
||||||
|
try ( Connection testSubject = dataSource.getConnection() ) {
|
||||||
|
assertFalse( testSubject.isClosed() );
|
||||||
|
|
||||||
|
assertEquals( 2, dataSource.getMetrics().creationCount() );
|
||||||
|
assertThrows( SQLException.class, dataSource::getConnection, "Expected exception due to pool being full" );
|
||||||
|
assertEquals( 2, dataSource.getMetrics().creationCount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for destruction of connection" ) );
|
||||||
|
if ( !destroyLatch.await( 2 * TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Flushed connections not sent for destruction" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// One connection flushed and another in use
|
||||||
|
assertEquals( 1, dataSource.getMetrics().flushCount() );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().activeCount() );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().availableCount() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert min-size is zero
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount() );
|
||||||
|
assertEquals( 2, dataSource.getMetrics().availableCount() );
|
||||||
|
assertEquals( 2, dataSource.getMetrics().flushCount() );
|
||||||
|
|
||||||
|
// Assert that closing a connection unblocks one waiting thread
|
||||||
|
assertDoesNotThrow( () -> {
|
||||||
|
Connection c = dataSource.getConnection();
|
||||||
|
dataSource.getConnection();
|
||||||
|
|
||||||
|
// just one of this threads will unblock
|
||||||
|
Collection<Thread> threads = new ArrayList<>( NUM_THREADS );
|
||||||
|
for ( int i = 0; i < NUM_THREADS; i++ ) {
|
||||||
|
threads.add( newConnectionThread( dataSource ) );
|
||||||
|
}
|
||||||
|
threads.forEach( Thread::start );
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep( TIMEOUT_MS );
|
||||||
|
assertEquals( 4, dataSource.getMetrics().awaitingCount(), "Insufficient number of blocked threads" );
|
||||||
|
assertEquals( 4, dataSource.getMetrics().creationCount() );
|
||||||
|
|
||||||
|
logger.info( "Closing connection to unblock one waiting thread" );
|
||||||
|
c.close();
|
||||||
|
|
||||||
|
Thread.sleep( TIMEOUT_MS );
|
||||||
|
|
||||||
|
assertEquals( 3, dataSource.getMetrics().awaitingCount(), "Insufficient number of blocked threads" );
|
||||||
|
assertEquals( 5, dataSource.getMetrics().creationCount() );
|
||||||
|
|
||||||
|
for ( Thread thread : threads ) {
|
||||||
|
thread.join( TIMEOUT_MS );
|
||||||
|
}
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( e );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep( TIMEOUT_MS );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Thread newConnectionThread(DataSource dataSource) {
|
||||||
|
return new Thread( () -> {
|
||||||
|
logger.info( currentThread().getName() + " is on the race for a connection" );
|
||||||
|
try {
|
||||||
|
Connection c = dataSource.getConnection();
|
||||||
|
assertFalse( c.isClosed() );
|
||||||
|
logger.info( currentThread().getName() + " got one connection !!!" );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
logger.info( currentThread().getName() + " got none" );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Exception on create connection" )
|
||||||
|
void createExceptionTest() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( AGROAL_POOLLESS )
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( ExceptionalDataSource.class )
|
||||||
|
) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
fail( "Got connection " + c );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
// test for AG-194 --- active count was incremented incorrectly
|
||||||
|
assertEquals( 0, dataSource.getMetrics().activeCount(), "Active count incremented after exception" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExceptionalDataSource extends MockDataSource.Empty {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
throw new SQLException( "Exceptional condition" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration;
|
||||||
|
import io.agroal.api.configuration.AgroalConnectionPoolConfiguration;
|
||||||
|
import io.agroal.api.configuration.AgroalDataSourceConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalPropertiesReader;
|
||||||
|
import io.agroal.api.exceptionsorter.PostgreSQLExceptionSorter;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
class PropertiesReaderTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( PropertiesReaderTests.class.getName() );
|
||||||
|
|
||||||
|
private static final Path basePath = Paths.get( "src", "test", "resources", "PropertiesReaderTests" );
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Properties File" )
|
||||||
|
void basicPropertiesReaderTest() throws IOException {
|
||||||
|
AgroalDataSourceConfiguration configuration = new AgroalPropertiesReader().readProperties( basePath.resolve( "agroal.properties" ) ).get();
|
||||||
|
|
||||||
|
logger.info( configuration.toString() );
|
||||||
|
|
||||||
|
// Not an exhaustive test, just a couple properties
|
||||||
|
Assertions.assertEquals( 1, configuration.connectionPoolConfiguration().acquisitionTimeout().getSeconds() );
|
||||||
|
Assertions.assertEquals( 60, configuration.connectionPoolConfiguration().validationTimeout().getSeconds() );
|
||||||
|
Assertions.assertEquals( AgroalConnectionFactoryConfiguration.TransactionIsolation.SERIALIZABLE, configuration.connectionPoolConfiguration().connectionFactoryConfiguration().jdbcTransactionIsolation() );
|
||||||
|
Assertions.assertInstanceOf( OddHoursConnectionValidator.class, configuration.connectionPoolConfiguration().connectionValidator() );
|
||||||
|
Assertions.assertInstanceOf( PostgreSQLExceptionSorter.class, configuration.connectionPoolConfiguration().exceptionSorter() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silly validator that drop connections on odd hours
|
||||||
|
*/
|
||||||
|
public static class OddHoursConnectionValidator implements AgroalConnectionPoolConfiguration.ConnectionValidator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(Connection connection) {
|
||||||
|
return LocalTime.now().getHour() % 2 == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/ResizeTests.java
Normal file
170
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/ResizeTests.java
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.cache.LocalConnectionCache;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.lang.Integer.max;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class ResizeTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( ResizeTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings( "AnonymousInnerClassMayBeStatic" )
|
||||||
|
@Test
|
||||||
|
@DisplayName( "resize Max" )
|
||||||
|
void resizeMax() throws SQLException {
|
||||||
|
int INITIAL_SIZE = 10, MAX_SIZE = 6, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( INITIAL_SIZE )
|
||||||
|
.maxSize( MAX_SIZE )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch creationLatch = new CountDownLatch( INITIAL_SIZE );
|
||||||
|
CountDownLatch destroyLatch = new CountDownLatch( INITIAL_SIZE - MAX_SIZE );
|
||||||
|
AgroalDataSourceListener listener = new AgroalDataSourceListener() {
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
creationLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionDestroy(Connection connection) {
|
||||||
|
destroyLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
logger.info( format( "Awaiting fill of all the {0} initial connections on the pool", INITIAL_SIZE ) );
|
||||||
|
if ( !creationLatch.await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", creationLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( INITIAL_SIZE, dataSource.getMetrics().availableCount(), "Pool not initialized correctly" );
|
||||||
|
|
||||||
|
for ( int i = INITIAL_SIZE; i > 0; i-- ) {
|
||||||
|
assertEquals( max( MAX_SIZE, i ), dataSource.getMetrics().availableCount(), "Pool not resized" );
|
||||||
|
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertNotNull( c );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info( format( "Waiting for destruction of {0} connections ", INITIAL_SIZE - MAX_SIZE ) );
|
||||||
|
if ( !destroyLatch.await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} flushed connections not sent for destruction", destroyLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( MAX_SIZE, dataSource.getMetrics().availableCount(), "Pool not resized" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "resize Min" )
|
||||||
|
void resizeMin() throws SQLException {
|
||||||
|
int INITIAL_SIZE = 10, NEW_MIN_SIZE = 15, MAX_SIZE = 35, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.connectionCache( LocalConnectionCache.single() ) // this test expects thread local cache
|
||||||
|
.maxSize( MAX_SIZE )
|
||||||
|
.initialSize( INITIAL_SIZE )
|
||||||
|
);
|
||||||
|
|
||||||
|
CountDownLatch creationLatch = new CountDownLatch( INITIAL_SIZE );
|
||||||
|
ReadyDataSourceListener listener = new ReadyDataSourceListener( creationLatch );
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
logger.info( format( "Awaiting fill of all the {0} initial connections on the pool", INITIAL_SIZE ) );
|
||||||
|
if ( !creationLatch.await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} connections not created", creationLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( INITIAL_SIZE, dataSource.getMetrics().availableCount(), "Pool not initialized correctly" );
|
||||||
|
dataSource.getConfiguration().connectionPoolConfiguration().setMinSize( NEW_MIN_SIZE );
|
||||||
|
|
||||||
|
CountDownLatch newMinLatch = new CountDownLatch( 1 );
|
||||||
|
listener.setCreationLatch( newMinLatch );
|
||||||
|
|
||||||
|
// This should cause a new connection to be created (not necessarily the one that's being returned)
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertNotNull( c );
|
||||||
|
}
|
||||||
|
if ( !newMinLatch.await( TIMEOUT_MS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "{0} new connections not created", newMinLatch.getCount() ) );
|
||||||
|
}
|
||||||
|
assertEquals( INITIAL_SIZE + 1, dataSource.getMetrics().availableCount(), "Pool not resized" );
|
||||||
|
|
||||||
|
// This will come from thread local cache, and unfortunately not increase the size of the pool
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertNotNull( c );
|
||||||
|
}
|
||||||
|
assertEquals( INITIAL_SIZE + 1, dataSource.getMetrics().availableCount(), "Pool not resized" );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ReadyDataSourceListener implements AgroalDataSourceListener {
|
||||||
|
private CountDownLatch creationLatch;
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
ReadyDataSourceListener(CountDownLatch creationLatch) {
|
||||||
|
this.creationLatch = creationLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationLatch(CountDownLatch latch) {
|
||||||
|
creationLatch = latch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionPooled(Connection connection) {
|
||||||
|
creationLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.api.security.AgroalSecurityProvider;
|
||||||
|
import io.agroal.api.security.NamePrincipal;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import io.agroal.test.MockDataSource;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class SecurityTests {
|
||||||
|
|
||||||
|
private static final Logger logger = getLogger( SecurityTests.class.getName() );
|
||||||
|
|
||||||
|
private static final String DEFAULT_USER = "def";
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Test password rotation" )
|
||||||
|
@SuppressWarnings( "InstantiationOfUtilityClass" )
|
||||||
|
void passwordRotation() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( RotationPassword.PASSWORDS.size() )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( CredentialsDataSource.class )
|
||||||
|
.principal( new NamePrincipal( DEFAULT_USER ) )
|
||||||
|
.credential( new RotationPassword() )
|
||||||
|
.addSecurityProvider( new PasswordRotationProvider() )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, new WarningsAgroalDatasourceListener() ) ) {
|
||||||
|
for ( String expectedPassword : RotationPassword.PASSWORDS ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
CredentialsConnection credentialsConnection = connection.unwrap( CredentialsConnection.class );
|
||||||
|
logger.info( format( "Got connection {0} with username {1} and password {2}", connection, credentialsConnection.getUser(), credentialsConnection.getPassword() ) );
|
||||||
|
|
||||||
|
assertEquals( DEFAULT_USER, credentialsConnection.getUser() );
|
||||||
|
assertEquals( expectedPassword, credentialsConnection.getPassword() );
|
||||||
|
|
||||||
|
// Connection leak
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@SuppressWarnings( {"UtilityClass", "UtilityClassWithoutPrivateConstructor"} )
|
||||||
|
private static final class RotationPassword {
|
||||||
|
|
||||||
|
public static final List<String> PASSWORDS = Collections.unmodifiableList( Arrays.asList( "one", "two", "secret", "unknown" ) );
|
||||||
|
|
||||||
|
private static final AtomicInteger COUNTER = new AtomicInteger( 0 );
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
RotationPassword() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static Properties asProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.setProperty( "password", getWord() );
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getWord() {
|
||||||
|
return PASSWORDS.get( COUNTER.getAndIncrement() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PasswordRotationProvider implements AgroalSecurityProvider {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
PasswordRotationProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings( "InstanceofConcreteClass" )
|
||||||
|
public Properties getSecurityProperties(Object securityObject) {
|
||||||
|
if ( securityObject instanceof RotationPassword ) {
|
||||||
|
return RotationPassword.asProperties();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class WarningsAgroalDatasourceListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
WarningsAgroalDatasourceListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
fail( "Unexpected warning: " + message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
fail( "Unexpected warning", throwable );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CredentialsDataSource implements MockDataSource {
|
||||||
|
|
||||||
|
private String user, password;
|
||||||
|
|
||||||
|
public void setUser(String user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return new CredentialsConnection( user, password );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
private static class CredentialsConnection implements MockConnection {
|
||||||
|
|
||||||
|
private final String user, password;
|
||||||
|
|
||||||
|
CredentialsConnection(String user, String password) {
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return iface.cast( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (C) 2023 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import javax.naming.Binding;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.Name;
|
||||||
|
import javax.naming.NameAlreadyBoundException;
|
||||||
|
import javax.naming.NameClassPair;
|
||||||
|
import javax.naming.NameParser;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
public class SimpleMapContext implements Context {
|
||||||
|
|
||||||
|
private final Map<String, Object> map = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookup(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookup(String name) throws NamingException {
|
||||||
|
return map.get( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(Name name, Object obj) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(String name, Object obj) throws NamingException {
|
||||||
|
if ( map.putIfAbsent( name, obj ) != null ) {
|
||||||
|
throw new NameAlreadyBoundException( "Name already bound: " + name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rebind(Name name, Object obj) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rebind(String name, Object obj) throws NamingException {
|
||||||
|
map.put( name, obj );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unbind(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unbind(String name) throws NamingException {
|
||||||
|
map.remove( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rename(Name oldName, Name newName) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rename(String oldName, String newName) throws NamingException {
|
||||||
|
bind( newName, lookup( oldName ) );
|
||||||
|
unbind( oldName );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NamingEnumeration<NameClassPair> list(String name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NamingEnumeration<Binding> listBindings(String name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroySubcontext(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroySubcontext(String name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context createSubcontext(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context createSubcontext(String name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookupLink(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookupLink(String name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NameParser getNameParser(Name name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NameParser getNameParser(String name) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Name composeName(Name name, Name prefix) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String composeName(String name, String prefix) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object addToEnvironment(String propName, Object propVal) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object removeFromEnvironment(String propName) throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hashtable<?, ?> getEnvironment() throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws NamingException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNameInNamespace() throws NamingException {
|
||||||
|
throw new NamingException( "Not implemented" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,368 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.cache.ConnectionCache;
|
||||||
|
import io.agroal.api.configuration.AgroalDataSourceConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import io.agroal.test.MockDataSource;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.lang.System.nanoTime;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.time.Duration.ofSeconds;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class TimeoutTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( TimeoutTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver();
|
||||||
|
if ( Utils.isWindowsOS() ) {
|
||||||
|
Utils.windowsTimerHack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Acquisition timeout" )
|
||||||
|
void basicAcquisitionTimeoutTest() throws SQLException {
|
||||||
|
int MAX_POOL_SIZE = 100, ACQUISITION_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.acquisitionTimeout( ofMillis( ACQUISITION_TIMEOUT_MS ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
|
||||||
|
for ( int i = 0; i < MAX_POOL_SIZE; i++ ) {
|
||||||
|
Connection connection = dataSource.getConnection();
|
||||||
|
assertFalse( connection.isClosed(), "Expected open connection" );
|
||||||
|
// connection.close();
|
||||||
|
}
|
||||||
|
logger.info( format( "Holding all {0} connections from the pool and requesting a new one", MAX_POOL_SIZE ) );
|
||||||
|
|
||||||
|
long start = nanoTime(), timeoutBound = (long) ( ACQUISITION_TIMEOUT_MS * 1.1 );
|
||||||
|
assertTimeoutPreemptively( ofMillis( timeoutBound ), () -> assertThrows( SQLException.class, dataSource::getConnection ), "Expecting acquisition timeout" );
|
||||||
|
|
||||||
|
long elapsed = NANOSECONDS.toMillis( nanoTime() - start );
|
||||||
|
logger.info( format( "Acquisition timeout after {0}ms - Configuration is {1}ms", elapsed, ACQUISITION_TIMEOUT_MS ) );
|
||||||
|
assertTrue( elapsed >= ACQUISITION_TIMEOUT_MS, "Acquisition timeout before time" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Acquisition timeout of new connection" )
|
||||||
|
void acquisitionTimeoutOfNewConnectionTest() throws SQLException {
|
||||||
|
int ACQUISITION_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 10 )
|
||||||
|
.acquisitionTimeout( ofMillis( ACQUISITION_TIMEOUT_MS ) )
|
||||||
|
.connectionCache( ConnectionCache.none() )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( SleepyDatasource.class )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
|
||||||
|
SleepyDatasource.setSleep();
|
||||||
|
|
||||||
|
long start = nanoTime(), timeoutBound = (long) ( ACQUISITION_TIMEOUT_MS * 1.1 );
|
||||||
|
assertTimeoutPreemptively( ofMillis( timeoutBound ), () -> assertThrows( SQLException.class, dataSource::getConnection ), "Expecting acquisition timeout" );
|
||||||
|
|
||||||
|
long elapsed = NANOSECONDS.toMillis( nanoTime() - start );
|
||||||
|
logger.info( format( "Acquisition timeout after {0}ms - Configuration is {1}ms", elapsed, ACQUISITION_TIMEOUT_MS ) );
|
||||||
|
assertTrue( elapsed >= ACQUISITION_TIMEOUT_MS, "Acquisition timeout before time" );
|
||||||
|
|
||||||
|
SleepyDatasource.unsetSleep();
|
||||||
|
|
||||||
|
// Try again, to ensure that the Agroal thread has not become stuck after that first getConnection call
|
||||||
|
logger.info( "Attempting another getConnection() call" );
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertFalse( c.isClosed(), "Expected a good, healthy connection" );
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.getConfiguration().connectionPoolConfiguration().setMinSize( 2 );
|
||||||
|
SleepyDatasource.setSleep();
|
||||||
|
|
||||||
|
// Try again, to ensure that even if new connections can't be established, getConnection calls still succeed
|
||||||
|
logger.info( "Attempting getConnection() on a sleepy datasource" );
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertFalse( c.isClosed(), "Expected a good, healthy connection" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Login timeout" )
|
||||||
|
void loginTimeoutTest() throws SQLException {
|
||||||
|
int LOGIN_TIMEOUT_S = 2;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( LoginTimeoutDatasource.class )
|
||||||
|
.loginTimeout( ofSeconds( LOGIN_TIMEOUT_S ) )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
|
||||||
|
LoginTimeoutDatasource.setTimeout();
|
||||||
|
|
||||||
|
long start = nanoTime(), timeoutBound = LOGIN_TIMEOUT_S * 1500;
|
||||||
|
assertTimeoutPreemptively( ofMillis( timeoutBound ), () -> assertThrows( SQLException.class, dataSource::getConnection ), "Expecting login timeout" );
|
||||||
|
|
||||||
|
long elapsed = NANOSECONDS.toMillis( nanoTime() - start );
|
||||||
|
logger.info( format( "Login timeout after {0}ms - Configuration is {1}s", elapsed, LOGIN_TIMEOUT_S ) );
|
||||||
|
assertTrue( elapsed >= LOGIN_TIMEOUT_S * 1000, "Login timeout before time" );
|
||||||
|
|
||||||
|
LoginTimeoutDatasource.unsetTimeout();
|
||||||
|
|
||||||
|
// Try again, to ensure that the Agroal thread has not become stuck after that first getConnection call
|
||||||
|
logger.info( "Attempting another getConnection() call" );
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertFalse( c.isClosed(), "Expected a good, healthy connection" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier bogusConfiguration = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.acquisitionTimeout( ofSeconds( LOGIN_TIMEOUT_S ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( LoginTimeoutDatasource.class )
|
||||||
|
.loginTimeout( ofSeconds( 2 * LOGIN_TIMEOUT_S ) )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
OnWarningListener warningListener = new OnWarningListener();
|
||||||
|
LoginTimeoutDatasource.setTimeout();
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( bogusConfiguration, warningListener ) ) {
|
||||||
|
assertTrue( warningListener.getWarning().get(), "Expected a warning on the size of acquisition timeout" );
|
||||||
|
|
||||||
|
logger.info( "Checking datasource health" );
|
||||||
|
assertTimeoutPreemptively( ofMillis( LOGIN_TIMEOUT_S * 1500 ), () -> assertThrows( SQLException.class, () -> dataSource.isHealthy( true ) ), "Expecting SQLException on heath check" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Login timeout on initial connections" )
|
||||||
|
void loginTimeoutInitialTest() throws SQLException {
|
||||||
|
int LOGIN_TIMEOUT_S = 1, INITIAL_SIZE = 5;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( INITIAL_SIZE )
|
||||||
|
.maxSize( INITIAL_SIZE )
|
||||||
|
.acquisitionTimeout( ofSeconds( 2 * LOGIN_TIMEOUT_S ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( LoginTimeoutDatasource.class )
|
||||||
|
.loginTimeout( ofSeconds( LOGIN_TIMEOUT_S ) )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
LoginTimeoutDatasource.setTimeout();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
|
||||||
|
long start = nanoTime(), timeoutBound = LOGIN_TIMEOUT_S * 2500;
|
||||||
|
assertTimeoutPreemptively( ofMillis( timeoutBound ), () -> assertThrows( SQLException.class, dataSource::getConnection ), "Expecting login timeout" );
|
||||||
|
|
||||||
|
long elapsed = NANOSECONDS.toMillis( nanoTime() - start );
|
||||||
|
logger.info( format( "Acquisition timeout after {0}ms - Configuration is {1}ms", elapsed, LOGIN_TIMEOUT_S * 2000 ) );
|
||||||
|
assertTrue( elapsed >= LOGIN_TIMEOUT_S * 2000, "Acquisition timeout before time" );
|
||||||
|
|
||||||
|
assertEquals( 0, dataSource.getMetrics().creationCount(), "Expected no created connection" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "Pool-less Login timeout" )
|
||||||
|
void poollessLoginTimeoutTest() throws SQLException, InterruptedException {
|
||||||
|
int ACQUISITION_TIMEOUT_MS = 1500, LOGIN_TIMEOUT_S = 1; // acquisition timeout > login timeout
|
||||||
|
CountDownLatch latch = new CountDownLatch( 1 );
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.dataSourceImplementation( AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL_POOLLESS )
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.acquisitionTimeout( ofMillis( ACQUISITION_TIMEOUT_MS ) )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( LoginTimeoutDatasource.class )
|
||||||
|
.loginTimeout( ofSeconds( LOGIN_TIMEOUT_S ) )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
LoginTimeoutDatasource.unsetTimeout();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try (Connection c = dataSource.getConnection() ) {
|
||||||
|
latch.countDown();
|
||||||
|
assertFalse( c.isClosed(), "Expected good connection" );
|
||||||
|
logger.info( "Holding connection and sleeping for a duration slightly smaller then acquisition timeout" );
|
||||||
|
Thread.sleep( (long) (ACQUISITION_TIMEOUT_MS * 0.8) );
|
||||||
|
} catch ( SQLException e ) {
|
||||||
|
fail( "Unexpected exception", e );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( e );
|
||||||
|
}
|
||||||
|
} ).start();
|
||||||
|
|
||||||
|
// await good connection to poison data source
|
||||||
|
assertTrue( latch.await( ACQUISITION_TIMEOUT_MS, MILLISECONDS ) );
|
||||||
|
LoginTimeoutDatasource.setTimeout();
|
||||||
|
|
||||||
|
long start = nanoTime(), timeoutBound = ACQUISITION_TIMEOUT_MS + LOGIN_TIMEOUT_S * 1500;
|
||||||
|
assertTimeoutPreemptively( ofMillis( timeoutBound ), () -> assertThrows( SQLException.class, dataSource::getConnection ), "Expecting login timeout" );
|
||||||
|
|
||||||
|
long elapsed = NANOSECONDS.toMillis( nanoTime() - start );
|
||||||
|
logger.info( format( "Login timeout after {0}ms - Configuration is {1}ms + {2}s", elapsed, ACQUISITION_TIMEOUT_MS, LOGIN_TIMEOUT_S ) );
|
||||||
|
assertTrue( elapsed >= ACQUISITION_TIMEOUT_MS * 0.8 + LOGIN_TIMEOUT_S * 1000, "Login timeout before time" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class OnWarningListener implements AgroalDataSourceListener {
|
||||||
|
|
||||||
|
private final AtomicBoolean warning = new AtomicBoolean( false );
|
||||||
|
|
||||||
|
OnWarningListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(String message) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWarning(Throwable throwable) {
|
||||||
|
warning.set( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicBoolean getWarning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SleepyDatasource implements MockDataSource {
|
||||||
|
|
||||||
|
private static boolean doSleep;
|
||||||
|
|
||||||
|
public static void setSleep() {
|
||||||
|
doSleep = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unsetSleep() {
|
||||||
|
doSleep = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
if ( !doSleep ) {
|
||||||
|
return new SleepyMockConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info( "This connection will take a while to get established ..." );
|
||||||
|
Thread.sleep( Integer.MAX_VALUE );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
logger.info( "Datasource disturbed in it's sleep" );
|
||||||
|
}
|
||||||
|
throw new SQLException( "I have a bad awakening!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SleepyMockConnection implements MockConnection {
|
||||||
|
SleepyMockConnection() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoginTimeoutDatasource implements MockDataSource {
|
||||||
|
|
||||||
|
private static boolean doTimeout;
|
||||||
|
private int loginTimeout;
|
||||||
|
|
||||||
|
public static void setTimeout() {
|
||||||
|
doTimeout = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unsetTimeout() {
|
||||||
|
doTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
assertNotEquals( 0, loginTimeout, "Expected login timeout to be set to something" );
|
||||||
|
if ( !doTimeout ) {
|
||||||
|
return new LoginTimeoutConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.info( "Pretending to wait for connection to be established ..." );
|
||||||
|
Thread.sleep( loginTimeout * 1000L );
|
||||||
|
throw new SQLException( "Login timeout after " + loginTimeout + " seconds." );
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
throw new SQLException( e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
loginTimeout = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LoginTimeoutConnection implements MockConnection {
|
||||||
|
LoginTimeoutConnection() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/Utils.java
Normal file
29
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/Utils.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright (C) 2023 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
public abstract class Utils {
|
||||||
|
/**
|
||||||
|
* This method will start a daemon thread that will sleep indefinitely in order to be able to park with a higher
|
||||||
|
* resolution for windows. Without this hack, LockSupport.parkNanos() will not be able to park for less than ~16ms
|
||||||
|
* on windows.
|
||||||
|
*
|
||||||
|
* @see <a href="https://hazelcast.com/blog/locksupport-parknanos-under-the-hood-and-the-curious-case-of-parking-part-ii-windows/">blog</a>
|
||||||
|
* @see <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6435126">jdk bug</a>
|
||||||
|
*/
|
||||||
|
public static void windowsTimerHack() {
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(Long.MAX_VALUE);
|
||||||
|
} catch (InterruptedException e) { // a delicious interrupt, omm, omm
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isWindowsOS() {
|
||||||
|
return System.getProperty( "os.name" ).startsWith( "Windows" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.AgroalDataSourceListener;
|
||||||
|
import io.agroal.api.configuration.AgroalConnectionPoolConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockConnection;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static io.agroal.test.MockDriver.deregisterMockDriver;
|
||||||
|
import static io.agroal.test.MockDriver.registerMockDriver;
|
||||||
|
import static java.text.MessageFormat.format;
|
||||||
|
import static java.time.Duration.ofMillis;
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class ValidationTests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( ValidationTests.class.getName() );
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setupMockDriver() {
|
||||||
|
registerMockDriver( ValidationThrowsConnection.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void teardown() {
|
||||||
|
deregisterMockDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "validation throws fatal exception" )
|
||||||
|
void validationThrowsTest() throws SQLException, InterruptedException {
|
||||||
|
int MAX_POOL_SIZE = 3, VALIDATION_MS = 1000, IDLE_VALIDATION_MS = 100;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( MAX_POOL_SIZE )
|
||||||
|
.maxSize( MAX_POOL_SIZE )
|
||||||
|
.validationTimeout( ofMillis( VALIDATION_MS ) )
|
||||||
|
.idleValidationTimeout( ofMillis( IDLE_VALIDATION_MS ) )
|
||||||
|
.acquisitionTimeout( ofMillis( 2 * VALIDATION_MS ) )
|
||||||
|
.connectionValidator( AgroalConnectionPoolConfiguration.ConnectionValidator.defaultValidator() )
|
||||||
|
.exceptionSorter( AgroalConnectionPoolConfiguration.ExceptionSorter.fatalExceptionSorter() )
|
||||||
|
);
|
||||||
|
|
||||||
|
InvalidationListener listener = new InvalidationListener( MAX_POOL_SIZE );
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
|
||||||
|
logger.info( format( "Awaiting for validation of all the {0} connections on the pool", MAX_POOL_SIZE ) );
|
||||||
|
listener.awaitValidation( 3 * VALIDATION_MS );
|
||||||
|
|
||||||
|
assertEquals( MAX_POOL_SIZE, dataSource.getMetrics().invalidCount(), "Expected connection invalid count" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().availableCount(), "Expected no available connections" );
|
||||||
|
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + 1, dataSource.getMetrics().creationCount(), "Expected connection creation" );
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info( format( "Short sleep to trigger idle validation" ) );
|
||||||
|
Thread.sleep( 2 * IDLE_VALIDATION_MS );
|
||||||
|
|
||||||
|
try ( Connection connection = dataSource.getConnection() ) {
|
||||||
|
assertNotNull( connection.getSchema(), "Expected non null value" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + 1, dataSource.getMetrics().invalidCount(), "Expected connection invalid count" );
|
||||||
|
assertEquals( MAX_POOL_SIZE + 2, dataSource.getMetrics().creationCount(), "Expected connection creation" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "idle validation test" )
|
||||||
|
void idleValidationTest() throws SQLException, InterruptedException {
|
||||||
|
int POOL_SIZE = 1, IDLE_VALIDATION_MS = 100, TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
.metricsEnabled()
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.initialSize( POOL_SIZE )
|
||||||
|
.maxSize( POOL_SIZE )
|
||||||
|
.idleValidationTimeout( ofMillis( IDLE_VALIDATION_MS ) )
|
||||||
|
.acquisitionTimeout( ofMillis( TIMEOUT_MS ) )
|
||||||
|
.connectionValidator( AgroalConnectionPoolConfiguration.ConnectionValidator.emptyValidator() )
|
||||||
|
);
|
||||||
|
|
||||||
|
BeforeValidationListener listener = new BeforeValidationListener();
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier, listener ) ) {
|
||||||
|
|
||||||
|
logger.info( format( "Short sleep to trigger idle validation" ) );
|
||||||
|
Thread.sleep( 2 * IDLE_VALIDATION_MS );
|
||||||
|
|
||||||
|
assertEquals( POOL_SIZE, dataSource.getMetrics().availableCount(), "Expected connection available count" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().invalidCount(), "Expected connection invalid count" );
|
||||||
|
assertEquals( 0, listener.getValidationAttempts(), "Expected validation count" );
|
||||||
|
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
assertEquals( 1, listener.getValidationAttempts(), "Expected validation count" );
|
||||||
|
logger.info( "Got valid idle connection " + c);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals( POOL_SIZE, dataSource.getMetrics().availableCount(), "Expected connection available count" );
|
||||||
|
assertEquals( 1, dataSource.getMetrics().acquireCount(), "Expected connection acquire count" );
|
||||||
|
assertEquals( 0, dataSource.getMetrics().invalidCount(), "Expected connection invalid count" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
private static class InvalidationListener implements AgroalDataSourceListener {
|
||||||
|
private final CountDownLatch latch;
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
InvalidationListener(int validationCount) {
|
||||||
|
latch = new CountDownLatch( validationCount );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionInvalid(Connection connection) {
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void awaitValidation(int timeoutMS) {
|
||||||
|
try {
|
||||||
|
if ( !latch.await( timeoutMS, MILLISECONDS ) ) {
|
||||||
|
fail( format( "Validation of {0} connections", latch.getCount() ) );
|
||||||
|
}
|
||||||
|
} catch ( InterruptedException e ) {
|
||||||
|
fail( "Test fail due to interrupt" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BeforeValidationListener implements AgroalDataSourceListener {
|
||||||
|
private final AtomicInteger counter = new AtomicInteger( 0 );
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
BeforeValidationListener() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeConnectionValidation(Connection connection) {
|
||||||
|
counter.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getValidationAttempts() {
|
||||||
|
return counter.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class ValidationThrowsConnection implements MockConnection {
|
||||||
|
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
return "validation_only";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(int timeout) throws SQLException {
|
||||||
|
logger.info( "Throwing exception on validation" );
|
||||||
|
throw new SQLException( "Throwing on validation" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/XATests.java
Normal file
86
jdbc-pool/src/test/java/org/xbib/jdbc/pool/test/XATests.java
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Copyright (C) 2017 Red Hat, Inc. and individual contributors as indicated by the @author tags.
|
||||||
|
// You may not use this file except in compliance with the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
package org.xbib.jdbc.pool.test;
|
||||||
|
|
||||||
|
import io.agroal.api.AgroalDataSource;
|
||||||
|
import io.agroal.api.configuration.AgroalDataSourceConfiguration;
|
||||||
|
import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier;
|
||||||
|
import io.agroal.test.MockXAConnection;
|
||||||
|
import io.agroal.test.MockXADataSource;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.sql.XAConnection;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static io.agroal.test.AgroalTestGroup.FUNCTIONAL;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="lbarreiro@redhat.com">Luis Barreiro</a>
|
||||||
|
*/
|
||||||
|
@Tag( FUNCTIONAL )
|
||||||
|
public class XATests {
|
||||||
|
|
||||||
|
static final Logger logger = getLogger( XATests.class.getName() );
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName( "XAConnection close test" )
|
||||||
|
void xaConnectionCloseTests() throws SQLException {
|
||||||
|
AgroalDataSourceConfigurationSupplier configurationSupplier = new AgroalDataSourceConfigurationSupplier()
|
||||||
|
// using pooless datasource as it closes connection on the calling thread
|
||||||
|
.dataSourceImplementation( AgroalDataSourceConfiguration.DataSourceImplementation.AGROAL_POOLLESS )
|
||||||
|
.connectionPoolConfiguration( cp -> cp
|
||||||
|
.maxSize( 1 )
|
||||||
|
.connectionFactoryConfiguration( cf -> cf
|
||||||
|
.connectionProviderClass( RequiresCloseXADataSource.class ) ) );
|
||||||
|
|
||||||
|
try ( AgroalDataSource dataSource = AgroalDataSource.from( configurationSupplier ) ) {
|
||||||
|
try ( Connection c = dataSource.getConnection() ) {
|
||||||
|
c.getSchema();
|
||||||
|
}
|
||||||
|
// ensure close() is called on the xaConnection object and not in the xaConnection.getConnection() object
|
||||||
|
assertEquals( 1, RequiresCloseXADataSource.getClosed(), "XAConnection not closed" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
public static class RequiresCloseXADataSource implements MockXADataSource {
|
||||||
|
|
||||||
|
private static int closed;
|
||||||
|
|
||||||
|
static void incrementClosed() {
|
||||||
|
closed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "WeakerAccess" )
|
||||||
|
static int getClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XAConnection getXAConnection() throws SQLException {
|
||||||
|
return new MyMockXAConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MyMockXAConnection implements MockXAConnection {
|
||||||
|
MyMockXAConnection() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings( "ObjectToString" )
|
||||||
|
public void close() throws SQLException {
|
||||||
|
logger.info( "Closing XAConnection " + this );
|
||||||
|
incrementClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue