org.apache.beam.it.jdbc.AbstractJDBCResourceManager Maven / Gradle / Ivy
Show all versions of beam-it-jdbc Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.beam.it.jdbc;
import static org.apache.beam.it.jdbc.JDBCResourceManagerUtils.checkValidTableName;
import static org.apache.beam.it.jdbc.JDBCResourceManagerUtils.generateDatabaseName;
import static org.apache.beam.it.jdbc.JDBCResourceManagerUtils.generateJdbcPassword;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.beam.it.testcontainers.TestContainerResourceManager;
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.JdbcDatabaseContainer;
/**
* Abstract class for implementation of {@link JDBCResourceManager} interface.
*
* The class supports one database, and multiple tables per database object. A database is
* created when the container first spins up, if one is not given.
*
*
The database name is formed using testId. The database name will be "{testId}-{ISO8601 time,
* microsecond precision}", with additional formatting.
*
*
The class is thread-safe.
*/
public abstract class AbstractJDBCResourceManager>
extends TestContainerResourceManager> implements JDBCResourceManager {
private static final Logger LOG = LoggerFactory.getLogger(AbstractJDBCResourceManager.class);
protected static final String DEFAULT_JDBC_USERNAME = "root";
protected final JDBCDriverFactory driver;
protected final String databaseName;
protected final String username;
protected final String password;
private final Map tableIds;
@VisibleForTesting
AbstractJDBCResourceManager(T container, Builder builder, JDBCDriverFactory driver) {
super(
container
.withUsername(builder.username)
.withPassword(builder.password)
.withDatabaseName(builder.databaseName),
builder);
this.databaseName = container.getDatabaseName();
this.username = container.getUsername();
this.password = container.getPassword();
this.tableIds = new HashMap<>();
this.driver = driver;
}
protected AbstractJDBCResourceManager(T container, Builder builder) {
this(container, builder, new JDBCDriverFactory());
}
/**
* Return the default port that this JDBC implementation listens on.
*
* @return the JDBC port.
*/
protected abstract int getJDBCPort();
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public synchronized String getUri() {
return String.format(
"jdbc:%s://%s:%d/%s",
getJDBCPrefix(), this.getHost(), this.getPort(getJDBCPort()), this.getDatabaseName());
}
public abstract String getJDBCPrefix();
@Override
public synchronized String getDatabaseName() {
return databaseName;
}
@Override
public boolean createTable(String tableName, JDBCSchema schema) {
// Check table ID
checkValidTableName(tableName);
// Check if table already exists
if (tableIds.containsKey(tableName)) {
throw new IllegalStateException(
"Table " + tableName + " already exists for database " + databaseName + ".");
}
LOG.info("Creating table using tableName '{}'.", tableName);
StringBuilder sql = new StringBuilder();
try (Connection con = driver.getConnection(getUri(), username, password)) {
Statement stmt = con.createStatement();
sql.append("CREATE TABLE ")
.append(tableName)
.append(" (")
.append(schema.toSqlStatement())
.append(")");
stmt.executeUpdate(sql.toString());
stmt.close();
} catch (Exception e) {
throw new JDBCResourceManagerException(
"Error creating table with SQL statement: "
+ sql
+ " (for connection with URL "
+ getUri()
+ ")",
e);
}
tableIds.put(tableName, schema.getIdColumn());
LOG.info("Successfully created table {}.{}", databaseName, tableName);
return true;
}
/**
* Writes the given mapped rows into the specified columns. This method requires {@link
* JDBCResourceManager#createTable(String, JDBCSchema)} to be called for the target table
* beforehand.
*
* The rows map must use the row id as the key, and the values will be inserted into the
* columns at the row with that id. i.e. {0: [val1, val2, ...], 1: [val1, val2, ...], ...}
*
* @param tableName The name of the table to insert the given rows into.
* @param rows A map representing the rows to be inserted into the table.
* @throws JDBCResourceManagerException if method is called after resources have been cleaned up,
* if the manager object has no dataset, if the table does not exist or if there is an
* Exception when attempting to insert the rows.
*/
@Override
@SuppressWarnings("nullness")
public boolean write(String tableName, List