Skip to content

Commit

Permalink
Add utility classes for database object creation (#12445)
Browse files Browse the repository at this point in the history
* Add utility classes for database object creation

* Remove unused variable
  • Loading branch information
jdpgrailsdev authored May 2, 2022
1 parent 915573e commit de70351
Show file tree
Hide file tree
Showing 9 changed files with 555 additions and 0 deletions.
1 change: 1 addition & 0 deletions airbyte-db/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {

dependencies {
api 'org.apache.commons:commons-dbcp2:2.7.0'
api 'com.zaxxer:HikariCP:5.0.1'
api 'org.jooq:jooq-meta:3.13.4'
api 'org.jooq:jooq:3.13.4'
api 'org.postgresql:postgresql:42.2.18'
Expand Down
11 changes: 11 additions & 0 deletions airbyte-db/lib/src/main/java/io/airbyte/db/Databases.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Provides utility methods to create configured {@link Database} instances.
*
* @deprecated This class has been marked as deprecated as we move to using an application framework
* to manage resources. This class will be removed in a future release.
*
* @see io.airbyte.db.factory.DataSourceFactory
* @see io.airbyte.db.factory.DSLContextFactory
* @see io.airbyte.db.factory.FlywayFactory
*/
@Deprecated(forRemoval = true)
public class Databases {

private static final Logger LOGGER = LoggerFactory.getLogger(Databases.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.db.factory;

import javax.sql.DataSource;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;

/**
* Temporary factory class that provides convenience methods for creating a {@link DSLContext}
* instances. This class will be removed once the project has been converted to leverage an
* application framework to manage the creation and injection of {@link DSLContext} objects.
*
* This class replaces direct calls to {@link io.airbyte.db.Databases}.
*/
public class DSLContextFactory {

/**
* Constructs a configured {@link DSLContext} instance using the provided configuration.
*
* @param dataSource The {@link DataSource} used to connect to the database.
* @param dialect The SQL dialect to use with objects created from this context.
* @return The configured {@link DSLContext}.
*/
public static DSLContext create(final DataSource dataSource, final SQLDialect dialect) {
return DSL.using(dataSource, dialect);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.db.factory;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.util.Map;
import javax.sql.DataSource;

/**
* Temporary factory class that provides convenience methods for creating a {@link DataSource}
* instance. This class will be removed once the project has been converted to leverage an
* application framework to manage the creation and injection of {@link DataSource} objects.
*
* This class replaces direct calls to {@link io.airbyte.db.Databases}.
*/
public class DataSourceFactory {

/**
* Constructs a new {@link DataSource} using the provided configuration.
*
* @param username The username of the database user.
* @param password The password of the database user.
* @param driverClassName The fully qualified name of the JDBC driver class.
* @param jdbcConnectionString The JDBC connection string.
* @return The configured {@link DataSource}.
*/
public static DataSource create(final String username,
final String password,
final String driverClassName,
final String jdbcConnectionString) {
return new DataSourceBuilder()
.withDriverClassName(driverClassName)
.withJdbcUrl(jdbcConnectionString)
.withPassword(password)
.withUsername(username)
.build();
}

/**
* Constructs a new {@link DataSource} using the provided configuration.
*
* @param username The username of the database user.
* @param password The password of the database user.
* @param driverClassName The fully qualified name of the JDBC driver class.
* @param jdbcConnectionString The JDBC connection string.
* @param connectionProperties Additional configuration properties for the underlying driver.
* @return The configured {@link DataSource}.
*/
public static DataSource create(final String username,
final String password,
final String driverClassName,
final String jdbcConnectionString,
final Map<String, String> connectionProperties) {
return new DataSourceBuilder()
.withConnectionProperties(connectionProperties)
.withDriverClassName(driverClassName)
.withJdbcUrl(jdbcConnectionString)
.withPassword(password)
.withUsername(username)
.build();
}

/**
* Constructs a new {@link DataSource} using the provided configuration.
*
* @param username The username of the database user.
* @param password The password of the database user.
* @param host The host address of the database.
* @param port The port of the database.
* @param database The name of the database.
* @param driverClassName The fully qualified name of the JDBC driver class.
* @return The configured {@link DataSource}.
*/
public static DataSource create(final String username,
final String password,
final String host,
final int port,
final String database,
final String driverClassName) {
return new DataSourceBuilder()
.withDatabase(database)
.withDriverClassName(driverClassName)
.withHost(host)
.withPort(port)
.withPassword(password)
.withUsername(username)
.build();
}

/**
* Constructs a new {@link DataSource} using the provided configuration.
*
* @param username The username of the database user.
* @param password The password of the database user.
* @param host The host address of the database.
* @param port The port of the database.
* @param database The name of the database.
* @param driverClassName The fully qualified name of the JDBC driver class.
* @param connectionProperties Additional configuration properties for the underlying driver.
* @return The configured {@link DataSource}.
*/
public static DataSource create(final String username,
final String password,
final String host,
final int port,
final String database,
final String driverClassName,
final Map<String, String> connectionProperties) {
return new DataSourceBuilder()
.withConnectionProperties(connectionProperties)
.withDatabase(database)
.withDriverClassName(driverClassName)
.withHost(host)
.withPort(port)
.withPassword(password)
.withUsername(username)
.build();
}

/**
* Convenience method that constructs a new {@link DataSource} for a PostgreSQL database using the
* provided configuration.
*
* @param username The username of the database user.
* @param password The password of the database user.
* @param host The host address of the database.
* @param port The port of the database.
* @param database The name of the database.
* @return The configured {@link DataSource}.
*/
public static DataSource createPostgres(final String username,
final String password,
final String host,
final int port,
final String database) {
return new DataSourceBuilder()
.withDatabase(database)
.withDriverClassName("org.postgresql.Driver")
.withHost(host)
.withPort(port)
.withPassword(password)
.withUsername(username)
.build();
}

/**
* Builder class used to configure and construct {@link DataSource} instances.
*/
private static class DataSourceBuilder {

private static final Map<String, String> JDBC_URL_FORMATS = Map.of("org.postgresql.Driver", "jdbc:postgresql://%s:%d/%s",
"com.amazon.redshift.jdbc.Driver", "jdbc:redshift://%s:%d/%s",
"com.mysql.cj.jdbc.Driver", "jdbc:mysql://%s:%d/%s",
"com.microsoft.sqlserver.jdbc.SQLServerDriver", "jdbc:sqlserver://%s:%d/%s",
"oracle.jdbc.OracleDriver", "jdbc:oracle:thin:@%s:%d:%s",
"ru.yandex.clickhouse.ClickHouseDriver", "jdbc:ch://%s:%d/%s",
"org.mariadb.jdbc.Driver", "jdbc:mariadb://%s:%d/%s");

private Map<String, String> connectionProperties = Map.of();
private String database;
private String driverClassName = "org.postgresql.Driver";
private String host;
private String jdbcUrl;
private Integer maximumPoolSize = 5;
private Integer minimumPoolSize = 0;
private String password;
private Integer port = 5432;
private String username;

private DataSourceBuilder() {}

public DataSourceBuilder withConnectionProperties(final Map<String, String> connectionProperties) {
if (connectionProperties != null) {
this.connectionProperties = connectionProperties;
}
return this;
}

public DataSourceBuilder withDatabase(final String database) {
this.database = database;
return this;
}

public DataSourceBuilder withDriverClassName(final String driverClassName) {
this.driverClassName = driverClassName;
return this;
}

public DataSourceBuilder withHost(final String host) {
this.host = host;
return this;
}

public DataSourceBuilder withJdbcUrl(final String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
return this;
}

public DataSourceBuilder withMaximumPoolSize(final Integer maximumPoolSize) {
if (maximumPoolSize != null) {
this.maximumPoolSize = maximumPoolSize;
}
return this;
}

public DataSourceBuilder withMinimumPoolSize(final Integer minimumPoolSize) {
if (minimumPoolSize != null) {
this.minimumPoolSize = minimumPoolSize;
}
return this;
}

public DataSourceBuilder withPassword(final String password) {
this.password = password;
return this;
}

public DataSourceBuilder withPort(final Integer port) {
if (port != null) {
this.port = port;
}
return this;
}

public DataSourceBuilder withUsername(final String username) {
this.username = username;
return this;
}

public DataSource build() {
final HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClassName);
config.setJdbcUrl(jdbcUrl != null ? jdbcUrl : String.format(JDBC_URL_FORMATS.getOrDefault(driverClassName, ""), host, port, database));
config.setMaximumPoolSize(maximumPoolSize);
config.setMinimumIdle(minimumPoolSize);
config.setPassword(password);
config.setUsername(username);

connectionProperties.forEach(config::addDataSourceProperty);

final HikariDataSource dataSource = new HikariDataSource(config);
dataSource.validate();
return dataSource;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.db.factory;

import javax.sql.DataSource;
import org.flywaydb.core.Flyway;

/**
* Temporary factory class that provides convenience methods for creating a {@link Flyway}
* instances. This class will be removed once the project has been converted to leverage an
* application framework to manage the creation and injection of {@link Flyway} objects.
*
* This class replaces direct calls to {@link io.airbyte.db.Databases}.
*/
public class FlywayFactory {

static final String MIGRATION_TABLE_FORMAT = "airbyte_%s_migrations";

// Constants for Flyway baseline. See here for details:
// https://flywaydb.org/documentation/command/baseline
static final String BASELINE_VERSION = "0.29.0.001";
static final String BASELINE_DESCRIPTION = "Baseline from file-based migration v1";
static final boolean BASELINE_ON_MIGRATION = true;

/**
* Constructs a configured {@link Flyway} instance using the provided configuration.
*
* @param dataSource The {@link DataSource} used to connect to the database.
* @param installedBy The name of the module performing the migration.
* @param dbIdentifier The name of the database to be migrated. This is used to name the table to
* hold the migration history for the database.
* @param migrationFileLocations The array of migration files to be used.
* @return The configured {@link Flyway} instance.
*/
public static Flyway create(final DataSource dataSource,
final String installedBy,
final String dbIdentifier,
final String... migrationFileLocations) {
return Flyway.configure()
.dataSource(dataSource)
.baselineVersion(BASELINE_VERSION)
.baselineDescription(BASELINE_DESCRIPTION)
.baselineOnMigrate(BASELINE_ON_MIGRATION)
.installedBy(installedBy)
.table(String.format(MIGRATION_TABLE_FORMAT, dbIdentifier))
.locations(migrationFileLocations)
.load();

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.db.factory;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.testcontainers.containers.PostgreSQLContainer;

/**
* Common test suite for the classes found in the {@code io.airbyte.db.factory} package.
*/
public abstract class AbstractFactoryTest {

private static final String DATABASE_NAME = "airbyte_test_database";

protected static PostgreSQLContainer<?> container;

@BeforeAll
public static void dbSetup() {
container = new PostgreSQLContainer<>("postgres:13-alpine")
.withDatabaseName(DATABASE_NAME)
.withUsername("docker")
.withPassword("docker");
container.start();
}

@AfterAll
public static void dbDown() {
container.close();
}

}
Loading

0 comments on commit de70351

Please sign in to comment.