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 {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
compileTestJava {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
jar {
@ -35,7 +35,7 @@ artifacts {
}
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:all,-fallthrough'
options.compilerArgs << '-Xlint:all,-fallthrough,-exports,-try'
}
javadoc {

View file

@ -1,6 +1,6 @@
module org.xbib.jdbc.connection.pool {
requires java.logging;
requires transitive java.sql;
requires java.sql;
exports org.xbib.jdbc.connection.pool;
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 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 final Properties properties;
@ -100,20 +92,28 @@ public class PoolConfig {
this.properties = properties;
this.minIdle = -1;
this.maxPoolSize = -1;
this.maxLifetime = MAX_LIFETIME;
this.connectionTimeout = CONNECTION_TIMEOUT;
this.validationTimeout = VALIDATION_TIMEOUT;
this.idleTimeout = IDLE_TIMEOUT;
this.maxLifetime = TimeUnit.MINUTES.toMillis(30);
this.connectionTimeout = TimeUnit.SECONDS.toMillis(30);
this.validationTimeout = TimeUnit.SECONDS.toMillis(5);
this.idleTimeout = TimeUnit.MINUTES.toMillis(1);
this.initializationFailTimeout = -1;
this.isAutoCommit = true;
this.isAutoCommit = true; // JDBC convention
this.aliveBypassWindowMs = TimeUnit.MILLISECONDS.toMillis(500);
this.housekeepingPeriodMs = TimeUnit.SECONDS.toMillis(30);
}
/**
* Set the JDBC URL.
* @param url the JDBC url as string
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Get the JDBC URL.
* @return the JDBC URL as string
*/
public String getUrl() {
return url;
}
@ -407,7 +407,7 @@ public class PoolConfig {
* case that a connection cannot be obtained. However, upon start the pool will
* attempt to obtain a connection and validate that the {@code connectionTestQuery}
* 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
* background. This can mean that callers to {@code DataSource#getConnection()} may
* 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
* 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
*/
@ -442,7 +442,7 @@ public class PoolConfig {
/**
* 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
*/
@ -450,6 +450,15 @@ public class PoolConfig {
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.
*
@ -459,19 +468,6 @@ public class PoolConfig {
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
* to uniquely identify the pool configuration.
@ -482,6 +478,10 @@ public class PoolConfig {
this.poolName = poolName;
}
public String getPoolName() {
return poolName;
}
/**
* Get the ScheduledExecutorService used for housekeeping.
*
@ -628,26 +628,26 @@ public class PoolConfig {
private void validateNumerics() {
if (maxLifetime != 0 && maxLifetime < TimeUnit.SECONDS.toMillis(30)) {
logger.log(Level.WARNING, "maxLifetime is less than 30000ms, setting to default ms: " +
poolName + " " + MAX_LIFETIME);
maxLifetime = MAX_LIFETIME;
logger.log(Level.WARNING, "maxLifetime is less than 30s, setting to default ms: " +
poolName + " " + TimeUnit.SECONDS.toMillis(30));
maxLifetime = TimeUnit.SECONDS.toMillis(30);
}
if (leakDetectionThreshold > 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);
leakDetectionThreshold = 0;
}
}
if (connectionTimeout < 250) {
logger.log(Level.WARNING, "connectionTimeout is less than 250ms, setting to ms: " +
poolName + " " + CONNECTION_TIMEOUT);
connectionTimeout = CONNECTION_TIMEOUT;
poolName + " " + TimeUnit.MILLISECONDS.toMillis(250));
connectionTimeout = TimeUnit.MILLISECONDS.toMillis(250);
}
if (validationTimeout < 250) {
logger.log(Level.WARNING, "validationTimeout is less than 250ms, setting to ms" +
poolName + " " + VALIDATION_TIMEOUT);
validationTimeout = VALIDATION_TIMEOUT;
poolName + " " + TimeUnit.MILLISECONDS.toMillis(250));
validationTimeout = TimeUnit.MILLISECONDS.toMillis(250);
}
if (maxPoolSize < 1) {
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);
idleTimeout = 0;
} else if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(10) && minIdle < maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout is less than 10000ms, setting to default ms: " +
poolName + " " + IDLE_TIMEOUT);
idleTimeout = IDLE_TIMEOUT;
} else if (idleTimeout != IDLE_TIMEOUT && idleTimeout != 0 && minIdle == maxPoolSize) {
logger.log(Level.WARNING, "idleTimeout is less than 10s, setting to default ms: " +
poolName + " " + TimeUnit.SECONDS.toMillis(10));
idleTimeout = TimeUnit.SECONDS.toMillis(10);
} 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);
}
}

View file

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

View file

@ -31,6 +31,7 @@ public class ConnectionCloseBlockingTest {
@Test
public void testConnectionCloseBlocking() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
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})",
minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs));
final PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(minIdle);
config.setMaximumPoolSize(maxPoolSize);
config.setInitializationFailTimeout(Long.MAX_VALUE);

View file

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

View file

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

View file

@ -34,6 +34,7 @@ public class ConnectionTest {
@Test
public void testCreate() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTestQuery("VALUES 1");
@ -69,6 +70,7 @@ public class ConnectionTest {
@Test
public void testMaxLifetime() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(1500);
@ -110,6 +112,7 @@ public class ConnectionTest {
@Test
public void testMaxLifetime2() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
@ -149,6 +152,7 @@ public class ConnectionTest {
@Test
public void testDoubleClose() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
@ -167,6 +171,7 @@ public class ConnectionTest {
@Test
public void testEviction() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500);
@ -184,11 +189,11 @@ public class ConnectionTest {
@Test
public void testEviction2() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
//config.setExceptionOverrideClassName(OverrideHandler.class.getName());
try (PoolDataSource ds = new PoolDataSource(config)) {
Pool pool = ds.getPool();
while (pool.getTotalConnections() < 5) {
@ -214,6 +219,7 @@ public class ConnectionTest {
@Test
public void testEviction3() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMaximumPoolSize(5);
config.setConnectionTimeout(2500);
config.setConnectionTestQuery("VALUES 1");
@ -243,6 +249,7 @@ public class ConnectionTest {
@Test
public void testEvictAllRefill() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5);
config.setMaximumPoolSize(10);
config.setConnectionTimeout(2500);
@ -268,6 +275,7 @@ public class ConnectionTest {
@Test
public void testBackfill() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(4);
config.setConnectionTimeout(1000);
@ -309,6 +317,7 @@ public class ConnectionTest {
@Test
public void testMaximumPoolLimit() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(4);
config.setConnectionTimeout(20000);
@ -348,6 +357,7 @@ public class ConnectionTest {
@Test
public void testOldDriver() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
@ -375,6 +385,7 @@ public class ConnectionTest {
StubDataSource stubDataSource = new StubDataSource();
stubDataSource.setThrowException(new SQLException("Connection refused"));
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2500);
@ -394,6 +405,7 @@ public class ConnectionTest {
StubDataSource stubDataSource = new StubDataSource();
stubDataSource.setThrowException(new SQLException("Connection refused"));
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource);
@ -420,6 +432,7 @@ public class ConnectionTest {
}
};
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
@ -448,6 +461,7 @@ public class ConnectionTest {
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
stubDataSource.setErrorOnConnection(true);
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setConnectionTestQuery("VALUES 1");
config.setDataSource(stubDataSource);
@ -465,6 +479,7 @@ public class ConnectionTest {
public void testDataSourceRaisesErrorAfterInitializationTestQuery() throws Exception {
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
@ -486,6 +501,7 @@ public class ConnectionTest {
@Test
public void testPopulationSlowAcquisition() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMaximumPoolSize(20);
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
@ -515,6 +531,7 @@ public class ConnectionTest {
@Test
public void testMinimumIdleZero() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(5);
config.setConnectionTimeout(1000L);
@ -545,11 +562,4 @@ public class ConnectionTest {
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
public void testConnectionRetries() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2800);
@ -46,6 +47,7 @@ public class ConnectionTimeoutRetryTest {
@Test
public void testConnectionRetries2() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(1);
config.setConnectionTimeout(2800);
@ -76,6 +78,7 @@ public class ConnectionTimeoutRetryTest {
@Test
public void testConnectionRetries3() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(2800);
@ -114,6 +117,7 @@ public class ConnectionTimeoutRetryTest {
@Test
public void testConnectionRetries5() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(0);
config.setMaximumPoolSize(2);
config.setConnectionTimeout(1000);
@ -151,6 +155,7 @@ public class ConnectionTimeoutRetryTest {
public void testConnectionIdleFill() throws Exception {
StubConnection.slowCreate = false;
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(5);
config.setMaximumPoolSize(10);
config.setConnectionTimeout(2000);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,6 +19,7 @@ public class UnwrapTest {
@Test
public void testUnwrapConnection() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
config.setInitializationFailTimeout(0);
@ -37,6 +38,7 @@ public class UnwrapTest {
@Test
public void testUnwrapDataSource() throws Exception {
PoolConfig config = new PoolConfig();
config.setUrl("jdbc:stub");
config.setMinimumIdle(1);
config.setMaximumPoolSize(1);
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;
public class MySQL implements Flavor {
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class MariaDB implements Flavor {
public MariaDB() {
}
@Override
public String getName() {
return "mysql";
return "mariadb";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:mysql:");
return url.startsWith("jdbc:mariadb:");
}
@Override
public String driverClass() {
return "com.mysql.jdbc.Driver";
return "org.mariadb.jdbc.Driver";
}
@Override
public boolean isNormalizedUpperCase() {
return true;
return false;
}
@Override
@ -41,7 +47,12 @@ public class MySQL implements Flavor {
@Override
public String typeFloat() {
return "real";
return "double";
}
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override
@ -49,6 +60,11 @@ public class MySQL implements Flavor {
return "double";
}
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
@ -56,6 +72,9 @@ public class MySQL implements Flavor {
@Override
public String typeStringVar(int length) {
if (length == 16777215) {
return "mediumtext";
}
return "varchar(" + length + ")";
}
@ -66,17 +85,17 @@ public class MySQL implements Flavor {
@Override
public String typeClob() {
return "clob";
return "mediumtext"; // mediumtext = varchar(16777215), longtext = varchar(0)
}
@Override
public String typeBlob() {
return "blob";
return "mediumblob";
}
@Override
public String typeLocalDateTime() {
return "timestamp";
return "datetime(3)";
}
@Override
@ -86,42 +105,32 @@ public class MySQL implements Flavor {
@Override
public boolean useStringForClob() {
return false;
return true;
}
@Override
public boolean useBytesForBlob() {
return false;
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName;
return "next value for " + sequenceName + "";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "values next value for " + sequenceName;
return "select " + sequenceNextVal(sequenceName) + fromAny();
}
@Override
public String sequenceDrop(String sequenceName) {
return "drop sequence " + sequenceName + " restrict";
public String sequenceDrop(String dbtestSeq) {
return "drop sequence if exists " + dbtestSeq;
}
@Override
public String tableDrop(String table) {
return "drop table " + table;
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return "";
return "drop table if exists " + table;
}
@Override
@ -131,22 +140,32 @@ public class MySQL implements Flavor {
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String dbTimeMillis() {
return "current_timestamp";
return "";
}
@Override
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
public String sequenceOptions() {
return " as bigint";
return "";
}
@Override
@ -156,22 +175,25 @@ public class MySQL implements Flavor {
@Override
public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException();
return "insert into " + table
+ " (channel, data, state, delay, modified) "
+ "values (?, ?, null, 0, ?)";
}
@Override
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
public String succeedInQueue(String table) {
throw new UnsupportedOperationException();
return "update" + table + " set state = 'OK', modified = " + dbTimeMillis() + " where id = ?";
}
@Override
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 java.sql.PreparedStatement;
import java.sql.SQLException;
public class Oracle implements Flavor {
public Oracle() {
}
@Override
public String getName() {
return "oracle";
@ -29,11 +37,31 @@ public class Oracle implements Flavor {
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
public String typeDouble() {
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
public String typeBigDecimal(int size, int 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 java.sql.PreparedStatement;
import java.sql.SQLException;
public class Postgresql implements Flavor {
public Postgresql() {
}
@Override
public String getName() {
return "postgresql";
@ -44,11 +50,21 @@ public class Postgresql implements Flavor {
return "real";
}
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override
public String typeDouble() {
return "double precision";
}
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override
public String typeBigDecimal(int size, int 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.testcontainers
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.Derby;
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;
module org.xbib.jdbc.query {
uses Flavor;
requires transitive org.xbib.jdbc.connection.pool;
requires org.xbib.jdbc.connection.pool;
requires java.sql;
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 value(String key, String value);
ConfigSupplier systemProperties();
ConfigSupplier property(String key, String value);
ConfigSupplier env();
ConfigSupplier systemProperties();
ConfigSupplier properties(Properties properties);
ConfigSupplier config(Config config);

View file

@ -47,7 +47,7 @@ public class ConfigSupplierImpl implements ConfigSupplier {
}
@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 + ")");
}

View file

@ -259,17 +259,17 @@ public class DatabaseImpl implements Database {
}
Duration duration = Duration.between(appDate, dbDate).abs();
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: "
+ dbDate);
}
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: "
+ dbDate);
}
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: "
+ dbDate);
}

View file

@ -16,6 +16,7 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
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
* 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 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 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;
private DatabaseProvider(DatabaseProviderBuilderImpl builder) {
this.builder = builder;
}
/**
@ -75,15 +63,15 @@ public final class DatabaseProvider implements Supplier<Database> {
*
* <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));
}
/**
* Use an externally configured DataSource and a Flavor.
*/
public static Builder builder(DataSource ds, Flavor flavor) {
return new BuilderImpl(ds, () -> {
public static DatabaseProviderBuilder builder(DataSource ds, Flavor flavor) {
return new DatabaseProviderBuilderImpl(ds, () -> {
try {
return ds.getConnection();
} 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
* 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);
}
@ -108,7 +96,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @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);
}
@ -118,7 +106,7 @@ public final class DatabaseProvider implements Supplier<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);
}
@ -129,7 +117,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @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);
}
@ -139,7 +127,7 @@ public final class DatabaseProvider implements Supplier<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);
}
@ -150,18 +138,18 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param flavor use this flavor rather than guessing based on the url
*/
public static Builder builder(String url,
Flavor flavor,
String user,
String password) {
public static DatabaseProviderBuilder builder(String url,
Flavor flavor,
String user,
String password) {
return builder(url, flavor, null, user, password);
}
private static Builder builder(String url,
Flavor flavor,
Properties info,
String user,
String password) {
private static DatabaseProviderBuilder builder(String url,
Flavor flavor,
Properties info,
String user,
String password) {
Options options = new OptionsDefault(flavor);
try {
DriverManager.getDriver(url);
@ -174,7 +162,7 @@ public final class DatabaseProvider implements Supplier<Database> {
}
}
}
return new BuilderImpl(null, () -> {
return new DatabaseProviderBuilderImpl(null, () -> {
try {
if (info != null) {
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
* @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());
}
@ -228,7 +216,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param decoder character encoding to use when reading the property file
* @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();
if (propertyFileName != null && propertyFileName.length() > 0) {
try (
@ -265,7 +253,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* pass "my." as the prefix)
* @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());
}
@ -291,7 +279,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param decoder character encoding to use when reading the property file
* @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();
if (filename != null && filename.length() > 0) {
try (
@ -323,7 +311,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* @param properties properties will be read from here
* @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);
}
@ -348,7 +336,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* pass "my." as the prefix)
* @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);
}
@ -374,7 +362,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* log entry will be entered, but it will attempt to proceed using
* solely the system properties
*/
public static Builder fromPropertyFileOrSystemProperties(String filename) {
public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename) {
return fromPropertyFileOrSystemProperties(filename, Charset.defaultCharset().newDecoder());
}
@ -400,7 +388,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* solely the system properties
* @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();
if (filename != null && filename.length() > 0) {
try (
@ -441,7 +429,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* (exactly, so if you want to use "my.database.url" you must
* 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());
}
@ -471,8 +459,8 @@ public final class DatabaseProvider implements Supplier<Database> {
* pass "my." as the prefix)
* @param decoder character encoding to use when reading the property file
*/
public static Builder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix,
CharsetDecoder decoder) {
public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix,
CharsetDecoder decoder) {
Properties properties = new Properties();
if (filename != null && filename.length() > 0) {
try (
@ -502,7 +490,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* </pre>
*/
public static Builder fromSystemProperties() {
public static DatabaseProviderBuilder fromSystemProperties() {
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)
*/
public static Builder fromSystemProperties(String propertyPrefix) {
public static DatabaseProviderBuilder fromSystemProperties(String propertyPrefix) {
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) {
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,
IllegalAccessException {
String url = config.getString("database.url");
@ -621,7 +609,7 @@ public final class DatabaseProvider implements Supplier<Database> {
return new PoolDataSource(poolConfig);
}
public static Flavor getFlavor(Config config) {
private static Flavor getFlavor(Config config) {
String url = config.getString("database.url");
if (url == null) {
throw new DatabaseException("You must provide database.url");
@ -755,30 +743,27 @@ public final class DatabaseProvider implements Supplier<Database> {
}
public Database get() {
if (delegateTo != null) {
return delegateTo.get();
}
if (database != null) {
return database;
}
if (connectionProvider == null) {
if (builder.isClosed()) {
throw new DatabaseException("Called get() on a DatabaseProvider after close()");
}
Metric metric = new Metric(log.isLoggable(Level.FINE));
try {
connection = connectionProvider.get();
connection = builder.connectionProvider.get();
metric.checkpoint("getConn");
try {
// JDBC specifies that autoCommit is the default for all new connections.
// Don't try to be clever about clearing it conditionally.
if (!options.flavor().isAutoCommitOnly()) {
if (!builder.options.flavor().isAutoCommitOnly()) {
connection.setAutoCommit(false);
metric.checkpoint("setAutoCommit");
}
} catch (SQLException 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");
} catch (RuntimeException e) {
metric.checkpoint("fail");
@ -786,7 +771,7 @@ public final class DatabaseProvider implements Supplier<Database> {
} finally {
metric.done();
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);
log.fine(buf.toString());
}
@ -794,78 +779,10 @@ public final class DatabaseProvider implements Supplier<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() {
if (delegateTo != null) {
log.fine("Ignoring commit() because this is a fake provider");
return;
}
if (connection != null) {
try {
if (!options.flavor().isAutoCommitOnly()) {
if (!builder.options.flavor().isAutoCommitOnly()) {
connection.commit();
}
} catch (Exception e) {
@ -875,31 +792,22 @@ public final class DatabaseProvider implements Supplier<Database> {
}
public void commitAndClose() {
if (delegateTo != null) {
log.fine("Ignoring commitAndClose() because this is a fake provider");
return;
}
if (connection != null) {
try {
if (!options.flavor().isAutoCommitOnly()) {
try {
if (connection != null) {
if (!builder.options.flavor().isAutoCommitOnly()) {
connection.commit();
}
} catch (Exception e) {
throw new DatabaseException("Unable to commit the transaction", e);
}
close();
} catch (Exception e) {
throw new DatabaseException("Unable to commit the transaction", e);
}
}
public void rollback() {
if (delegateTo != null) {
log.fine("Ignoring rollback() because this is a fake provider");
return;
}
if (connection != null) {
try {
if (!options.flavor().isAutoCommitOnly()) {
if (!builder.options.flavor().isAutoCommitOnly()) {
connection.rollback();
}
} catch (Exception e) {
@ -909,24 +817,20 @@ public final class DatabaseProvider implements Supplier<Database> {
}
public void rollbackAndClose() {
if (delegateTo != null) {
log.fine("Ignoring rollbackAndClose() because this is a fake provider");
return;
}
if (connection != null) {
try {
if (!options.flavor().isAutoCommitOnly()) {
try {
if (connection != null) {
if (!builder.options.flavor().isAutoCommitOnly()) {
connection.rollback();
}
} catch (Exception e) {
log.log(Level.SEVERE, "Unable to rollback the transaction", e);
}
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) {
try {
connection.close();
@ -936,7 +840,7 @@ public final class DatabaseProvider implements Supplier<Database> {
}
connection = 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
* 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.
*/
Builder withSqlParameterLogging();
DatabaseProviderBuilder withSqlParameterLogging();
/**
* 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.
*/
Builder withSqlInExceptionMessages();
DatabaseProviderBuilder withSqlInExceptionMessages();
/**
* 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.
*/
Builder withDatePerAppOnly();
DatabaseProviderBuilder withDatePerAppOnly();
/**
* Allow provided Database instances to explicitly control transactions using the
@ -977,14 +881,14 @@ public final class DatabaseProvider implements Supplier<Database> {
* throw an exception.
*/
Builder withTransactionControl();
DatabaseProviderBuilder withTransactionControl();
/**
* 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.
*/
Builder withTransactionControlSilentlyIgnored();
DatabaseProviderBuilder withTransactionControlSilentlyIgnored();
/**
* 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.
*/
Builder withConnectionAccess();
DatabaseProviderBuilder withConnectionAccess();
/**
* 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 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 Options options;
private BuilderImpl(DataSource ds, Supplier<Connection> connectionProvider, Options options) {
this.ds = ds;
private final AtomicBoolean closed;
private DatabaseProviderBuilderImpl(DataSource dataSource,
Supplier<Connection> connectionProvider,
Options options) {
this.dataSource = dataSource;
this.connectionProvider = connectionProvider;
this.options = options;
this.closed = new AtomicBoolean(false);
}
@Override
public Builder withOptions(OptionsOverride options) {
return new BuilderImpl(ds, connectionProvider, options.withParent(this.options));
public DatabaseProviderBuilder withOptions(OptionsOverride options) {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, options.withParent(this.options));
}
@Override
public Builder withSqlParameterLogging() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() {
public DatabaseProviderBuilder withSqlParameterLogging() {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override
public boolean isLogParameters() {
return true;
@ -1093,8 +1001,8 @@ public final class DatabaseProvider implements Supplier<Database> {
}
@Override
public Builder withSqlInExceptionMessages() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() {
public DatabaseProviderBuilder withSqlInExceptionMessages() {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override
public boolean isDetailedExceptions() {
return true;
@ -1103,8 +1011,8 @@ public final class DatabaseProvider implements Supplier<Database> {
}
@Override
public Builder withDatePerAppOnly() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() {
public DatabaseProviderBuilder withDatePerAppOnly() {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override
public boolean useLocalDateTimeOnly() {
return true;
@ -1113,8 +1021,8 @@ public final class DatabaseProvider implements Supplier<Database> {
}
@Override
public Builder withTransactionControl() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() {
public DatabaseProviderBuilder withTransactionControl() {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override
public boolean allowTransactionControl() {
return true;
@ -1123,8 +1031,8 @@ public final class DatabaseProvider implements Supplier<Database> {
}
@Override
public Builder withTransactionControlSilentlyIgnored() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() {
public DatabaseProviderBuilder withTransactionControlSilentlyIgnored() {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override
public boolean ignoreTransactionControl() {
return true;
@ -1133,8 +1041,8 @@ public final class DatabaseProvider implements Supplier<Database> {
}
@Override
public Builder withConnectionAccess() {
return new BuilderImpl(ds, connectionProvider, new OptionsOverride() {
public DatabaseProviderBuilder withConnectionAccess() {
return new DatabaseProviderBuilderImpl(dataSource, connectionProvider, new OptionsOverride() {
@Override
public boolean allowConnectionAccess() {
return true;
@ -1144,7 +1052,7 @@ public final class DatabaseProvider implements Supplier<Database> {
@Override
public DatabaseProvider build() {
return new DatabaseProvider(connectionProvider, options);
return new DatabaseProvider(this);
}
@Override
@ -1164,12 +1072,18 @@ public final class DatabaseProvider implements Supplier<Database> {
@Override
public void close() throws IOException {
if (ds != null) {
if (ds instanceof Closeable) {
((Closeable) ds).close();
if (closed.compareAndSet(false, true)) {
if (dataSource != null) {
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.Metric;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.logging.Level;
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 logQuiet = Logger.getLogger(Database.class.getName() + ".quiet");
private final Connection connection;
private final String sql;
@ -28,15 +26,15 @@ public class DdlImpl implements Ddl {
}
private void updateInternal(boolean quiet) {
CallableStatement ps = null;
PreparedStatement preparedStatement = null;
Metric metric = new Metric(log.isLoggable(Level.FINE));
boolean isSuccess = false;
String errorCode = null;
Exception logEx = null;
try {
ps = connection.prepareCall(sql);
preparedStatement = connection.prepareStatement(sql);
metric.checkpoint("prep");
ps.execute();
preparedStatement.execute();
metric.checkpoint("exec");
isSuccess = true;
} catch (Exception e) {
@ -44,15 +42,13 @@ public class DdlImpl implements Ddl {
logEx = e;
throw DatabaseException.wrap(DebugSql.exceptionMessage(sql, null, errorCode, options), e);
} finally {
close(ps);
close(preparedStatement);
metric.checkpoint("close");
// PostgreSQL requires explicit commit since we are running with setAutoCommit(false)
commit(connection);
metric.done("commit");
if (isSuccess) {
DebugSql.logSuccess("DDL", log, metric, sql, null, options);
} else if (quiet) {
DebugSql.logWarning("DDL", logQuiet, metric, errorCode, sql, null, options, logEx);
} else {
DebugSql.logError("DDL", log, metric, errorCode, sql, null, options, logEx);
}

View file

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

View file

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

View file

@ -33,7 +33,7 @@ public class SqlInsertImpl implements SqlInsert {
private final Connection connection;
private final StatementAdaptor adaptor;
private final StatementAdapter adaptor;
private final String sql;
@ -57,7 +57,7 @@ public class SqlInsertImpl implements SqlInsert {
this.connection = connection;
this.sql = sql;
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 StatementAdaptor adaptor;
private final StatementAdapter adaptor;
private final Object cancelLock = new Object();
@ -52,7 +52,7 @@ public class SqlSelectImpl implements SqlSelect {
this.connection = connection;
this.sql = sql;
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 StatementAdaptor adaptor;
private final StatementAdapter adaptor;
private final String sql;
@ -47,7 +47,7 @@ public class SqlUpdateImpl implements SqlUpdate {
this.connection = connection;
this.sql = sql;
this.options = options;
adaptor = new StatementAdaptor(options);
adaptor = new StatementAdapter(options);
}
@Override

View file

@ -22,13 +22,13 @@ import java.util.stream.Collectors;
/**
* 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;
public StatementAdaptor(Options options) {
public StatementAdapter(Options options) {
this.options = options;
}
@ -90,9 +90,9 @@ public class StatementAdaptor {
ps.setBinaryStream(i + 1, (InputStream) parameter);
}
} else if (parameter instanceof Float) {
ps.setFloat(i + 1, (Float) parameter);
options.flavor().setFloat(ps, i + 1, (Float) parameter);
} else if (parameter instanceof Double) {
ps.setDouble(i + 1, (Double) parameter);
options.flavor().setDouble(ps, i + 1, (Double) parameter);
} else {
ps.setObject(i + 1, parameter);
}

View file

@ -1,8 +1,6 @@
package org.xbib.jdbc.query;
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 java.util.Objects;
@ -21,7 +19,7 @@ public class When {
}
public When oracle(String sql) {
if (actualFlavor instanceof Oracle) {
if ("oracle".equals(actualFlavor.getName())) {
chosen = sql;
}
return this;
@ -35,7 +33,7 @@ public class When {
}
public When postgres(String sql) {
if (actualFlavor instanceof Postgresql) {
if ("postgresql".equals(actualFlavor.getName())) {
chosen = sql;
}
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 java.sql.PreparedStatement;
import java.sql.SQLException;
public class Derby implements Flavor {
@Override
@ -44,11 +47,21 @@ public class Derby implements Flavor {
return "real";
}
@Override
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
preparedStatement.setFloat(i, floatValue);
}
@Override
public String typeDouble() {
return "double";
}
@Override
public void setDouble(PreparedStatement preparedStatement, int i, Double doubleValue) throws SQLException {
preparedStatement.setDouble(i, doubleValue);
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";

View file

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

View file

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

View file

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

View file

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

View file

@ -120,7 +120,7 @@ public class ConfigTest {
@Test
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"));
}
@ -138,12 +138,12 @@ public class ConfigTest {
@Test
public void testTidyValues() {
Config config = ConfigSupplier.of().value("foo", " a ").get();
Config config = ConfigSupplier.of().property("foo", " a ").get();
// Strip whitespace
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
assertEquals("a", config.getString("foo"));
@ -152,35 +152,35 @@ public class ConfigTest {
@Test
public void testBoolean() {
// 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.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("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.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("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.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("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.getBooleanOrTrue("foo"));
assertFalse(config.getBooleanOrFalse("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"));
assertTrue(config.getBooleanOrTrue("foo"));
@ -190,7 +190,7 @@ public class ConfigTest {
@Test
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"));
assertNull(config.getInteger("bad"));
assertNull(config.getInteger("missing"));
@ -201,7 +201,7 @@ public class ConfigTest {
@Test
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"));
assertNull(config.getLong("bad"));
@ -213,7 +213,7 @@ public class ConfigTest {
@Test
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"));
assertNull(config.getFloat("bad"));
@ -225,7 +225,7 @@ public class ConfigTest {
@Test
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"));
assertNull(config.getDouble("bad"));
@ -237,7 +237,7 @@ public class ConfigTest {
@Test
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"));
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).
*/
@Disabled
public class DerbyTest extends CommonTest {
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
}
void example(DatabaseProvider.Builder dbb, final String[] args) {
void example(DatabaseProvider.DatabaseProviderBuilder dbb, final String[] args) {
dbb.transact(db -> {
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.FileHandler.level=ALL
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
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 {
libs {
version('gradle', '7.5.1')
version('junit', '5.8.2')
version('testcontainers', '1.17.3')
version('junit', '5.9.1')
version('testcontainers', '1.17.5')
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-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.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('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-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('mockito-core', 'org.mockito', 'mockito-core').version('4.6.1')
library('testcontainers-postgresql', 'org.testcontainers', 'postgresql').versionRef('testcontainers')
}
}
}
include 'jdbc-connection-pool'
include 'jdbc-query'
include 'jdbc-test'
include 'jdbc-mariadb'
include 'jdbc-oracle'
include 'jdbc-postgresql'