From ca784dc6cfb5862958473a7d7ce1cab02b3c3797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Sun, 30 Oct 2022 01:20:18 +0200 Subject: [PATCH] factor out subprojects: oracle, postgresql, mariadb, fix pool tests --- gradle/compile/java.gradle | 10 +- .../src/main/java/module-info.java | 2 +- .../xbib/jdbc/connection/pool/PoolConfig.java | 82 +- .../jdbc/ConcurrentCloseConnectionTest.java | 4 +- .../jdbc/ConnectionCloseBlockingTest.java | 1 + .../jdbc/ConnectionPoolSizeVsThreadsTest.java | 1 + .../jdbc/ConnectionRaceConditionTest.java | 1 + .../io/pool/jdbc/ConnectionStateTest.java | 8 +- .../org/xbib/io/pool/jdbc/ConnectionTest.java | 26 +- .../pool/jdbc/ConnectionTimeoutRetryTest.java | 5 + .../io/pool/jdbc/HouseKeeperCleanupTest.java | 1 + .../org/xbib/io/pool/jdbc/IsolationTest.java | 2 + .../org/xbib/io/pool/jdbc/JdbcDriverTest.java | 4 +- .../java/org/xbib/io/pool/jdbc/PoolTest.java | 11 +- .../xbib/io/pool/jdbc/PoolTestExtension.java | 20 - .../org/xbib/io/pool/jdbc/ProxiesTest.java | 4 + .../org/xbib/io/pool/jdbc/RampUpDownTest.java | 1 + .../io/pool/jdbc/SaturatedPoolTest830.java | 1 + .../org/xbib/io/pool/jdbc/ShutdownTest.java | 5 + .../org/xbib/io/pool/jdbc/StatementTest.java | 1 + .../org/xbib/io/pool/jdbc/UnwrapTest.java | 2 + .../src/test/resources/logging.properties | 10 + jdbc-mariadb/build.gradle | 12 + jdbc-mariadb/src/main/java/module-info.java | 10 + .../java/org/xbib/jdbc/mariadb/MariaDB.java | 102 +- .../services/org.xbib.jdbc.query.Flavor | 1 + .../xbib/jdbc/mariadb/test/MariaDBTest.java | 108 ++ .../src/test/resources/logging.properties | 10 + jdbc-oracle/build.gradle | 12 + jdbc-oracle/src/main/java/module-info.java | 12 + .../java/org/xbib/jdbc/oracle}/Oracle.java | 30 +- .../services/org.xbib.jdbc.query.Flavor | 1 + .../org/xbib/jdbc/oracle/test/OracleTest.java | 67 + .../src/test/resources/logging.properties | 10 + jdbc-postgresql/build.gradle | 12 + .../src/main/java/module-info.java | 10 + .../org/xbib/jdbc/postgresql}/Postgresql.java | 18 +- .../services/org.xbib.jdbc.query.Flavor | 1 + .../jdbc/postgresql/test/PostgresqlTest.java | 71 + .../src/test/resources/logging.properties | 10 + jdbc-query/build.gradle | 1 - jdbc-query/src/main/java/module-info.java | 7 +- .../org/xbib/jdbc/query/ConfigSupplier.java | 6 +- .../xbib/jdbc/query/ConfigSupplierImpl.java | 2 +- .../org/xbib/jdbc/query/DatabaseImpl.java | 6 +- .../org/xbib/jdbc/query/DatabaseProvider.java | 296 +-- .../java/org/xbib/jdbc/query/DdlImpl.java | 14 +- .../main/java/org/xbib/jdbc/query/Flavor.java | 6 + .../org/xbib/jdbc/query/OptionsOverride.java | 6 +- .../main/java/org/xbib/jdbc/query/Schema.java | 43 +- .../java/org/xbib/jdbc/query/SqlArgs.java | 3 + .../org/xbib/jdbc/query/SqlInsertImpl.java | 4 +- .../org/xbib/jdbc/query/SqlSelectImpl.java | 4 +- .../org/xbib/jdbc/query/SqlUpdateImpl.java | 4 +- ...mentAdaptor.java => StatementAdapter.java} | 10 +- .../main/java/org/xbib/jdbc/query/When.java | 6 +- .../org/xbib/jdbc/query/flavor/BigQuery.java | 177 -- .../org/xbib/jdbc/query/flavor/Derby.java | 13 + .../java/org/xbib/jdbc/query/flavor/H2.java | 13 + .../java/org/xbib/jdbc/query/flavor/Hsql.java | 13 + .../org/xbib/jdbc/query/flavor/SqlServer.java | 13 + .../services/org.xbib.jdbc.query.Flavor | 4 - .../org/xbib/jdbc/query/test/CommonTest.java | 13 +- .../org/xbib/jdbc/query/test/ConfigTest.java | 26 +- .../org/xbib/jdbc/query/test/DerbyTest.java | 1 + .../org/xbib/jdbc/query/test/OracleTest.java | 44 - .../xbib/jdbc/query/test/PostgreSqlTest.java | 52 - .../jdbc/query/test/example/DerbyExample.java | 2 +- .../jdbc/query/test/example/FakeBuilder.java | 65 - .../src/test/resources/logging.properties | 3 +- jdbc-test/build.gradle | 5 + .../java/org/xbib/jdbc/test/CommonTest.java | 1721 +++++++++++++++++ settings.gradle | 19 +- 73 files changed, 2546 insertions(+), 755 deletions(-) create mode 100644 jdbc-connection-pool/src/test/resources/logging.properties create mode 100644 jdbc-mariadb/build.gradle create mode 100644 jdbc-mariadb/src/main/java/module-info.java rename jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/MySQL.java => jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java (57%) create mode 100644 jdbc-mariadb/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor create mode 100644 jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java create mode 100644 jdbc-mariadb/src/test/resources/logging.properties create mode 100644 jdbc-oracle/build.gradle create mode 100644 jdbc-oracle/src/main/java/module-info.java rename {jdbc-query/src/main/java/org/xbib/jdbc/query/flavor => jdbc-oracle/src/main/java/org/xbib/jdbc/oracle}/Oracle.java (75%) create mode 100644 jdbc-oracle/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor create mode 100644 jdbc-oracle/src/test/java/org/xbib/jdbc/oracle/test/OracleTest.java create mode 100644 jdbc-oracle/src/test/resources/logging.properties create mode 100644 jdbc-postgresql/build.gradle create mode 100644 jdbc-postgresql/src/main/java/module-info.java rename {jdbc-query/src/main/java/org/xbib/jdbc/query/flavor => jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql}/Postgresql.java (88%) create mode 100644 jdbc-postgresql/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor create mode 100644 jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java create mode 100644 jdbc-postgresql/src/test/resources/logging.properties rename jdbc-query/src/main/java/org/xbib/jdbc/query/{StatementAdaptor.java => StatementAdapter.java} (95%) delete mode 100644 jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/BigQuery.java delete mode 100644 jdbc-query/src/test/java/org/xbib/jdbc/query/test/OracleTest.java delete mode 100644 jdbc-query/src/test/java/org/xbib/jdbc/query/test/PostgreSqlTest.java delete mode 100644 jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/FakeBuilder.java create mode 100644 jdbc-test/build.gradle create mode 100644 jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle index c9bba7f..33d7316 100644 --- a/gradle/compile/java.gradle +++ b/gradle/compile/java.gradle @@ -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 { diff --git a/jdbc-connection-pool/src/main/java/module-info.java b/jdbc-connection-pool/src/main/java/module-info.java index c22579b..76d6b69 100644 --- a/jdbc-connection-pool/src/main/java/module-info.java +++ b/jdbc-connection-pool/src/main/java/module-info.java @@ -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; } diff --git a/jdbc-connection-pool/src/main/java/org/xbib/jdbc/connection/pool/PoolConfig.java b/jdbc-connection-pool/src/main/java/org/xbib/jdbc/connection/pool/PoolConfig.java index f190178..7bb9a4e 100644 --- a/jdbc-connection-pool/src/main/java/org/xbib/jdbc/connection/pool/PoolConfig.java +++ b/jdbc-connection-pool/src/main/java/org/xbib/jdbc/connection/pool/PoolConfig.java @@ -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. @@ -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); } } diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConcurrentCloseConnectionTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConcurrentCloseConnectionTest.java index 023073f..016de9c 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConcurrentCloseConnectionTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConcurrentCloseConnectionTest.java @@ -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()) { diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionCloseBlockingTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionCloseBlockingTest.java index b72133e..3f3d78b 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionCloseBlockingTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionCloseBlockingTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionPoolSizeVsThreadsTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionPoolSizeVsThreadsTest.java index 4dbd059..4bca296 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionPoolSizeVsThreadsTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionPoolSizeVsThreadsTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionRaceConditionTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionRaceConditionTest.java index 203c7a6..2ded83a 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionRaceConditionTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionRaceConditionTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionStateTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionStateTest.java index a6ce92e..d3d8ef0 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionStateTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionStateTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTest.java index 3c388d0..7078938 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTest.java @@ -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; - } - }*/ } diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTimeoutRetryTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTimeoutRetryTest.java index edd94b0..ab61e31 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTimeoutRetryTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ConnectionTimeoutRetryTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/HouseKeeperCleanupTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/HouseKeeperCleanupTest.java index 902fbe4..af21255 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/HouseKeeperCleanupTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/HouseKeeperCleanupTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/IsolationTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/IsolationTest.java index 6fb818a..184d1ee 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/IsolationTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/IsolationTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/JdbcDriverTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/JdbcDriverTest.java index 9288dc1..74e4491 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/JdbcDriverTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/JdbcDriverTest.java @@ -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"); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTest.java index 5985ec4..b6edee8 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTest.java @@ -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"); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTestExtension.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTestExtension.java index 2fe50b1..763c778 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTestExtension.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/PoolTestExtension.java @@ -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) { diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ProxiesTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ProxiesTest.java index f4c9e25..73fa59f 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ProxiesTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ProxiesTest.java @@ -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"); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/RampUpDownTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/RampUpDownTest.java index c78e6d4..aa34426 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/RampUpDownTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/RampUpDownTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java index 330370f..8534919 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ShutdownTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ShutdownTest.java index 1f6aa60..16d1c94 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ShutdownTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/ShutdownTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/StatementTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/StatementTest.java index 62d2e9e..bd6f257 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/StatementTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/StatementTest.java @@ -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"); diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/UnwrapTest.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/UnwrapTest.java index 61ef008..3e77323 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/UnwrapTest.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/UnwrapTest.java @@ -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); diff --git a/jdbc-connection-pool/src/test/resources/logging.properties b/jdbc-connection-pool/src/test/resources/logging.properties new file mode 100644 index 0000000..5885e4d --- /dev/null +++ b/jdbc-connection-pool/src/test/resources/logging.properties @@ -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 diff --git a/jdbc-mariadb/build.gradle b/jdbc-mariadb/build.gradle new file mode 100644 index 0000000..915dc9a --- /dev/null +++ b/jdbc-mariadb/build.gradle @@ -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' +} diff --git a/jdbc-mariadb/src/main/java/module-info.java b/jdbc-mariadb/src/main/java/module-info.java new file mode 100644 index 0000000..2a1f9ee --- /dev/null +++ b/jdbc-mariadb/src/main/java/module-info.java @@ -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; +} diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/MySQL.java b/jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java similarity index 57% rename from jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/MySQL.java rename to jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java index a7e1930..39fbed0 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/MySQL.java +++ b/jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java @@ -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 = ?"; } - } diff --git a/jdbc-mariadb/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor b/jdbc-mariadb/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor new file mode 100644 index 0000000..d2a3453 --- /dev/null +++ b/jdbc-mariadb/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor @@ -0,0 +1 @@ +org.xbib.jdbc.mariadb.MariaDB \ No newline at end of file diff --git a/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java b/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java new file mode 100644 index 0000000..122d29f --- /dev/null +++ b/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java @@ -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(); + } +} diff --git a/jdbc-mariadb/src/test/resources/logging.properties b/jdbc-mariadb/src/test/resources/logging.properties new file mode 100644 index 0000000..5885e4d --- /dev/null +++ b/jdbc-mariadb/src/test/resources/logging.properties @@ -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 diff --git a/jdbc-oracle/build.gradle b/jdbc-oracle/build.gradle new file mode 100644 index 0000000..4881904 --- /dev/null +++ b/jdbc-oracle/build.gradle @@ -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' +} diff --git a/jdbc-oracle/src/main/java/module-info.java b/jdbc-oracle/src/main/java/module-info.java new file mode 100644 index 0000000..16a596e --- /dev/null +++ b/jdbc-oracle/src/main/java/module-info.java @@ -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; +} diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Oracle.java b/jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java similarity index 75% rename from jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Oracle.java rename to jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java index 943c13d..e431f6e 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Oracle.java +++ b/jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java @@ -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 + ")"; diff --git a/jdbc-oracle/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor b/jdbc-oracle/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor new file mode 100644 index 0000000..a0041ef --- /dev/null +++ b/jdbc-oracle/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor @@ -0,0 +1 @@ +org.xbib.jdbc.oracle.Oracle diff --git a/jdbc-oracle/src/test/java/org/xbib/jdbc/oracle/test/OracleTest.java b/jdbc-oracle/src/test/java/org/xbib/jdbc/oracle/test/OracleTest.java new file mode 100644 index 0000000..6dad096 --- /dev/null +++ b/jdbc-oracle/src/test/java/org/xbib/jdbc/oracle/test/OracleTest.java @@ -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(); + } +} diff --git a/jdbc-oracle/src/test/resources/logging.properties b/jdbc-oracle/src/test/resources/logging.properties new file mode 100644 index 0000000..5885e4d --- /dev/null +++ b/jdbc-oracle/src/test/resources/logging.properties @@ -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 diff --git a/jdbc-postgresql/build.gradle b/jdbc-postgresql/build.gradle new file mode 100644 index 0000000..7214212 --- /dev/null +++ b/jdbc-postgresql/build.gradle @@ -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' +} diff --git a/jdbc-postgresql/src/main/java/module-info.java b/jdbc-postgresql/src/main/java/module-info.java new file mode 100644 index 0000000..13bf83e --- /dev/null +++ b/jdbc-postgresql/src/main/java/module-info.java @@ -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; +} diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Postgresql.java b/jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java similarity index 88% rename from jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Postgresql.java rename to jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java index e3fac12..f82ec5c 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Postgresql.java +++ b/jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java @@ -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 + ")"; diff --git a/jdbc-postgresql/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor b/jdbc-postgresql/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor new file mode 100644 index 0000000..d7c266c --- /dev/null +++ b/jdbc-postgresql/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor @@ -0,0 +1 @@ +org.xbib.jdbc.postgresql.Postgresql diff --git a/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java b/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java new file mode 100644 index 0000000..c62b72c --- /dev/null +++ b/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java @@ -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; + }); + } +} diff --git a/jdbc-postgresql/src/test/resources/logging.properties b/jdbc-postgresql/src/test/resources/logging.properties new file mode 100644 index 0000000..5885e4d --- /dev/null +++ b/jdbc-postgresql/src/test/resources/logging.properties @@ -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 diff --git a/jdbc-query/build.gradle b/jdbc-query/build.gradle index d097b06..19de6b8 100644 --- a/jdbc-query/build.gradle +++ b/jdbc-query/build.gradle @@ -4,5 +4,4 @@ dependencies { testImplementation libs.hsqldb testImplementation libs.testcontainers testImplementation libs.testcontainers.junit.jupiter - testImplementation libs.testcontainers.oracle.xe } diff --git a/jdbc-query/src/main/java/module-info.java b/jdbc-query/src/main/java/module-info.java index 7990550..19df7a3 100644 --- a/jdbc-query/src/main/java/module-info.java +++ b/jdbc-query/src/main/java/module-info.java @@ -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; } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplier.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplier.java index e89fdb8..58834e7 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplier.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplier.java @@ -28,12 +28,12 @@ public interface ConfigSupplier extends Supplier { ConfigSupplier custom(Function 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); diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplierImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplierImpl.java index da13199..2ca119e 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplierImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigSupplierImpl.java @@ -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 + ")"); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java index f4b0682..8c635e1 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java @@ -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); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java index 906e7d9..02636a5 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java @@ -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 { +public final class DatabaseProvider implements Supplier, Closeable { private static final Logger log = Logger.getLogger(DatabaseProvider.class.getName()); private static final AtomicInteger poolNameCounter = new AtomicInteger(1); - - private final Options options; - - private DatabaseProvider delegateTo = null; - - private Supplier connectionProvider; - - private Connection connection = null; - - private Database database = null; - private DatabaseProvider(Supplier connectionProvider, Options options) { - if (connectionProvider == null) { - throw new IllegalArgumentException("connection provider cannot be null"); - } - this.connectionProvider = connectionProvider; - this.options = options; - } + private final DatabaseProviderBuilderImpl builder; - private DatabaseProvider(DatabaseProvider delegateTo) { - this.delegateTo = delegateTo; - this.options = delegateTo.options; + private Connection connection; + + private Database database; + + private DatabaseProvider(DatabaseProviderBuilderImpl builder) { + this.builder = builder; } /** @@ -75,15 +63,15 @@ public final class DatabaseProvider implements Supplier { * *

