DBClient integration tests for MySQL, MadiaDB, PostgreSQL and MS SQL (#2383)

* DBClient integration tests for MySQL, MadiaDB, PostgreSQL and MS SQL Server.
* Changes requested in PR.
* Fixed javadoc build issue.

Signed-off-by: Tomas Kraus <Tomas.Kraus@oracle.com>
This commit is contained in:
Tomáš Kraus
2020-10-12 15:04:14 +02:00
committed by GitHub
parent 154786c3e2
commit 317d5e36fb
15 changed files with 1290 additions and 90 deletions

View File

@@ -57,10 +57,17 @@ public interface DbClient {
*/
<U, T extends Subscribable<U>> T execute(Function<DbExecute, T> executor);
/**
* Name of the named statement used in database health checks.
*/
String PING_STATEMENT_NAME = "ping";
/**
* Pings the database, completes when DB is up and ready, completes exceptionally if not.
* Executes simple SQL query defined as {@code db.statements.ping} configuration property.
*
* @return stage that completes when the ping finished
* @deprecated Use {@code io.helidon.dbclient.health.DbClientHealthCheck} instead.
*/
Single<Void> ping();

View File

@@ -16,7 +16,10 @@
package io.helidon.dbclient.health;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.helidon.common.reactive.Awaitable;
import io.helidon.dbclient.DbClient;
import org.eclipse.microprofile.health.HealthCheck;
@@ -26,18 +29,16 @@ import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
/**
* Database health check.
*/
public final class DbClientHealthCheck implements HealthCheck {
public abstract class DbClientHealthCheck implements HealthCheck {
private final DbClient dbClient;
private final String name;
private DbClientHealthCheck(Builder builder) {
this.dbClient = builder.database;
this.name = builder.name;
}
/* Local logger instance. */
private static final Logger LOGGER = Logger.getLogger(DbClientHealthCheck.class.getName());
/* Default hHealth check timeout in seconds (to wait for statement execution response). */
private static final int DEFAULT_TIMEOUT_SECONDS = 10;
/**
* Create a health check for the database.
* Create a health check with default settings for the database.
* This health check will execute DML statement named {@code ping} to verify database status.
*
* @param dbClient A database that implements {@link io.helidon.dbclient.DbClient#ping()}
* @return health check that can be used with
@@ -58,39 +59,220 @@ public final class DbClientHealthCheck implements HealthCheck {
return new Builder(dbClient);
}
/* Helidon database client. */
private final DbClient dbClient;
/* Health check name. */
private final String name;
/* Health check timeout length (to wait for statement execution response). */
private final long timeoutDuration;
/* Health check timeout units (to wait for statement execution response). */
private final TimeUnit timeoutUnit;
private DbClientHealthCheck(Builder builder) {
this.dbClient = builder.database;
this.name = builder.name;
this.timeoutDuration = builder.timeoutDuration;
this.timeoutUnit = builder.timeoutUnit;
}
/**
* Execute the ping statement.
*
* @return {@code Awaitable} instance to wait for
*/
protected abstract Awaitable<?> execPing();
@Override
public HealthCheckResponse call() {
HealthCheckResponseBuilder builder = HealthCheckResponse.builder()
.name(name);
HealthCheckResponseBuilder builder = HealthCheckResponse.builder().name(name);
try {
dbClient.ping().await(10, TimeUnit.SECONDS);
execPing().await(timeoutDuration, timeoutUnit);
builder.up();
} catch (Throwable e) {
builder.down();
builder.withData("ErrorMessage", e.getMessage());
builder.withData("ErrorClass", e.getClass().getName());
e.printStackTrace();
LOGGER.log(Level.FINER, e, () -> String.format(
"Database %s is not responding: %s", dbClient.dbType(), e.getMessage()));
}
return builder.build();
}
protected DbClient dbClient() {
return dbClient;
}
/**
* Database health check which calls default DBClient's {@code ping} method.
*/
private static final class DbClientHealthCheckAsPing extends DbClientHealthCheck {
private DbClientHealthCheckAsPing(Builder builder) {
super(builder);
LOGGER.finest("Created an instance of DbClientHealthCheckAsPing");
}
@Override
protected Awaitable<Void> execPing() {
return dbClient().ping();
}
}
/**
* Database health check which calls DBClient's {@code namedDml} method.
*/
private static final class DbClientHealthCheckAsNamedDml extends DbClientHealthCheck {
/* Name of the statement. */
private final String statementName;
private DbClientHealthCheckAsNamedDml(Builder builder) {
super(builder);
this.statementName = builder.statementName;
LOGGER.finest("Created an instance of DbClientHealthCheckAsNamedDml");
}
@Override
protected Awaitable<?> execPing() {
return dbClient().execute(exec -> exec.namedDml(statementName));
}
}
/**
* Database health check which calls DBClient's {@code dml} method.
*/
private static final class DbClientHealthCheckAsDml extends DbClientHealthCheck {
/* Custom statement. */
private final String statement;
private DbClientHealthCheckAsDml(Builder builder) {
super(builder);
this.statement = builder.statement;
LOGGER.finest("Created an instance of DbClientHealthCheckAsDml");
}
@Override
protected Awaitable<?> execPing() {
return dbClient().execute(exec -> exec.dml(statement));
}
}
/**
* Database health check which calls DBClient's {@code namedQuery} method.
*/
private static final class DbClientHealthCheckAsNamedQuery extends DbClientHealthCheck {
/* Name of the statement. */
private final String statementName;
private DbClientHealthCheckAsNamedQuery(Builder builder) {
super(builder);
this.statementName = builder.statementName;
LOGGER.finest("Created an instance of DbClientHealthCheckAsNamedQuery");
}
@Override
protected Awaitable<?> execPing() {
return dbClient()
.execute(exec -> exec.namedQuery(statementName).forEach(it -> {}));
}
}
/**
* Database health check which calls DBClient's {@code query} method.
*/
private static final class DbClientHealthCheckAsQuery extends DbClientHealthCheck {
/* Custom statement. */
private final String statement;
private DbClientHealthCheckAsQuery(Builder builder) {
super(builder);
this.statement = builder.statement;
LOGGER.finest("Created an instance of DbClientHealthCheckAsQuery");
}
@Override
protected Awaitable<?> execPing() {
return dbClient()
.execute(exec -> exec.query(statement).forEach(it -> {}));
}
}
/**
* Fluent API builder for {@link DbClientHealthCheck}.
* Default health check setup will call named DML statement with name {@code ping}.
* This named DML statement shall be configured in {@code statements} section
* of the DBClient configuration file.
*/
public static final class Builder implements io.helidon.common.Builder<DbClientHealthCheck> {
/* Helidon database client. */
private final DbClient database;
/* Health check name. */
private String name;
/* Health check timeout length (to wait for statement execution response). */
private long timeoutDuration;
/* Health check timeout units (to wait for statement execution response). */
private TimeUnit timeoutUnit;
// Those two boolean variables define 4 ways of query execution:
//
// +-----------+----------+--------+------------+-------+
// | DbExecute | namedDML | dml | namedQuery | query |
// +-----------+----------+--------+------------+-------+
// | isDML | true | true | false | false |
// | named | true | faslse | true | false |
// +-----------+----------+--------+------------+-------+
// The best performance optimized solution seems to be polymorphysm for part of check method.
/* Health check statement is DML when {@code true} and query when {@code false}. */
private boolean isDML;
/* Whether to use named statement or statement passed as an argument. */
private boolean isNamedstatement;
/** Name of the statement. */
private String statementName;
/** Custom statement. */
private String statement;
private Builder(DbClient database) {
this.database = database;
this.name = database.dbType();
this.timeoutDuration = DEFAULT_TIMEOUT_SECONDS;
this.timeoutUnit = TimeUnit.SECONDS;
this.isDML = true;
this.isNamedstatement = true;
this.statementName = null;
this.statement = null;
}
// Defines polymorphysm for ping statement execution based on isDML and isNamedstatement values.
// Default health check is to call DBClient's ping method (when no customization is set).
@Override
public DbClientHealthCheck build() {
return new DbClientHealthCheck(this);
if (isDML) {
if (isNamedstatement) {
return statementName == null
? new DbClientHealthCheckAsPing(this) : new DbClientHealthCheckAsNamedDml(this);
} else {
return new DbClientHealthCheckAsDml(this);
}
} else {
if (isNamedstatement && statementName == null) {
statementName = DbClient.PING_STATEMENT_NAME;
}
return isNamedstatement
? new DbClientHealthCheckAsNamedQuery(this) : new DbClientHealthCheckAsQuery(this);
}
}
/**
@@ -104,6 +286,66 @@ public final class DbClientHealthCheck implements HealthCheck {
this.name = name;
return this;
}
/**
* Set health check statement type to query.
* Default health check statement type is DML.
*
* @return updated builder instance
*/
public Builder query() {
this.isDML = false;
this.isNamedstatement = true;
return this;
}
/**
* Set custom statement name.
* Default statement name value is {@code ping}.
*
* @param name custom statement name.
* @return updated builder instance
*/
public Builder statementName(String name) {
if (statement != null) {
throw new UnsupportedOperationException(
"Can't use both statementName and statement methods in a single builder instance!");
}
this.isNamedstatement = true;
this.statementName = name;
return this;
}
/**
* Set custom statement.
*
* @param statement custom statement name.
* @return updated builder instance
*/
public Builder statement(String statement) {
if (statementName != null) {
throw new UnsupportedOperationException(
"Can't use both statementName and statement methods in a single builder instance!");
}
this.isNamedstatement = false;
this.statement = statement;
return this;
}
/**
* Set custom timeout to wait for statement execution response.
* Default value is {@code 10} seconds.
*
* @param duration the maximum time to wait for statement execution response
* @param timeUnit the time unit of the timeout argument
* @return updated builder instance
*/
public Builder timeout(long duration, TimeUnit timeUnit) {
this.timeoutDuration = duration;
this.timeoutUnit = timeUnit;
return this;
}
}
}

View File

@@ -149,12 +149,12 @@ class JdbcDbClient implements DbClient {
@Override
public T apply(Throwable t) {
LOGGER.log(level,
String.format("Transaction rollback: %s", t.getMessage()),
t);
t,
() -> String.format("Transaction rollback: %s", t.getMessage()));
execute.doRollback().exceptionally(t2 -> {
LOGGER.log(level,
String.format("Transaction rollback failed: %s", t2.getMessage()),
t2);
t2,
() -> String.format("Transaction rollback failed: %s", t2.getMessage()));
return null;
});
return null;
@@ -190,8 +190,8 @@ class JdbcDbClient implements DbClient {
execute.close();
}).exceptionally(throwable -> {
LOGGER.log(Level.WARNING,
String.format("Execution failed: %s", throwable.getMessage()),
throwable);
throwable,
() -> String.format("Execution failed: %s", throwable.getMessage()));
execute.close();
return null;
});
@@ -199,8 +199,8 @@ class JdbcDbClient implements DbClient {
result = result.onError(throwable -> {
LOGGER.log(Level.FINEST,
String.format("Execution failed: %s", throwable.getMessage()),
throwable);
throwable,
() -> String.format("Execution failed: %s", throwable.getMessage()));
execute.close();
});
@@ -209,7 +209,7 @@ class JdbcDbClient implements DbClient {
@Override
public Single<Void> ping() {
return execute(exec -> exec.namedUpdate("ping"))
return execute(exec -> exec.namedUpdate(PING_STATEMENT_NAME))
.flatMapSingle(it -> Single.empty());
}
@@ -372,7 +372,7 @@ class JdbcDbClient implements DbClient {
try {
conn.close();
} catch (SQLException e) {
LOGGER.log(Level.WARNING, String.format("Could not close connection: %s", e.getMessage()), e);
LOGGER.log(Level.WARNING, e, () -> String.format("Could not close connection: %s", e.getMessage()));
}
});
}

