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
|
||||
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.cache;
|
||||
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.security;
|
||||
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