package com.ning.metrics.goodwill.sink;
import com.google.inject.Inject;
import com.ning.metrics.goodwill.access.GoodwillSchema;
import com.ning.metrics.goodwill.access.GoodwillSchemaField;
import com.ning.metrics.goodwill.binder.config.GoodwillConfig;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.netezza.datasource.NzDatasource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class NetezzaSink implements GoodwillSink
{
private final Logger log = Logger.getLogger(NetezzaSink.class);
private final GoodwillConfig config;
@Inject
public NetezzaSink(
GoodwillConfig config
)
{
this.config = config;
}
/**
* Add a new type to the sink
* <p/>
* For Netezza, this means creating a table where the data can be dumped. The CREATE TABLE statement
* is constructed from the SQL information documented in the ThriftFields.
*
* @param schema GoodwillSchema to add
*/
@Override
public boolean addType(GoodwillSchema schema)
throws SQLException, IOException, ClassNotFoundException
{
boolean success = false;
try {
Connection connection = connectToNetezza(config.getSinkDBFirstHost(), config.getSinkDBFirstPort(), config.getSinkDBFirstSchema(),
config.getSinkDBFirstUsername(), config.getSinkDBFirstPassword());
String createTableStatement = getCreateTableStatement(schema);
Statement statement = connection.createStatement();
statement.addBatch(createTableStatement);
statement.executeBatch();
log.info(String.format("Added Thrift to Netezza: %s", schema.getName()));
connection.commit();
connection.close();
success = true;
if (config.getSinkFirstExtraSQL() != null) {
success = executeExtraSql(config.getSinkDBFirstHost(), config.getSinkDBFirstPort(), config.getSinkDBFirstSchema(),
config.getSinkDBFirstUsername(), config.getSinkDBFirstPassword(), config.getSinkFirstExtraSQL(), schema);
if (success && config.getSinkSecondExtraSQL() != null) {
success = executeExtraSql(config.getSinkDBSecondHost(), config.getSinkDBSecondPort(), config.getSinkDBSecondSchema(),
config.getSinkDBSecondUsername(), config.getSinkDBSecondPassword(), config.getSinkSecondExtraSQL(), schema);
}
}
return success;
}
catch (SQLException e) {
log.warn(String.format("Unable to add Type to Netezza: %s", e));
return success;
}
}
private boolean executeExtraSql(String host, int port, String database, String username,
String password, String statement, GoodwillSchema schema)
{
try {
Connection connection = connectToNetezza(host, port, database, username, password);
String safeSQL = getUnescapedStatement(statement, schema);
log.info(String.format("Running extra SQL in Netezza: %s", safeSQL));
Statement extraStatement = connection.createStatement();
extraStatement.execute(safeSQL);
connection.commit();
connection.close();
return true;
}
catch (SQLException e) {
log.warn(String.format("Unable to run extra SQL in Netezza: %s", e));
return false;
}
catch (ClassNotFoundException e) {
log.warn(String.format("Unable to run extra SQL in Netezza: %s", e));
return false;
}
}
private String getUnescapedStatement(String statement, GoodwillSchema schema)
{
// TODO: hack. We do a manual replacement first because escaping can do strange things. More thoughts needed here.
// TODO: hack. Maven escapes strangely parameters on the command line, replace manually \* with *.
String safeSQL = StringUtils.replace(statement, "\\*", "*");
safeSQL = StringUtils.replace(safeSQL, "?", getTableName(schema));
return safeSQL;
}
private String getCreateTableStatement(GoodwillSchema schema)
{
String tableName = getTableName(schema);
String statement = String.format("CREATE TABLE %s (", tableName);
for (GoodwillSchemaField field : schema.getSchema()) {
statement += String.format("%s %s,", sanitizeThriftName(field.getName()), field.getFullSQLType());
}
statement = StringUtils.chop(statement); // remove last comma
statement += ") DISTRIBUTE ON RANDOM;";
return statement;
}
private String getTableName(GoodwillSchema schema)
{
return String.format(config.getSinkDBTableNameFormat(), sanitizeThriftName(schema.getName()));
}
private String sanitizeThriftName(String name)
{
return StringUtils.lowerCase(StringUtils.deleteWhitespace(name));
}
/**
* Update a type to the sink
* <p/>
* Updating a table in Netezza can be quite tricky. Don't do it.
*
* @param schema ThriftType to update
* @return true is success, false otherwise
*/
@Override
public boolean updateType(GoodwillSchema schema)
{
return false;
}
/**
* Give information on how to add a Type in the sink
*
* @param schema ThriftType to add
* @return info how to create a Type in the sink
*/
@Override
public String addTypeInfo(GoodwillSchema schema)
{
String info = String.format("%s\n", getCreateTableStatement(schema));
if (config.getSinkFirstExtraSQL() != null) {
info += getUnescapedStatement(config.getSinkFirstExtraSQL(), schema);
if (config.getSinkSecondExtraSQL() != null) {
info += getUnescapedStatement(config.getSinkSecondExtraSQL(), schema);
}
}
return info;
}
private Connection connectToNetezza(String host, int port, String db, String username, String password) throws SQLException, ClassNotFoundException
{
NzDatasource datasource = new NzDatasource();
datasource.setHost(host);
datasource.setPort(port);
datasource.setDatabase(db);
datasource.setUser(username);
datasource.setPassword(password);
Connection connection = datasource.getConnection();
connection.setAutoCommit(false);
return connection;
}
}