18
dependencies/pom.xml vendored
View File

@@ -79,6 +79,7 @@
<version.lib.jsonp-impl>1.1.6</version.lib.jsonp-impl>
<version.lib.junit>5.6.2</version.lib.junit>
<version.lib.junit4>4.12</version.lib.junit4>
<version.lib.mariadb-java-client>2.6.2</version.lib.mariadb-java-client>
<version.lib.maven-wagon>2.10</version.lib.maven-wagon>
<version.lib.microprofile-config>1.4</version.lib.microprofile-config>
<version.lib.microprofile-health>2.2</version.lib.microprofile-health>
@@ -93,6 +94,7 @@
<version.lib.microprofile-reactive-streams-operators-core>1.0.1</version.lib.microprofile-reactive-streams-operators-core>
<version.lib.mockito>2.23.4</version.lib.mockito>
<version.lib.mongodb.reactivestreams>1.11.0</version.lib.mongodb.reactivestreams>
<version.lib.mssql-jdbc>8.4.1.jre8</version.lib.mssql-jdbc>
<version.lib.oracle.ojdbc10>19.3.0.0</version.lib.oracle.ojdbc10>
<version.lib.mysql-connector-java>8.0.11</version.lib.mysql-connector-java>
<version.lib.narayana>5.9.3.Final</version.lib.narayana>
@@ -103,6 +105,7 @@
<version.lib.opentracing.grpc>0.2.1</version.lib.opentracing.grpc>
<version.lib.opentracing.tracerresolver>0.1.8</version.lib.opentracing.tracerresolver>
<version.lib.persistence-api>2.2.3</version.lib.persistence-api>
<version.lib.postgresql>42.2.16</version.lib.postgresql>
<version.lib.prometheus>0.9.0</version.lib.prometheus>
<version.lib.reactivestreams>1.0.3</version.lib.reactivestreams>
<version.lib.slf4j>1.7.26</version.lib.slf4j>
@@ -782,6 +785,21 @@
<artifactId>mysql-connector-java</artifactId>
<version>${version.lib.mysql-connector-java}</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>${version.lib.mariadb-java-client}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${version.lib.postgresql}</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>${version.lib.mssql-jdbc}</version>
</dependency>
<dependency>
<groupId>com.oracle.oci.sdk</groupId>
<artifactId>oci-java-sdk-objectstorage</artifactId>

