factor out subprojects: oracle, postgresql, mariadb, fix pool tests

This commit is contained in:
Jörg Prante 2022-10-30 01:20:18 +02:00
parent 1409d0e42d
commit ca784dc6cf
73 changed files with 2546 additions and 755 deletions

View file

@ -6,13 +6,13 @@ java {
} }
compileJava { compileJava {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
compileTestJava { compileTestJava {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
jar { jar {
@ -35,7 +35,7 @@ artifacts {
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:all,-fallthrough' options.compilerArgs << '-Xlint:all,-fallthrough,-exports,-try'
} }
javadoc { javadoc {

View file

@ -1,6 +1,6 @@
module org.xbib.jdbc.connection.pool { module org.xbib.jdbc.connection.pool {
requires java.logging; requires java.logging;
requires transitive java.sql; requires java.sql;
exports org.xbib.jdbc.connection.pool; exports org.xbib.jdbc.connection.pool;
exports org.xbib.jdbc.connection.pool.util; exports org.xbib.jdbc.connection.pool.util;
} }

View file

@ -16,14 +16,6 @@ public class PoolConfig {
private static final AtomicLong POOL_COUNTER = new AtomicLong(); private static final AtomicLong POOL_COUNTER = new AtomicLong();
private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
private static final long VALIDATION_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
private static final long MAX_LIFETIME = TimeUnit.MINUTES.toMillis(30);
private static final int DEFAULT_POOL_SIZE = 8; private static final int DEFAULT_POOL_SIZE = 8;
private final Properties properties; private final Properties properties;
@ -100,20 +92,28 @@ public class PoolConfig {
this.properties = properties; this.properties = properties;
this.minIdle = -1; this.minIdle = -1;
this.maxPoolSize = -1; this.maxPoolSize = -1;
this.maxLifetime = MAX_LIFETIME; this.maxLifetime = TimeUnit.MINUTES.toMillis(30);
this.connectionTimeout = CONNECTION_TIMEOUT; this.connectionTimeout = TimeUnit.SECONDS.toMillis(30);
this.validationTimeout = VALIDATION_TIMEOUT; this.validationTimeout = TimeUnit.SECONDS.toMillis(5);
this.idleTimeout = IDLE_TIMEOUT; this.idleTimeout = TimeUnit.MINUTES.toMillis(1);
this.initializationFailTimeout = -1; this.initializationFailTimeout = -1;
this.isAutoCommit = true; this.isAutoCommit = true; // JDBC convention
this.aliveBypassWindowMs = TimeUnit.MILLISECONDS.toMillis(500); this.aliveBypassWindowMs = TimeUnit.MILLISECONDS.toMillis(500);
this.housekeepingPeriodMs = TimeUnit.SECONDS.toMillis(30); this.housekeepingPeriodMs = TimeUnit.SECONDS.toMillis(30);
} }
/**
* Set the JDBC URL.
* @param url the JDBC url as string
*/
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
/**
* Get the JDBC URL.
* @return the JDBC URL as string
*/
public String getUrl() { public String getUrl() {
return url; return url;
} }
@ -407,7 +407,7 @@ public class PoolConfig {
* case that a connection cannot be obtained. However, upon start the pool will * case that a connection cannot be obtained. However, upon start the pool will
* attempt to obtain a connection and validate that the {@code connectionTestQuery} * attempt to obtain a connection and validate that the {@code connectionTestQuery}
* and {@code connectionInitSql} are valid. If those validations fail, an exception * and {@code connectionInitSql} are valid. If those validations fail, an exception
* will be thrown. If a connection cannot be obtained, the validation is skipped * will be thrown. If a connection cannot be obtained, the validation is skipped
* and the the pool will start and continue to try to obtain connections in the * and the the pool will start and continue to try to obtain connections in the
* background. This can mean that callers to {@code DataSource#getConnection()} may * background. This can mean that callers to {@code DataSource#getConnection()} may
* encounter exceptions. </li> * encounter exceptions. </li>
@ -432,7 +432,7 @@ public class PoolConfig {
/** /**
* Determine whether internal pool queries, principally aliveness checks, will be isolated in their own transaction * Determine whether internal pool queries, principally aliveness checks, will be isolated in their own transaction
* via {@link Connection#rollback()}. Defaults to {@code false}. * via {@link Connection#rollback()}. Defaults to {@code false}.
* *
* @return {@code true} if internal pool queries are isolated, {@code false} if not * @return {@code true} if internal pool queries are isolated, {@code false} if not
*/ */
@ -442,7 +442,7 @@ public class PoolConfig {
/** /**
* Configure whether internal pool queries, principally aliveness checks, will be isolated in their own transaction * Configure whether internal pool queries, principally aliveness checks, will be isolated in their own transaction
* via {@link Connection#rollback()}. Defaults to {@code false}. * via {@link Connection#rollback()}. Defaults to {@code false}.
* *
* @param isolate {@code true} if internal pool queries should be isolated, {@code false} if not * @param isolate {@code true} if internal pool queries should be isolated, {@code false} if not
*/ */
@ -450,6 +450,15 @@ public class PoolConfig {
this.isIsolateInternalQueries = isolate; this.isIsolateInternalQueries = isolate;
} }
/**
* Configures the Connections to be added to the pool as read-only Connections.
*
* @param readOnly {@code true} if the Connections in the pool are read-only, {@code false} if not
*/
public void setReadOnly(boolean readOnly) {
this.isReadOnly = readOnly;
}
/** /**
* Determine whether the Connections in the pool are in read-only mode. * Determine whether the Connections in the pool are in read-only mode.
* *
@ -459,19 +468,6 @@ public class PoolConfig {
return isReadOnly; return isReadOnly;
} }
/**
* Configures the Connections to be added to the pool as read-only Connections.
*
* @param readOnly {@code true} if the Connections in the pool are read-only, {@code false} if not
*/
public void setReadOnly(boolean readOnly) {
this.isReadOnly = readOnly;
}
public String getPoolName() {
return poolName;
}
/** /**
* Set the name of the connection pool. This is primarily used for the MBean * Set the name of the connection pool. This is primarily used for the MBean
* to uniquely identify the pool configuration. * to uniquely identify the pool configuration.
@ -482,6 +478,10 @@ public class PoolConfig {
this.poolName = poolName; this.poolName = poolName;
} }
public String getPoolName() {
return poolName;
}
/** /**
* Get the ScheduledExecutorService used for housekeeping. * Get the ScheduledExecutorService used for housekeeping.
* *
@ -628,26 +628,26 @@ public class PoolConfig {
private void validateNumerics() { private void validateNumerics() {
if (maxLifetime != 0 && maxLifetime < TimeUnit.SECONDS.toMillis(30)) { if (maxLifetime != 0 && maxLifetime < TimeUnit.SECONDS.toMillis(30)) {
logger.log(Level.WARNING, "maxLifetime is less than 30000ms, setting to default ms: " + logger.log(Level.WARNING, "maxLifetime is less than 30s, setting to default ms: " +
poolName + " " + MAX_LIFETIME); poolName + " " + TimeUnit.SECONDS.toMillis(30));
maxLifetime = MAX_LIFETIME; maxLifetime = TimeUnit.SECONDS.toMillis(30);
} }
if (leakDetectionThreshold > 0) { if (leakDetectionThreshold > 0) {
if (leakDetectionThreshold < TimeUnit.SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) { if (leakDetectionThreshold < TimeUnit.SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) {
logger.log(Level.WARNING, "leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it: " + logger.log(Level.WARNING, "leakDetectionThreshold is less than 2s or more than maxLifetime, disabling it: " +
poolName); poolName);
leakDetectionThreshold = 0; leakDetectionThreshold = 0;
} }
} }
if (connectionTimeout < 250) { if (connectionTimeout < 250) {
logger.log(Level.WARNING, "connectionTimeout is less than 250ms, setting to ms: " + logger.log(Level.WARNING, "connectionTimeout is less than 250ms, setting to ms: " +
poolName + " " + CONNECTION_TIMEOUT); poolName + " " + TimeUnit.MILLISECONDS.toMillis(250));
connectionTimeout = CONNECTION_TIMEOUT; connectionTimeout = TimeUnit.MILLISECONDS.toMillis(250);
} }
if (validationTimeout < 250) { if (validationTimeout < 250) {
logger.log(Level.WARNING, "validationTimeout is less than 250ms, setting to ms" + logger.log(Level.WARNING, "validationTimeout is less than 250ms, setting to ms" +
poolName + " " + VALIDATION_TIMEOUT); poolName + " " + TimeUnit.MILLISECONDS.toMillis(250));
validationTimeout = VALIDATION_TIMEOUT; validationTimeout = TimeUnit.MILLISECONDS.toMillis(250);
} }
if (maxPoolSize < 1) { if (maxPoolSize < 1) {
maxPoolSize = DEFAULT_POOL_SIZE; maxPoolSize = DEFAULT_POOL_SIZE;
@ -659,10 +659,10 @@ public class PoolConfig {
logger.log(Level.WARNING, "idleTimeout is close to or more than maxLifetime, disabling it:" + poolName); logger.log(Level.WARNING, "idleTimeout is close to or more than maxLifetime, disabling it:" + poolName);
idleTimeout = 0; idleTimeout = 0;
} else if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(10) && minIdle < maxPoolSize) { } else if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(10) && minIdle < maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout is less than 10000ms, setting to default ms: " + logger.log(Level.WARNING, "idleTimeout is less than 10s, setting to default ms: " +
poolName + " " + IDLE_TIMEOUT); poolName + " " + TimeUnit.SECONDS.toMillis(10));
idleTimeout = IDLE_TIMEOUT; idleTimeout = TimeUnit.SECONDS.toMillis(10);
} else if (idleTimeout != IDLE_TIMEOUT && idleTimeout != 0 && minIdle == maxPoolSize) { } else if (idleTimeout != TimeUnit.SECONDS.toMillis(10) && idleTimeout != 0 && minIdle == maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout has been set but has no effect because the pool is operating as a fixed size pool: " + poolName); logger.log(Level.WARNING, "idleTimeout has been set but has no effect because the pool is operating as a fixed size pool: " + poolName);
} }
} }

View file

@ -15,9 +15,9 @@ import org.xbib.jdbc.connection.pool.PoolDataSource;
public class ConcurrentCloseConnectionTest public class ConcurrentCloseConnectionTest
{ {
@Test @Test
public void testConcurrentClose() throws Exception public void testConcurrentClose() throws Exception {
{
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource"); config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config); try (PoolDataSource ds = new PoolDataSource(config);
final Connection connection = ds.getConnection()) { final Connection connection = ds.getConnection()) {

View file

@ -31,6 +31,7 @@ public class ConnectionCloseBlockingTest {
@Test @Test
public void testConnectionCloseBlocking() throws Exception { public void testConnectionCloseBlocking() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(1500); config.setConnectionTimeout(1500);

View file

@ -63,6 +63,7 @@ public class ConnectionPoolSizeVsThreadsTest {
LOGGER.info(MessageFormat.format("Starting test (minIdle={0}, maxPoolSize={1}, threadCount={2}, workTimeMs={3}, restTimeMs={4}, connectionAcquisitionTimeMs={5}, iterations={6}, postTestTimeMs={7})", LOGGER.info(MessageFormat.format("Starting test (minIdle={0}, maxPoolSize={1}, threadCount={2}, workTimeMs={3}, restTimeMs={4}, connectionAcquisitionTimeMs={5}, iterations={6}, postTestTimeMs={7})",
minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs)); minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs));
final PoolConfig config = new PoolConfig(); final PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(minIdle); config.setMinimumIdle(minIdle);
config.setMaximumPoolSize(maxPoolSize); config.setMaximumPoolSize(maxPoolSize);
config.setInitializationFailTimeout(Long.MAX_VALUE); config.setInitializationFailTimeout(Long.MAX_VALUE);

View file

@ -21,6 +21,7 @@ public class ConnectionRaceConditionTest {
@Test @Test
public void testRaceCondition() throws Exception { public void testRaceCondition() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(10); config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE); config.setInitializationFailTimeout(Long.MAX_VALUE);

View file

@ -20,9 +20,9 @@ public class ConnectionStateTest {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("user", "bar"); properties.put("user", "bar");
properties.put("password", "secret"); properties.put("password", "secret");
properties.put("url", "baf");
properties.put("loginTimeout", "10"); properties.put("loginTimeout", "10");
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:stub");
config.setAutoCommit(true); config.setAutoCommit(true);
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
@ -41,6 +41,7 @@ public class ConnectionStateTest {
public void testTransactionIsolation() throws Exception { public void testTransactionIsolation() throws Exception {
Properties properties = new Properties(); Properties properties = new Properties();
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:stub");
config.setTransactionIsolation("TRANSACTION_READ_COMMITTED"); config.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
@ -58,6 +59,7 @@ public class ConnectionStateTest {
@Test @Test
public void testIsolation() { public void testIsolation() {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource"); config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ"); config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ");
config.validate(); config.validate();
@ -68,6 +70,7 @@ public class ConnectionStateTest {
@Test @Test
public void testReadOnly() throws Exception { public void testReadOnly() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setCatalog("test"); config.setCatalog("test");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
@ -85,6 +88,7 @@ public class ConnectionStateTest {
@Test @Test
public void testCatalog() throws Exception { public void testCatalog() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setCatalog("test"); config.setCatalog("test");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
@ -95,6 +99,7 @@ public class ConnectionStateTest {
Connection unwrap = connection.unwrap(Connection.class); Connection unwrap = connection.unwrap(Connection.class);
connection.setCatalog("other"); connection.setCatalog("other");
connection.close(); connection.close();
// after close, we can access unwrap
assertEquals("test", unwrap.getCatalog()); assertEquals("test", unwrap.getCatalog());
} }
} }
@ -103,6 +108,7 @@ public class ConnectionStateTest {
@Test @Test
public void testCommitTracking() throws Exception { public void testCommitTracking() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setAutoCommit(false); config.setAutoCommit(false);
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);

View file

@ -34,6 +34,7 @@ public class ConnectionTest {
@Test @Test
public void testCreate() throws Exception { public void testCreate() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
@ -69,6 +70,7 @@ public class ConnectionTest {
@Test @Test
public void testMaxLifetime() throws Exception { public void testMaxLifetime() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(1500); config.setConnectionTimeout(1500);
@ -110,6 +112,7 @@ public class ConnectionTest {
@Test @Test
public void testMaxLifetime2() throws Exception { public void testMaxLifetime2() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
@ -149,6 +152,7 @@ public class ConnectionTest {
@Test @Test
public void testDoubleClose() throws Exception { public void testDoubleClose() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
@ -167,6 +171,7 @@ public class ConnectionTest {
@Test @Test
public void testEviction() throws Exception { public void testEviction() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
@ -184,11 +189,11 @@ public class ConnectionTest {
@Test @Test
public void testEviction2() throws Exception { public void testEviction2() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource"); config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
//config.setExceptionOverrideClassName(OverrideHandler.class.getName());
try (PoolDataSource ds = new PoolDataSource(config)) { try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool(); Pool pool = ds.getPool();
while (pool.getTotalConnections() < 5) { while (pool.getTotalConnections() < 5) {
@ -214,6 +219,7 @@ public class ConnectionTest {
@Test @Test
public void testEviction3() throws Exception { public void testEviction3() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
@ -243,6 +249,7 @@ public class ConnectionTest {
@Test @Test
public void testEvictAllRefill() throws Exception { public void testEvictAllRefill() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(10); config.setMaximumPoolSize(10);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
@ -268,6 +275,7 @@ public class ConnectionTest {
@Test @Test
public void testBackfill() throws Exception { public void testBackfill() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(4); config.setMaximumPoolSize(4);
config.setConnectionTimeout(1000); config.setConnectionTimeout(1000);
@ -309,6 +317,7 @@ public class ConnectionTest {
@Test @Test
public void testMaximumPoolLimit() throws Exception { public void testMaximumPoolLimit() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(4); config.setMaximumPoolSize(4);
config.setConnectionTimeout(20000); config.setConnectionTimeout(20000);
@ -348,6 +357,7 @@ public class ConnectionTest {
@Test @Test
public void testOldDriver() throws Exception { public void testOldDriver() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
@ -375,6 +385,7 @@ public class ConnectionTest {
StubDataSource stubDataSource = new StubDataSource(); StubDataSource stubDataSource = new StubDataSource();
stubDataSource.setThrowException(new SQLException("Connection refused")); stubDataSource.setThrowException(new SQLException("Connection refused"));
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500); config.setConnectionTimeout(2500);
@ -394,6 +405,7 @@ public class ConnectionTest {
StubDataSource stubDataSource = new StubDataSource(); StubDataSource stubDataSource = new StubDataSource();
stubDataSource.setThrowException(new SQLException("Connection refused")); stubDataSource.setThrowException(new SQLException("Connection refused"));
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource); config.setDataSource(stubDataSource);
@ -420,6 +432,7 @@ public class ConnectionTest {
} }
}; };
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3)); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
@ -448,6 +461,7 @@ public class ConnectionTest {
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch(); StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
stubDataSource.setErrorOnConnection(true); stubDataSource.setErrorOnConnection(true);
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource); config.setDataSource(stubDataSource);
@ -465,6 +479,7 @@ public class ConnectionTest {
public void testDataSourceRaisesErrorAfterInitializationTestQuery() throws Exception { public void testDataSourceRaisesErrorAfterInitializationTestQuery() throws Exception {
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch(); StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3)); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
@ -486,6 +501,7 @@ public class ConnectionTest {
@Test @Test
public void testPopulationSlowAcquisition() throws Exception { public void testPopulationSlowAcquisition() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMaximumPoolSize(20); config.setMaximumPoolSize(20);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource"); config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
@ -515,6 +531,7 @@ public class ConnectionTest {
@Test @Test
public void testMinimumIdleZero() throws Exception { public void testMinimumIdleZero() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000L); config.setConnectionTimeout(1000L);
@ -545,11 +562,4 @@ public class ConnectionTest {
this.errorOnConnection = errorOnConnection; this.errorOnConnection = errorOnConnection;
} }
} }
/*public static class OverrideHandler implements SQLExceptionOverride {
@java.lang.Override
public Override adjudicate(SQLException sqlException) {
return (sqlException.getSQLState().equals("08999")) ? Override.DO_NOT_EVICT : Override.CONTINUE_EVICT;
}
}*/
} }

View file

@ -22,6 +22,7 @@ public class ConnectionTimeoutRetryTest {
@Test @Test
public void testConnectionRetries() throws Exception { public void testConnectionRetries() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(2800); config.setConnectionTimeout(2800);
@ -46,6 +47,7 @@ public class ConnectionTimeoutRetryTest {
@Test @Test
public void testConnectionRetries2() throws Exception { public void testConnectionRetries2() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(2800); config.setConnectionTimeout(2800);
@ -76,6 +78,7 @@ public class ConnectionTimeoutRetryTest {
@Test @Test
public void testConnectionRetries3() throws Exception { public void testConnectionRetries3() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTimeout(2800); config.setConnectionTimeout(2800);
@ -114,6 +117,7 @@ public class ConnectionTimeoutRetryTest {
@Test @Test
public void testConnectionRetries5() throws Exception { public void testConnectionRetries5() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTimeout(1000); config.setConnectionTimeout(1000);
@ -151,6 +155,7 @@ public class ConnectionTimeoutRetryTest {
public void testConnectionIdleFill() throws Exception { public void testConnectionIdleFill() throws Exception {
StubConnection.slowCreate = false; StubConnection.slowCreate = false;
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(10); config.setMaximumPoolSize(10);
config.setConnectionTimeout(2000); config.setConnectionTimeout(2000);

View file

@ -28,6 +28,7 @@ public class HouseKeeperCleanupTest {
@Test @Test
public void testHouseKeeperCleanupWithCustomExecutor() throws Exception { public void testHouseKeeperCleanupWithCustomExecutor() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(10); config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE); config.setInitializationFailTimeout(Long.MAX_VALUE);

View file

@ -12,6 +12,7 @@ public class IsolationTest {
@Test @Test
public void testIsolation() throws Exception { public void testIsolation() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setIsolateInternalQueries(true); config.setIsolateInternalQueries(true);
@ -31,6 +32,7 @@ public class IsolationTest {
@Test @Test
public void testNonIsolation() throws Exception { public void testNonIsolation() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setIsolateInternalQueries(false); config.setIsolateInternalQueries(false);

View file

@ -25,10 +25,10 @@ public class JdbcDriverTest {
@Test @Test
public void driverTest1() throws Exception { public void driverTest1() throws Exception {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("url", "jdbc:stub");
properties.put("user", "bart"); properties.put("user", "bart");
properties.put("password", "simpson"); properties.put("password", "simpson");
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
@ -46,8 +46,8 @@ public class JdbcDriverTest {
@Test @Test
public void driverTest2() throws Exception { public void driverTest2() throws Exception {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("url", "jdbc:invalid");
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:invalid");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");

View file

@ -23,18 +23,17 @@ public class PoolTest {
@BeforeAll @BeforeAll
static void setup() throws Exception { static void setup() throws Exception {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:h2:mem:test");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTestQuery("SELECT 1"); config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
try (PoolDataSource ds = new PoolDataSource(config); try (PoolDataSource ds = new PoolDataSource(config);
Connection conn = ds.getConnection(); Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) { Statement stmt = conn.createStatement()) {
stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test"); stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test");
stmt.executeUpdate("CREATE TABLE basic_pool_test (" stmt.executeUpdate("CREATE TABLE basic_pool_test ("
+ "id INTEGER NOT NULL IDENTITY PRIMARY KEY, " + "id INTEGER PRIMARY KEY, "
+ "timestamp TIMESTAMP, " + "timestamp TIMESTAMP, "
+ "string VARCHAR(128), " + "string VARCHAR(128), "
+ "string_from_number NUMERIC " + "string_from_number NUMERIC "
@ -45,12 +44,11 @@ public class PoolTest {
@Test @Test
public void testIdleTimeout() throws Exception { public void testIdleTimeout() throws Exception {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:h2:mem:test");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(10); config.setMaximumPoolSize(10);
config.setConnectionTestQuery("SELECT 1"); config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
System.setProperty("pool.jdbc.housekeeping.periodMs", "1000"); System.setProperty("pool.jdbc.housekeeping.periodMs", "1000");
try (PoolDataSource ds = new PoolDataSource(config)) { try (PoolDataSource ds = new PoolDataSource(config)) {
System.clearProperty("pool.jdbc.housekeeping.periodMs"); System.clearProperty("pool.jdbc.housekeeping.periodMs");
@ -79,11 +77,10 @@ public class PoolTest {
@Test @Test
public void testIdleTimeout2() throws Exception { public void testIdleTimeout2() throws Exception {
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
PoolConfig config = new PoolConfig(properties); PoolConfig config = new PoolConfig(properties);
config.setUrl("jdbc:h2:mem:test");
config.setMaximumPoolSize(50); config.setMaximumPoolSize(50);
config.setConnectionTestQuery("SELECT 1"); config.setConnectionTestQuery("SELECT 1");
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
System.setProperty("pool.jdbc.housekeeping.periodMs", "1000"); System.setProperty("pool.jdbc.housekeeping.periodMs", "1000");
try (PoolDataSource ds = new PoolDataSource(config)) { try (PoolDataSource ds = new PoolDataSource(config)) {
System.clearProperty("pool.jdbc.housekeeping.periodMs"); System.clearProperty("pool.jdbc.housekeeping.periodMs");

View file

@ -3,32 +3,12 @@ package org.xbib.io.pool.jdbc;
import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
import java.util.Locale; import java.util.Locale;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.xbib.jdbc.connection.pool.IsolationLevel; import org.xbib.jdbc.connection.pool.IsolationLevel;
public class PoolTestExtension implements BeforeAllCallback { public class PoolTestExtension implements BeforeAllCallback {
@Override @Override
public void beforeAll(ExtensionContext context) { public void beforeAll(ExtensionContext context) {
//Level level = Level.INFO;
Level level = Level.ALL;
System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n");
LogManager.getLogManager().reset();
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter());
rootLogger.addHandler(handler);
rootLogger.setLevel(level);
for (Handler h : rootLogger.getHandlers()) {
handler.setFormatter(new SimpleFormatter());
h.setLevel(level);
}
} }
public static void quietlySleep(long millis) { public static void quietlySleep(long millis) {

View file

@ -22,6 +22,7 @@ public class ProxiesTest {
@Test @Test
public void testProxyCreation() throws Exception { public void testProxyCreation() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
@ -55,6 +56,7 @@ public class ProxiesTest {
@Test @Test
public void testStatementProxy() throws Exception { public void testStatementProxy() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");
@ -81,6 +83,7 @@ public class ProxiesTest {
@Test @Test
public void testStatementExceptions() throws Exception { public void testStatementExceptions() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1)); config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1));
@ -168,6 +171,7 @@ public class ProxiesTest {
@Test @Test
public void testOtherExceptions() throws Exception { public void testOtherExceptions() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");

View file

@ -14,6 +14,7 @@ public class RampUpDownTest {
@Test @Test
public void rampUpDownTest() throws Exception { public void rampUpDownTest() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(60); config.setMaximumPoolSize(60);
config.setInitializationFailTimeout(0); config.setInitializationFailTimeout(0);

View file

@ -29,6 +29,7 @@ public class SaturatedPoolTest830 {
@Test @Test
public void saturatedPoolTest() throws Exception { public void saturatedPoolTest() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(MAX_POOL_SIZE); config.setMaximumPoolSize(MAX_POOL_SIZE);
config.setInitializationFailTimeout(Long.MAX_VALUE); config.setInitializationFailTimeout(Long.MAX_VALUE);

View file

@ -117,6 +117,7 @@ public class ShutdownTest {
public void testShutdown4() throws Exception { public void testShutdown4() throws Exception {
StubConnection.slowCreate = true; StubConnection.slowCreate = true;
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(10); config.setMinimumIdle(10);
config.setMaximumPoolSize(10); config.setMaximumPoolSize(10);
config.setInitializationFailTimeout(Long.MAX_VALUE); config.setInitializationFailTimeout(Long.MAX_VALUE);
@ -137,6 +138,7 @@ public class ShutdownTest {
public void testShutdown5() throws Exception { public void testShutdown5() throws Exception {
assertSame( 0, StubConnection.count.get(), "StubConnection count not as expected"); assertSame( 0, StubConnection.count.get(), "StubConnection count not as expected");
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setInitializationFailTimeout(0); config.setInitializationFailTimeout(0);
@ -158,6 +160,7 @@ public class ShutdownTest {
@Test @Test
public void testAfterShutdown() throws Exception { public void testAfterShutdown() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0); config.setMinimumIdle(0);
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setInitializationFailTimeout(0); config.setInitializationFailTimeout(0);
@ -177,6 +180,7 @@ public class ShutdownTest {
@Test @Test
public void testShutdownDuringInit() throws Exception { public void testShutdownDuringInit() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000); config.setConnectionTimeout(1000);
@ -193,6 +197,7 @@ public class ShutdownTest {
@Test @Test
public void testThreadedShutdown() throws Exception { public void testThreadedShutdown() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5); config.setMinimumIdle(5);
config.setMaximumPoolSize(5); config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000); config.setConnectionTimeout(1000);

View file

@ -19,6 +19,7 @@ public class StatementTest {
@BeforeEach @BeforeEach
public void setup() throws Exception { public void setup() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(2); config.setMaximumPoolSize(2);
config.setConnectionTestQuery("VALUES 1"); config.setConnectionTestQuery("VALUES 1");

View file

@ -19,6 +19,7 @@ public class UnwrapTest {
@Test @Test
public void testUnwrapConnection() throws Exception { public void testUnwrapConnection() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setInitializationFailTimeout(0); config.setInitializationFailTimeout(0);
@ -37,6 +38,7 @@ public class UnwrapTest {
@Test @Test
public void testUnwrapDataSource() throws Exception { public void testUnwrapDataSource() throws Exception {
PoolConfig config = new PoolConfig(); PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1); config.setMinimumIdle(1);
config.setMaximumPoolSize(1); config.setMaximumPoolSize(1);
config.setInitializationFailTimeout(0); config.setInitializationFailTimeout(0);

View file

@ -0,0 +1,10 @@
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern=build/database.log
jdk.event.security.level=INFO
javax.management.level=INFO

12
jdbc-mariadb/build.gradle Normal file
View file

@ -0,0 +1,12 @@
dependencies {
api project(':jdbc-query')
implementation libs.mariadb
testImplementation project(':jdbc-test')
testImplementation libs.testcontainers
testImplementation libs.testcontainers.junit.jupiter
testImplementation libs.testcontainers.mariadb
}
test {
systemProperty 'user.timezone', 'GMT'
}

View file

@ -0,0 +1,10 @@
import org.xbib.jdbc.query.Flavor;
import org.xbib.jdbc.mariadb.MariaDB;
module org.xbib.jdbc.mariadb {
requires org.xbib.jdbc.query;
requires java.sql;
uses Flavor;
exports org.xbib.jdbc.mariadb;
provides Flavor with MariaDB;
}

View file

@ -1,27 +1,33 @@
package org.xbib.jdbc.query.flavor; package org.xbib.jdbc.mariadb;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
public class MySQL implements Flavor { import java.sql.PreparedStatement;
import java.sql.SQLException;
public class MariaDB implements Flavor {
public MariaDB() {
}
@Override @Override
public String getName() { public String getName() {
return "mysql"; return "mariadb";
} }
@Override @Override
public boolean supports(String url) { public boolean supports(String url) {
return url.startsWith("jdbc:mysql:"); return url.startsWith("jdbc:mariadb:");
} }
@Override @Override
public String driverClass() { public String driverClass() {
return "com.mysql.jdbc.Driver"; return "org.mariadb.jdbc.Driver";
} }
@Override @Override
public boolean isNormalizedUpperCase() { public boolean isNormalizedUpperCase() {
return true; return false;
} }
@Override @Override
@ -41,7 +47,12 @@ public class MySQL implements Flavor {
@Override @Override
public String typeFloat() { public String typeFloat() {
return "real"; return "double";
}
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
} }
@Override @Override
@ -49,6 +60,11 @@ public class MySQL implements Flavor {
return "double"; return "double";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";
@ -56,6 +72,9 @@ public class MySQL implements Flavor {
@Override @Override
public String typeStringVar(int length) { public String typeStringVar(int length) {
if (length == 16777215) {
return "mediumtext";
}
return "varchar(" + length + ")"; return "varchar(" + length + ")";
} }
@ -66,17 +85,17 @@ public class MySQL implements Flavor {
@Override @Override
public String typeClob() { public String typeClob() {
return "clob"; return "mediumtext"; // mediumtext = varchar(16777215), longtext = varchar(0)
} }
@Override @Override
public String typeBlob() { public String typeBlob() {
return "blob"; return "mediumblob";
} }
@Override @Override
public String typeLocalDateTime() { public String typeLocalDateTime() {
return "timestamp"; return "datetime(3)";
} }
@Override @Override
@ -86,42 +105,32 @@ public class MySQL implements Flavor {
@Override @Override
public boolean useStringForClob() { public boolean useStringForClob() {
return false; return true;
} }
@Override @Override
public boolean useBytesForBlob() { public boolean useBytesForBlob() {
return false; return true;
} }
@Override @Override
public String sequenceNextVal(String sequenceName) { public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName; return "next value for " + sequenceName + "";
} }
@Override @Override
public String sequenceSelectNextVal(String sequenceName) { public String sequenceSelectNextVal(String sequenceName) {
return "values next value for " + sequenceName; return "select " + sequenceNextVal(sequenceName) + fromAny();
} }
@Override @Override
public String sequenceDrop(String sequenceName) { public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + sequenceName + " restrict"; return "drop sequence if exists " + dbtestSeq;
} }
@Override @Override
public String tableDrop(String table) { public String tableDrop(String table) {
return "drop table " + table; return "drop table if exists " + table;
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return "";
} }
@Override @Override
@ -131,22 +140,32 @@ public class MySQL implements Flavor {
@Override @Override
public String sequenceCycleClause(boolean cycle) { public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle"; return "";
}
@Override
public String dbTimeMillis() {
return "current_timestamp";
} }
@Override @Override
public String fromAny() { public String fromAny() {
return " from sysibm.sysdummy1"; return "";
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String dbTimeMillis() {
return "localtimestamp(3)";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return " cache " + nbrValuesToCache;
} }
@Override @Override
public String sequenceOptions() { public String sequenceOptions() {
return " as bigint"; return "";
} }
@Override @Override
@ -156,22 +175,25 @@ public class MySQL implements Flavor {
@Override @Override
public String writeNextIntoQueue(String table) { public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException(); return "insert into " + table
+ " (channel, data, state, delay, modified) "
+ "values (?, ?, null, 0, ?)";
} }
@Override @Override
public String readNextFromQueue(String table) { public String readNextFromQueue(String table) {
throw new UnsupportedOperationException(); return "select id, data from " + table +
" where channel = ? and state is NULL and pushed_at <= (CAST (? as TIMESTAMP) - (INTERVAL '1 minute' * delay))" +
" order by id asc limit 1 for update skip lock";
} }
@Override @Override
public String succeedInQueue(String table) { public String succeedInQueue(String table) {
throw new UnsupportedOperationException(); return "update" + table + " set state = 'OK', modified = " + dbTimeMillis() + " where id = ?";
} }
@Override @Override
public String failInQueue(String table) { public String failInQueue(String table) {
throw new UnsupportedOperationException(); return "update" + table + " set state = 'FAIL', modified = " + dbTimeMillis() + " where id = ?";
} }
} }

View file

@ -0,0 +1 @@
org.xbib.jdbc.mariadb.MariaDB

View file

@ -0,0 +1,108 @@
package org.xbib.jdbc.mariadb.test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MariaDBContainer;
import org.xbib.jdbc.query.Config;
import org.xbib.jdbc.query.ConfigSupplier;
import org.xbib.jdbc.query.DatabaseProvider;
import org.xbib.jdbc.query.OptionsOverride;
import org.xbib.jdbc.test.CommonTest;
public class MariaDBTest extends CommonTest {
static MariaDBContainer<?> mariaDBContainer;
static {
mariaDBContainer = new MariaDBContainer<>("mariadb")
.withDatabaseName("testDB")
.withUsername("testUser")
.withPassword("testPassword");
}
@BeforeAll
public static void startContainer() {
mariaDBContainer.start();
}
@AfterAll
public static void stopContainer() {
mariaDBContainer.stop();
}
@Override
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception {
Config config = ConfigSupplier.of()
.property("database.url", mariaDBContainer.getJdbcUrl())
.property("database.user", "testUser")
.property("database.password", "testPassword")
.get();
return DatabaseProvider.builder(config)
.withSqlParameterLogging()
.withSqlInExceptionMessages()
.withOptions(options)
.build();
}
@Disabled("MariaDB prohibits NaN and Infinity")
@Test
public void argFloatNaN() {
super.argFloatNaN();
}
@Disabled("MariaDB prohibits NaN and Infinity")
@Test
public void argFloatInfinity() {
super.argFloatInfinity();
}
@Disabled("MariaDB prohibits NaN and Infinity")
@Test
public void argDoubleNaN() {
super.argDoubleNaN();
}
@Disabled("MariaDB prohibits NaN and Infinity")
@Test
public void argDoubleInfinity() {
super.argDoubleInfinity();
}
@Disabled("MariaDB prohibits NaN and Infinity")
@Test
public void argFloatNegativeZero() {
super.argFloatNegativeZero();
}
@Disabled("MariaDB prohibits NaN and Infinity")
@Test
public void argDoubleNegativeZero() {
super.argDoubleNegativeZero();
}
@Disabled("MariaDB temporarily disabled")
@Override
public void intervals() {
super.intervals();
}
@Disabled("MariaDB temporarily disabled")
@Override
public void metadataColumnNames() {
super.intervals();
}
@Disabled("MariaDB temporarily disabled")
@Override
public void metadataColumnTypes() {
super.metadataColumnTypes();
}
@Disabled("MariaDB temporarily disabled")
@Override
public void saveResultAsTable() {
super.saveResultAsTable();
}
}

View file

@ -0,0 +1,10 @@
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern=build/database.log
jdk.event.security.level=INFO
javax.management.level=INFO

12
jdbc-oracle/build.gradle Normal file
View file

@ -0,0 +1,12 @@
dependencies {
api project(':jdbc-query')
implementation libs.oracle
testImplementation project(':jdbc-test')
testImplementation libs.testcontainers
testImplementation libs.testcontainers.junit.jupiter
testImplementation libs.testcontainers.oracle.xe
}
test {
systemProperty 'user.timezone', 'GMT'
}

View file

@ -0,0 +1,12 @@
import org.xbib.jdbc.oracle.Oracle;
import org.xbib.jdbc.query.Flavor;
module org.xbib.jdbc.oracle {
requires org.xbib.jdbc.query;
requires org.xbib.jdbc.connection.pool;
requires com.oracle.database.jdbc;
requires java.sql;
uses Flavor;
exports org.xbib.jdbc.oracle;
provides Flavor with Oracle;
}

View file

@ -1,9 +1,17 @@
package org.xbib.jdbc.query.flavor; package org.xbib.jdbc.oracle;
import oracle.jdbc.OraclePreparedStatement;
import org.xbib.jdbc.connection.pool.ProxyPreparedStatement;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Oracle implements Flavor { public class Oracle implements Flavor {
public Oracle() {
}
@Override @Override
public String getName() { public String getName() {
return "oracle"; return "oracle";
@ -29,11 +37,31 @@ public class Oracle implements Flavor {
return "binary_float"; return "binary_float";
} }
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
if (preparedStatement instanceof ProxyPreparedStatement) {
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
((OraclePreparedStatement) proxyPreparedStatement.getDelegate()).setBinaryFloat(i, floatValue);
} else {
((OraclePreparedStatement) preparedStatement).setBinaryFloat(i, floatValue);
}
}
@Override @Override
public String typeDouble() { public String typeDouble() {
return "binary_double"; return "binary_double";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
if (preparedStatement instanceof ProxyPreparedStatement) {
ProxyPreparedStatement proxyPreparedStatement = (ProxyPreparedStatement) preparedStatement;
((OraclePreparedStatement) proxyPreparedStatement.getDelegate()).setBinaryDouble(i, doubleValue);
} else {
((OraclePreparedStatement) preparedStatement).setBinaryDouble(i, doubleValue);
}
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";

View file

@ -0,0 +1 @@
org.xbib.jdbc.oracle.Oracle

View file

@ -0,0 +1,67 @@
package org.xbib.jdbc.oracle.test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.OracleContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.xbib.jdbc.query.Config;
import org.xbib.jdbc.query.ConfigSupplier;
import org.xbib.jdbc.query.DatabaseProvider;
import org.xbib.jdbc.query.OptionsOverride;
import org.xbib.jdbc.test.CommonTest;
/**
* Exercise Database functionality with a real Oracle database.
*/
@Testcontainers
public class OracleTest extends CommonTest {
static OracleContainer oracleContainer;
static {
oracleContainer = new OracleContainer("gvenzl/oracle-xe:21.3.0-slim")
.withDatabaseName("testDB")
.withUsername("testUser")
.withPassword("testPassword")
.withExposedPorts(1521)
.withReuse(true);
}
@BeforeAll
public static void startContainer() {
oracleContainer.start();
}
@AfterAll
public static void stopContainer() {
oracleContainer.stop();
}
@Override
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception {
Config config = ConfigSupplier.of()
.property("database.url", oracleContainer.getJdbcUrl())
.property("database.user", "testUser")
.property("database.password", "testPassword")
.get();
return DatabaseProvider.builder(config)
.withSqlParameterLogging()
.withSqlInExceptionMessages()
.withOptions(options)
.build();
}
@Disabled("Current Oracle behavior is to convert -0f to 0f")
@Test
public void argFloatNegativeZero() {
super.argFloatNegativeZero();
}
@Disabled("Current Oracle behavior is to convert -0d to 0d")
@Test
public void argDoubleNegativeZero() {
super.argDoubleNegativeZero();
}
}

View file

@ -0,0 +1,10 @@
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern=build/database.log
jdk.event.security.level=INFO
javax.management.level=INFO

View file

@ -0,0 +1,12 @@
dependencies {
api project(':jdbc-query')
implementation libs.postgresql
testImplementation project(':jdbc-test')
testImplementation libs.testcontainers
testImplementation libs.testcontainers.junit.jupiter
testImplementation libs.testcontainers.postgresql
}
test {
systemProperty 'user.timezone', 'GMT'
}

View file

@ -0,0 +1,10 @@
import org.xbib.jdbc.query.Flavor;
import org.xbib.jdbc.postgresql.Postgresql;
module org.xbib.jdbc.postgresql {
requires org.xbib.jdbc.query;
requires java.sql;
uses Flavor;
exports org.xbib.jdbc.postgresql;
provides Flavor with Postgresql;
}

View file

@ -1,9 +1,15 @@
package org.xbib.jdbc.query.flavor; package org.xbib.jdbc.postgresql;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Postgresql implements Flavor { public class Postgresql implements Flavor {
public Postgresql() {
}
@Override @Override
public String getName() { public String getName() {
return "postgresql"; return "postgresql";
@ -44,11 +50,21 @@ public class Postgresql implements Flavor {
return "real"; return "real";
} }
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override @Override
public String typeDouble() { public String typeDouble() {
return "double precision"; return "double precision";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";

View file

@ -0,0 +1 @@
org.xbib.jdbc.postgresql.Postgresql

View file

@ -0,0 +1,71 @@
package org.xbib.jdbc.postgresql.test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.xbib.jdbc.query.Config;
import org.xbib.jdbc.query.ConfigSupplier;
import org.xbib.jdbc.query.DatabaseProvider;
import org.xbib.jdbc.query.OptionsOverride;
import org.xbib.jdbc.query.Schema;
import org.xbib.jdbc.test.CommonTest;
/**
* Exercise Database functionality with a real PostgreSQL database.
*/
public class PostgresqlTest extends CommonTest {
static PostgreSQLContainer<?> postgreSQLContainer;
static {
postgreSQLContainer = new PostgreSQLContainer<>("postgres")
.withDatabaseName("testDB")
.withUsername("testUser")
.withPassword("testPassword");
}
@BeforeAll
public static void startContainer() {
postgreSQLContainer.start();
}
@AfterAll
public static void stopContainer() {
postgreSQLContainer.stop();
}
@Override
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception {
Config config = ConfigSupplier.of()
.property("database.url", postgreSQLContainer.getJdbcUrl())
.property("database.user", "testUser")
.property("database.password", "testPassword")
.get();
return DatabaseProvider.builder(config)
.withSqlParameterLogging()
.withSqlInExceptionMessages()
.withOptions(options)
.build();
}
/**
* PostgreSQL seems to have different behavior in that is does not convert
* column names to uppercase (it actually converts them to lowercase).
* I haven't figured out how to smooth over this difference, since all databases
* seem to respect the provided case when it is inside quotes, but don't provide
* a way to tell whether a particular parameter was quoted.
*/
@Override
@Test
public void metadataColumnNames() {
db.dropTableQuietly("dbtest");
new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db);
db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest")
.query(rs -> {
Assertions.assertArrayEquals(new String[]{"pk", "foo", "Foo"}, rs.getColumnLabels());
return null;
});
}
}

View file

@ -0,0 +1,10 @@
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern=build/database.log
jdk.event.security.level=INFO
javax.management.level=INFO

View file

@ -4,5 +4,4 @@ dependencies {
testImplementation libs.hsqldb testImplementation libs.hsqldb
testImplementation libs.testcontainers testImplementation libs.testcontainers
testImplementation libs.testcontainers.junit.jupiter testImplementation libs.testcontainers.junit.jupiter
testImplementation libs.testcontainers.oracle.xe
} }

View file

@ -1,13 +1,12 @@
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import org.xbib.jdbc.query.flavor.Derby; import org.xbib.jdbc.query.flavor.Derby;
import org.xbib.jdbc.query.flavor.Hsql; import org.xbib.jdbc.query.flavor.Hsql;
import org.xbib.jdbc.query.flavor.Oracle;
import org.xbib.jdbc.query.flavor.Postgresql;
import org.xbib.jdbc.query.flavor.SqlServer; import org.xbib.jdbc.query.flavor.SqlServer;
module org.xbib.jdbc.query { module org.xbib.jdbc.query {
uses Flavor; uses Flavor;
requires transitive org.xbib.jdbc.connection.pool; requires org.xbib.jdbc.connection.pool;
requires java.sql;
exports org.xbib.jdbc.query; exports org.xbib.jdbc.query;
provides Flavor with Derby, Hsql, Oracle, Postgresql, SqlServer; provides Flavor with Derby, Hsql, SqlServer;
} }

View file

@ -28,12 +28,12 @@ public interface ConfigSupplier extends Supplier<Config> {
ConfigSupplier custom(Function<String, String> keyValueLookup); ConfigSupplier custom(Function<String, String> keyValueLookup);
ConfigSupplier value(String key, String value); ConfigSupplier property(String key, String value);
ConfigSupplier systemProperties();
ConfigSupplier env(); ConfigSupplier env();
ConfigSupplier systemProperties();
ConfigSupplier properties(Properties properties); ConfigSupplier properties(Properties properties);
ConfigSupplier config(Config config); ConfigSupplier config(Config config);

View file

@ -47,7 +47,7 @@ public class ConfigSupplierImpl implements ConfigSupplier {
} }
@Override @Override
public ConfigSupplier value(String key, String value) { public ConfigSupplier property(String key, String value) {
return custom(k -> k.equals(key) ? value : null, "value(" + key + ")"); return custom(k -> k.equals(key) ? value : null, "value(" + key + ")");
} }

View file

@ -259,17 +259,17 @@ public class DatabaseImpl implements Database {
} }
Duration duration = Duration.between(appDate, dbDate).abs(); Duration duration = Duration.between(appDate, dbDate).abs();
if (duration.getSeconds() > 3600) { if (duration.getSeconds() > 3600) {
throw new DatabaseException("App and db time are over an hour apart (check your timezones) app: " throw new DatabaseException("JDBC and database time are over an hour apart (check your timezones) app: "
+ appDate + " db: " + appDate + " db: "
+ dbDate); + dbDate);
} }
if (duration.getSeconds() * 1000 > millisToError) { if (duration.getSeconds() * 1000 > millisToError) {
throw new DatabaseException("App and db time over " + millisToError + " millis apart (check your clocks) app: " throw new DatabaseException("JDBC and database time over " + millisToError + " millis apart (check your clocks) app: "
+ appDate + " db: " + appDate + " db: "
+ dbDate); + dbDate);
} }
if (duration.getSeconds() * 1000 > millisToWarn) { if (duration.getSeconds() * 1000 > millisToWarn) {
logger.warning("App and db time are over " + millisToWarn + " millis apart (check your clocks) app: " logger.warning("JDBC and database time are over " + millisToWarn + " millis apart (check your clocks) app: "
+ appDate + " db: " + appDate + " db: "
+ dbDate); + dbDate);
} }

View file

@ -16,6 +16,7 @@ import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.logging.Level; import java.util.logging.Level;
@ -27,33 +28,20 @@ import java.util.logging.Logger;
* of this laziness, the underlying resources require explicit cleanup by calling either * of this laziness, the underlying resources require explicit cleanup by calling either
* commitAndClose() or rollbackAndClose(). * commitAndClose() or rollbackAndClose().
*/ */
public final class DatabaseProvider implements Supplier<Database> { public final class DatabaseProvider implements Supplier<Database>, Closeable {
private static final Logger log = Logger.getLogger(DatabaseProvider.class.getName()); private static final Logger log = Logger.getLogger(DatabaseProvider.class.getName());
private static final AtomicInteger poolNameCounter = new AtomicInteger(1); private static final AtomicInteger poolNameCounter = new AtomicInteger(1);
private final Options options; private final DatabaseProviderBuilderImpl builder;
private DatabaseProvider delegateTo = null; private Connection connection;
private Supplier<Connection> connectionProvider; private Database database;
private Connection connection = null; private DatabaseProvider(DatabaseProviderBuilderImpl builder) {
this.builder = builder;
private Database database = null;
private DatabaseProvider(Supplier<Connection> connectionProvider, Options options) {
if (connectionProvider == null) {
throw new IllegalArgumentException("connection provider cannot be null");
}
this.connectionProvider = connectionProvider;
this.options = options;
}
private DatabaseProvider(DatabaseProvider delegateTo) {
this.delegateTo = delegateTo;
this.options = delegateTo.options;
} }
/** /**
@ -75,15 +63,15 @@ public final class DatabaseProvider implements Supplier<Database> {
* *
* <p>A database pool will be created using jdbc-connection-pool.</p> * <p>A database pool will be created using jdbc-connection-pool.</p>
*/ */
public static Builder builder(Config config) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { public static DatabaseProviderBuilder builder(Config config) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
return builder(createDataSource(config), getFlavor(config)); return builder(createDataSource(config), getFlavor(config));
} }
/** /**
* Use an externally configured DataSource and a Flavor. * Use an externally configured DataSource and a Flavor.
*/ */
public static Builder builder(DataSource ds, Flavor flavor) { public static DatabaseProviderBuilder builder(DataSource ds, Flavor flavor) {
return new BuilderImpl(ds, () -> { return new DatabaseProviderBuilderImpl(ds, () -> {
try { try {
return ds.getConnection(); return ds.getConnection();
} catch (Exception e) { } catch (Exception e) {
@ -97,7 +85,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* the JDBC standard DriverManager method. The url parameter will be inspected * the JDBC standard DriverManager method. The url parameter will be inspected
* to determine the Flavor for this database. * to determine the Flavor for this database.
*/ */
public static Builder builder(String url) { public static DatabaseProviderBuilder builder(String url) {
return builder(url, Flavor.fromJdbcUrl(url), null, null, null); return builder(url, Flavor.fromJdbcUrl(url), null, null, null);
} }
@ -108,7 +96,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param flavor use this flavor rather than guessing based on the url * @param flavor use this flavor rather than guessing based on the url
*/ */
public static Builder builder(String url, Flavor flavor) { public static DatabaseProviderBuilder builder(String url, Flavor flavor) {
return builder(url, flavor, null, null, null); return builder(url, flavor, null, null, null);
} }
@ -118,7 +106,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* to determine the Flavor for this database. * to determine the Flavor for this database.
*/ */
public static Builder builder(String url, Properties info) { public static DatabaseProviderBuilder builder(String url, Properties info) {
return builder(url, Flavor.fromJdbcUrl(url), info, null, null); return builder(url, Flavor.fromJdbcUrl(url), info, null, null);
} }
@ -129,7 +117,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param flavor use this flavor rather than guessing based on the url * @param flavor use this flavor rather than guessing based on the url
*/ */
public static Builder builder(String url, Flavor flavor, Properties info) { public static DatabaseProviderBuilder builder(String url, Flavor flavor, Properties info) {
return builder(url, flavor, info, null, null); return builder(url, flavor, info, null, null);
} }
@ -139,7 +127,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* to determine the Flavor for this database. * to determine the Flavor for this database.
*/ */
public static Builder builder(String url, String user, String password) { public static DatabaseProviderBuilder builder(String url, String user, String password) {
return builder(url, Flavor.fromJdbcUrl(url), null, user, password); return builder(url, Flavor.fromJdbcUrl(url), null, user, password);
} }
@ -150,18 +138,18 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param flavor use this flavor rather than guessing based on the url * @param flavor use this flavor rather than guessing based on the url
*/ */
public static Builder builder(String url, public static DatabaseProviderBuilder builder(String url,
Flavor flavor, Flavor flavor,
String user, String user,
String password) { String password) {
return builder(url, flavor, null, user, password); return builder(url, flavor, null, user, password);
} }
private static Builder builder(String url, private static DatabaseProviderBuilder builder(String url,
Flavor flavor, Flavor flavor,
Properties info, Properties info,
String user, String user,
String password) { String password) {
Options options = new OptionsDefault(flavor); Options options = new OptionsDefault(flavor);
try { try {
DriverManager.getDriver(url); DriverManager.getDriver(url);
@ -174,7 +162,7 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
} }
} }
return new BuilderImpl(null, () -> { return new DatabaseProviderBuilderImpl(null, () -> {
try { try {
if (info != null) { if (info != null) {
return DriverManager.getConnection(url, info); return DriverManager.getConnection(url, info);
@ -206,7 +194,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param propertyFileName path to the properties file we will attempt to read * @param propertyFileName path to the properties file we will attempt to read
* @throws DatabaseException if the property file could not be read for any reason * @throws DatabaseException if the property file could not be read for any reason
*/ */
public static Builder fromPropertyFile(String propertyFileName) { public static DatabaseProviderBuilder fromPropertyFile(String propertyFileName) {
return fromPropertyFile(propertyFileName, Charset.defaultCharset().newDecoder()); return fromPropertyFile(propertyFileName, Charset.defaultCharset().newDecoder());
} }
@ -228,7 +216,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param decoder character encoding to use when reading the property file * @param decoder character encoding to use when reading the property file
* @throws DatabaseException if the property file could not be read for any reason * @throws DatabaseException if the property file could not be read for any reason
*/ */
public static Builder fromPropertyFile(String propertyFileName, CharsetDecoder decoder) { public static DatabaseProviderBuilder fromPropertyFile(String propertyFileName, CharsetDecoder decoder) {
Properties properties = new Properties(); Properties properties = new Properties();
if (propertyFileName != null && propertyFileName.length() > 0) { if (propertyFileName != null && propertyFileName.length() > 0) {
try ( try (
@ -265,7 +253,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* pass "my." as the prefix) * pass "my." as the prefix)
* @throws DatabaseException if the property file could not be read for any reason * @throws DatabaseException if the property file could not be read for any reason
*/ */
public static Builder fromPropertyFile(String filename, String propertyPrefix) { public static DatabaseProviderBuilder fromPropertyFile(String filename, String propertyPrefix) {
return fromPropertyFile(filename, propertyPrefix, Charset.defaultCharset().newDecoder()); return fromPropertyFile(filename, propertyPrefix, Charset.defaultCharset().newDecoder());
} }
@ -291,7 +279,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param decoder character encoding to use when reading the property file * @param decoder character encoding to use when reading the property file
* @throws DatabaseException if the property file could not be read for any reason * @throws DatabaseException if the property file could not be read for any reason
*/ */
public static Builder fromPropertyFile(String filename, String propertyPrefix, CharsetDecoder decoder) { public static DatabaseProviderBuilder fromPropertyFile(String filename, String propertyPrefix, CharsetDecoder decoder) {
Properties properties = new Properties(); Properties properties = new Properties();
if (filename != null && filename.length() > 0) { if (filename != null && filename.length() > 0) {
try ( try (
@ -323,7 +311,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param properties properties will be read from here * @param properties properties will be read from here
* @throws DatabaseException if the property file could not be read for any reason * @throws DatabaseException if the property file could not be read for any reason
*/ */
public static Builder fromProperties(Properties properties) { public static DatabaseProviderBuilder fromProperties(Properties properties) {
return fromProperties(properties, "", false); return fromProperties(properties, "", false);
} }
@ -348,7 +336,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* pass "my." as the prefix) * pass "my." as the prefix)
* @throws DatabaseException if the property file could not be read for any reason * @throws DatabaseException if the property file could not be read for any reason
*/ */
public static Builder fromProperties(Properties properties, String propertyPrefix) { public static DatabaseProviderBuilder fromProperties(Properties properties, String propertyPrefix) {
return fromProperties(properties, propertyPrefix, false); return fromProperties(properties, propertyPrefix, false);
} }
@ -374,7 +362,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* log entry will be entered, but it will attempt to proceed using * log entry will be entered, but it will attempt to proceed using
* solely the system properties * solely the system properties
*/ */
public static Builder fromPropertyFileOrSystemProperties(String filename) { public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename) {
return fromPropertyFileOrSystemProperties(filename, Charset.defaultCharset().newDecoder()); return fromPropertyFileOrSystemProperties(filename, Charset.defaultCharset().newDecoder());
} }
@ -400,7 +388,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* solely the system properties * solely the system properties
* @param decoder character encoding to use when reading the property file * @param decoder character encoding to use when reading the property file
*/ */
public static Builder fromPropertyFileOrSystemProperties(String filename, CharsetDecoder decoder) { public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, CharsetDecoder decoder) {
Properties properties = new Properties(); Properties properties = new Properties();
if (filename != null && filename.length() > 0) { if (filename != null && filename.length() > 0) {
try ( try (
@ -441,7 +429,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* (exactly, so if you want to use "my.database.url" you must * (exactly, so if you want to use "my.database.url" you must
* pass "my." as the prefix) * pass "my." as the prefix)
*/ */
public static Builder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix) { public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix) {
return fromPropertyFileOrSystemProperties(filename, propertyPrefix, Charset.defaultCharset().newDecoder()); return fromPropertyFileOrSystemProperties(filename, propertyPrefix, Charset.defaultCharset().newDecoder());
} }
@ -471,8 +459,8 @@ public final class DatabaseProvider implements Supplier<Database> {
* pass "my." as the prefix) * pass "my." as the prefix)
* @param decoder character encoding to use when reading the property file * @param decoder character encoding to use when reading the property file
*/ */
public static Builder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix, public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix,
CharsetDecoder decoder) { CharsetDecoder decoder) {
Properties properties = new Properties(); Properties properties = new Properties();
if (filename != null && filename.length() > 0) { if (filename != null && filename.length() > 0) {
try ( try (
@ -502,7 +490,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* </pre> * </pre>
*/ */
public static Builder fromSystemProperties() { public static DatabaseProviderBuilder fromSystemProperties() {
return fromProperties(null, "", true); return fromProperties(null, "", true);
} }
@ -524,11 +512,11 @@ public final class DatabaseProvider implements Supplier<Database> {
* dot if desired (e.g. "mydb." for properties like -Dmydb.database.url) * dot if desired (e.g. "mydb." for properties like -Dmydb.database.url)
*/ */
public static Builder fromSystemProperties(String propertyPrefix) { public static DatabaseProviderBuilder fromSystemProperties(String propertyPrefix) {
return fromProperties(null, propertyPrefix, true); return fromProperties(null, propertyPrefix, true);
} }
private static Builder fromProperties(Properties properties, String propertyPrefix, boolean useSystemProperties) { private static DatabaseProviderBuilder fromProperties(Properties properties, String propertyPrefix, boolean useSystemProperties) {
if (propertyPrefix == null) { if (propertyPrefix == null) {
propertyPrefix = ""; propertyPrefix = "";
} }
@ -599,7 +587,7 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
} }
public static DataSource createDataSource(Config config) private static DataSource createDataSource(Config config)
throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException,
IllegalAccessException { IllegalAccessException {
String url = config.getString("database.url"); String url = config.getString("database.url");
@ -621,7 +609,7 @@ public final class DatabaseProvider implements Supplier<Database> {
return new PoolDataSource(poolConfig); return new PoolDataSource(poolConfig);
} }
public static Flavor getFlavor(Config config) { private static Flavor getFlavor(Config config) {
String url = config.getString("database.url"); String url = config.getString("database.url");
if (url == null) { if (url == null) {
throw new DatabaseException("You must provide database.url"); throw new DatabaseException("You must provide database.url");
@ -755,30 +743,27 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
public Database get() { public Database get() {
if (delegateTo != null) {
return delegateTo.get();
}
if (database != null) { if (database != null) {
return database; return database;
} }
if (connectionProvider == null) { if (builder.isClosed()) {
throw new DatabaseException("Called get() on a DatabaseProvider after close()"); throw new DatabaseException("Called get() on a DatabaseProvider after close()");
} }
Metric metric = new Metric(log.isLoggable(Level.FINE)); Metric metric = new Metric(log.isLoggable(Level.FINE));
try { try {
connection = connectionProvider.get(); connection = builder.connectionProvider.get();
metric.checkpoint("getConn"); metric.checkpoint("getConn");
try { try {
// JDBC specifies that autoCommit is the default for all new connections. // JDBC specifies that autoCommit is the default for all new connections.
// Don't try to be clever about clearing it conditionally. // Don't try to be clever about clearing it conditionally.
if (!options.flavor().isAutoCommitOnly()) { if (!builder.options.flavor().isAutoCommitOnly()) {
connection.setAutoCommit(false); connection.setAutoCommit(false);
metric.checkpoint("setAutoCommit"); metric.checkpoint("setAutoCommit");
} }
} catch (SQLException e) { } catch (SQLException e) {
throw new DatabaseException("Unable to set autoCommit for the connection", e); throw new DatabaseException("Unable to set autoCommit for the connection", e);
} }
database = new DatabaseImpl(connection, options); database = new DatabaseImpl(connection, builder.options);
metric.checkpoint("dbInit"); metric.checkpoint("dbInit");
} catch (RuntimeException e) { } catch (RuntimeException e) {
metric.checkpoint("fail"); metric.checkpoint("fail");
@ -786,7 +771,7 @@ public final class DatabaseProvider implements Supplier<Database> {
} finally { } finally {
metric.done(); metric.done();
if (log.isLoggable(Level.FINE)) { if (log.isLoggable(Level.FINE)) {
StringBuilder buf = new StringBuilder("Get ").append(options.flavor()).append(" database: "); StringBuilder buf = new StringBuilder("Get ").append(builder.options.flavor()).append(" database: ");
metric.printMessage(buf); metric.printMessage(buf);
log.fine(buf.toString()); log.fine(buf.toString());
} }
@ -794,78 +779,10 @@ public final class DatabaseProvider implements Supplier<Database> {
return database; return database;
} }
public Builder fakeBuilder() {
return new Builder() {
@Override
public Builder withOptions(OptionsOverride optionsOverride) {
return this;
}
@Override
public Builder withSqlParameterLogging() {
return this;
}
@Override
public Builder withSqlInExceptionMessages() {
return this;
}
@Override
public Builder withDatePerAppOnly() {
return this;
}
@Override
public Builder withTransactionControl() {
return this;
}
@Override
public Builder withTransactionControlSilentlyIgnored() {
return this;
}
@Override
public Builder withConnectionAccess() {
return this;
}
@Override
public DatabaseProvider build() {
return new DatabaseProvider(DatabaseProvider.this);
}
@Override
public void transact(DbCode tx) {
build().transact(tx);
}
@Override
public <T> T transactReturning(DbCodeTyped<T> tx) {
return build().transactReturning(tx);
}
@Override
public void transact(DbCodeTx tx) {
build().transact(tx);
}
@Override
public void close() {
log.fine("Ignoring close call on fakeBuilder");
}
};
}
public void commit() { public void commit() {
if (delegateTo != null) {
log.fine("Ignoring commit() because this is a fake provider");
return;
}
if (connection != null) { if (connection != null) {
try { try {
if (!options.flavor().isAutoCommitOnly()) { if (!builder.options.flavor().isAutoCommitOnly()) {
connection.commit(); connection.commit();
} }
} catch (Exception e) { } catch (Exception e) {
@ -875,31 +792,22 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
public void commitAndClose() { public void commitAndClose() {
if (delegateTo != null) { try {
log.fine("Ignoring commitAndClose() because this is a fake provider"); if (connection != null) {
return; if (!builder.options.flavor().isAutoCommitOnly()) {
}
if (connection != null) {
try {
if (!options.flavor().isAutoCommitOnly()) {
connection.commit(); connection.commit();
} }
} catch (Exception e) {
throw new DatabaseException("Unable to commit the transaction", e);
} }
close(); close();
} catch (Exception e) {
throw new DatabaseException("Unable to commit the transaction", e);
} }
} }
public void rollback() { public void rollback() {
if (delegateTo != null) {
log.fine("Ignoring rollback() because this is a fake provider");
return;
}
if (connection != null) { if (connection != null) {
try { try {
if (!options.flavor().isAutoCommitOnly()) { if (!builder.options.flavor().isAutoCommitOnly()) {
connection.rollback(); connection.rollback();
} }
} catch (Exception e) { } catch (Exception e) {
@ -909,24 +817,20 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
public void rollbackAndClose() { public void rollbackAndClose() {
if (delegateTo != null) { try {
log.fine("Ignoring rollbackAndClose() because this is a fake provider"); if (connection != null) {
return; if (!builder.options.flavor().isAutoCommitOnly()) {
}
if (connection != null) {
try {
if (!options.flavor().isAutoCommitOnly()) {
connection.rollback(); connection.rollback();
} }
} catch (Exception e) {
log.log(Level.SEVERE, "Unable to rollback the transaction", e);
} }
close(); close();
} catch (Exception e) {
log.log(Level.SEVERE, "Unable to rollback the transaction", e);
} }
} }
private void close() { @Override
public void close() throws IOException {
if (connection != null) { if (connection != null) {
try { try {
connection.close(); connection.close();
@ -936,7 +840,7 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
connection = null; connection = null;
database = null; database = null;
connectionProvider = null; builder.close();
} }
/** /**
@ -944,15 +848,15 @@ public final class DatabaseProvider implements Supplier<Database> {
* the previous instance. This is intended to make it safe to pass builders * the previous instance. This is intended to make it safe to pass builders
* around without risk someone will reconfigure it. * around without risk someone will reconfigure it.
*/ */
public interface Builder { public interface DatabaseProviderBuilder {
Builder withOptions(OptionsOverride options); DatabaseProviderBuilder withOptions(OptionsOverride options);
/** /**
* Enable logging of parameter values along with the SQL. * Enable logging of parameter values along with the SQL.
*/ */
Builder withSqlParameterLogging(); DatabaseProviderBuilder withSqlParameterLogging();
/** /**
* Include SQL in exception messages. This will also include parameters in the * Include SQL in exception messages. This will also include parameters in the
@ -961,7 +865,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* dependent on how the exception are caught and handled. * dependent on how the exception are caught and handled.
*/ */
Builder withSqlInExceptionMessages(); DatabaseProviderBuilder withSqlInExceptionMessages();
/** /**
* Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is * Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is
@ -969,7 +873,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* own system clock that will be used for time travel. * own system clock that will be used for time travel.
*/ */
Builder withDatePerAppOnly(); DatabaseProviderBuilder withDatePerAppOnly();
/** /**
* Allow provided Database instances to explicitly control transactions using the * Allow provided Database instances to explicitly control transactions using the
@ -977,14 +881,14 @@ public final class DatabaseProvider implements Supplier<Database> {
* throw an exception. * throw an exception.
*/ */
Builder withTransactionControl(); DatabaseProviderBuilder withTransactionControl();
/** /**
* This can be useful when testing code, as it can pretend to use transactions, * This can be useful when testing code, as it can pretend to use transactions,
* while giving you control over whether it actually commits or rolls back. * while giving you control over whether it actually commits or rolls back.
*/ */
Builder withTransactionControlSilentlyIgnored(); DatabaseProviderBuilder withTransactionControlSilentlyIgnored();
/** /**
* Allow direct access to the underlying database connection. Normally this is * Allow direct access to the underlying database connection. Normally this is
@ -992,7 +896,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* legacy code that works with raw JDBC. * legacy code that works with raw JDBC.
*/ */
Builder withConnectionAccess(); DatabaseProviderBuilder withConnectionAccess();
/** /**
* WARNING: You should try to avoid using this method. If you use it more * WARNING: You should try to avoid using this method. If you use it more
@ -1060,31 +964,35 @@ public final class DatabaseProvider implements Supplier<Database> {
*/ */
void transact(DbCodeTx code); void transact(DbCodeTx code);
void close() throws IOException;
} }
private static class BuilderImpl implements Builder { private static class DatabaseProviderBuilderImpl implements DatabaseProviderBuilder, Closeable {
private DataSource ds; private DataSource dataSource;
private final Supplier<Connection> connectionProvider; private final Supplier<Connection> connectionProvider;
private final Options options; private final Options options;
private BuilderImpl(DataSource ds, Supplier<Connection> connectionProvider, Options options) { private final AtomicBoolean closed;
this.ds = ds;
private DatabaseProviderBuilderImpl(DataSource dataSource,
Supplier<Connection> connectionProvider,
Options options) {
this.dataSource = dataSource;
this.connectionProvider = connectionProvider; this.connectionProvider = connectionProvider;
this.options = options; this.options = options;
this.closed = new AtomicBoolean(false);
} }
@Override @Override
public Builder withOptions(OptionsOverride options) { public DatabaseProviderBuilder withOptions(OptionsOverride options) {
return new BuilderImpl(ds, connectionProvider, options.withParent(this.options)); return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, options.withParent(this.options));
} }
@Override @Override
public Builder withSqlParameterLogging() { public DatabaseProviderBuilder withSqlParameterLogging() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() { return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override @Override
public boolean isLogParameters() { public boolean isLogParameters() {
return true; return true;
@ -1093,8 +1001,8 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
@Override @Override
public Builder withSqlInExceptionMessages() { public DatabaseProviderBuilder withSqlInExceptionMessages() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() { return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override @Override
public boolean isDetailedExceptions() { public boolean isDetailedExceptions() {
return true; return true;
@ -1103,8 +1011,8 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
@Override @Override
public Builder withDatePerAppOnly() { public DatabaseProviderBuilder withDatePerAppOnly() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() { return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override @Override
public boolean useLocalDateTimeOnly() { public boolean useLocalDateTimeOnly() {
return true; return true;
@ -1113,8 +1021,8 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
@Override @Override
public Builder withTransactionControl() { public DatabaseProviderBuilder withTransactionControl() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() { return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override @Override
public boolean allowTransactionControl() { public boolean allowTransactionControl() {
return true; return true;
@ -1123,8 +1031,8 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
@Override @Override
public Builder withTransactionControlSilentlyIgnored() { public DatabaseProviderBuilder withTransactionControlSilentlyIgnored() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() { return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override @Override
public boolean ignoreTransactionControl() { public boolean ignoreTransactionControl() {
return true; return true;
@ -1133,8 +1041,8 @@ public final class DatabaseProvider implements Supplier<Database> {
} }
@Override @Override
public Builder withConnectionAccess() { public DatabaseProviderBuilder withConnectionAccess() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() { return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override @Override
public boolean allowConnectionAccess() { public boolean allowConnectionAccess() {
return true; return true;
@ -1144,7 +1052,7 @@ public final class DatabaseProvider implements Supplier<Database> {
@Override @Override
public DatabaseProvider build() { public DatabaseProvider build() {
return new DatabaseProvider(connectionProvider, options); return new DatabaseProvider(this);
} }
@Override @Override
@ -1164,12 +1072,18 @@ public final class DatabaseProvider implements Supplier<Database> {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (ds != null) { if (closed.compareAndSet(false, true)) {
if (ds instanceof Closeable) { if (dataSource != null) {
((Closeable) ds).close(); if (dataSource instanceof Closeable) {
((Closeable) dataSource).close();
}
dataSource = null;
} }
ds = null;
} }
} }
public boolean isClosed() {
return closed.get();
}
} }
} }

View file

@ -3,8 +3,8 @@ package org.xbib.jdbc.query;
import org.xbib.jdbc.query.util.DebugSql; import org.xbib.jdbc.query.util.DebugSql;
import org.xbib.jdbc.query.util.Metric; import org.xbib.jdbc.query.util.Metric;
import java.sql.CallableStatement;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement; import java.sql.Statement;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -13,8 +13,6 @@ public class DdlImpl implements Ddl {
private static final Logger log = Logger.getLogger(Database.class.getName()); private static final Logger log = Logger.getLogger(Database.class.getName());
private static final Logger logQuiet = Logger.getLogger(Database.class.getName() + ".quiet");
private final Connection connection; private final Connection connection;
private final String sql; private final String sql;
@ -28,15 +26,15 @@ public class DdlImpl implements Ddl {
} }
private void updateInternal(boolean quiet) { private void updateInternal(boolean quiet) {
CallableStatement ps = null; PreparedStatement preparedStatement = null;
Metric metric = new Metric(log.isLoggable(Level.FINE)); Metric metric = new Metric(log.isLoggable(Level.FINE));
boolean isSuccess = false; boolean isSuccess = false;
String errorCode = null; String errorCode = null;
Exception logEx = null; Exception logEx = null;
try { try {
ps = connection.prepareCall(sql); preparedStatement = connection.prepareStatement(sql);
metric.checkpoint("prep"); metric.checkpoint("prep");
ps.execute(); preparedStatement.execute();
metric.checkpoint("exec"); metric.checkpoint("exec");
isSuccess = true; isSuccess = true;
} catch (Exception e) { } catch (Exception e) {
@ -44,15 +42,13 @@ public class DdlImpl implements Ddl {
logEx = e; logEx = e;
throw DatabaseException.wrap(DebugSql.exceptionMessage(sql, null, errorCode, options), e); throw DatabaseException.wrap(DebugSql.exceptionMessage(sql, null, errorCode, options), e);
} finally { } finally {
close(ps); close(preparedStatement);
metric.checkpoint("close"); metric.checkpoint("close");
// PostgreSQL requires explicit commit since we are running with setAutoCommit(false) // PostgreSQL requires explicit commit since we are running with setAutoCommit(false)
commit(connection); commit(connection);
metric.done("commit"); metric.done("commit");
if (isSuccess) { if (isSuccess) {
DebugSql.logSuccess("DDL", log, metric, sql, null, options); DebugSql.logSuccess("DDL", log, metric, sql, null, options);
} else if (quiet) {
DebugSql.logWarning("DDL", logQuiet, metric, errorCode, sql, null, options, logEx);
} else { } else {
DebugSql.logError("DDL", log, metric, errorCode, sql, null, options, logEx); DebugSql.logError("DDL", log, metric, errorCode, sql, null, options, logEx);
} }

View file

@ -1,5 +1,7 @@
package org.xbib.jdbc.query; package org.xbib.jdbc.query;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ServiceLoader; import java.util.ServiceLoader;
public interface Flavor { public interface Flavor {
@ -24,8 +26,12 @@ public interface Flavor {
String typeFloat(); String typeFloat();
void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException;
String typeDouble(); String typeDouble();
void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException;
String typeBigDecimal(int size, int precision); String typeBigDecimal(int size, int precision);
String typeStringVar(int length); String typeStringVar(int length);

View file

@ -1,6 +1,6 @@
package org.xbib.jdbc.query; package org.xbib.jdbc.query;
import org.xbib.jdbc.query.flavor.Postgresql; import org.xbib.jdbc.query.flavor.Derby;
/** /**
* Base class for selectively overriding another Options object. * Base class for selectively overriding another Options object.
@ -18,10 +18,10 @@ public class OptionsOverride implements Options {
} }
/** /**
* Defer to OptionsDefault for anything that is not specified, and use postgresql flavor. * Defer to OptionsDefault for anything that is not specified.
*/ */
public OptionsOverride() { public OptionsOverride() {
parent = new OptionsDefault(new Postgresql()); parent = new OptionsDefault(new Derby());
} }
/** /**

View file

@ -5,8 +5,6 @@ import org.xbib.jdbc.query.Schema.Table.Column;
import org.xbib.jdbc.query.Schema.Table.ForeignKey; import org.xbib.jdbc.query.Schema.Table.ForeignKey;
import org.xbib.jdbc.query.Schema.Table.Index; import org.xbib.jdbc.query.Schema.Table.Index;
import org.xbib.jdbc.query.Schema.Table.Unique; import org.xbib.jdbc.query.Schema.Table.Unique;
import org.xbib.jdbc.query.flavor.Oracle;
import org.xbib.jdbc.query.flavor.Postgresql;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
@ -93,13 +91,14 @@ public class Schema {
table.addColumn(names[i]).asLong(); table.addColumn(names[i]).asLong();
break; break;
case Types.REAL: case Types.REAL:
case 100: // Oracle proprietary it seems case 100: // Oracle "binary float"
table.addColumn(names[i]).asFloat(); table.addColumn(names[i]).asFloat();
break; break;
case Types.DOUBLE: case Types.DOUBLE:
case 101: // Oracle proprietary it seems case 101: // Oracle "binary double"
table.addColumn(names[i]).asDouble(); table.addColumn(names[i]).asDouble();
break; break;
case Types.DECIMAL:
case Types.NUMERIC: case Types.NUMERIC:
int precision1 = metadata.getPrecision(i + 1); int precision1 = metadata.getPrecision(i + 1);
int scale = metadata.getScale(i + 1); int scale = metadata.getScale(i + 1);
@ -121,6 +120,7 @@ public class Schema {
break; break;
case Types.BINARY: case Types.BINARY:
case Types.VARBINARY: case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB: case Types.BLOB:
table.addColumn(names[i]).asBlob(); table.addColumn(names[i]).asBlob();
break; break;
@ -128,14 +128,12 @@ public class Schema {
case Types.NCLOB: case Types.NCLOB:
table.addColumn(names[i]).asClob(); table.addColumn(names[i]).asClob();
break; break;
// The date type is used for a true date - no time info. // The date type is used for a true date - no time info.
// It must be checked before TimeStamp because sql dates are also // It must be checked before TimeStamp because sql dates are also
// recognized as sql timestamp. // recognized as sql timestamp.
case Types.DATE: case Types.DATE:
table.addColumn(names[i]).asLocalDate(); table.addColumn(names[i]).asLocalDate();
break; break;
// This is the type dates and times with time and time zone associated. // This is the type dates and times with time and time zone associated.
// Note that Oracle dates are always really Timestamps. // Note that Oracle dates are always really Timestamps.
case Types.TIMESTAMP: case Types.TIMESTAMP:
@ -149,9 +147,9 @@ public class Schema {
table.addColumn(names[i]).asLocalDateTime(); table.addColumn(names[i]).asLocalDateTime();
} }
break; break;
case Types.NVARCHAR: case Types.NVARCHAR:
case Types.VARCHAR: case Types.VARCHAR:
case Types.LONGVARCHAR:
int precision = metadata.getPrecision(i + 1); int precision = metadata.getPrecision(i + 1);
if (precision >= 2147483647) { if (precision >= 2147483647) {
// Postgres seems to report clobs are varchar(2147483647) // Postgres seems to report clobs are varchar(2147483647)
@ -160,8 +158,8 @@ public class Schema {
table.addColumn(names[i]).asString(precision); table.addColumn(names[i]).asString(precision);
} }
break; break;
case Types.CHAR:
case Types.NCHAR: case Types.NCHAR:
case Types.CHAR:
table.addColumn(names[i]).asStringFixed(metadata.getPrecision(i + 1)); table.addColumn(names[i]).asStringFixed(metadata.getPrecision(i + 1));
break; break;
default: default:
@ -184,12 +182,10 @@ public class Schema {
private String executeOrPrint(Database db, Flavor flavor) { private String executeOrPrint(Database db, Flavor flavor) {
validate(); validate();
if (flavor == null) { if (flavor == null) {
flavor = db.flavor(); flavor = db.flavor();
} }
StringBuilder script = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
for (Table table : tables) { for (Table table : tables) {
Sql sql = new Sql(); Sql sql = new Sql();
sql.append("create table ").append(table.name).append(" (\n"); sql.append("create table ").append(table.name).append(" (\n");
@ -228,10 +224,10 @@ public class Schema {
sql.append(flavor.typeStringFixed(column.scale)); sql.append(flavor.typeStringFixed(column.scale));
break; break;
case LocalDateTime: case LocalDateTime:
sql.append(flavor.typeLocalDateTime()); // Append a date with time sql.append(flavor.typeLocalDateTime());
break; break;
case LocalDate: case LocalDate:
sql.append(flavor.typeLocalDate()); // Append a true date - no time sql.append(flavor.typeLocalDate());
break; break;
case Clob: case Clob:
sql.append(flavor.typeClob()); sql.append(flavor.typeClob());
@ -244,7 +240,6 @@ public class Schema {
sql.append(" not null"); sql.append(" not null");
} }
} }
if (table.primaryKey != null) { if (table.primaryKey != null) {
sql.append(",\n constraint "); sql.append(",\n constraint ");
sql.append(rpad(table.primaryKey.name, 30)); sql.append(rpad(table.primaryKey.name, 30));
@ -278,16 +273,16 @@ public class Schema {
if (table.customClauses.containsKey(flavor)) { if (table.customClauses.containsKey(flavor)) {
sql.append(" ").append(table.customClauses.get(flavor)); sql.append(" ").append(table.customClauses.get(flavor));
} }
executeOrPrint(sql, db, script); executeOrPrint(sql, db, stringBuilder);
sql = new Sql(); sql = new Sql();
if (flavor instanceof Oracle || flavor instanceof Postgresql) { if ("oracle".equals(flavor.getName()) || "postgresql".equals(flavor.getName())) {
if (table.comment != null) { if (table.comment != null) {
sql.append("comment on table "); sql.append("comment on table ");
sql.append(table.name); sql.append(table.name);
sql.append(" is \n'"); sql.append(" is \n'");
sql.append(table.comment.replace("'", "''")); sql.append(table.comment.replace("'", "''"));
sql.append("'"); sql.append("'");
executeOrPrint(sql, db, script); executeOrPrint(sql, db, stringBuilder);
sql = new Sql(); sql = new Sql();
} }
for (Column c : table.columns) { for (Column c : table.columns) {
@ -299,7 +294,7 @@ public class Schema {
sql.append(" is \n'"); sql.append(" is \n'");
sql.append(c.comment.replace("'", "''")); sql.append(c.comment.replace("'", "''"));
sql.append("'"); sql.append("'");
executeOrPrint(sql, db, script); executeOrPrint(sql, db, stringBuilder);
sql = new Sql(); sql = new Sql();
} }
} }
@ -322,7 +317,7 @@ public class Schema {
if (fk.onDeleteCascade) { if (fk.onDeleteCascade) {
sql.append(" on delete cascade"); sql.append(" on delete cascade");
} }
executeOrPrint(sql, db, script); executeOrPrint(sql, db, stringBuilder);
} }
} }
for (Table table : tables) { for (Table table : tables) {
@ -342,10 +337,9 @@ public class Schema {
sql.append(name); sql.append(name);
} }
sql.listEnd(")"); sql.listEnd(")");
executeOrPrint(sql, db, script); executeOrPrint(sql, db, stringBuilder);
} }
} }
for (Sequence sequence : sequences) { for (Sequence sequence : sequences) {
Sql sql = new Sql(); Sql sql = new Sql();
sql.append("create sequence "); sql.append("create sequence ");
@ -362,11 +356,10 @@ public class Schema {
sql.append(flavor.sequenceCacheClause(sequence.cache)); sql.append(flavor.sequenceCacheClause(sequence.cache));
sql.append(flavor.sequenceOrderClause(sequence.order)); sql.append(flavor.sequenceOrderClause(sequence.order));
sql.append(flavor.sequenceCycleClause(sequence.cycle)); sql.append(flavor.sequenceCycleClause(sequence.cycle));
executeOrPrint(sql, db, script); executeOrPrint(sql, db, stringBuilder);
} }
if (db == null) { if (db == null) {
return script.toString(); return stringBuilder.toString();
} }
return null; return null;
} }
@ -865,12 +858,10 @@ public class Schema {
return asType(ColumnType.StringFixed); return asType(ColumnType.StringFixed);
} }
// This type is for dates that have time associated
public Column asLocalDateTime() { public Column asLocalDateTime() {
return asType(ColumnType.LocalDateTime); return asType(ColumnType.LocalDateTime);
} }
// This type is for true dates with no time associated
public Column asLocalDate() { public Column asLocalDate() {
return asType(ColumnType.LocalDate); return asType(ColumnType.LocalDate);
} }

View file

@ -668,6 +668,7 @@ public class SqlArgs implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Appl
case 101: // Oracle binary double case 101: // Oracle binary double
args.argDouble(names[i], r.getDoubleOrNull()); args.argDouble(names[i], r.getDoubleOrNull());
break; break;
case Types.DECIMAL:
case Types.NUMERIC: case Types.NUMERIC:
if (precision[i] <= 10 && scale[i] == 0) { if (precision[i] <= 10 && scale[i] == 0) {
args.argInteger(names[i], r.getIntegerOrNull()); args.argInteger(names[i], r.getIntegerOrNull());
@ -703,6 +704,8 @@ public class SqlArgs implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Appl
case Types.VARCHAR: case Types.VARCHAR:
case Types.CHAR: case Types.CHAR:
case Types.NCHAR: case Types.NCHAR:
case Types.LONGVARCHAR:
case Types.LONGNVARCHAR:
if (precision[i] >= 2147483647) { if (precision[i] >= 2147483647) {
// Postgres seems to report clobs are varchar(2147483647) // Postgres seems to report clobs are varchar(2147483647)
args.argClobString(names[i], r.getClobStringOrNull()); args.argClobString(names[i], r.getClobStringOrNull());

View file

@ -33,7 +33,7 @@ public class SqlInsertImpl implements SqlInsert {
private final Connection connection; private final Connection connection;
private final StatementAdaptor adaptor; private final StatementAdapter adaptor;
private final String sql; private final String sql;
@ -57,7 +57,7 @@ public class SqlInsertImpl implements SqlInsert {
this.connection = connection; this.connection = connection;
this.sql = sql; this.sql = sql;
this.options = options; this.options = options;
adaptor = new StatementAdaptor(options); adaptor = new StatementAdapter(options);
} }

View file

@ -28,7 +28,7 @@ public class SqlSelectImpl implements SqlSelect {
private final Connection connection; private final Connection connection;
private final StatementAdaptor adaptor; private final StatementAdapter adaptor;
private final Object cancelLock = new Object(); private final Object cancelLock = new Object();
@ -52,7 +52,7 @@ public class SqlSelectImpl implements SqlSelect {
this.connection = connection; this.connection = connection;
this.sql = sql; this.sql = sql;
this.options = options; this.options = options;
adaptor = new StatementAdaptor(options); adaptor = new StatementAdapter(options);
} }

View file

@ -31,7 +31,7 @@ public class SqlUpdateImpl implements SqlUpdate {
private final Connection connection; private final Connection connection;
private final StatementAdaptor adaptor; private final StatementAdapter adaptor;
private final String sql; private final String sql;
@ -47,7 +47,7 @@ public class SqlUpdateImpl implements SqlUpdate {
this.connection = connection; this.connection = connection;
this.sql = sql; this.sql = sql;
this.options = options; this.options = options;
adaptor = new StatementAdaptor(options); adaptor = new StatementAdapter(options);
} }
@Override @Override

View file

@ -22,13 +22,13 @@ import java.util.stream.Collectors;
/** /**
* Deal with mapping parameters into prepared statements. * Deal with mapping parameters into prepared statements.
*/ */
public class StatementAdaptor { public class StatementAdapter {
private static final Logger logger = Logger.getLogger(StatementAdaptor.class.getName()); private static final Logger logger = Logger.getLogger(StatementAdapter.class.getName());
private final Options options; private final Options options;
public StatementAdaptor(Options options) { public StatementAdapter(Options options) {
this.options = options; this.options = options;
} }
@ -90,9 +90,9 @@ public class StatementAdaptor {
ps.setBinaryStream(i + 1, (InputStream) parameter); ps.setBinaryStream(i + 1, (InputStream) parameter);
} }
} else if (parameter instanceof Float) { } else if (parameter instanceof Float) {
ps.setFloat(i + 1, (Float) parameter); options.flavor().setFloat(ps, i + 1, (Float) parameter);
} else if (parameter instanceof Double) { } else if (parameter instanceof Double) {
ps.setDouble(i + 1, (Double) parameter); options.flavor().setDouble(ps, i + 1, (Double) parameter);
} else { } else {
ps.setObject(i + 1, parameter); ps.setObject(i + 1, parameter);
} }

View file

@ -1,8 +1,6 @@
package org.xbib.jdbc.query; package org.xbib.jdbc.query;
import org.xbib.jdbc.query.flavor.Derby; import org.xbib.jdbc.query.flavor.Derby;
import org.xbib.jdbc.query.flavor.Oracle;
import org.xbib.jdbc.query.flavor.Postgresql;
import org.xbib.jdbc.query.flavor.SqlServer; import org.xbib.jdbc.query.flavor.SqlServer;
import java.util.Objects; import java.util.Objects;
@ -21,7 +19,7 @@ public class When {
} }
public When oracle(String sql) { public When oracle(String sql) {
if (actualFlavor instanceof Oracle) { if ("oracle".equals(actualFlavor.getName())) {
chosen = sql; chosen = sql;
} }
return this; return this;
@ -35,7 +33,7 @@ public class When {
} }
public When postgres(String sql) { public When postgres(String sql) {
if (actualFlavor instanceof Postgresql) { if ("postgresql".equals(actualFlavor.getName())) {
chosen = sql; chosen = sql;
} }
return this; return this;

View file

@ -1,177 +0,0 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
public class BigQuery implements Flavor {
@Override
public String getName() {
return "bigQuery";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:bigquery:");
}
@Override
public String driverClass() {
return "com.simba.googlebigquery.jdbc42.Driver";
}
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeInteger() {
return "int64";
}
@Override
public String typeBoolean() {
// BigQuery has a native boolean type, but we're not trying to use it
return "string";
}
@Override
public String typeLong() {
return "int64";
}
@Override
public String typeFloat() {
return "float64";
}
@Override
public String typeDouble() {
return "float64";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric";
}
@Override
public String typeStringVar(int length) {
return "string";
}
@Override
public String typeStringFixed(int length) {
return "string";
}
@Override
public String typeClob() {
return "string";
}
@Override
public String typeBlob() {
return "bytes";
}
@Override
public String typeLocalDateTime() {
return "datetime";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceDrop(String dbtestSeq) {
throw new UnsupportedOperationException();
}
@Override
public String tableDrop(String table) {
return "drop table " + table;
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String dbTimeMillis() {
return "current_timestamp()";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceOrderClause(boolean order) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceCycleClause(boolean cycle) {
throw new UnsupportedOperationException();
}
@Override
public String fromAny() {
return "";
}
@Override
public String sequenceOptions() {
throw new UnsupportedOperationException();
}
@Override
public boolean isAutoCommitOnly() {
return true;
}
@Override
public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String readNextFromQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String succeedInQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String failInQueue(String table) {
throw new UnsupportedOperationException();
}
}

View file

@ -2,6 +2,9 @@ package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Derby implements Flavor { public class Derby implements Flavor {
@Override @Override
@ -44,11 +47,21 @@ public class Derby implements Flavor {
return "real"; return "real";
} }
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override @Override
public String typeDouble() { public String typeDouble() {
return "double"; return "double";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";

View file

@ -2,6 +2,9 @@ package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class H2 implements Flavor { public class H2 implements Flavor {
@Override @Override
@ -44,11 +47,21 @@ public class H2 implements Flavor {
return "double"; return "double";
} }
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override @Override
public String typeDouble() { public String typeDouble() {
return "double"; return "double";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";

View file

@ -2,6 +2,9 @@ package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Hsql implements Flavor { public class Hsql implements Flavor {
@Override @Override
@ -44,11 +47,21 @@ public class Hsql implements Flavor {
return "double"; return "double";
} }
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override @Override
public String typeDouble() { public String typeDouble() {
return "double"; return "double";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";

View file

@ -2,6 +2,9 @@ package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.Flavor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class SqlServer implements Flavor { public class SqlServer implements Flavor {
@Override @Override
@ -29,11 +32,21 @@ public class SqlServer implements Flavor {
return "float(24)"; return "float(24)";
} }
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override @Override
public String typeDouble() { public String typeDouble() {
return "float(53)"; return "float(53)";
} }
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override @Override
public String typeBigDecimal(int size, int precision) { public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")"; return "numeric(" + size + "," + precision + ")";

View file

@ -1,8 +1,4 @@
org.xbib.jdbc.query.flavor.BigQuery
org.xbib.jdbc.query.flavor.Derby org.xbib.jdbc.query.flavor.Derby
org.xbib.jdbc.query.flavor.Hsql org.xbib.jdbc.query.flavor.Hsql
org.xbib.jdbc.query.flavor.H2 org.xbib.jdbc.query.flavor.H2
org.xbib.jdbc.query.flavor.MySQL
org.xbib.jdbc.query.flavor.Oracle
org.xbib.jdbc.query.flavor.Postgresql
org.xbib.jdbc.query.flavor.SqlServer org.xbib.jdbc.query.flavor.SqlServer

View file

@ -15,7 +15,7 @@ import org.xbib.jdbc.query.RowsHandler;
import org.xbib.jdbc.query.Schema; import org.xbib.jdbc.query.Schema;
import org.xbib.jdbc.query.Sql; import org.xbib.jdbc.query.Sql;
import org.xbib.jdbc.query.SqlArgs; import org.xbib.jdbc.query.SqlArgs;
import org.xbib.jdbc.query.StatementAdaptor; import org.xbib.jdbc.query.StatementAdapter;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -59,7 +59,7 @@ public abstract class CommonTest {
final static String TEST_TABLE_NAME = "dbtest"; final static String TEST_TABLE_NAME = "dbtest";
protected DatabaseProvider dbp; protected static DatabaseProvider dbp;
protected Database db; protected Database db;
@ -67,6 +67,8 @@ public abstract class CommonTest {
protected LocalDate localDateNow; protected LocalDate localDateNow;
protected abstract DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception;
@BeforeEach @BeforeEach
public void setupJdbc() throws Exception { public void setupJdbc() throws Exception {
now = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS); now = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS);
@ -77,8 +79,6 @@ public abstract class CommonTest {
db.dropTableQuietly(TEST_TABLE_NAME); db.dropTableQuietly(TEST_TABLE_NAME);
} }
protected abstract DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception;
@AfterEach @AfterEach
public void closeJdbc() { public void closeJdbc() {
if (dbp != null) { if (dbp != null) {
@ -92,12 +92,10 @@ public abstract class CommonTest {
String lowercaseTable = TEST_TABLE_NAME.toLowerCase(); String lowercaseTable = TEST_TABLE_NAME.toLowerCase();
testTableLookup(lowercaseTable); testTableLookup(lowercaseTable);
db.dropTableQuietly(lowercaseTable); db.dropTableQuietly(lowercaseTable);
// Let's try creating a table with an upper case name and verify it works // Let's try creating a table with an upper case name and verify it works
String uppercaseTable = TEST_TABLE_NAME.toUpperCase(); String uppercaseTable = TEST_TABLE_NAME.toUpperCase();
testTableLookup(uppercaseTable); testTableLookup(uppercaseTable);
db.dropTableQuietly(uppercaseTable); db.dropTableQuietly(uppercaseTable);
// Verify that null or empty name is handled gracefully // Verify that null or empty name is handled gracefully
assertFalse(db.tableExists(null)); assertFalse(db.tableExists(null));
assertFalse(db.tableExists("")); assertFalse(db.tableExists(""));
@ -106,7 +104,6 @@ public abstract class CommonTest {
private void testTableLookup(String tableName) { private void testTableLookup(String tableName) {
// Verify test table does not exist // Verify test table does not exist
assertFalse(db.tableExists(tableName)); assertFalse(db.tableExists(tableName));
// Create and verify it exists. // Create and verify it exists.
new Schema().addTable(tableName).addColumn("pk").primaryKey().schema().execute(db); new Schema().addTable(tableName).addColumn("pk").primaryKey().schema().execute(db);
assertTrue(db.tableExists(tableName)); assertTrue(db.tableExists(tableName));
@ -1529,7 +1526,7 @@ public abstract class CommonTest {
LocalDate lastStdDateSpring = LocalDate.of(2019, Month.MARCH, 9); LocalDate lastStdDateSpring = LocalDate.of(2019, Month.MARCH, 9);
LocalDate firstDSTDateSpring = LocalDate.of(2019, Month.MARCH, 10); LocalDate firstDSTDateSpring = LocalDate.of(2019, Month.MARCH, 10);
// Verify that the original LocalDate matches the driver SQL LocalDate generated. // Verify that the original LocalDate matches the driver SQL LocalDate generated.
StatementAdaptor adaptor = new StatementAdaptor(new OptionsDefault(db.flavor())); StatementAdapter adaptor = new StatementAdapter(new OptionsDefault(db.flavor()));
assertEquals(lastStdDateSpring.toString(), adaptor.nullLocalDate(lastStdDateSpring).toString()); assertEquals(lastStdDateSpring.toString(), adaptor.nullLocalDate(lastStdDateSpring).toString());
assertEquals(firstDSTDateSpring.toString(), adaptor.nullLocalDate(firstDSTDateSpring).toString()); assertEquals(firstDSTDateSpring.toString(), adaptor.nullLocalDate(firstDSTDateSpring).toString());
} }

View file

@ -120,7 +120,7 @@ public class ConfigTest {
@Test @Test
public void testStripPrefixConflict() { public void testStripPrefixConflict() {
Config config = ConfigSupplier.of().value("a.foo", "a").value("foo", "bar").removePrefix("a.").get(); Config config = ConfigSupplier.of().property("a.foo", "a").property("foo", "bar").removePrefix("a.").get();
assertEquals("bar", config.getString("foo")); assertEquals("bar", config.getString("foo"));
} }
@ -138,12 +138,12 @@ public class ConfigTest {
@Test @Test
public void testTidyValues() { public void testTidyValues() {
Config config = ConfigSupplier.of().value("foo", " a ").get(); Config config = ConfigSupplier.of().property("foo", " a ").get();
// Strip whitespace // Strip whitespace
assertEquals("a", config.getString("foo")); assertEquals("a", config.getString("foo"));
config = ConfigSupplier.of().value("foo", " ").value("foo", "").value("foo", null).value("foo", "a").get(); config = ConfigSupplier.of().property("foo", " ").property("foo", "").property("foo", null).property("foo", "a").get();
// Skip over the garbage ones // Skip over the garbage ones
assertEquals("a", config.getString("foo")); assertEquals("a", config.getString("foo"));
@ -152,35 +152,35 @@ public class ConfigTest {
@Test @Test
public void testBoolean() { public void testBoolean() {
// Case insensitive, allow either true/false or yes/no // Case insensitive, allow either true/false or yes/no
Config config = ConfigSupplier.of().value("foo", "tRuE").get(); Config config = ConfigSupplier.of().property("foo", "tRuE").get();
assertTrue(config.getBooleanOrFalse("foo")); assertTrue(config.getBooleanOrFalse("foo"));
assertTrue(config.getBooleanOrTrue("foo")); assertTrue(config.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("unknown")); assertFalse(config.getBooleanOrFalse("unknown"));
assertTrue(config.getBooleanOrTrue("unknown")); assertTrue(config.getBooleanOrTrue("unknown"));
config = ConfigSupplier.of().value("foo", "yEs").get(); config = ConfigSupplier.of().property("foo", "yEs").get();
assertTrue(config.getBooleanOrFalse("foo")); assertTrue(config.getBooleanOrFalse("foo"));
assertTrue(config.getBooleanOrTrue("foo")); assertTrue(config.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("unknown")); assertFalse(config.getBooleanOrFalse("unknown"));
assertTrue(config.getBooleanOrTrue("unknown")); assertTrue(config.getBooleanOrTrue("unknown"));
config = ConfigSupplier.of().value("foo", "fAlSe").get(); config = ConfigSupplier.of().property("foo", "fAlSe").get();
assertFalse(config.getBooleanOrFalse("foo")); assertFalse(config.getBooleanOrFalse("foo"));
assertFalse(config.getBooleanOrTrue("foo")); assertFalse(config.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("unknown")); assertFalse(config.getBooleanOrFalse("unknown"));
assertTrue(config.getBooleanOrTrue("unknown")); assertTrue(config.getBooleanOrTrue("unknown"));
config = ConfigSupplier.of().value("foo", "nO").get(); config = ConfigSupplier.of().property("foo", "nO").get();
assertFalse(config.getBooleanOrFalse("foo")); assertFalse(config.getBooleanOrFalse("foo"));
assertFalse(config.getBooleanOrTrue("foo")); assertFalse(config.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("unknown")); assertFalse(config.getBooleanOrFalse("unknown"));
assertTrue(config.getBooleanOrTrue("unknown")); assertTrue(config.getBooleanOrTrue("unknown"));
config = ConfigSupplier.of().value("foo", "bad value").get(); config = ConfigSupplier.of().property("foo", "bad value").get();
assertFalse(config.getBooleanOrFalse("foo")); assertFalse(config.getBooleanOrFalse("foo"));
assertTrue(config.getBooleanOrTrue("foo")); assertTrue(config.getBooleanOrTrue("foo"));
@ -190,7 +190,7 @@ public class ConfigTest {
@Test @Test
public void testInteger() { public void testInteger() {
Config config = ConfigSupplier.of().value("good", "123").value("bad", "hi").get(); Config config = ConfigSupplier.of().property("good", "123").property("bad", "hi").get();
assertEquals(Integer.valueOf(123), config.getInteger("good")); assertEquals(Integer.valueOf(123), config.getInteger("good"));
assertNull(config.getInteger("bad")); assertNull(config.getInteger("bad"));
assertNull(config.getInteger("missing")); assertNull(config.getInteger("missing"));
@ -201,7 +201,7 @@ public class ConfigTest {
@Test @Test
public void testLong() { public void testLong() {
Config config = ConfigSupplier.of().value("good", "123").value("bad", "hi").get(); Config config = ConfigSupplier.of().property("good", "123").property("bad", "hi").get();
assertEquals(Long.valueOf(123), config.getLong("good")); assertEquals(Long.valueOf(123), config.getLong("good"));
assertNull(config.getLong("bad")); assertNull(config.getLong("bad"));
@ -213,7 +213,7 @@ public class ConfigTest {
@Test @Test
public void testFloat() { public void testFloat() {
Config config = ConfigSupplier.of().value("good", "123.45").value("bad", "hi").get(); Config config = ConfigSupplier.of().property("good", "123.45").property("bad", "hi").get();
assertEquals(Float.valueOf(123.45f), config.getFloat("good")); assertEquals(Float.valueOf(123.45f), config.getFloat("good"));
assertNull(config.getFloat("bad")); assertNull(config.getFloat("bad"));
@ -225,7 +225,7 @@ public class ConfigTest {
@Test @Test
public void testDouble() { public void testDouble() {
Config config = ConfigSupplier.of().value("good", "123.45").value("bad", "hi").get(); Config config = ConfigSupplier.of().property("good", "123.45").property("bad", "hi").get();
assertEquals(Double.valueOf(123.45), config.getDouble("good")); assertEquals(Double.valueOf(123.45), config.getDouble("good"));
assertNull(config.getDouble("bad")); assertNull(config.getDouble("bad"));
@ -237,7 +237,7 @@ public class ConfigTest {
@Test @Test
public void testBigDecimal() { public void testBigDecimal() {
Config config = ConfigSupplier.of().value("good", "123.45").value("bad", "hi").get(); Config config = ConfigSupplier.of().property("good", "123.45").property("bad", "hi").get();
assertEquals(new BigDecimal("123.45"), config.getBigDecimal("good")); assertEquals(new BigDecimal("123.45"), config.getBigDecimal("good"));
assertNull(config.getBigDecimal("bad")); assertNull(config.getBigDecimal("bad"));

View file

@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
/** /**
* Exercise Database functionality with a real database (Derby). * Exercise Database functionality with a real database (Derby).
*/ */
@Disabled
public class DerbyTest extends CommonTest { public class DerbyTest extends CommonTest {
private static final Logger logger = Logger.getLogger(DerbyTest.class.getName()); private static final Logger logger = Logger.getLogger(DerbyTest.class.getName());

View file

@ -1,44 +0,0 @@
package org.xbib.jdbc.query.test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.OracleContainer;
import org.testcontainers.junit.jupiter.Container;
import org.xbib.jdbc.query.DatabaseProvider;
import org.xbib.jdbc.query.OptionsOverride;
import java.io.FileReader;
import java.util.Properties;
/**
* Exercise Database functionality with a real Oracle database.
*/
@Disabled
public class OracleTest extends CommonTest {
@Container
public OracleContainer oracleContainer = new OracleContainer("");
@Override
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception {
Properties properties = new Properties();
properties.load(new FileReader(System.getProperty("local.properties", "local.properties")));
return DatabaseProvider.builder(
properties.getProperty("database.url"),
properties.getProperty("database.user"),
properties.getProperty("database.password")
).withSqlParameterLogging().withSqlInExceptionMessages().withOptions(options).build();
}
@Disabled("Current Oracle behavior is to convert -0f to 0f")
@Test
public void argFloatNegativeZero() {
super.argFloatNegativeZero();
}
@Disabled("Current Oracle behavior is to convert -0d to 0d")
@Test
public void argDoubleNegativeZero() {
super.argDoubleNegativeZero();
}
}

View file

@ -1,52 +0,0 @@
package org.xbib.jdbc.query.test;
import java.io.IOException;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.xbib.jdbc.query.DatabaseProvider;
import org.xbib.jdbc.query.OptionsOverride;
import org.xbib.jdbc.query.Schema;
import java.io.FileReader;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
/**
* Exercise Database functionality with a real PostgreSQL database.
*/
@Disabled
public class PostgreSqlTest extends CommonTest {
@Override
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) throws IOException {
Properties properties = new Properties();
properties.load(new FileReader(System.getProperty("local.properties", "local.properties")));
return DatabaseProvider.builder(
properties.getProperty("postgres.database.url"),
properties.getProperty("postgres.database.user"),
properties.getProperty("postgres.database.password")
).withOptions(options).withSqlParameterLogging().withSqlInExceptionMessages().build();
}
/**
* PostgreSQL seems to have different behavior in that is does not convert
* column names to uppercase (it actually converts them to lowercase).
* I haven't figured out how to smooth over this difference, since all databases
* seem to respect the provided case when it is inside quotes, but don't provide
* a way to tell whether a particular parameter was quoted.
*/
@Override
@Test
public void metadataColumnNames() {
db.dropTableQuietly("dbtest");
new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db);
db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest")
.query(rs -> {
assertArrayEquals(new String[]{"pk", "foo", "Foo"}, rs.getColumnLabels());
return null;
});
}
}

View file

@ -12,7 +12,7 @@ public abstract class DerbyExample {
// For subclasses to override // For subclasses to override
} }
void example(DatabaseProvider.Builder dbb, final String[] args) { void example(DatabaseProvider.DatabaseProviderBuilder dbb, final String[] args) {
dbb.transact(db -> { dbb.transact(db -> {
example(db.get(), args); example(db.get(), args);
}); });

View file

@ -1,65 +0,0 @@
package org.xbib.jdbc.query.test.example;
import org.xbib.jdbc.query.DatabaseException;
import org.xbib.jdbc.query.DatabaseProvider;
import org.xbib.jdbc.query.Schema;
/**
* Demo of how to use the {@code DatabaseProvider.fakeBuilder()} to control
* transactions for testing purposes.
*/
public class FakeBuilder extends DerbyExample {
public static void main(String[] args) {
new FakeBuilder().launch(args);
}
void example(DatabaseProvider.Builder dbb, String[] args) {
DatabaseProvider realDbp = null;
try {
realDbp = dbb.build();
dbb.transact(db -> {
// Drops in case we are running this multiple times
db.get().dropTableQuietly("t");
// Create and populate a simple table
new Schema().addTable("t").addColumn("pk").primaryKey().schema().execute(db.get());
});
DatabaseProvider.Builder fakeBuilder = realDbp.fakeBuilder();
// Trying all three transact methods, just for completeness
fakeBuilder.transact(db -> {
db.get().toInsert("insert into t (pk) values (?)").argLong(1L).insert(1);
});
fakeBuilder.transact((db, tx) -> {
db.get().toInsert("insert into t (pk) values (?)").argLong(2L).insert(1);
});
fakeBuilder.transact(db -> {
println("Rows before rollback: " + db.get().toSelect("select count(*) from t").queryLongOrZero());
});
realDbp.rollbackAndClose();
// Can't use fakeBuilder after close
try {
fakeBuilder.transact(db -> {
db.get().tableExists("foo");
println("Eeek...shouldn't get here!");
});
} catch (DatabaseException e) {
println("Correctly threw exception: " + e.getMessage());
}
dbb.transact(db -> {
println("Rows after rollback: " + db.get().toSelect("select count(*) from t").queryLongOrZero());
});
} finally {
if (realDbp != null) {
realDbp.rollbackAndClose();
}
}
}
}

View file

@ -5,5 +5,6 @@ java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level=ALL java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern=build/marc.log java.util.logging.FileHandler.pattern=build/database.log
jdk.event.security.level=INFO jdk.event.security.level=INFO
javax.management.level=INFO

5
jdbc-test/build.gradle Normal file
View file

@ -0,0 +1,5 @@
dependencies {
api project(":jdbc-query")
implementation libs.junit.jupiter.api
implementation libs.hamcrest
}

File diff suppressed because it is too large Load diff

View file

@ -2,23 +2,32 @@ dependencyResolutionManagement {
versionCatalogs { versionCatalogs {
libs { libs {
version('gradle', '7.5.1') version('gradle', '7.5.1')
version('junit', '5.8.2') version('junit', '5.9.1')
version('testcontainers', '1.17.3') version('testcontainers', '1.17.5')
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit') library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit') library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit')
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit') library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2') library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2')
library('junit4', 'junit', 'junit').version('4.13.2') library('junit4', 'junit', 'junit').version('4.13.2')
library('derby', 'org.apache.derby', 'derby').version('10.15.2.0') library('derby', 'org.apache.derby', 'derby').version('10.16.1.1')
library('hsqldb', 'org.hsqldb', 'hsqldb').version('2.7.1') library('hsqldb', 'org.hsqldb', 'hsqldb').version('2.7.1')
library('h2', 'com.h2database', 'h2').version('1.4.200') library('h2', 'com.h2database', 'h2').version('2.1.214')
library('mariadb', 'org.mariadb.jdbc', 'mariadb-java-client').version('3.0.8')
library('oracle', 'com.oracle.database.jdbc','ojdbc11').version('21.7.0.0')
library('postgresql', 'org.postgresql', 'postgresql').version('42.5.0')
library('mockito-core', 'org.mockito', 'mockito-core').version('4.8.1')
library('testcontainers', 'org.testcontainers', 'testcontainers').versionRef('testcontainers') library('testcontainers', 'org.testcontainers', 'testcontainers').versionRef('testcontainers')
library('testcontainers-junit-jupiter', 'org.testcontainers', 'junit-jupiter').versionRef('testcontainers') library('testcontainers-junit-jupiter', 'org.testcontainers', 'junit-jupiter').versionRef('testcontainers')
library('testcontainers-mariadb', 'org.testcontainers', 'mariadb').versionRef('testcontainers')
library('testcontainers-oracle-xe', 'org.testcontainers', 'oracle-xe').versionRef('testcontainers') library('testcontainers-oracle-xe', 'org.testcontainers', 'oracle-xe').versionRef('testcontainers')
library('mockito-core', 'org.mockito', 'mockito-core').version('4.6.1') library('testcontainers-postgresql', 'org.testcontainers', 'postgresql').versionRef('testcontainers')
} }
} }
} }
include 'jdbc-connection-pool' include 'jdbc-connection-pool'
include 'jdbc-query' include 'jdbc-query'
include 'jdbc-test'
include 'jdbc-mariadb'
include 'jdbc-oracle'
include 'jdbc-postgresql'