A database pool will be created using jdbc-connection-pool.

*/ - 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 { * 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 { * @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 { * 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 { * @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 { * 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 { * @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 { } } } - 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 { * @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 { * @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 { * 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 { * @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 { * @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 { * 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 { * 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 { * 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 { * (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 { * 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 { * */ - public static Builder fromSystemProperties() { + public static DatabaseProviderBuilder fromSystemProperties() { return fromProperties(null, "", true); } @@ -524,11 +512,11 @@ public final class DatabaseProvider implements Supplier { * 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 { } } - 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 { 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 { } 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 { } 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 { 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 transactReturning(DbCodeTyped 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 { } 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 { } 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 { } connection = null; database = null; - connectionProvider = null; + builder.close(); } /** @@ -944,15 +848,15 @@ public final class DatabaseProvider implements Supplier { * 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 { * 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 { * 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 { * 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 { * 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 { */ 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 connectionProvider; private final Options options; - private BuilderImpl(DataSource ds, Supplier connectionProvider, Options options) { - this.ds = ds; + private final AtomicBoolean closed; + + private DatabaseProviderBuilderImpl(DataSource dataSource, + Supplier 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 { } @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 { } @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 { } @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 { } @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 { } @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 { @Override public DatabaseProvider build() { - return new DatabaseProvider(connectionProvider, options); + return new DatabaseProvider(this); } @Override @@ -1164,12 +1072,18 @@ public final class DatabaseProvider implements Supplier { @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(); + } } } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/DdlImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/DdlImpl.java index 3139452..58e919b 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/DdlImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/DdlImpl.java @@ -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); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java index 7c9c67b..175b95e 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java @@ -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); diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/OptionsOverride.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/OptionsOverride.java index dcb3951..1f031a0 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/OptionsOverride.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/OptionsOverride.java @@ -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()); } /** diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/Schema.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/Schema.java index da39399..f40e4d6 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/Schema.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/Schema.java @@ -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); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlArgs.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlArgs.java index 2f49453..021ae4b 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlArgs.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlArgs.java @@ -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()); diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlInsertImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlInsertImpl.java index b9010ff..f05df57 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlInsertImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlInsertImpl.java @@ -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); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlSelectImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlSelectImpl.java index 065690c..5461a0a 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlSelectImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlSelectImpl.java @@ -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); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlUpdateImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlUpdateImpl.java index 06b81e5..334040a 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlUpdateImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/SqlUpdateImpl.java @@ -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 diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/StatementAdaptor.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/StatementAdapter.java similarity index 95% rename from jdbc-query/src/main/java/org/xbib/jdbc/query/StatementAdaptor.java rename to jdbc-query/src/main/java/org/xbib/jdbc/query/StatementAdapter.java index 906f0f5..19609d3 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/StatementAdaptor.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/StatementAdapter.java @@ -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); } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java index a6b7d2c..e81ea02 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java @@ -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; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/BigQuery.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/BigQuery.java deleted file mode 100644 index 0ae20d3..0000000 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/BigQuery.java +++ /dev/null @@ -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(); - } -} diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java index 3d6b213..6dffb16 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java @@ -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 + ")"; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java index 1e4b3f3..8725875 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java @@ -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 + ")"; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java index d07d358..2ec425b 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java @@ -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 + ")"; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java index e533746..ff98110 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java @@ -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 + ")"; diff --git a/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor b/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor index f7efe8d..0e4363b 100644 --- a/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor +++ b/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor @@ -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 \ No newline at end of file diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java index 87bbdda..f47ad3d 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java @@ -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()); } diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/ConfigTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/ConfigTest.java index 980e840..c8d0310 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/ConfigTest.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/ConfigTest.java @@ -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")); diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java index 12a20e2..1ea67e6 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java @@ -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()); diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/OracleTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/OracleTest.java deleted file mode 100644 index 817672b..0000000 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/OracleTest.java +++ /dev/null @@ -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(); - } -} diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/PostgreSqlTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/PostgreSqlTest.java deleted file mode 100644 index 0e983ae..0000000 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/PostgreSqlTest.java +++ /dev/null @@ -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; - }); - } -} diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java index 8485dcb..fb09d69 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java @@ -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); }); diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/FakeBuilder.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/FakeBuilder.java deleted file mode 100644 index a43673d..0000000 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/FakeBuilder.java +++ /dev/null @@ -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(); - } - } - } -} diff --git a/jdbc-query/src/test/resources/logging.properties b/jdbc-query/src/test/resources/logging.properties index d5f5e0b..5885e4d 100644 --- a/jdbc-query/src/test/resources/logging.properties +++ b/jdbc-query/src/test/resources/logging.properties @@ -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 diff --git a/jdbc-test/build.gradle b/jdbc-test/build.gradle new file mode 100644 index 0000000..a9a12e6 --- /dev/null +++ b/jdbc-test/build.gradle @@ -0,0 +1,5 @@ +dependencies { + api project(":jdbc-query") + implementation libs.junit.jupiter.api + implementation libs.hamcrest +} diff --git a/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java b/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java new file mode 100644 index 0000000..4748633 --- /dev/null +++ b/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java @@ -0,0 +1,1721 @@ +package org.xbib.jdbc.test; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.xbib.jdbc.query.ConstraintViolationException; +import org.xbib.jdbc.query.Database; +import org.xbib.jdbc.query.DatabaseException; +import org.xbib.jdbc.query.DatabaseProvider; +import org.xbib.jdbc.query.OptionsDefault; +import org.xbib.jdbc.query.OptionsOverride; +import org.xbib.jdbc.query.Row; +import org.xbib.jdbc.query.RowHandler; +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.StatementAdapter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.ResultSetMetaData; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TimeZone; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Exercise Database functionality with a real database. + */ +public abstract class CommonTest { + + private static final Logger logger = Logger.getLogger(CommonTest.class.getName()); + + final static String TEST_TABLE_NAME = "dbtest"; + + protected static DatabaseProvider dbp; + + protected Database db; + + protected LocalDateTime now; + + protected LocalDate localDateNow; + + protected abstract DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception; + + @BeforeEach + public void setupJdbc() throws Exception { + now = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS); + localDateNow = LocalDate.now(); + dbp = createDatabaseProvider(new OptionsOverride() { + }); + db = dbp.get(); + db.dropTableQuietly(TEST_TABLE_NAME); + } + + @AfterEach + public void closeJdbc() { + if (dbp != null) { + dbp.commitAndClose(); + } + } + + @Test + public void tableExists() { + // Verify dbtest table does not exist + 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("")); + } + + 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)); + } + + @Test + public void normalizeTableName() { + // Verify that null and empty cases are handled gracefully + assertNull(db.normalizeTableName(null)); + assertEquals("", db.normalizeTableName("")); + + // Verify a quoted table name is returned in exactly the same case, with quotes removed. + String camelCaseTableName = "\"DbTest\""; + assertEquals(camelCaseTableName.substring(1, camelCaseTableName.length() - 1), + db.normalizeTableName(camelCaseTableName)); + + // Verify that the database flavor gets the expected normalized case + boolean isUpperCase = db.flavor().isNormalizedUpperCase(); + if (isUpperCase) { + assertEquals(TEST_TABLE_NAME.toUpperCase(), db.normalizeTableName(TEST_TABLE_NAME)); + } else { + assertEquals(TEST_TABLE_NAME.toLowerCase(), db.normalizeTableName(TEST_TABLE_NAME)); + } + } + + @Test + public void selectNewTable() { + new Schema() + .addTable("dbtest") + .addColumn("nbr_integer").asInteger().primaryKey().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().table().schema().execute(db); + + BigDecimal bigDecimal = new BigDecimal("5.3"); + db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?)").argInteger(1).argLong(2L).argFloat(3.2f).argDouble(4.2) + .argBigDecimal(bigDecimal).argString("Hello").argString("T").argClobString("World") + .argBlobBytes("More".getBytes()).argLocalDateTime(now).argLocalDate(localDateNow).insert(1); + + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest") + .query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull(1)); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull("nbr_integer")); + assertEquals(1, rs.getIntegerOrZero(1)); + assertEquals(1, rs.getIntegerOrZero("nbr_integer")); + assertEquals(Long.valueOf(2), rs.getLongOrNull(2)); + assertEquals(Long.valueOf(2), rs.getLongOrNull("nbr_long")); + assertEquals(2, rs.getLongOrZero(2)); + assertEquals(2, rs.getLongOrZero("nbr_long")); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull(3)); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull("nbr_float")); + assertEquals(3.2, rs.getFloatOrZero(3), 0.01); + assertEquals(3.2, rs.getFloatOrZero("nbr_float"), 0.01); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull(4)); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull("nbr_double")); + assertEquals(4.2, rs.getDoubleOrZero(4), 0.01); + assertEquals(4.2, rs.getDoubleOrZero("nbr_double"), 0.01); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull(5)); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull("nbr_big_decimal")); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrZero(5)); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrZero("nbr_big_decimal")); + assertEquals("Hello", rs.getStringOrNull(6)); + assertEquals("Hello", rs.getStringOrNull("str_varchar")); + assertEquals("Hello", rs.getStringOrEmpty(6)); + assertEquals("Hello", rs.getStringOrEmpty("str_varchar")); + assertEquals("T", rs.getStringOrNull(7)); + assertEquals("T", rs.getStringOrNull("str_fixed")); + assertEquals("T", rs.getStringOrEmpty(7)); + assertEquals("T", rs.getStringOrEmpty("str_fixed")); + assertEquals("World", rs.getClobStringOrNull(8)); + assertEquals("World", rs.getClobStringOrNull("str_lob")); + assertEquals("World", rs.getClobStringOrEmpty(8)); + assertEquals("World", rs.getClobStringOrEmpty("str_lob")); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(9)); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrZeroLen(9)); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrZeroLen("bin_blob")); + assertEquals(now, rs.getLocalDateTimeOrNull(10)); + assertEquals(now, rs.getLocalDateTimeOrNull("date_millis")); + assertEquals(localDateNow, rs.getLocalDateOrNull(11)); + assertEquals(localDateNow, rs.getLocalDateOrNull("local_date")); + return null; + }); + // Repeat the above query, using the various methods that automatically infer the column + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest") + .query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull()); + assertEquals(Long.valueOf(2), rs.getLongOrNull()); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull()); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull()); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull()); + assertEquals("Hello", rs.getStringOrNull()); + assertEquals("T", rs.getStringOrNull()); + assertEquals("World", rs.getClobStringOrNull()); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull()); + assertEquals(now, rs.getLocalDateTimeOrNull()); + assertEquals(localDateNow, rs.getLocalDateOrNull()); + return null; + }); + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest") + .query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(1, rs.getIntegerOrZero()); + assertEquals(2, rs.getLongOrZero()); + assertEquals(3.2, rs.getFloatOrZero(), 0.01); + assertEquals(4.2, rs.getDoubleOrZero(), 0.01); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrZero()); + assertEquals("Hello", rs.getStringOrEmpty()); + assertEquals("T", rs.getStringOrEmpty()); + assertEquals("World", rs.getClobStringOrEmpty()); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrZeroLen()); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", readerToString(rs.getClobReaderOrNull(1))); + assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrNull(2))); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", readerToString(rs.getClobReaderOrEmpty(1))); + assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrEmpty(2))); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", readerToString(rs.getClobReaderOrNull())); + assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrNull())); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", readerToString(rs.getClobReaderOrEmpty())); + assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrEmpty())); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", readerToString(rs.getClobReaderOrNull("str_lob"))); + assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrNull("bin_blob"))); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", readerToString(rs.getClobReaderOrEmpty("str_lob"))); + assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrEmpty("bin_blob"))); + return null; + }); + assertEquals(Long.valueOf(1), db.toSelect("select count(*) from dbtest where nbr_integer=:i and nbr_long=:l and " + + "abs(nbr_float-:f)<0.01 and abs(nbr_double-:d)<0.01 and nbr_big_decimal=:bd and str_varchar=:s " + + "and str_fixed=:sf and date_millis=:date and local_date=:local_date") + .argInteger("i", 1) + .argLong("l", 2L) + .argFloat("f", 3.2f) + .argDouble("d", 4.2) + .argBigDecimal("bd", bigDecimal) + .argString("s", "Hello") + .argString("sf", "T") + .argLocalDateTime("date", now) + .argLocalDate("local_date", localDateNow) + .queryLongOrNull()); + List result = db.toSelect("select count(*) from dbtest where nbr_integer=:i and nbr_long=:l and " + + "abs(nbr_float-:f)<0.01 and abs(nbr_double-:d)<0.01 and nbr_big_decimal=:bd and str_varchar=:s " + + "and str_fixed=:sf and date_millis=:date and local_date=:local_date").argInteger("i", 1).argLong("l", 2L).argFloat("f", 3.2f) + .argDouble("d", 4.2).argBigDecimal("bd", bigDecimal).argString("s", "Hello").argString("sf", "T") + .argLocalDateTime("date", now).argLocalDate("local_date", localDateNow).queryLongs(); + assertEquals(1, result.size()); + assertEquals(Long.valueOf(1), result.get(0)); + } + + @Test + public void updatePositionalArgs() { + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("nbr_integer").asInteger().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().table().schema().execute(db); + + BigDecimal bigDecimal = new BigDecimal("5.3"); + assertEquals(1, db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?,?)") + .argLong(1L) + .argInteger(1) + .argLong(2L) + .argFloat(3.2f) + .argDouble(4.2) + .argBigDecimal(bigDecimal) + .argString("Hello") + .argString("T") + .argClobString("World") + .argBlobBytes("More".getBytes()) + .argLocalDateTime(now) + .argLocalDate(localDateNow).insert()); + db.toUpdate("update dbtest set nbr_integer=?, nbr_long=?, nbr_float=?, nbr_double=?, nbr_big_decimal=?, " + + "str_varchar=?, str_fixed=?, str_lob=?, bin_blob=?, date_millis=?, local_date=?").argInteger(null).argLong(null) + .argFloat(null).argDouble(null).argBigDecimal(null).argString(null).argString(null).argClobString(null) + .argBlobBytes(null).argLocalDateTime(null).argLocalDate(null).update(1); + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getIntegerOrNull(1)); + assertNull(rs.getIntegerOrNull("nbr_integer")); + assertNull(rs.getLongOrNull(2)); + assertNull(rs.getLongOrNull("nbr_long")); + assertNull(rs.getFloatOrNull(3)); + assertNull(rs.getFloatOrNull("nbr_float")); + assertNull(rs.getDoubleOrNull(4)); + assertNull(rs.getDoubleOrNull("nbr_double")); + assertNull(rs.getBigDecimalOrNull(5)); + assertNull(rs.getBigDecimalOrNull("nbr_big_decimal")); + assertNull(rs.getStringOrNull(6)); + assertNull(rs.getStringOrNull("str_varchar")); + assertNull(rs.getStringOrNull(7)); + assertNull(rs.getStringOrNull("str_fixed")); + assertNull(rs.getClobStringOrNull(8)); + assertNull(rs.getClobStringOrNull("str_lob")); + assertNull(rs.getBlobBytesOrNull(9)); + assertNull(rs.getBlobBytesOrNull("bin_blob")); + assertNull(rs.getLocalDateTimeOrNull(10)); + assertNull(rs.getLocalDateTimeOrNull("date_millis")); + assertNull(rs.getLocalDateOrNull(11)); + assertNull(rs.getLocalDateOrNull("local_date")); + return null; + }); + assertEquals(1, db.toUpdate("update dbtest set nbr_integer=?, nbr_long=?, nbr_float=?, nbr_double=?, " + + "nbr_big_decimal=?, str_varchar=?, str_fixed=?, str_lob=?, bin_blob=?, date_millis=?, local_date=?").argInteger(1) + .argLong(2L).argFloat(3.2f).argDouble(4.2).argBigDecimal(bigDecimal).argString("Hello").argString("T") + .argClobString("World").argBlobBytes("More".getBytes()).argLocalDateTime(now).argLocalDate(localDateNow).update()); + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull(1)); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull("nbr_integer")); + assertEquals(Long.valueOf(2), rs.getLongOrNull(2)); + assertEquals(Long.valueOf(2), rs.getLongOrNull("nbr_long")); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull(3)); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull("nbr_float")); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull(4)); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull("nbr_double")); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull(5)); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull("nbr_big_decimal")); + assertEquals("Hello", rs.getStringOrNull(6)); + assertEquals("Hello", rs.getStringOrNull("str_varchar")); + assertEquals("T", rs.getStringOrNull(7)); + assertEquals("T", rs.getStringOrNull("str_fixed")); + assertEquals("World", rs.getClobStringOrNull(8)); + assertEquals("World", rs.getClobStringOrNull("str_lob")); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(9)); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); + assertEquals(now, rs.getLocalDateTimeOrNull(10)); + assertEquals(now, rs.getLocalDateTimeOrNull("date_millis")); + assertEquals(localDateNow, rs.getLocalDateOrNull(11)); + assertEquals(localDateNow, rs.getLocalDateOrNull("local_date")); + return null; + }); + db.toUpdate("update dbtest set str_lob=?, bin_blob=?").argClobReader(null).argBlobStream(null).update(1); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getClobStringOrNull(1)); + assertNull(rs.getClobStringOrNull("str_lob")); + assertNull(rs.getBlobBytesOrNull(2)); + assertNull(rs.getBlobBytesOrNull("bin_blob")); + return null; + }); + db.toUpdate("update dbtest set str_lob=?, bin_blob=?").argClobReader(new StringReader("World")) + .argBlobStream(new ByteArrayInputStream("More".getBytes())).update(1); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", rs.getClobStringOrNull(1)); + assertEquals("World", rs.getClobStringOrNull("str_lob")); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(2)); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); + return null; + }); + } + + @Test + public void updateNamedArgs() { + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("nbr_integer").asInteger().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().table().schema().execute(db); + BigDecimal bigDecimal = new BigDecimal("5.3"); + db.toInsert("insert into dbtest values (:pk,:a,:b,:c,:d,:e,:f,:sf,:g,:h,:i,:j)").argLong(":pk", 1L).argInteger(":a", 1) + .argLong(":b", 2L).argFloat(":c", 3.2f).argDouble(":d", 4.2).argBigDecimal(":e", bigDecimal) + .argString(":f", "Hello").argString(":sf", "T") + .argClobString(":g", "World").argBlobBytes(":h", "More".getBytes()) + .argLocalDateTime(":i", now).argLocalDate(":j", localDateNow).insert(1); + db.toUpdate("update dbtest set nbr_integer=:a, nbr_long=:b, nbr_float=:c, nbr_double=:d, nbr_big_decimal=:e, " + + "str_varchar=:f, str_fixed=:sf, str_lob=:g, bin_blob=:h, date_millis=:i, local_date=:j").argInteger(":a", null) + .argLong(":b", null).argFloat(":c", null).argDouble(":d", null).argBigDecimal(":e", null) + .argString(":f", null).argString(":sf", null) + .argClobString(":g", null).argBlobBytes(":h", null) + .argLocalDateTime(":i", null).argLocalDate(":j", null).update(1); + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getIntegerOrNull(1)); + assertNull(rs.getIntegerOrNull("nbr_integer")); + assertNull(rs.getLongOrNull(2)); + assertNull(rs.getLongOrNull("nbr_long")); + assertNull(rs.getFloatOrNull(3)); + assertNull(rs.getFloatOrNull("nbr_float")); + assertNull(rs.getDoubleOrNull(4)); + assertNull(rs.getDoubleOrNull("nbr_double")); + assertNull(rs.getBigDecimalOrNull(5)); + assertNull(rs.getBigDecimalOrNull("nbr_big_decimal")); + assertNull(rs.getStringOrNull(6)); + assertNull(rs.getStringOrNull("str_varchar")); + assertNull(rs.getStringOrNull(7)); + assertNull(rs.getStringOrNull("str_fixed")); + assertNull(rs.getClobStringOrNull(8)); + assertNull(rs.getClobStringOrNull("str_lob")); + assertNull(rs.getBlobBytesOrNull(9)); + assertNull(rs.getBlobBytesOrNull("bin_blob")); + assertNull(rs.getLocalDateTimeOrNull(10)); + assertNull(rs.getLocalDateTimeOrNull("date_millis")); + assertNull(rs.getLocalDateOrNull(11)); + assertNull(rs.getLocalDateOrNull("local_date")); + return null; + }); + db.toUpdate("update dbtest set nbr_integer=:a, nbr_long=:b, nbr_float=:c, nbr_double=:d, nbr_big_decimal=:e, " + + "str_varchar=:f, str_fixed=:sf, str_lob=:g, bin_blob=:h, date_millis=:i, local_date=:j").argInteger(":a", 1) + .argLong(":b", 2L).argFloat(":c", 3.2f).argDouble(":d", 4.2).argBigDecimal(":e", bigDecimal) + .argString(":f", "Hello").argString(":sf", "T") + .argClobString(":g", "World").argBlobBytes(":h", "More".getBytes()) + .argLocalDateTime(":i", now).argLocalDate(":j", localDateNow).update(1); + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest") + .query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull(1)); + assertEquals(Integer.valueOf(1), rs.getIntegerOrNull("nbr_integer")); + assertEquals(Long.valueOf(2), rs.getLongOrNull(2)); + assertEquals(Long.valueOf(2), rs.getLongOrNull("nbr_long")); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull(3)); + assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull("nbr_float")); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull(4)); + assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull("nbr_double")); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull(5)); + assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull("nbr_big_decimal")); + assertEquals("Hello", rs.getStringOrNull(6)); + assertEquals("Hello", rs.getStringOrNull("str_varchar")); + assertEquals("T", rs.getStringOrNull(7)); + assertEquals("T", rs.getStringOrNull("str_fixed")); + assertEquals("World", rs.getClobStringOrNull(8)); + assertEquals("World", rs.getClobStringOrNull("str_lob")); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(9)); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); + assertEquals(now, rs.getLocalDateTimeOrNull(10)); + assertEquals(now, rs.getLocalDateTimeOrNull("date_millis")); + assertEquals(localDateNow, rs.getLocalDateOrNull(11)); + assertEquals(localDateNow, rs.getLocalDateOrNull("local_date")); + return null; + }); + + db.toUpdate("update dbtest set str_lob=:a, bin_blob=:b").argClobReader(":a", null).argBlobStream(":b", null).update(1); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getClobStringOrNull(1)); + assertNull(rs.getClobStringOrNull("str_lob")); + assertNull(rs.getBlobBytesOrNull(2)); + assertNull(rs.getBlobBytesOrNull("bin_blob")); + return null; + }); + db.toUpdate("update dbtest set str_lob=:a, bin_blob=:b").argClobReader(":a", new StringReader("World")) + .argBlobStream(":b", new ByteArrayInputStream("More".getBytes())).update(1); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals("World", rs.getClobStringOrNull(1)); + assertEquals("World", rs.getClobStringOrNull("str_lob")); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(2)); + assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); + return null; + }); + } + + @Test + public void nullValues() { + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("nbr_integer").asInteger().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().table().schema().execute(db); + db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?,?)").argLong(1L).argInteger(null).argLong(null) + .argFloat(null).argDouble(null).argBigDecimal(null).argString(null).argString(null).argClobString(null) + .argBlobBytes(null).argLocalDateTime(null).argLocalDate(null).insert(1); + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + + "bin_blob, date_millis, local_date from dbtest") + .query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getIntegerOrNull(1)); + assertNull(rs.getIntegerOrNull("nbr_integer")); + assertNull(rs.getLongOrNull(2)); + assertNull(rs.getLongOrNull("nbr_long")); + assertNull(rs.getFloatOrNull(3)); + assertNull(rs.getFloatOrNull("nbr_float")); + assertNull(rs.getDoubleOrNull(4)); + assertNull(rs.getDoubleOrNull("nbr_double")); + assertNull(rs.getBigDecimalOrNull(5)); + assertNull(rs.getBigDecimalOrNull("nbr_big_decimal")); + assertNull(rs.getStringOrNull(6)); + assertNull(rs.getStringOrNull("str_varchar")); + assertNull(rs.getStringOrNull(7)); + assertNull(rs.getStringOrNull("str_fixed")); + assertNull(rs.getClobStringOrNull(8)); + assertNull(rs.getClobStringOrNull("str_lob")); + assertNull(rs.getBlobBytesOrNull(9)); + assertNull(rs.getBlobBytesOrNull("bin_blob")); + assertNull(rs.getLocalDateTimeOrNull(10)); + assertNull(rs.getLocalDateTimeOrNull("date_millis")); + assertNull(rs.getLocalDateOrNull(11)); + assertNull(rs.getLocalDateOrNull("local_date")); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getClobReaderOrNull(1)); + assertNull(rs.getBlobInputStreamOrNull(2)); + return null; + }); + db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertNull(rs.getClobReaderOrNull("str_lob")); + assertNull(rs.getBlobInputStreamOrNull("bin_blob")); + return null; + }); + } + + @Test + public void fromAny() { + assertEquals(db.toSelect("select 1" + db.flavor().fromAny()).queryIntegerOrZero(), 1); + } + + @Test + public void metadataColumnNames() { + 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; + }); + } + + @Test + public void metadataColumnTypes() { + String timestampColumnName = "data_millis"; + String dateColumnName = "local_date"; + new Schema() + .addTable("dbtest") + .addColumn(timestampColumnName).asLocalDateTime().table() + .addColumn(dateColumnName).asLocalDate().table().schema().execute(db); + db.toSelect("select * from dbtest").query((RowsHandler) rs -> { + ResultSetMetaData metadata = rs.getMetadata(); + for (int i = 1; i <= metadata.getColumnCount(); i++) { + String columnName = metadata.getColumnName(i); + String columnType = metadata.getColumnTypeName(i); + if (columnName.equalsIgnoreCase(timestampColumnName)) { + if ("sqlserver".equals(db.flavor().getName())) { + assertEquals("DATETIME2", columnType.toUpperCase()); + } else if ("hsqldb".equals(db.flavor().getName())) { + assertEquals("TIMESTAMP WITH TIME ZONE", columnType.toUpperCase()); + } else { + assertEquals("TIMESTAMP", columnType.toUpperCase()); + } + } else if (columnName.equalsIgnoreCase(dateColumnName)) { + assertEquals("DATE", columnType.toUpperCase()); + } else { + fail("Unexpected column " + columnName + " of type " + columnType); + } + } + return null; + }); + } + + @Test + public void intervals() { + new Schema().addTable("dbtest").addColumn("d").asLocalDateTime().schema().execute(db); + db.toInsert("insert into dbtest (d) values (?)").argLocalDateTime(now).insert(1); + assertEquals(1, db.toSelect("select count(1) from dbtest where d - interval '1' hour * ? < ?") + .argInteger(2) + .argLocalDateTime(now) + .queryIntegerOrZero()); + } + + @Test + public void saveResultAsTable() { + new Schema().addTable("dbtest") + .addColumn("nbr_integer").asInteger().primaryKey().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("boolean_flag").asBoolean().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().schema().execute(db); + db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," + + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") + .argInteger(Integer.MAX_VALUE).argLong(Long.MAX_VALUE).argFloat(Float.MAX_VALUE) + .argDouble(Double.MAX_VALUE).argBigDecimal(new BigDecimal("123.456")) + .argString("hello").argString("Z").argClobString("hello again") + .argBlobBytes(new byte[]{'1', '2'}).argBoolean(true) + .argLocalDateTime(now) + .argLocalDate(localDateNow).insert(1); + db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," + + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") + .argInteger(Integer.MIN_VALUE).argLong(Long.MIN_VALUE).argFloat(0.000001f) + .argDouble(Double.MIN_VALUE).argBigDecimal(new BigDecimal("-123.456")) + .argString("goodbye").argString("A").argClobString("bye again") + .argBlobBytes(new byte[]{'3', '4'}).argBoolean(false) + .argLocalDateTime(now) + .argLocalDate(localDateNow).insert(1); + String expectedSchema = new Schema().addTable("dbtest2") + .addColumn("nbr_integer").asInteger().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("boolean_flag").asBoolean().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().schema().print(db.flavor()); + List args = db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest") + .query(rs -> { + List result = new ArrayList<>(); + while (rs.next()) { + if (result.size() == 0) { + db.dropTableQuietly("dbtest2"); + Schema schema = new Schema().addTableFromRow("dbtest2", rs).schema(); + assertEquals(expectedSchema, schema.print(db.flavor())); + schema.execute(db); + } + result.add(SqlArgs.readRow(rs)); + } + return result; + }); + + db.toInsert(Sql.insert("dbtest2", args)).insertBatch(); + + assertEquals(2, db.toSelect("select count(*) from dbtest2").queryIntegerOrZero()); + + assertEquals( + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest order by 1") + .queryMany(SqlArgs::readRow), + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest2 order by 1") + .queryMany(SqlArgs::readRow)); + + assertEquals( + Arrays.asList( + new SqlArgs().argInteger("nbr_integer", Integer.MIN_VALUE) + .argLong("nbr_long", Long.MIN_VALUE) + .argFloat("nbr_float", 0.000001f) + .argDouble("nbr_double", Double.MIN_VALUE) + .argBigDecimal("nbr_big_decimal", new BigDecimal("-123.456")) + .argString("str_varchar", "goodbye") + .argString("str_fixed", "A") + .argClobString("str_lob", "bye again") + .argBlobBytes("bin_blob", new byte[]{'3', '4'}) + .argString("boolean_flag", "N")//.argBoolean("boolean_flag", false) + .argLocalDateTime("date_millis", now) + .argLocalDate("local_date", localDateNow), + new SqlArgs().argInteger("nbr_integer", Integer.MAX_VALUE) + .argLong("nbr_long", Long.MAX_VALUE) + .argFloat("nbr_float", Float.MAX_VALUE) + .argDouble("nbr_double", Double.MAX_VALUE) + .argBigDecimal("nbr_big_decimal", new BigDecimal("123.456")) + .argString("str_varchar", "hello") + .argString("str_fixed", "Z") + .argClobString("str_lob", "hello again") + .argBlobBytes("bin_blob", new byte[]{'1', '2'}) + .argString("boolean_flag", "Y")//.argBoolean("boolean_flag", true) + .argLocalDateTime("date_millis", now) + .argLocalDate("local_date", localDateNow)), + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest2 order by 1") + .queryMany(SqlArgs::readRow)); + } + + @Test + public void readSqlArgs() { + new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); + db.toInsert("insert into dbtest (pk) values (?)").argInteger(1).insert(1); + SqlArgs args = db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\", pk as \"g arB#G!\"," + + " pk as \"TitleCase\" from dbtest") + .queryOneOrThrow(SqlArgs::readRow); + + assertEquals(Arrays.asList("pk", "foo", "foo_2", "g_ar_b_g", "title_case"), args.names()); + } + + @Test + public void clockSync() { + db.assertTimeSynchronized(); + } + + @Test + public void booleanColumn() { + new Schema().addTable("dbtest") + .addColumn("t").asBoolean().table() + .addColumn("f").asBoolean().table() + .addColumn("n").asBoolean().schema().execute(db); + db.toInsert("insert into dbtest (t,f,n) values (?,:f,?)") + .argBoolean(true).argBoolean("f", false).argBoolean(null).insert(1); + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertSame(rs.getBooleanOrNull(), Boolean.TRUE); + assertSame(rs.getBooleanOrNull(), Boolean.FALSE); + assertNull(rs.getBooleanOrNull()); + return null; + }); + // Verify use of getBooleanOrNull(int) followed by default getBooleanOrNull() tracks + // the current column index correctly (picks up where the explicit one left off) + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertSame(rs.getBooleanOrNull(2), Boolean.FALSE); + assertNull(rs.getBooleanOrNull()); + return null; + }); + // Verify use of getBooleanOrNull(String) followed by default getBooleanOrNull() tracks + // the current column index correctly (picks up where the explicit one left off) + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertSame(rs.getBooleanOrNull("f"), Boolean.FALSE); + assertNull(rs.getBooleanOrNull()); + return null; + }); + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertTrue(rs.getBooleanOrFalse()); + assertFalse(rs.getBooleanOrFalse()); + assertFalse(rs.getBooleanOrFalse()); + return null; + }); + // Verify use of getBooleanOrFalse(int) followed by default getBooleanOrFalse() tracks + // the current column index correctly (picks up where the explicit one left off) + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertFalse(rs.getBooleanOrFalse(2)); + assertFalse(rs.getBooleanOrFalse()); + return null; + }); + // Verify use of getBooleanOrFalse(String) followed by default getBooleanOrFalse() tracks + // the current column index correctly (picks up where the explicit one left off) + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertFalse(rs.getBooleanOrFalse("f")); + assertFalse(rs.getBooleanOrFalse()); + return null; + }); + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertTrue(rs.getBooleanOrTrue()); + assertFalse(rs.getBooleanOrTrue()); + assertTrue(rs.getBooleanOrTrue()); + return null; + }); + // Verify use of getBooleanOrTrue(int) followed by default getBooleanOrTrue() tracks + // the current column index correctly (picks up where the explicit one left off) + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertFalse(rs.getBooleanOrTrue(2)); + assertTrue(rs.getBooleanOrTrue()); + return null; + }); + // Verify use of getBooleanOrTrue(String) followed by default getBooleanOrTrue() tracks + // the current column index correctly (picks up where the explicit one left off) + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertFalse(rs.getBooleanOrTrue("f")); + assertTrue(rs.getBooleanOrTrue()); + return null; + }); + db.toDelete("delete from dbtest where t=? and f=?") + .argBoolean(true).argBoolean(false).update(1); + + db.toInsert("insert into dbtest (t,f,n) values (:t,:f,:n)") + .argBoolean("t", true).argBoolean("f", false).argBoolean("n", null).insert(1); + db.toSelect("select t,f,n from dbtest") + .query(rs -> { + assertTrue(rs.next()); + assertSame(rs.getBooleanOrNull(1), Boolean.TRUE); + assertSame(rs.getBooleanOrNull(2), Boolean.FALSE); + assertNull(rs.getBooleanOrNull(3)); + assertEquals(rs.getBooleanOrFalse(1), Boolean.TRUE); + assertEquals(rs.getBooleanOrFalse(2), Boolean.FALSE); + assertEquals(rs.getBooleanOrFalse(3), Boolean.FALSE); + assertEquals(rs.getBooleanOrTrue(1), Boolean.TRUE); + assertEquals(rs.getBooleanOrTrue(2), Boolean.FALSE); + assertEquals(rs.getBooleanOrTrue(3), Boolean.TRUE); + assertSame(rs.getBooleanOrNull("t"), Boolean.TRUE); + assertSame(rs.getBooleanOrNull("f"), Boolean.FALSE); + assertNull(rs.getBooleanOrNull("n")); + assertEquals(rs.getBooleanOrFalse("t"), Boolean.TRUE); + assertEquals(rs.getBooleanOrFalse("f"), Boolean.FALSE); + assertEquals(rs.getBooleanOrFalse("n"), Boolean.FALSE); + assertEquals(rs.getBooleanOrTrue("t"), Boolean.TRUE); + assertEquals(rs.getBooleanOrTrue("f"), Boolean.FALSE); + assertEquals(rs.getBooleanOrTrue("n"), Boolean.TRUE); + return null; + }); + assertSame(db.toSelect("select t from dbtest").queryBooleanOrNull(), Boolean.TRUE); + assertTrue(db.toSelect("select t from dbtest").queryBooleanOrFalse()); + assertTrue(db.toSelect("select t from dbtest").queryBooleanOrTrue()); + assertSame(db.toSelect("select f from dbtest").queryBooleanOrNull(), Boolean.FALSE); + assertFalse(db.toSelect("select f from dbtest").queryBooleanOrFalse()); + assertFalse(db.toSelect("select f from dbtest").queryBooleanOrTrue()); + assertNull(db.toSelect("select n from dbtest").queryBooleanOrNull()); + assertFalse(db.toSelect("select n from dbtest").queryBooleanOrFalse()); + assertTrue(db.toSelect("select n from dbtest").queryBooleanOrTrue()); + } + + @Test + public void batchInsert() { + new Schema().addTable("dbtest") + .addColumn("pk").primaryKey().schema().execute(db); + + db.toInsert("insert into dbtest (pk) values (?)") + .argInteger(1).batch() + .argInteger(2).batch() + .argInteger(3).batch().insertBatch(); + + assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + } + + @Test + public void batchInsertPkLong() { + new Schema().addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("s").asString(10).schema().execute(db); + + db.toInsert("insert into dbtest (pk,s) values (?,?)") + .argPkLong(1L).argString("hi").batch() + .argPkLong(2L).argString("hello").batch() + .argPkLong(3L).argString("howdy").batch().insertBatch(); + + assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + + try { + db.toInsert("insert into dbtest (pk,s) values (?,?)") + .argPkLong(1L).argString("hi").batch() + // argPkLong in different position ==> error + .argString("hello").argPkLong(2L).batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("The argPkLong() calls must be in the same position across batch records", e.getMessage()); + } + + try { + db.toInsert("insert into dbtest (pk,s) values (?,?)") + // multiple pk calls ==> error + .argPkLong(1L).argPkLong(1L).batch() + .argPkLong(2L).argString("hello").batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("Only call one argPk*() method", e.getMessage()); + } + } + + @Test + public void batchInsertPkLongNamed() { + new Schema().addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("s").asString(10).schema().execute(db); + + db.toInsert("insert into dbtest (pk,s) values (:pk,?)") + .argPkLong("pk", 1L).argString("hi").batch() + .argPkLong("pk", 2L).argString("hello").batch() + .argPkLong("pk", 3L).argString("howdy").batch().insertBatch(); + + assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + + db.toInsert("insert into dbtest (pk,s) values (:pk,?)") + .batch().argPkLong("pk", 4L).argString("hi").batch() + .argString("hello").argPkLong("pk", 5L).insertBatch(); + + assertEquals(5, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + + try { + db.toInsert("insert into dbtest (pk,s) values (:pk,?)") + // multiple pk calls ==> error + .argPkLong("pk", 1L).argPkLong(1L).batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("Only call one argPk*() method", e.getMessage()); + } + + try { + db.toInsert("insert into dbtest (pk,s) values (?,?)") + .argPkLong("pk", 1L).argString("howdy").batch() + // different name for pk on second batch ==> error + .argPkLong("na", 2L).argString("hello").batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("The primary key argument name must match across batch rows", e.getMessage()); + } + } + + @Test + public void batchInsertPkSeq() { + db.dropSequenceQuietly("seq"); + new Schema().addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("s").asString(10).schema() + .addSequence("seq").schema().execute(db); + + db.toInsert("insert into dbtest (pk,s) values (?,?)") + .argPkSeq("seq").argString("hi").batch() + .argPkSeq("seq").argString("hello").batch() + .argPkSeq("seq").argString("howdy").batch().insertBatch(); + + assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + + try { + db.toInsert("insert into dbtest (pk,s) values (?,?)") + .argPkSeq("seq").argString("hi").batch() + // argPkLong in different position ==> error + .argString("hello").argPkSeq("seq").batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("The argPkSeq() calls must be in the same position across batch records", e.getMessage()); + } + + try { + db.toInsert("insert into dbtest (pk,s) values (?,?)") + // multiple pk calls ==> error + .argPkSeq("seq").argPkSeq("seq").batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("Only call one argPk*() method", e.getMessage()); + } + } + + @Test + public void batchInsertPkSeqNamed() { + db.dropSequenceQuietly("seq"); + new Schema().addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("s").asString(10).schema() + .addSequence("seq").schema().execute(db); + + db.toInsert("insert into dbtest (pk,s) values (:pk,?)") + .argPkSeq("pk", "seq").argString("hi").batch() + .argPkSeq("pk", "seq").argString("hello").batch() + .argPkSeq("pk", "seq").argString("howdy").batch().insertBatch(); + + assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + + db.toInsert("insert into dbtest (pk,s) values (:pk,?)") + .batch().argPkSeq("pk", "seq").argString("hi").batch() + .argString("hello").argPkSeq("pk", "seq").insertBatch(); + + assertEquals(5, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); + + try { + db.toInsert("insert into dbtest (pk,s) values (:pk,?)") + // multiple pk calls ==> error + .argPkSeq("pk", "seq").argPkSeq("pk", "seq").batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("Only call one argPk*() method", e.getMessage()); + } + + try { + db.toInsert("insert into dbtest (pk,s) values (?,?)") + .argPkSeq("pk", "seq").argString("howdy").batch() + // different name for pk on second batch ==> error + .argPkSeq("na", "seq").argString("hello").batch().insertBatch(); + fail("Expecting an exception to be thrown"); + } catch (DatabaseException e) { + assertEquals("The primary key argument name must match across batch rows", e.getMessage()); + } + } + + @Test + public void bigClob() { + new Schema().addTable("dbtest").addColumn("str_lob").asClob().schema().execute(db); + final String longString = "0123456789".repeat(40000); + db.toInsert("insert into dbtest values (?)").argClobString(longString).insert(1); + db.toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(longString, rs.getClobStringOrNull(1)); + assertEquals(longString, rs.getClobStringOrNull("str_lob")); + assertEquals(longString, readerToString(rs.getClobReaderOrNull(1))); + return null; + }); + // Intentional slight variation here to test get() + db.get().toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(longString, readerToString(rs.getClobReaderOrNull("str_lob"))); + return null; + }); + db.toDelete("delete from dbtest").update(1); + db.toInsert("insert into dbtest values (?)").argClobReader(new StringReader(longString)).insert(1); + db.toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(longString, rs.getClobStringOrNull(1)); + assertEquals(longString, rs.getClobStringOrNull("str_lob")); + assertEquals(longString, readerToString(rs.getClobReaderOrNull(1))); + return null; + }); + db.toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertEquals(longString, readerToString(rs.getClobReaderOrNull("str_lob"))); + return null; + }); + } + + @Test + public void bigBlob() { + new Schema().addTable("dbtest").addColumn("bin_blob").asBlob().schema().execute(db); + final byte[] bigBytes = "0123456789".repeat(40000).getBytes(); + db.toInsert("insert into dbtest values (?)").argBlobBytes(bigBytes).insert(1); + db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertArrayEquals(bigBytes, rs.getBlobBytesOrNull(1)); + assertArrayEquals(bigBytes, rs.getBlobBytesOrNull("bin_blob")); + assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull(1))); + return null; + }); + db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull("bin_blob"))); + return null; + }); + db.toDelete("delete from dbtest").update(1); + db.toInsert("insert into dbtest values (?)").argBlobStream(new ByteArrayInputStream(bigBytes)).insert(1); + db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertArrayEquals(bigBytes, rs.getBlobBytesOrNull(1)); + assertArrayEquals(bigBytes, rs.getBlobBytesOrNull("bin_blob")); + assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull(1))); + return null; + }); + db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { + assertTrue(rs.next()); + assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull("bin_blob"))); + return null; + }); + } + + @Test + public void argLocalDateTimeZones() { + LocalDate januaryOne2000 = LocalDate.of(2000, Month.JANUARY, 1); + // Verify we always get the same LocalDate regardless of time zone and DB across all drivers + new Schema().addTable("dbtest").addColumn("i").asLocalDate().schema().execute(db); + db.toInsert("insert into dbtest (i) values (?)").argLocalDate(januaryOne2000).insert(1); + // Query without specifying a zone + assertEquals(januaryOne2000, + db.toSelect("select i from dbtest where i=?").argLocalDate(januaryOne2000).queryLocalDateOrNull()); + TimeZone defaultTZ = TimeZone.getDefault(); + try { + String[] availableTZs = TimeZone.getAvailableIDs(); + for (String tz : availableTZs) { + TimeZone.setDefault(TimeZone.getTimeZone(tz)); + LocalDate result = + db.toSelect("select i from dbtest where i=?").argLocalDate(januaryOne2000).queryLocalDateOrNull(); + assertEquals(januaryOne2000, result); + } + } finally { + TimeZone.setDefault(defaultTZ); + } + } + + @Test + public void argLocalDateLeapYear() { + new Schema().addTable("dbtest").addColumn("testdate").asLocalDate().schema().execute(db); + + // Start by adding Febriary 28 and March 1 of 1900. This was not a leap year. + LocalDate feb1900 = LocalDate.of(1900, Month.FEBRUARY, 28); + db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(feb1900).insert(1); + assertEquals(feb1900, + db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(feb1900).queryLocalDateOrNull()); + + LocalDate mar1900 = LocalDate.of(1900, Month.MARCH, 1); + db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(mar1900).insert(1); + assertEquals(mar1900, + db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(mar1900).queryLocalDateOrNull()); + + // Now try Feb 28, 29, and March 1 of 2000. This was a leap year + LocalDate feb2000 = LocalDate.of(2000, Month.FEBRUARY, 28); + db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(feb2000).insert(1); + assertEquals(feb2000, + db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(feb2000).queryLocalDateOrNull()); + + LocalDate febLeap2000 = LocalDate.of(2000, Month.FEBRUARY, 29); + db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(febLeap2000).insert(1); + assertEquals(febLeap2000, + db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(febLeap2000).queryLocalDateOrNull()); + + LocalDate mar2000 = LocalDate.of(2000, Month.MARCH, 1); + db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(mar2000).insert(1); + assertEquals(mar2000, + db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(mar2000).queryLocalDateOrNull()); + } + + @Test + public void argIntegerMinMax() { + new Schema().addTable("dbtest").addColumn("i").asInteger().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argInteger(Integer.MIN_VALUE).insert(1); + assertEquals(Integer.valueOf(Integer.MIN_VALUE), + db.toSelect("select i from dbtest where i=?").argInteger(Integer.MIN_VALUE).queryIntegerOrNull()); + + db.toInsert("insert into dbtest (i) values (?)").argInteger(Integer.MAX_VALUE).insert(1); + assertEquals(Integer.valueOf(Integer.MAX_VALUE), + db.toSelect("select i from dbtest where i=?").argInteger(Integer.MAX_VALUE).queryIntegerOrNull()); + } + + @Test + public void argLongMinMax() { + new Schema().addTable("dbtest").addColumn("i").asLong().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argLong(Long.MIN_VALUE).insert(1); + assertEquals(Long.valueOf(Long.MIN_VALUE), + db.toSelect("select i from dbtest where i=?").argLong(Long.MIN_VALUE).queryLongOrNull()); + + db.toInsert("insert into dbtest (i) values (?)").argLong(Long.MAX_VALUE).insert(1); + assertEquals(Long.valueOf(Long.MAX_VALUE), + db.toSelect("select i from dbtest where i=?").argLong(Long.MAX_VALUE).queryLongOrNull()); + } + + @Test + public void argFloatMinMax() { + new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.MIN_VALUE).insert(1); + assertEquals(Float.valueOf(Float.MIN_VALUE), + db.toSelect("select i from dbtest where i=?").argFloat(Float.MIN_VALUE).queryFloatOrNull()); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.MAX_VALUE).insert(1); + assertEquals(Float.valueOf(Float.MAX_VALUE), + db.toSelect("select i from dbtest where i=?").argFloat(Float.MAX_VALUE).queryFloatOrNull()); + } + + @Test + public void argFloatNaN() { + new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.NaN).insert(1); + assertEquals(Float.valueOf(Float.NaN), + db.toSelect("select i from dbtest where i=?").argFloat(Float.NaN).queryFloatOrNull()); + } + + @Test + public void argFloatInfinity() { + new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.NEGATIVE_INFINITY).insert(1); + assertEquals(Float.valueOf(Float.NEGATIVE_INFINITY), + db.toSelect("select i from dbtest where i=?").argFloat(Float.NEGATIVE_INFINITY).queryFloatOrNull()); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.POSITIVE_INFINITY).insert(1); + assertEquals(Float.valueOf(Float.POSITIVE_INFINITY), + db.toSelect("select i from dbtest where i=?").argFloat(Float.POSITIVE_INFINITY).queryFloatOrNull()); + } + + @Test + public void argFloatZero() { + new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(0f).insert(1); + assertEquals(Float.valueOf(0f), + db.toSelect("select i from dbtest where i=?").argFloat(0f).queryFloatOrNull()); + } + + @Test + public void argFloatNegativeZero() { + new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argFloat(-0f).insert(1); + assertEquals(Float.valueOf(-0f), + db.toSelect("select i from dbtest where i=?").argFloat(-0f).queryFloatOrNull()); + } + + @Test + public void argDoubleMinMax() { + new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.MIN_VALUE).insert(1); + assertEquals(Double.valueOf(Double.MIN_VALUE), + db.toSelect("select i from dbtest where i=?").argDouble(Double.MIN_VALUE).queryDoubleOrNull()); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.MAX_VALUE).insert(1); + assertEquals(Double.valueOf(Double.MAX_VALUE), + db.toSelect("select i from dbtest where i=?").argDouble(Double.MAX_VALUE).queryDoubleOrNull()); + } + + @Test + public void argDoubleNaN() { + new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.NaN).insert(1); + assertEquals(Double.valueOf(Double.NaN), + db.toSelect("select i from dbtest where i=?").argDouble(Double.NaN).queryDoubleOrNull()); + } + + @Test + public void argDoubleInfinity() { + new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.NEGATIVE_INFINITY).insert(1); + assertEquals(Double.valueOf(Double.NEGATIVE_INFINITY), + db.toSelect("select i from dbtest where i=?").argDouble(Double.NEGATIVE_INFINITY).queryDoubleOrNull()); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.POSITIVE_INFINITY).insert(1); + assertEquals(Double.valueOf(Double.POSITIVE_INFINITY), + db.toSelect("select i from dbtest where i=?").argDouble(Double.POSITIVE_INFINITY).queryDoubleOrNull()); + } + + @Test + public void argDoubleZero() { + new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(0d).insert(1); + assertEquals(Double.valueOf(0d), + db.toSelect("select i from dbtest where i=?").argDouble(0d).queryDoubleOrNull()); + } + + @Test + public void argDoubleNegativeZero() { + new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); + + db.toInsert("insert into dbtest (i) values (?)").argDouble(-0d).insert(1); + assertEquals(Double.valueOf(-0d), + db.toSelect("select i from dbtest where i=?").argDouble(-0d).queryDoubleOrNull()); + } + + @Test + public void argBigDecimal38Precision0() { + new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 0).schema().execute(db); + + BigDecimal value = new BigDecimal("99999999999999999999999999999999999999"); // 38 digits + db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); + assertEquals(value, + db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); + } + + @Test + public void argBigDecimal38Precision1() { + new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 1).schema().execute(db); + + BigDecimal value = new BigDecimal("9999999999999999999999999999999999999.9"); // 38 digits + db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); + assertEquals(value, + db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); + } + + @Test + public void argBigDecimal38Precision37() { + new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 37).schema().execute(db); + + BigDecimal value = new BigDecimal("9.9999999999999999999999999999999999999"); // 38 digits + db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); + assertEquals(value, + db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); + } + + @Test + public void argBigDecimal38Precision38() { + new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 38).schema().execute(db); + BigDecimal value = new BigDecimal("0.99999999999999999999999999999999999999"); // 38 digits + db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); + logger.info(db.toSelect("select i from dbtest").queryBigDecimalOrNull().toString()); + assertEquals(value, + db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); + } + + @Test + public void dropTableQuietly() { + db.dropTableQuietly("dbtest"); + new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); + db.dropTableQuietly("dbtest"); + // Verify the quietly part really kicks in, since the table might have existed above + db.dropTableQuietly("dbtest"); + new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); + } + + @Test + public void dropSequenceQuietly() { + db.dropSequenceQuietly("dbtest_seq"); + // Verify the quietly part really kicks in, since the sequence might have existed above + db.dropSequenceQuietly("dbtest_seq"); + } + + @Test + public void insertReturningPkSeq() { + db.dropSequenceQuietly("dbtest_seq"); + + db.ddl("create table dbtest (pk numeric)").execute(); + db.ddl("create sequence dbtest_seq start with 1").execute(); + + assertEquals(Long.valueOf(1L), db.toInsert("insert into dbtest (pk) values (:seq)") + .argPkSeq(":seq", "dbtest_seq").insertReturningPkSeq("pk")); + assertEquals(Long.valueOf(2L), db.toInsert("insert into dbtest (pk) values (:seq)") + .argPkSeq(":seq", "dbtest_seq").insertReturningPkSeq("pk")); + } + + @Test + public void insertReturningAppDate() { + db.dropSequenceQuietly("dbtest_seq"); + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("d") + .asLocalDateTime() + .table() + .schema() + .addSequence("dbtest_seq").schema() + .execute(db); + db.toInsert("insert into dbtest (pk, d) values (:seq, :d)") + .argPkSeq(":seq", "dbtest_seq") + .argLocalDateTime(":d", now) + .insertReturning("dbtest", "pk", rs -> { + assertTrue(rs.next()); + assertEquals(Long.valueOf(1L), rs.getLongOrNull(1)); + assertThat(rs.getLocalDateTimeOrNull(2, ZoneId.systemDefault()), equalTo(now)); + assertFalse(rs.next()); + return null; + }, "d"); + assertEquals(Long.valueOf(1L), db.toSelect("select count(*) from dbtest where d=?").argLocalDateTime(now).queryLongOrNull()); + } + + @Test + public void quickQueries() { + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("d").asLocalDateTime().table() + .addColumn("d2").asLocalDateTime().table() + .addColumn("d3").asLocalDate().table() + .addColumn("d4").asLocalDate().table() + .addColumn("s").asString(5).table() + .addColumn("s2").asString(5).table() + .addColumn("i").asInteger().table().schema() + .execute(db); + db.toInsert("insert into dbtest (pk, d, d3, s) values (?,?,?,?)") + .argLong(1L) + .argLocalDateTime(now) + .argLocalDate(localDateNow) + .argString("foo") + .insert(1); + assertEquals(Long.valueOf(1L), db.toSelect("select pk from dbtest").queryLongOrNull()); + assertNull(db.toSelect("select pk from dbtest where 1=0").queryLongOrNull()); + assertNull(db.toSelect("select i from dbtest").queryLongOrNull()); + assertEquals(1L, db.toSelect("select pk from dbtest").queryLongOrZero()); + assertEquals(0L, db.toSelect("select pk from dbtest where 1=0").queryLongOrZero()); + assertEquals(0L, db.toSelect("select i from dbtest").queryLongOrZero()); + assertEquals(1L, (long) db.toSelect("select pk from dbtest").queryLongs().get(0)); + assertTrue(db.toSelect("select pk from dbtest where 1=0").queryLongs().isEmpty()); + assertTrue(db.toSelect("select i from dbtest").queryLongs().isEmpty()); + assertEquals(Integer.valueOf(1), db.toSelect("select pk from dbtest").queryIntegerOrNull()); + assertNull(db.toSelect("select pk from dbtest where 1=0").queryIntegerOrNull()); + assertNull(db.toSelect("select i from dbtest").queryIntegerOrNull()); + assertEquals(1, db.toSelect("select pk from dbtest").queryIntegerOrZero()); + assertEquals(0, db.toSelect("select pk from dbtest where 1=0").queryIntegerOrZero()); + assertEquals(0, db.toSelect("select i from dbtest").queryIntegerOrZero()); + assertEquals(1L, (int) db.toSelect("select pk from dbtest").queryIntegers().get(0)); + assertTrue(db.toSelect("select pk from dbtest where 1=0").queryIntegers().isEmpty()); + assertTrue(db.toSelect("select i from dbtest").queryIntegers().isEmpty()); + assertEquals("foo", db.toSelect("select s from dbtest").queryStringOrNull()); + assertNull(db.toSelect("select s from dbtest where 1=0").queryStringOrNull()); + assertNull(db.toSelect("select s2 from dbtest").queryStringOrNull()); + assertEquals("foo", db.toSelect("select s from dbtest").queryStringOrEmpty()); + assertEquals("", db.toSelect("select s from dbtest where 1=0").queryStringOrEmpty()); + assertEquals("", db.toSelect("select s2 from dbtest").queryStringOrEmpty()); + assertEquals("foo", db.toSelect("select s from dbtest").queryStrings().get(0)); + assertTrue(db.toSelect("select s from dbtest where 1=0").queryStrings().isEmpty()); + assertTrue(db.toSelect("select s2 from dbtest").queryStrings().isEmpty()); + assertEquals(now, db.toSelect("select d from dbtest").queryLocalDateTimeOrNull()); + assertNull(db.toSelect("select d from dbtest where 1=0").queryLocalDateTimeOrNull()); + assertNull(db.toSelect("select d2 from dbtest").queryLocalDateTimeOrNull()); + assertEquals(db.toSelect("select d from dbtest").queryLocalDateTimes().get(0), now); + assertTrue(db.toSelect("select d from dbtest where 1=0").queryLocalDateTimes().isEmpty()); + assertTrue(db.toSelect("select d2 from dbtest").queryLocalDateTimes().isEmpty()); + + assertEquals(localDateNow, db.toSelect("select d3 from dbtest").queryLocalDateOrNull()); + assertNull(db.toSelect("select d3 from dbtest where 1=0").queryLocalDateOrNull()); + assertEquals(db.toSelect("select d3 from dbtest").queryLocalDates().get(0), localDateNow); + assertEquals(Long.valueOf(1L), + db.toSelect("select count(*) from dbtest where d3=?").argLocalDate(localDateNow).queryLongOrNull()); + + assertNull(db.toSelect("select d4 from dbtest").queryLocalDateOrNull()); + assertNull(db.toSelect("select d4 from dbtest where 1=0").queryLocalDateOrNull()); + assertTrue(db.toSelect("select d4 from dbtest").queryLocalDates().isEmpty()); + } + + @Test + public void rowHandlerQueries() { + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().schema() + .execute(db); + + db.toInsert("insert into dbtest (pk) values (?)").argLong(1L).insert(1); + db.toInsert("insert into dbtest (pk) values (?)").argLong(2L).insert(1); + + RowHandler rowHandler = Row::getLongOrNull; + + List many = db.toSelect("select pk from dbtest").queryMany(rowHandler); + assertEquals(2, many.size()); + + assertEquals(Long.valueOf(1), db.toSelect("select pk from dbtest where pk=1").queryOneOrNull(rowHandler)); + assertNull(db.toSelect("select pk from dbtest where pk=9").queryOneOrNull(rowHandler)); + try { + db.toSelect("select pk from dbtest").queryOneOrNull(rowHandler); + fail("Should have thrown an exception"); + } catch (ConstraintViolationException e) { + assertEquals("Expected exactly one row to be returned but found multiple", e.getCause().getMessage()); + } + try { + db.toSelect("select pk from dbtest where pk=9").queryOneOrThrow(rowHandler); + fail("Should have thrown an exception"); + } catch (ConstraintViolationException e) { + assertEquals("Expected exactly one row to be returned but found none", e.getMessage()); + } + + assertEquals(Long.valueOf(1), db.toSelect("select pk from dbtest where pk=1").queryFirstOrNull(rowHandler)); + assertEquals(Long.valueOf(1), db.toSelect("select pk from dbtest order by 1").queryFirstOrNull(rowHandler)); + assertNull(db.toSelect("select pk from dbtest where pk=9").queryFirstOrNull(rowHandler)); + try { + db.toSelect("select pk from dbtest where pk=9").queryFirstOrThrow(rowHandler); + fail("Should have thrown an exception"); + } catch (ConstraintViolationException e) { + assertEquals("Expected one or more rows to be returned but found none", e.getMessage()); + } + } + + @Test + public void nextSequenceValue() { + db.dropSequenceQuietly("dbtest_seq"); + new Schema() + .addSequence("dbtest_seq").schema() + .execute(db); + + assertEquals(Long.valueOf(1L), db.nextSequenceValue("dbtest_seq")); + } + + @Test + public void insertReturningDbDate() { + db.dropSequenceQuietly("dbtest_seq"); + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("d").asLocalDateTime().table().schema() + .addSequence("dbtest_seq").schema() + .execute(db); + LocalDateTime dbNow = db.toInsert("insert into dbtest (pk, d) values (:seq, :d)") + .argPkSeq(":seq", "dbtest_seq") + .argLocalDateTimeNowPerDb(":d") + .insertReturning("dbtest", "pk", rs -> { + assertTrue(rs.next()); + assertEquals(Long.valueOf(1L), rs.getLongOrNull(1)); + LocalDateTime dbDate = rs.getLocalDateTimeOrNull(2); + assertFalse(rs.next()); + return dbDate; + }, "d"); + assertEquals(Long.valueOf(1L), + db.toSelect("select count(*) from dbtest where d = ?").argLocalDateTime(dbNow).queryLongOrNull()); + } + + @Test + public void daylightSavings() { + 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. + StatementAdapter adaptor = new StatementAdapter(new OptionsDefault(db.flavor())); + assertEquals(lastStdDateSpring.toString(), adaptor.nullLocalDate(lastStdDateSpring).toString()); + assertEquals(firstDSTDateSpring.toString(), adaptor.nullLocalDate(firstDSTDateSpring).toString()); + } + + @Test + public void insertLocalDate() { + // Date without time + new Schema() + .addTable("dbtest") + .addColumn("d").asLocalDate().table().schema() + .execute(db); + LocalDate dateOfBirth = LocalDate.of(1951, Month.AUGUST, 9); + db.toInsert("insert into dbtest (d) values (?)") + .argLocalDate(dateOfBirth) + .insert(1); + LocalDate testDate = db.toSelect("select d from dbtest").queryLocalDateOrNull(); + assertEquals(dateOfBirth, testDate); + } + + @Test + public void localDateRoundTrip() { + new Schema() + .addTable("dbtest") + .addColumn("d1").asLocalDate().table() + .addColumn("d2").asLocalDate().table().schema() + .execute(db); + // Store current time as per the database + db.toInsert("insert into dbtest (d1) values (?)") + .argLocalDate(localDateNow) + .insert(1); + // Now pull it out, put it back in, and verify it matches in the database + LocalDate queryRsDate = db.toSelect("select d1 from dbtest").queryLocalDateOrNull(); + db.toUpdate("update dbtest set d2=?") + .argLocalDate(queryRsDate) + .update(1); + assertEquals(Long.valueOf(1L), db.toSelect("select count(*) from dbtest where d1=d2").queryLongOrNull()); + } + + /** + * Make sure database times are inserted with at least millisecond precision. + * This test is non-deterministic since it is checking the timestamp provided + * by the database, so we use a retry to give it up to ten attempts. + */ + @Test + public void dateMillis() { + for (int attempts = 1; attempts <= 10; attempts++) { + new Schema() + .addTable("dbtest") + .addColumn("d").asLocalDateTime().table().schema() + .execute(db); + db.toInsert("insert into dbtest (d) values (?)") + .argLocalDateTimeNowPerDb() + .insert(1); + LocalDateTime dbNow = db.toSelect("select d from dbtest").queryLocalDateTimeOrNull(); + if (dbNow != null && dbNow.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() % 10 != 0) { + return; + } + logger.info("Zero in least significant digit (attempt " + attempts + ")"); + db.dropTableQuietly(TEST_TABLE_NAME); + } + fail("Timestamp had zero in the least significant digit"); + } + + @Test + public void dateRoundTrip() { + new Schema() + .addTable("dbtest") + .addColumn("d1").asLocalDateTime().table() + .addColumn("d2").asLocalDateTime().table() + .schema() + .execute(db); + db.toInsert("insert into dbtest (d1) values (?)") + .argLocalDateTimeNowPerDb() + .insert(1); + LocalDateTime dbNow = db.toSelect("select d1 from dbtest").queryLocalDateTimeOrNull(); + db.toUpdate("update dbtest set d2 = ?") + .argLocalDateTime(dbNow) + .update(1); + assertEquals(Long.valueOf(1L), db.toSelect("select count(*) from dbtest where d1=d2").queryLongOrNull()); + } + + @Test + public void dateRoundTripTimezones() { + new Schema() + .addTable("dbtest") + .addColumn("d") + .asLocalDateTime() + .table() + .schema() + .execute(db); + Instant instant = Instant.ofEpochMilli(166656789L); + ZoneId dateMinusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)); + LocalDateTime dateMinus = LocalDateTime.ofInstant(instant, dateMinusZone); + ZoneId datePlusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+4)); + LocalDateTime datePlus = LocalDateTime.ofInstant(instant,datePlusZone); + logger.log(Level.INFO, "dateMinus = " + dateMinus); + db.toInsert("insert into dbtest (d) values (?)") + .argLocalDateTime(dateMinus) + .insert(1); + LocalDateTime localDateTimeMinus = db.toSelect("select d from dbtest") + .queryLocalDateTimeOrNull(); + assertEquals(dateMinus, localDateTimeMinus); + assertEquals("1970-01-02 18:17:36.789", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTimeMinus)); + db.toDelete("delete from dbtest where d = ?").argLocalDateTime(dateMinus).update(1); + db.toInsert("insert into dbtest (d) values (?)") + .argLocalDateTime(datePlus) + .insert(1); + LocalDateTime localDateTimePlus = db.toSelect("select d from dbtest") + .queryLocalDateTimeOrNull(datePlusZone); + assertEquals(datePlus, localDateTimePlus); + assertEquals("1970-01-03 02:17:36.789", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTimePlus)); + db.toDelete("delete from dbtest where d = ?").argLocalDateTime(datePlus).update(1); + } + + /** + * Verify the appropriate database flavor can correctly convert a {@code LocalDateTime} + * into a SQL function representing a conversion from string to timestamp. This + * function is used to write debug SQL to the log in a way that could be manually + * executed if desired. + */ + @Test + public void stringLocalDateTimeFunctions() { + Instant instant = Instant.ofEpochMilli(166656789L); + ZoneId dateMinusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)); + LocalDateTime dateMinus = LocalDateTime.ofInstant(instant, dateMinusZone); + ZoneId datePlusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+4)); + LocalDateTime datePlus = LocalDateTime.ofInstant(instant, datePlusZone); + logger.info("LocalDateTime: dateMinus=" + dateMinus + " datePlus=" + datePlus); + new Schema().addTable("dbtest").addColumn("d") + .asLocalDateTime() + .schema() + .execute(db); + db.toInsert("insert into dbtest (d) values (?)") + .argLocalDateTime(dateMinus) + .insert(1); + LocalDateTime localDateTime = db.toSelect("select d from dbtest") + .queryLocalDateTimeOrNull(); + assertEquals("1970-01-02 18:17:36.789", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTime.atZone(dateMinusZone))); + db.toDelete("delete from dbtest where d = ?") + .argLocalDateTime(dateMinus) + .update(1); + db.toInsert("insert into dbtest (d) values (?)") + .argLocalDateTime(datePlus) + .insert(1); + localDateTime = db.toSelect("select d from dbtest") + .queryLocalDateTimeOrNull(datePlusZone); + assertEquals("1970-01-03 02:17:36.789", + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTime.atZone(datePlusZone))); + db.toDelete("delete from dbtest where d = ?") + .argLocalDateTime(datePlus) + .update(1); + } + + @Test + public void mixPositionalAndNamedParameters() { + new Schema() + .addTable("dbtest") + .addColumn("pk").primaryKey().table() + .addColumn("d").asLocalDateTime().table() + .addColumn("a").asInteger().table().schema() + .execute(db); + + db.toSelect("select pk as \"time:: now??\" from dbtest where a=? and d=:now") + .argInteger(1).argLocalDateTimeNowPerDb("now").query(rs -> { + assertFalse(rs.next()); + return null; + }); + } + + public String readerToString(Reader reader) throws IOException { + char[] buffer = new char[1024]; + StringBuilder out = new StringBuilder(); + int byteCount; + while ((byteCount = reader.read(buffer, 0, buffer.length)) >= 0) { + out.append(buffer, 0, byteCount); + } + return out.toString(); + } + + public byte[] inputStreamToString(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int byteCount; + while ((byteCount = inputStream.read(buffer, 0, buffer.length)) >= 0) { + out.write(buffer, 0, byteCount); + } + return out.toByteArray(); + } +} diff --git a/settings.gradle b/settings.gradle index 92ce4c8..112d795 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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'