View File

@@ -1,5 +1,3 @@
package io.helidon.tests.integration.dbclient.common;
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
@@ -15,11 +13,13 @@ package io.helidon.tests.integration.dbclient.common;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.tests.integration.dbclient.common;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.helidon.config.Config;
@@ -36,10 +36,15 @@ public abstract class AbstractIT {
/** Local logger instance. */
private static final Logger LOGGER = Logger.getLogger(AbstractIT.class.getName());
public static final Config CONFIG = Config.create(ConfigSources.classpath("test.yaml"));
public static final Config CONFIG = Config.create(ConfigSources.classpath(ConfigIT.configFile()));
public static final DbClient DB_CLIENT = initDbClient();
/**
* Initialize database client.
*
* @return database client instance
*/
public static DbClient initDbClient() {
Config dbConfig = CONFIG.get("db");
return DbClient.builder(dbConfig).build();

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.tests.integration.dbclient.common;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Configuration utilities.
*/
public class ConfigIT {
/** Local logger instance. */
private static final Logger LOGGER = Logger.getLogger(ConfigIT.class.getName());
private static final String CONFIG_PROPERTY_NAME="io.helidon.tests.integration.dbclient.config";
private static final String DEFAULT_CONFIG_FILE="test.yaml";
/**
* Retrieve configuration file from {@code io.helidon.tests.integration.dbclient.config}
* property if exists.
* Default {@code test.yaml} value is used when no property is set.
*
* @return tests configuration file name
*/
public static String configFile() {
String configFile = System.getProperty(CONFIG_PROPERTY_NAME, DEFAULT_CONFIG_FILE);
LOGGER.info(() -> String.format("Configuration file: %s", configFile));
return configFile;
}
}

View File

@@ -15,13 +15,20 @@
*/
package io.helidon.tests.integration.dbclient.common.tests.health;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.helidon.config.Config;
import io.helidon.dbclient.health.DbClientHealthCheck;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static io.helidon.tests.integration.dbclient.common.AbstractIT.CONFIG;
import static io.helidon.tests.integration.dbclient.common.AbstractIT.DB_CLIENT;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@@ -30,12 +37,31 @@ import static org.hamcrest.Matchers.equalTo;
*/
public class HealthCheckIT {
/** Local logger instance. */
private static final Logger LOGGER = Logger.getLogger(HealthCheckIT.class.getName());
private static boolean pingDml = true;
@BeforeAll
public static void setup() {
Config cfgPingDml = CONFIG.get("test.ping-dml");
pingDml = cfgPingDml.exists() ? cfgPingDml.asBoolean().get() : true;
}
/**
* Verify health BASIC check implementation.
* Verify health check implementation with default settings.
*/
@Test
public void testHealthCheck() {
HealthCheck check = DbClientHealthCheck.create(DB_CLIENT);
LOGGER.log(Level.INFO, "Running test testHealthCheck");
HealthCheck check;
if (!pingDml) {
LOGGER.log(Level.INFO, () -> String.format("Database %s does not support DML ping, using query", DB_CLIENT.dbType()));
check = DbClientHealthCheck.builder(DB_CLIENT).query().build();
} else {
LOGGER.log(Level.INFO, () -> String.format("Database %s supports DML ping, using default method", DB_CLIENT.dbType()));
check = DbClientHealthCheck.create(DB_CLIENT);
}
HealthCheckResponse response = check.call();
HealthCheckResponse.State state = response.getState();
assertThat("Healthcheck failed, response: " + response.getData(), state, equalTo(HealthCheckResponse.State.UP));
@@ -46,8 +72,16 @@ public class HealthCheckIT {
*/
@Test
public void testHealthCheckWithName() {
LOGGER.log(Level.INFO, "Running test testHealthCheckWithName");
final String hcName = "TestHC";
HealthCheck check = DbClientHealthCheck.builder(DB_CLIENT).name(hcName).build();
HealthCheck check;
if (!pingDml) {
LOGGER.log(Level.INFO, () -> String.format("Database %s does not support DML ping, using query", DB_CLIENT.dbType()));
check = DbClientHealthCheck.builder(DB_CLIENT).name(hcName).query().build();
} else {
LOGGER.log(Level.INFO, () -> String.format("Database %s supports DML ping, using default method", DB_CLIENT.dbType()));
check = DbClientHealthCheck.builder(DB_CLIENT).name(hcName).build();
}
HealthCheckResponse response = check.call();
String name = response.getName();
HealthCheckResponse.State state = response.getState();
@@ -55,4 +89,71 @@ public class HealthCheckIT {
assertThat(state, equalTo(HealthCheckResponse.State.UP));
}
/**
* Verify health check implementation using custom DML named statement.
*/
@Test
public void testHealthCheckWithCustomNamedDML() {
LOGGER.log(Level.INFO, "Running test testHealthCheckWithCustomNamedDML");
if (!pingDml) {
LOGGER.log(Level.INFO, () -> String.format("Database %s does not support DML ping, skipping this test", DB_CLIENT.dbType()));
return;
}
HealthCheck check = DbClientHealthCheck.builder(DB_CLIENT).statementName("ping-dml").build();
HealthCheckResponse response = check.call();
HealthCheckResponse.State state = response.getState();
assertThat("Healthcheck failed, response: " + response.getData(), state, equalTo(HealthCheckResponse.State.UP));
}
/**
* Verify health check implementation using custom DML statement.
*/
@Test
public void testHealthCheckWithCustomDML() {
LOGGER.log(Level.INFO, "Running test testHealthCheckWithCustomDML");
if (!pingDml) {
LOGGER.log(Level.INFO, () -> String.format("Database %s does not support DML ping, skipping this test", DB_CLIENT.dbType()));
return;
}
Config cfgStatement = CONFIG.get("db.statements.ping-dml");
assertThat("Missing ping-dml statement in database configuration!", cfgStatement.exists(), equalTo(true));
String statement = cfgStatement.asString().get();
assertThat("Missing ping-dml statement String in database configuration!", statement, is(notNullValue()));
LOGGER.log(Level.INFO, () -> String.format("Using db.statements.ping-dml value %s", statement));
HealthCheck check = DbClientHealthCheck.builder(DB_CLIENT).statement(statement).build();
HealthCheckResponse response = check.call();
HealthCheckResponse.State state = response.getState();
assertThat("Healthcheck failed, response: " + response.getData(), state, equalTo(HealthCheckResponse.State.UP));
}
/**
* Verify health check implementation using custom query named statement.
*/
@Test
public void testHealthCheckWithCustomNamedQuery() {
LOGGER.log(Level.INFO, "Running test testHealthCheckWithCustomNamedQuery");
HealthCheck check = DbClientHealthCheck.builder(DB_CLIENT).query().statementName("ping-query").build();
HealthCheckResponse response = check.call();
HealthCheckResponse.State state = response.getState();
assertThat("Healthcheck failed, response: " + response.getData(), state, equalTo(HealthCheckResponse.State.UP));
}
/**
* Verify health check implementation using custom query statement.
*/
@Test
public void testHealthCheckWithCustomQuery() {
LOGGER.log(Level.INFO, "Running test testHealthCheckWithCustomQuery");
Config cfgStatement = CONFIG.get("db.statements.ping-query");
assertThat("Missing ping-query statement in database configuration!", cfgStatement.exists(), equalTo(true));
String statement = cfgStatement.asString().get();
assertThat("Missing ping-query statement String in database configuration!", statement, is(notNullValue()));
LOGGER.log(Level.INFO, () -> String.format("Using db.statements.ping-query value %s", statement));
HealthCheck check = DbClientHealthCheck.builder(DB_CLIENT).query().statement(statement).build();
HealthCheckResponse response = check.call();
HealthCheckResponse.State state = response.getState();
assertThat("Healthcheck failed, response: " + response.getData(), state, equalTo(HealthCheckResponse.State.UP));
}
}

View File

@@ -34,12 +34,14 @@ import javax.json.JsonValue;
import javax.json.stream.JsonParsingException;
import io.helidon.common.reactive.Multi;
import io.helidon.config.Config;
import io.helidon.dbclient.DbRow;
import io.helidon.dbclient.health.DbClientHealthCheck;
import io.helidon.health.HealthSupport;
import io.helidon.webserver.Routing;
import io.helidon.webserver.WebServer;
import org.eclipse.microprofile.health.HealthCheck;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -63,8 +65,13 @@ public class ServerHealthCheckIT {
private static String URL;
private static Routing createRouting() {
Config cfgPingDml = CONFIG.get("test.ping-dml");
boolean pingDml = cfgPingDml.exists() ? cfgPingDml.asBoolean().get() : true;
HealthCheck check = pingDml
? DbClientHealthCheck.create(DB_CLIENT)
: DbClientHealthCheck.builder(DB_CLIENT).query().build();
final HealthSupport health = HealthSupport.builder()
.addLiveness(DbClientHealthCheck.create(DB_CLIENT))
.addLiveness(check)
.build();
return Routing.builder()
.register(health) // Health at "/health"

View File

@@ -32,12 +32,6 @@
<name>Integration Tests: DB Client JDBC</name>
<properties>
<mysql.port>3306</mysql.port>
<mysql.host>127.0.0.1</mysql.host>
<mysql.database>pokemon</mysql.database>
<mysql.user>user</mysql.user>
<mysql.password>password</mysql.password>
<mysql.roootpw>root</mysql.roootpw>
</properties>
<dependencies>
@@ -72,11 +66,6 @@
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
@@ -97,12 +86,31 @@
</testResource>
</testResources>
<pluginManagement>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.33.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${version.plugin.surefire}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${version.plugin.surefire}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${version.plugin.surefire}</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
@@ -111,7 +119,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${version.plugin.surefire}</version>
<configuration>
<parallel>methods</parallel>
<threadCount>10</threadCount>
@@ -126,7 +133,8 @@
</goals>
<configuration>
<includes>
<include>io.helidon.tests.integration.dbclient.jdbc.init.*IT</include>
<include>io.helidon.tests.integration.dbclient.jdbc.init.CheckIT</include>
<include>io.helidon.tests.integration.dbclient.jdbc.init.InitIT</include>
</includes>
</configuration>
</execution>
@@ -174,6 +182,315 @@
</build>
<profiles>
<profile>
<id>mysql</id>
<activation>
<property>
<name>mysql</name>
</property>
</activation>
<properties>
<db.port>3306</db.port>
<db.host>127.0.0.1</db.host>
<db.database>pokemon</db.database>
<db.user>user</db.user>
<db.password>password</db.password>
<db.roootpw>root</db.roootpw>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<io.helidon.tests.integration.dbclient.config>mysql.yaml</io.helidon.tests.integration.dbclient.config>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>mysql:8</name>
<alias>mysql</alias>
<run>
<env>
<MYSQL_USER>${db.user}</MYSQL_USER>
<MYSQL_PASSWORD>${db.password}</MYSQL_PASSWORD>
<MYSQL_ROOT_PASSWORD>${db.roootpw}</MYSQL_ROOT_PASSWORD>
<MYSQL_DATABASE>${db.database}</MYSQL_DATABASE>
</env>
<hostname>${db.host}</hostname>
<ports>
<port>${db.host}:${db.port}:3306</port>
</ports>
<wait>
<log>MySQL server is up an running</log>
<tcp>
<host>127.0.0.1</host>
<ports>
<port>${db.port}</port>
</ports>
</tcp>
<time>120000</time>
</wait>
</run>
</image>
</images>
<showLogs>true</showLogs>
<startParallel>false</startParallel>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mariadb</id>
<activation>
<property>
<name>mariadb</name>
</property>
</activation>
<properties>
<db.port>3306</db.port>
<db.host>127.0.0.1</db.host>
<db.database>pokemon</db.database>
<db.user>user</db.user>
<db.password>password</db.password>
<db.roootpw>root</db.roootpw>
</properties>
<dependencies>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<io.helidon.tests.integration.dbclient.config>mariadb.yaml</io.helidon.tests.integration.dbclient.config>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>mariadb</name>
<alias>mariadb</alias>
<run>
<env>
<MYSQL_USER>${db.user}</MYSQL_USER>
<MYSQL_PASSWORD>${db.password}</MYSQL_PASSWORD>
<MYSQL_ROOT_PASSWORD>${db.roootpw}</MYSQL_ROOT_PASSWORD>
<MYSQL_DATABASE>${db.database}</MYSQL_DATABASE>
</env>
<hostname>${db.host}</hostname>
<ports>
<port>${db.host}:${db.port}:3306</port>
</ports>
<wait>
<log>MySQL server is up an running</log>
<tcp>
<host>127.0.0.1</host>
<ports>
<port>${db.port}</port>
</ports>
</tcp>
<time>120000</time>
</wait>
</run>
</image>
</images>
<showLogs>true</showLogs>
<startParallel>false</startParallel>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>pgsql</id>
<activation>
<property>
<name>pgsql</name>
</property>
</activation>
<properties>
<db.port>5432</db.port>
<db.host>127.0.0.1</db.host>
<db.database>pokemon</db.database>
<db.user>user</db.user>
<db.password>password</db.password>
</properties>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<io.helidon.tests.integration.dbclient.config>pgsql.yaml</io.helidon.tests.integration.dbclient.config>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>postgres</name>
<alias>postgres</alias>
<run>
<env>
<POSTGRES_USER>${db.user}</POSTGRES_USER>
<POSTGRES_PASSWORD>${db.password}</POSTGRES_PASSWORD>
<POSTGRES_DB>${db.database}</POSTGRES_DB>
</env>
<hostname>${db.host}</hostname>
<ports>
<port>${db.host}:${db.port}:5432</port>
</ports>
<wait>
<log>MySQL server is up an running</log>
<tcp>
<host>127.0.0.1</host>
<ports>
<port>${db.port}</port>
</ports>
</tcp>
<time>120000</time>
</wait>
</run>
</image>
</images>
<showLogs>true</showLogs>
<startParallel>false</startParallel>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mssql</id>
<activation>
<property>
<name>mssql</name>
</property>
</activation>
<properties>
<accept.eula>Y</accept.eula>
<db.sa.password>MsH4sN0r00t</db.sa.password>
<db.port>1433</db.port>
<db.host>127.0.0.1</db.host>
<db.database>pokemon</db.database>
<db.user>test_user</db.user>
<db.password>P4ss_W0rd</db.password>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<io.helidon.tests.integration.dbclient.config>mssql.yaml</io.helidon.tests.integration.dbclient.config>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>mcr.microsoft.com/mssql/server:2017-latest</name>
<alias>mssql</alias>
<run>
<env>
<ACCEPT_EULA>${accept.eula}</ACCEPT_EULA>
<SA_PASSWORD>${db.sa.password}</SA_PASSWORD>
</env>
<hostname>${db.host}</hostname>
<ports>
<port>${db.host}:${db.port}:1433</port>
</ports>
<wait>
<log>SQL server is up an running</log>
<tcp>
<host>127.0.0.1</host>
<ports>
<port>${db.port}</port>
</ports>
</tcp>
<time>120000</time>
</wait>
</run>
</image>
</images>
<showLogs>true</showLogs>
<startParallel>false</startParallel>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<parallel>methods</parallel>
<threadCount>10</threadCount>
</configuration>
<executions>
<!-- Wait for database to start up and initialize database-->
<execution>
<id>init</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
</goals>
<configuration>
<includes>
<include>io.helidon.tests.integration.dbclient.jdbc.init.CheckMsSqlIT</include>
<include>io.helidon.tests.integration.dbclient.jdbc.init.InitIT</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!--
mvn -pl common,jdbc -Pdebug,docker install \
-Dit.jdbc.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787 -Xnoagent -Djava.compiler=NONE" \
@@ -227,7 +544,6 @@
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.31.0</version>
<executions>
<execution>
<id>start</id>
@@ -244,38 +560,6 @@
</goals>
</execution>
</executions>
<configuration>
<images>
<image>
<name>mysql:8</name>
<alias>mysql</alias>
<run>
<env>
<MYSQL_USER>${mysql.user}</MYSQL_USER>
<MYSQL_PASSWORD>${mysql.password}</MYSQL_PASSWORD>
<MYSQL_ROOT_PASSWORD>${mysql.roootpw}</MYSQL_ROOT_PASSWORD>
<MYSQL_DATABASE>${mysql.database}</MYSQL_DATABASE>
</env>
<hostname>${mysql.host}</hostname>
<ports>
<port>${mysql.host}:${mysql.port}:3306</port>
</ports>
<wait>
<log>MySQL server is up an running</log>
<tcp>
<host>127.0.0.1</host>
<ports>
<port>${mysql.port}</port>
</ports>
</tcp>
<time>120000</time>
</wait>
</run>
</image>
</images>
<showLogs>true</showLogs>
<startParallel>false</startParallel>
</configuration>
</plugin>
</plugins>
</build>

View File

@@ -17,12 +17,14 @@ package io.helidon.tests.integration.dbclient.jdbc.init;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.function.Consumer;
import java.util.logging.Logger;
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
import io.helidon.tests.integration.dbclient.common.ConfigIT;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -41,7 +43,7 @@ public class CheckIT {
private static final Logger LOGGER = Logger.getLogger(CheckIT.class.getName());
/** Test configuration. */
public static final Config CONFIG = Config.create(ConfigSources.classpath("test.yaml"));
public static final Config CONFIG = Config.create(ConfigSources.classpath(ConfigIT.configFile()));
/** Timeout in seconds to wait for database to come up. */
private static final int TIMEOUT = 60;
@@ -69,6 +71,7 @@ public class CheckIT {
connected = true;
return;
} catch (SQLException ex) {
LOGGER.info(() -> String.format("Connection check: %s", ex.getMessage()));
if (System.currentTimeMillis() > endTm) {
return;
}
@@ -85,14 +88,14 @@ public class CheckIT {
/**
* Store database connection configuration and build {@link Connection} instance.
*/
private static final class ConnectionBuilder implements Consumer<Config> {
static final class ConnectionBuilder implements Consumer<Config> {
private boolean hasConfig;
private String url;
private String username;
private String password;
private ConnectionBuilder() {
ConnectionBuilder() {
hasConfig = false;
}
@@ -104,7 +107,7 @@ public class CheckIT {
hasConfig = true;
}
private Connection createConnection() throws SQLException {
Connection createConnection() throws SQLException {
if (!hasConfig) {
fail("No db.connection configuration node was found.");
}
@@ -143,11 +146,21 @@ public class CheckIT {
public void testDmlStatementExecution() throws SQLException {
ConnectionBuilder builder = new ConnectionBuilder();
String ping = CONFIG.get("db.statements.ping").asString().get();
Config cfgPingDml = CONFIG.get("test.ping-dml");
boolean pingDml = cfgPingDml.exists() ? cfgPingDml.asBoolean().get() : true;
CONFIG.get("db.connection").ifExists(builder);
Connection conn = builder.createConnection();
int result = conn.createStatement().executeUpdate(ping);
assertThat(result, equalTo(0));
LOGGER.info(() -> String.format("Command ping result: %d", result));
if (pingDml) {
int result = conn.createStatement().executeUpdate(ping);
assertThat(result, equalTo(0));
LOGGER.info(() -> String.format("Command ping result: %d", result));
} else {
ResultSet rs = conn.createStatement().executeQuery(ping);
rs.next();
int result = rs.getInt(1);
assertThat(result, equalTo(0));
LOGGER.info(() -> String.format("Command ping result: %d", result));
}
}
}

View File

@@ -0,0 +1,219 @@
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.tests.integration.dbclient.jdbc.init;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.helidon.config.Config;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static io.helidon.tests.integration.dbclient.jdbc.init.CheckIT.CONFIG;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Check minimal functionality needed before running database schema initialization.
* First test class being executed after database startup.
*/
public class CheckMsSqlIT {
/** Local logger instance. */
private static final Logger LOGGER = Logger.getLogger(CheckIT.class.getName());
/** Timeout in seconds to wait for database to come up. */
private static final int TIMEOUT = 60;
/** Database connection. */
private static Connection conn = null;
/**
* Wait until database starts up when its configuration node is available.
*/
private static final class ConnectionCheck implements Consumer<Config> {
private boolean connected;
private ConnectionCheck() {
connected = false;
}
@Override
public void accept(Config config) {
String url = config.get("sa-url").asString().get();
String username = config.get("sa-user").asString().get();
String password = config.get("sa-password").asString().get();
long endTm = 1000 * TIMEOUT + System.currentTimeMillis();
while (true) {
try {
conn = DriverManager.getConnection(url, username, password);
connected = true;
return;
} catch (SQLException ex) {
LOGGER.info(() -> String.format("Connection check: %s", ex.getMessage()));
if (System.currentTimeMillis() > endTm) {
conn = null;
return;
}
}
}
}
private boolean connected() {
return connected;
}
}
/**
* Create user and database and set permissions.
*/
private static final class DbInit implements Consumer<Config> {
@Override
public void accept(Config config) {
if (conn == null) {
fail("Database connection is not available!");
}
String username = config.get("username").asString().get();
String password = config.get("password").asString().get();
String database = CheckIT.CONFIG.get("test.db-database").asString().get();
try {
Statement stmt = conn.createStatement();
final int dbCount = stmt.executeUpdate(String.format("EXEC sp_configure 'CONTAINED DATABASE AUTHENTICATION', 1", database));
LOGGER.log(Level.INFO, () -> String.format("Executed EXEC statement. %d records modified.", dbCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not configure database:", ex);
} try {
Statement stmt = conn.createStatement();
final int dbCount = stmt.executeUpdate(String.format("RECONFIGURE", database));
LOGGER.log(Level.INFO, () -> String.format("Executed RECONFIGURE statement. %d records modified.", dbCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not reconfigure database:", ex);
} try {
Statement stmt = conn.createStatement();
final int dbCount = stmt.executeUpdate(String.format("CREATE DATABASE %s CONTAINMENT = PARTIAL", database));
LOGGER.log(Level.INFO, () -> String.format("Executed CREATE DATABASE statement. %d records modified.", dbCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not create database:", ex);
} try {
Statement stmt = conn.createStatement();
final int useCount = stmt.executeUpdate(String.format("USE %s", database));
LOGGER.log(Level.INFO, () -> String.format("Executed USE statement. %d records modified.", useCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not use database:", ex);
} try {
Statement stmt = conn.createStatement();//"CREATE USER ? WITH PASSWORD = ?");
final int userCount = stmt.executeUpdate(String.format("CREATE USER %s WITH PASSWORD = '%s'", username, password));
LOGGER.log(Level.INFO, () -> String.format("Executed CREATE USER statement. %d records modified.", userCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not create database user:", ex);
} try {
Statement stmt = conn.createStatement();//"CREATE USER ? WITH PASSWORD = ?");
final int userCount = stmt.executeUpdate(String.format("GRANT ALL TO %s", username));
LOGGER.log(Level.INFO, () -> String.format("Executed GRANT statement. %d records modified.", userCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not grant database privilegs to user:", ex);
} try {
Statement stmt = conn.createStatement();//"CREATE USER ? WITH PASSWORD = ?");
final int userCount = stmt.executeUpdate(String.format("GRANT CONTROL ON SCHEMA::dbo TO %s", username));
LOGGER.log(Level.INFO, () -> String.format("Executed GRANT statement. %d records modified.", userCount));
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not grant database privilegs to user:", ex);
}
}
}
/**
* Wait for database server to start.
*/
private static void waitForStart() {
ConnectionCheck check = new ConnectionCheck();
CheckIT.CONFIG.get("test").ifExists(check);
if (!check.connected()) {
fail("Database startup failed!");
}
}
/**
* Initialize database user.
*/
private static void initDb() {
if (conn == null) {
fail("Database connection is not available!");
}
DbInit init = new DbInit();
CheckIT.CONFIG.get("db.connection").ifExists(init);
}
/**
* Setup database for tests.
* Wait for database to start and create user and database for tests.
* Returns after ping query completed successfully or timeout passed.
*/
@BeforeAll
public static void setup() {
waitForStart();
initDb();
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Could not close database connection:", ex);
}
}
}
/**
* Simple test to verify that DML query execution works.
* Used before running database schema initialization.
*
* @throws SQLException when database query failed
*/
@Test
public void testDmlStatementExecution() throws SQLException {
CheckIT.ConnectionBuilder builder = new CheckIT.ConnectionBuilder();
String ping = CONFIG.get("db.statements.ping").asString().get();
Config cfgPingDml = CONFIG.get("test.ping-dml");
boolean pingDml = cfgPingDml.exists() ? cfgPingDml.asBoolean().get() : true;
CONFIG.get("db.connection").ifExists(builder);
Connection conn = builder.createConnection();
if (pingDml) {
int result = conn.createStatement().executeUpdate(ping);
assertThat(result, equalTo(0));
LOGGER.info(() -> String.format("Command ping result: %d", result));
} else {
ResultSet rs = conn.createStatement().executeQuery(ping);
rs.next();
int result = rs.getInt(1);
assertThat(result, equalTo(0));
LOGGER.info(() -> String.format("Command ping result: %d", result));
}
}
}

View File

@@ -0,0 +1,81 @@
#
# Copyright (c) 2019, 2020 Oracle and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
server:
port: 0
host: 0.0.0.0
db:
source: jdbc
connection:
url: jdbc:mariadb://${db.host}:${db.port}/${db.database}?useSSL=false&allowPublicKeyRetrieval=true
username: ${db.user}
password: ${db.password}
statements:
# required ping statement
ping: "DO 0"
# custom DML ping statement
ping-dml: "DO 0"
# custom query ping statement
ping-query: "SELECT 0"
# database schema initialization statements
create-types: "CREATE TABLE Types (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-poketypes: "CREATE TABLE PokemonTypes (id_pokemon INTEGER NOT NULL REFERENCES Pokemons(id), id_type INTEGER NOT NULL REFERENCES Types(id))"
# database schema cleanup statements
drop-types: "DROP TABLE Types"
drop-pokemons: "DROP TABLE Pokemons"
drop-poketypes: "DROP TABLE PokemonTypes"
# data initialization statements
insert-type: "INSERT INTO Types(id, name) VALUES(?, ?)"
insert-pokemon: "INSERT INTO Pokemons(id, name) VALUES(?, ?)"
insert-poketype: "INSERT INTO PokemonTypes(id_pokemon, id_type) VALUES(?, ?)"
# data initialization verification statements
select-types: "SELECT id, name FROM Types"
select-pokemons: "SELECT id, name FROM Pokemons"
select-poketypes: "SELECT id_pokemon, id_type FROM PokemonTypes p WHERE id_pokemon = ?"
# data cleanup verification statements
select-poketypes-all: "SELECT id_pokemon, id_type FROM PokemonTypes"
# retrieve max. Pokemon ID
select-max-id: "SELECT MAX(id) FROM Pokemons"
# test queries
select-pokemon-named-arg: "SELECT id, name FROM Pokemons WHERE name=:name"
select-pokemon-order-arg: "SELECT id, name FROM Pokemons WHERE name=?"
# test DML insert
insert-pokemon-named-arg: "INSERT INTO Pokemons(id, name) VALUES(:id, :name)"
insert-pokemon-order-arg: "INSERT INTO Pokemons(id, name) VALUES(?, ?)"
# Pokemon mapper uses reverse order of indexed arguments
insert-pokemon-order-arg-rev: "INSERT INTO Pokemons(name, id) VALUES(?, ?)"
# test DML update
select-pokemon-by-id: "SELECT id, name FROM Pokemons WHERE id=?"
update-pokemon-named-arg: "UPDATE Pokemons SET name=:name WHERE id=:id"
update-pokemon-order-arg: "UPDATE Pokemons SET name=? WHERE id=?"
# test DML delete
delete-pokemon-named-arg: "DELETE FROM Pokemons WHERE id=:id"
delete-pokemon-order-arg: "DELETE FROM Pokemons WHERE id=?"
# Pokemon mapper uses full list of attributes
delete-pokemon-full-named-arg: "DELETE FROM Pokemons WHERE name=:name AND id=:id"
delete-pokemon-full-order-arg: "DELETE FROM Pokemons WHERE name=? AND id=?"
# test DbStatementQuery methods
select-pokemons-idrng-named-arg: "SELECT id, name FROM Pokemons WHERE id > :idmin AND id < :idmax"
select-pokemons-idrng-order-arg: "SELECT id, name FROM Pokemons WHERE id > ? AND id < ?"
# Test query with both named and ordered parameters (shall cause an exception)
select-pokemons-error-arg: "SELECT id, name FROM Pokemons WHERE id > :id AND name = ?"
# Tests configuration
test:
# Whether database supports ping statement as DML (default value is true)
#ping-dml: true

View File

@@ -0,0 +1,89 @@
#
# Copyright (c) 2019, 2020 Oracle and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
server:
port: 0
host: 0.0.0.0
db:
source: jdbc
connection:
url: jdbc:sqlserver://${db.host}:${db.port};databaseName=${db.database}
username: ${db.user}
password: ${db.password}
statements:
# required ping statement
ping: "SELECT 0"
# custom query ping statement
ping-query: "SELECT 0"
# database schema initialization statements
create-types: "CREATE TABLE Types (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-poketypes: "CREATE TABLE PokemonTypes (id_pokemon INTEGER NOT NULL REFERENCES Pokemons(id), id_type INTEGER NOT NULL REFERENCES Types(id))"
# database schema cleanup statements
drop-types: "DROP TABLE Types"
drop-pokemons: "DROP TABLE Pokemons"
drop-poketypes: "DROP TABLE PokemonTypes"
# data initialization statements
insert-type: "INSERT INTO Types(id, name) VALUES(?, ?)"
insert-pokemon: "INSERT INTO Pokemons(id, name) VALUES(?, ?)"
insert-poketype: "INSERT INTO PokemonTypes(id_pokemon, id_type) VALUES(?, ?)"
# data initialization verification statements
select-types: "SELECT id, name FROM Types"
select-pokemons: "SELECT id, name FROM Pokemons"
select-poketypes: "SELECT id_pokemon, id_type FROM PokemonTypes p WHERE id_pokemon = ?"
# data cleanup verification statements
select-poketypes-all: "SELECT id_pokemon, id_type FROM PokemonTypes"
# retrieve max. Pokemon ID
select-max-id: "SELECT MAX(id) FROM Pokemons"
# test queries
select-pokemon-named-arg: "SELECT id, name FROM Pokemons WHERE name=:name"
select-pokemon-order-arg: "SELECT id, name FROM Pokemons WHERE name=?"
# test DML insert
insert-pokemon-named-arg: "INSERT INTO Pokemons(id, name) VALUES(:id, :name)"
insert-pokemon-order-arg: "INSERT INTO Pokemons(id, name) VALUES(?, ?)"
# Pokemon mapper uses reverse order of indexed arguments
insert-pokemon-order-arg-rev: "INSERT INTO Pokemons(name, id) VALUES(?, ?)"
# test DML update
select-pokemon-by-id: "SELECT id, name FROM Pokemons WHERE id=?"
update-pokemon-named-arg: "UPDATE Pokemons SET name=:name WHERE id=:id"
update-pokemon-order-arg: "UPDATE Pokemons SET name=? WHERE id=?"
# test DML delete
delete-pokemon-named-arg: "DELETE FROM Pokemons WHERE id=:id"
delete-pokemon-order-arg: "DELETE FROM Pokemons WHERE id=?"
# Pokemon mapper uses full list of attributes
delete-pokemon-full-named-arg: "DELETE FROM Pokemons WHERE name=:name AND id=:id"
delete-pokemon-full-order-arg: "DELETE FROM Pokemons WHERE name=? AND id=?"
# test DbStatementQuery methods
select-pokemons-idrng-named-arg: "SELECT id, name FROM Pokemons WHERE id > :idmin AND id < :idmax"
select-pokemons-idrng-order-arg: "SELECT id, name FROM Pokemons WHERE id > ? AND id < ?"
# Test query with both named and ordered parameters (shall cause an exception)
select-pokemons-error-arg: "SELECT id, name FROM Pokemons WHERE id > :id AND name = ?"
# Tests configuration
test:
## Microsoft SQL Server specific setup
# SQL database administrator's user name
sa-user: sa
# SQL database administrator's password
sa-password: ${db.sa.password}
# Sql database URL for administrator
sa-url: jdbc:sqlserver://${db.host}:${db.port}
# SQL database name (will be created and granted to regular test user)
db-database: ${db.database}
# Whether database supports ping statement as DML (default value is true)
ping-dml: false

View File

@@ -0,0 +1,81 @@
#
# Copyright (c) 2019, 2020 Oracle and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
server:
port: 0
host: 0.0.0.0
db:
source: jdbc
connection:
url: jdbc:mysql://${db.host}:${db.port}/${db.database}?useSSL=false&allowPublicKeyRetrieval=true
username: ${db.user}
password: ${db.password}
statements:
# required ping statement
ping: "DO 0"
# custom DML ping statement
ping-dml: "DO 0"
# custom query ping statement
ping-query: "SELECT 0"
# database schema initialization statements
create-types: "CREATE TABLE Types (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-poketypes: "CREATE TABLE PokemonTypes (id_pokemon INTEGER NOT NULL REFERENCES Pokemons(id), id_type INTEGER NOT NULL REFERENCES Types(id))"
# database schema cleanup statements
drop-types: "DROP TABLE Types"
drop-pokemons: "DROP TABLE Pokemons"
drop-poketypes: "DROP TABLE PokemonTypes"
# data initialization statements
insert-type: "INSERT INTO Types(id, name) VALUES(?, ?)"
insert-pokemon: "INSERT INTO Pokemons(id, name) VALUES(?, ?)"
insert-poketype: "INSERT INTO PokemonTypes(id_pokemon, id_type) VALUES(?, ?)"
# data initialization verification statements
select-types: "SELECT id, name FROM Types"
select-pokemons: "SELECT id, name FROM Pokemons"
select-poketypes: "SELECT id_pokemon, id_type FROM PokemonTypes p WHERE id_pokemon = ?"
# data cleanup verification statements
select-poketypes-all: "SELECT id_pokemon, id_type FROM PokemonTypes"
# retrieve max. Pokemon ID
select-max-id: "SELECT MAX(id) FROM Pokemons"
# test queries
select-pokemon-named-arg: "SELECT id, name FROM Pokemons WHERE name=:name"
select-pokemon-order-arg: "SELECT id, name FROM Pokemons WHERE name=?"
# test DML insert
insert-pokemon-named-arg: "INSERT INTO Pokemons(id, name) VALUES(:id, :name)"
insert-pokemon-order-arg: "INSERT INTO Pokemons(id, name) VALUES(?, ?)"
# Pokemon mapper uses reverse order of indexed arguments
insert-pokemon-order-arg-rev: "INSERT INTO Pokemons(name, id) VALUES(?, ?)"
# test DML update
select-pokemon-by-id: "SELECT id, name FROM Pokemons WHERE id=?"
update-pokemon-named-arg: "UPDATE Pokemons SET name=:name WHERE id=:id"
update-pokemon-order-arg: "UPDATE Pokemons SET name=? WHERE id=?"
# test DML delete
delete-pokemon-named-arg: "DELETE FROM Pokemons WHERE id=:id"
delete-pokemon-order-arg: "DELETE FROM Pokemons WHERE id=?"
# Pokemon mapper uses full list of attributes
delete-pokemon-full-named-arg: "DELETE FROM Pokemons WHERE name=:name AND id=:id"
delete-pokemon-full-order-arg: "DELETE FROM Pokemons WHERE name=? AND id=?"
# test DbStatementQuery methods
select-pokemons-idrng-named-arg: "SELECT id, name FROM Pokemons WHERE id > :idmin AND id < :idmax"
select-pokemons-idrng-order-arg: "SELECT id, name FROM Pokemons WHERE id > ? AND id < ?"
# Test query with both named and ordered parameters (shall cause an exception)
select-pokemons-error-arg: "SELECT id, name FROM Pokemons WHERE id > :id AND name = ?"
# Tests configuration
test:
# Whether database supports ping statement as DML (default value is true)
#ping-dml: true

View File

@@ -21,16 +21,18 @@ server:
db:
source: jdbc
connection:
url: jdbc:mysql://${mysql.host}:${mysql.port}/${mysql.database}?useSSL=false&allowPublicKeyRetrieval=true
username: ${mysql.user}
password: ${mysql.password}
url: jdbc:postgresql://${db.host}:${db.port}/${db.database}
username: ${db.user}
password: ${db.password}
statements:
# required ping statement
ping: "DO 0"
ping: "SELECT 0"
# custom query ping statement
ping-query: "SELECT 0"
# database schema initialization statements
create-types: "CREATE TABLE Types (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-pokemons: "CREATE TABLE Pokemons (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(64) NOT NULL)"
create-poketypes: "CREATE TABLE PokemonTypes (id_pokemon INTEGER NOT NULL REFERENCES Pokemon(id), id_type INTEGER NOT NULL REFERENCES Type(id))"
create-poketypes: "CREATE TABLE PokemonTypes (id_pokemon INTEGER NOT NULL REFERENCES Pokemons(id), id_type INTEGER NOT NULL REFERENCES Types(id))"
# database schema cleanup statements
drop-types: "DROP TABLE Types"
drop-pokemons: "DROP TABLE Pokemons"
@@ -70,3 +72,8 @@ db:
select-pokemons-idrng-order-arg: "SELECT id, name FROM Pokemons WHERE id > ? AND id < ?"
# Test query with both named and ordered parameters (shall cause an exception)
select-pokemons-error-arg: "SELECT id, name FROM Pokemons WHERE id > :id AND name = ?"
# Tests configuration
test:
# Whether database supports ping statement as DML (default value is true)
ping-dml: false