package com.anjlab.csv2db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import com.codahale.metrics.Timer;
public class InsertRecordHandler extends AbstractRecordHandler
{
private PreparedStatement insertStatement;
private final Timer insertStatementTimer;
private int numberOfStatementsInBatch;
private final Set<String> duplicatesTracker;
public InsertRecordHandler(
Configuration config,
Connection connection,
ScriptEngine scriptEngine,
Router router,
int threadId,
int threadCount)
throws SQLException, ScriptException
{
super(config, scriptEngine, connection, router, threadId, threadCount);
StringBuilder insertClause =
new StringBuilder("INSERT INTO ")
.append(config.getTargetTable())
.append(" (");
StringBuilder valuesClause = new StringBuilder();
for (String targetTableColumnName : getColumnNamesWithInsertValues())
{
if (valuesClause.length() > 0)
{
insertClause.append(", ");
valuesClause.append(", ");
}
insertClause.append(targetTableColumnName);
ValueDefinition definition = config.getInsertValues().get(targetTableColumnName);
if (definition.producesSQL())
{
valuesClause.append(definition.eval(targetTableColumnName, null, scriptEngine));
}
else
{
valuesClause.append("?");
}
}
for (String targetTableColumnName : getOrderedTableColumnNames())
{
if (valuesClause.length() > 0)
{
insertClause.append(", ");
valuesClause.append(", ");
}
insertClause.append(targetTableColumnName);
valuesClause.append("?");
}
insertClause.append(") VALUES (")
.append(valuesClause)
.append(")");
if (Import.isVerboseEnabled())
{
Import.logVerbose("INSERT statement used: " + insertClause);
}
insertStatement = connection.prepareStatement(insertClause.toString());
insertStatementTimer = Import.METRIC_REGISTRY.timer("thread-" + threadId + ".inserts");
duplicatesTracker = new HashSet<>(config.getBatchSize());
}
@Override
public void handleRecord(Map<String, Object> nameValues)
throws SQLException, ConfigurationException, ScriptException, InterruptedException
{
if (Import.isVerboseEnabled())
{
printNameValues(nameValues);
}
// XXX Check duplicates should be performed on eval'ed/transformed values,
// right now it's partially true (i.e. eval'ed but not transformed values are used),
// and only if map function is declared in configuration
if (config.isIgnoreDuplicatePK())
{
// If needed re-route this to another handler based on keys hash
String keys = config.joinPrimaryKeys(nameValues);
int partitionId = Math.abs(keys.hashCode() % threadCount);
if (partitionId != threadId)
{
router.dispatch(nameValues, partitionId);
return;
}
if (duplicatesTracker.contains(keys))
{
// This record will be ignored
if (Import.isVerboseEnabled())
{
Import.logVerbose("Duplicate already in batch for keys: " + keys);
}
return;
}
duplicatesTracker.add(keys);
}
int parameterIndex = 1;
for (String targetTableColumnName : getColumnNamesWithInsertValues())
{
ValueDefinition definition = config.getInsertValues().get(targetTableColumnName);
if (!definition.producesSQL())
{
Object columnValue = eval(definition, targetTableColumnName, nameValues);
if (Import.isVerboseEnabled())
{
printNameValue(targetTableColumnName, columnValue);
}
insertStatement.setObject(parameterIndex++, columnValue);
}
}
for (String targetTableColumnName : getOrderedTableColumnNames())
{
Object columnValue = transform(targetTableColumnName, nameValues);
if (Import.isVerboseEnabled())
{
printNameValue(targetTableColumnName, columnValue);
}
insertStatement.setObject(parameterIndex++, columnValue);
}
numberOfStatementsInBatch++;
insertStatement.addBatch();
checkBatchExecution(config.getBatchSize());
}
@Override
protected void enableBatchExecution() throws SQLException
{
super.enableBatchExecution();
checkBatchExecution(config.getBatchSize());
}
private void checkBatchExecution(int limit) throws SQLException
{
if (batchExecutionDisabled || numberOfStatementsInBatch == 0)
{
return;
}
if (numberOfStatementsInBatch >= limit)
{
if (Import.isVerboseEnabled())
{
Import.logVerbose("About to flush INSERT batch");
}
Import.measureTime(insertStatementTimer, new VoidCallable<SQLException>()
{
@Override
public void run() throws SQLException
{
insertStatement.executeBatch();
}
});
insertStatement.clearParameters();
numberOfStatementsInBatch = 0;
duplicatesTracker.clear();
}
}
@Override
public void close()
{
try
{
checkBatchExecution(0);
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
finally
{
closeQuietly(insertStatement);
super.close();
}
}
}