/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* 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 org.pentaho.di.trans.steps.combinationlookup;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.RowMetaAndData;
import org.pentaho.di.core.database.Database;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleConfigException;
import org.pentaho.di.core.exception.KettleDatabaseException;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleStepException;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaDate;
import org.pentaho.di.core.row.value.ValueMetaInteger;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStep;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
/**
* Manages or looks up information in a Type 1 or junk dimension.
* <p>
* <p>
* 1) Lookup combination field1..n in a dimension
* <p>
* 2) If this combination exists, return technical key
* <p>
* 3) If this combination doesn't exist, insert & return technical key
* <p>
* 4) if replace is Y, remove all key fields from output.
* <p>
* <p>
*
* @author Matt
* @since 22-jul-2003
*/
public class CombinationLookup extends BaseStep implements StepInterface {
private static Class<?> PKG = CombinationLookupMeta.class; // for i18n purposes, needed by Translator2!!
private static final int CREATION_METHOD_AUTOINC = 1;
private static final int CREATION_METHOD_SEQUENCE = 2;
private static final int CREATION_METHOD_TABLEMAX = 3;
private int techKeyCreation;
private CombinationLookupMeta meta;
private CombinationLookupData data;
public CombinationLookup( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr,
TransMeta transMeta, Trans trans ) {
super( stepMeta, stepDataInterface, copyNr, transMeta, trans );
meta = (CombinationLookupMeta) getStepMeta().getStepMetaInterface();
data = (CombinationLookupData) stepDataInterface;
}
private void setTechKeyCreation( int method ) {
techKeyCreation = method;
}
private int getTechKeyCreation() {
return techKeyCreation;
}
private void determineTechKeyCreation() {
String keyCreation = meta.getTechKeyCreation();
if ( meta.getDatabaseMeta().supportsAutoinc()
&& CombinationLookupMeta.CREATION_METHOD_AUTOINC.equals( keyCreation ) ) {
setTechKeyCreation( CREATION_METHOD_AUTOINC );
} else if ( meta.getDatabaseMeta().supportsSequences()
&& CombinationLookupMeta.CREATION_METHOD_SEQUENCE.equals( keyCreation ) ) {
setTechKeyCreation( CREATION_METHOD_SEQUENCE );
} else {
setTechKeyCreation( CREATION_METHOD_TABLEMAX );
}
}
private Long lookupInCache( RowMetaInterface rowMeta, Object[] row ) {
// Short circuit if cache is disabled.
if ( meta.getCacheSize() == -1 ) {
return null;
}
// try to find the row in the cache...
//
Long tk = data.cache.get( new RowMetaAndData( rowMeta, row ) );
return tk;
}
/**
* Adds a row to the cache In case we are doing updates, we need to store the complete rows from the database. These
* are the values we need to store
*
* Key: - natural key fields Value: - Technical key - lookup fields / extra fields (allows us to compare or retrieve)
* - Date_from - Date_to
*
* @param row
* @param returnValues
* @throws KettleValueException
*/
private void addToCache( RowMetaInterface rowMeta, Object[] row, Long tk ) throws KettleValueException {
// Short circuit if cache is disabled.
if ( meta.getCacheSize() == -1 ) {
return;
}
// store it in the cache if needed.
data.cache.put( new RowMetaAndData( rowMeta, row ), tk );
// check if the size is not too big...
// Allow for a buffer overrun of 20% and then remove those 20% in one go.
// Just to keep performance in track.
//
int tenPercent = meta.getCacheSize() / 10;
if ( meta.getCacheSize() > 0 && data.cache.size() > meta.getCacheSize() + tenPercent ) {
// Which cache entries do we delete here?
// We delete those with the lowest technical key...
// Those would arguably be the "oldest" dimension entries.
// Oh well... Nothing is going to be perfect here...
//
// Getting the lowest 20% requires some kind of sorting algorithm and I'm not sure we want to do that.
// Sorting is slow and even in the best case situation we need to do 2 passes over the cache entries...
//
// Perhaps we should get 20% random values and delete everything below the lowest but one TK.
//
List<RowMetaAndData> keys = new ArrayList<RowMetaAndData>( data.cache.keySet() );
int sizeBefore = keys.size();
List<Long> samples = new ArrayList<Long>();
// Take 10 sample technical keys....
int stepsize = keys.size() / 5;
if ( stepsize < 1 ) {
stepsize = 1; // make sure we have no endless loop
}
for ( int i = 0; i < keys.size(); i += stepsize ) {
RowMetaAndData key = keys.get( i );
Long value = data.cache.get( key );
if ( value != null ) {
samples.add( value );
}
}
// Sort these 5 elements...
Collections.sort( samples );
// What is the smallest?
// Take the second, not the fist in the list, otherwise we would be removing a single entry = not good.
if ( samples.size() > 1 ) {
data.smallestCacheKey = samples.get( 1 ).longValue();
} else { // except when there is only one sample
data.smallestCacheKey = samples.get( 0 ).longValue();
}
// Remove anything in the cache <= smallest.
// This makes it almost single pass...
// This algorithm is not 100% correct, but I guess it beats sorting the whole cache all the time.
//
for ( int i = 0; i < keys.size(); i++ ) {
RowMetaAndData key = keys.get( i );
Long value = data.cache.get( key );
if ( value != null ) {
if ( value.longValue() <= data.smallestCacheKey ) {
data.cache.remove( key ); // this one has to go.
}
}
}
int sizeAfter = data.cache.size();
logDetailed( "Reduced the lookup cache from " + sizeBefore + " to " + sizeAfter + " rows." );
}
if ( isRowLevel() ) {
logRowlevel( "Cache store: key=" + rowMeta.getString( row ) + " key=" + tk );
}
}
protected boolean isAutoIncrement() {
return techKeyCreation == CREATION_METHOD_AUTOINC;
}
@SuppressWarnings( "deprecation" )
private Object[] lookupValues( RowMetaInterface rowMeta, Object[] row ) throws KettleException {
Long val_key = null;
Long val_hash = null;
Object[] hashRow = null;
Object[] lookupRow = new Object[data.lookupRowMeta.size()];
int lookupIndex = 0;
if ( meta.useHash() || meta.getCacheSize() >= 0 ) {
hashRow = new Object[data.hashRowMeta.size()];
for ( int i = 0; i < meta.getKeyField().length; i++ ) {
hashRow[i] = row[data.keynrs[i]];
}
if ( meta.useHash() ) {
val_hash = new Long( data.hashRowMeta.oldXORHashCode( hashRow ) );
lookupRow[lookupIndex] = val_hash;
lookupIndex++;
}
}
for ( int i = 0; i < meta.getKeyField().length; i++ ) {
// Determine the index of this Key Field in the row meta/data
int rowIndex = data.keynrs[i];
lookupRow[lookupIndex] = row[rowIndex]; // KEYi = ?
lookupIndex++;
if ( meta.getDatabaseMeta().requiresCastToVariousForIsNull()
&& rowMeta.getValueMeta( rowIndex ).getType() == ValueMetaInterface.TYPE_STRING ) {
lookupRow[lookupIndex] = rowMeta.getValueMeta( rowIndex ).isNull( row[rowIndex] ) ? null : "NotNull"; // KEYi IS
// NULL or
// ? IS
// NULL
} else {
lookupRow[lookupIndex] = row[data.keynrs[i]]; // KEYi IS NULL or ? IS NULL
}
lookupIndex++;
}
// Before doing the actual lookup in the database, see if it's not in the cache...
val_key = lookupInCache( data.hashRowMeta, hashRow );
if ( val_key == null ) {
data.db.setValues( data.lookupRowMeta, lookupRow, data.prepStatementLookup );
Object[] add = data.db.getLookup( data.prepStatementLookup );
incrementLinesInput();
if ( add == null ) { // The dimension entry was not found, we need to add it!
// First try to use an AUTOINCREMENT field
switch ( getTechKeyCreation() ) {
case CREATION_METHOD_TABLEMAX:
// Use our own counter: what's the next value for the technical key?
val_key =
data.db.getNextValue( getTransMeta().getCounters(), data.realSchemaName, data.realTableName, meta
.getTechnicalKeyField() );
break;
case CREATION_METHOD_AUTOINC:
val_key = new Long( 0 ); // value to accept new key...
break;
case CREATION_METHOD_SEQUENCE:
val_key =
data.db.getNextSequenceValue( data.realSchemaName, meta.getSequenceFrom(), meta
.getTechnicalKeyField() );
if ( val_key != null && isRowLevel() ) {
logRowlevel( BaseMessages.getString( PKG, "CombinationLookup.Log.FoundNextSequenceValue" )
+ val_key.toString() );
}
break;
default:
break;
}
val_key = combiInsert( rowMeta, row, val_key, val_hash );
incrementLinesOutput();
if ( isRowLevel() ) {
logRowlevel( BaseMessages.getString( PKG, "CombinationLookup.Log.AddedDimensionEntry" ) + val_key );
}
// Also store it in our Hashtable...
addToCache( data.hashRowMeta, hashRow, val_key );
} else {
// Entry already exists...
//
val_key = data.db.getReturnRowMeta().getInteger( add, 0 ); // Sometimes it's not an integer, believe it or not.
addToCache( data.hashRowMeta, hashRow, val_key );
}
}
Object[] outputRow = new Object[data.outputRowMeta.size()];
int outputIndex = 0;
// See if we need to replace the fields with the technical key
if ( meta.replaceFields() ) {
for ( int i = 0; i < rowMeta.size(); i++ ) {
if ( !data.removeField[i] ) {
outputRow[outputIndex] = row[i];
outputIndex++;
}
}
} else {
// Just copy the input row and add the technical key
for ( outputIndex = 0; outputIndex < rowMeta.size(); outputIndex++ ) {
outputRow[outputIndex] = row[outputIndex];
}
}
// Add the technical key...
outputRow[outputIndex] = val_key;
return outputRow;
}
public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException {
Object[] r = getRow(); // Get row from input rowset & set row busy!
// if no more input to be expected set done
if ( r == null ) {
setOutputDone();
return false;
}
if ( first ) {
first = false;
data.outputRowMeta = getInputRowMeta().clone();
meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore );
data.schemaTable =
meta.getDatabaseMeta().getQuotedSchemaTableCombination( data.realSchemaName, data.realTableName );
determineTechKeyCreation();
// The indexes of the key values...
//
data.keynrs = new int[meta.getKeyField().length];
for ( int i = 0; i < meta.getKeyField().length; i++ ) {
data.keynrs[i] = getInputRowMeta().indexOfValue( meta.getKeyField()[i] );
if ( data.keynrs[i] < 0 ) {
// couldn't find field!
throw new KettleStepException( BaseMessages.getString(
PKG, "CombinationLookup.Exception.FieldNotFound", meta.getKeyField()[i] ) );
}
}
// Determine for each input field if we want it removed or not.
//
data.removeField = new boolean[getInputRowMeta().size()];
// Sort lookup values keys so that we
//
for ( int i = 0; i < getInputRowMeta().size(); i++ ) {
ValueMetaInterface valueMeta = getInputRowMeta().getValueMeta( i );
// Is this one of the keys?
int idx = Const.indexOfString( valueMeta.getName(), meta.getKeyField() );
data.removeField[i] = idx >= 0;
}
// Determine the metadata row to calculate hashcodes.
//
data.hashRowMeta = new RowMeta();
for ( int i = 0; i < meta.getKeyField().length; i++ ) {
data.hashRowMeta.addValueMeta( getInputRowMeta().getValueMeta( data.keynrs[i] ) ); // KEYi = ?
}
setCombiLookup( getInputRowMeta() );
preloadCache( data.hashRowMeta );
}
try {
Object[] outputRow = lookupValues( getInputRowMeta(), r ); // add new values to the row in rowset[0].
putRow( data.outputRowMeta, outputRow ); // copy row to output rowset(s);
if ( checkFeedback( getLinesRead() ) ) {
if ( log.isBasic() ) {
logBasic( BaseMessages.getString( PKG, "CombinationLookup.Log.LineNumber" ) + getLinesRead() );
}
}
} catch ( KettleException e ) {
if ( getStepMeta().isDoingErrorHandling() ) {
putError( getInputRowMeta(), r, 1L, Const.getStackTracker( e ), null, "CBL001" );
} else {
logError( BaseMessages.getString( PKG, "CombinationLookup.Log.ErrorInStepRunning" ) + e.getMessage() );
setErrors( 1 );
stopAll();
setOutputDone(); // signal end to receiver(s)
return false;
}
}
return true;
}
/**
* CombinationLookup table: dimension table keys[]: which dim-fields do we use to look up key? retval: name of the key
* to return
*/
public void setCombiLookup( RowMetaInterface inputRowMeta ) throws KettleDatabaseException {
DatabaseMeta databaseMeta = meta.getDatabaseMeta();
String sql = "";
boolean comma;
data.lookupRowMeta = new RowMeta();
/*
* SELECT <retval> FROM <table> WHERE ( ( <key1> = ? ) OR ( <key1> IS NULL AND ? IS NULL ) ) AND ( ( <key2> = ? ) OR
* ( <key1> IS NULL AND ? IS NULL ) ) ... ;
*
* OR
*
* SELECT <retval> FROM <table> WHERE <crcfield> = ? AND ( ( <key1> = ? ) OR ( <key1> IS NULL AND ? IS NULL ) ) AND
* ( ( <key2> = ? ) OR ( <key1> IS NULL AND ? IS NULL ) ) ... ;
*/
sql += "SELECT " + databaseMeta.quoteField( meta.getTechnicalKeyField() ) + Const.CR;
sql += "FROM " + data.schemaTable + Const.CR;
sql += "WHERE ";
comma = false;
if ( meta.useHash() ) {
sql += databaseMeta.quoteField( meta.getHashField() ) + " = ? " + Const.CR;
comma = true;
data.lookupRowMeta.addValueMeta( new ValueMetaInteger( meta.getHashField() ) );
} else {
sql += "( ( ";
}
for ( int i = 0; i < meta.getKeyLookup().length; i++ ) {
if ( comma ) {
sql += " AND ( ( ";
} else {
comma = true;
}
sql +=
databaseMeta.quoteField( meta.getKeyLookup()[i] )
+ " = ? ) OR ( " + databaseMeta.quoteField( meta.getKeyLookup()[i] );
data.lookupRowMeta.addValueMeta( inputRowMeta.getValueMeta( data.keynrs[i] ) );
sql += " IS NULL AND ";
if ( databaseMeta.requiresCastToVariousForIsNull() ) {
sql += "CAST(? AS VARCHAR(256)) IS NULL";
} else {
sql += "? IS NULL";
}
// Add the ValueMeta for the null check, BUT cloning needed.
// Otherwise the field gets renamed and gives problems when referenced by previous steps.
data.lookupRowMeta.addValueMeta( inputRowMeta.getValueMeta( data.keynrs[i] ).clone() );
sql += " ) )";
sql += Const.CR;
}
try {
if ( log.isDebug() ) {
logDebug( "preparing combi-lookup statement:" + Const.CR + sql );
}
data.prepStatementLookup = data.db.getConnection().prepareStatement( databaseMeta.stripCR( sql ) );
if ( databaseMeta.supportsSetMaxRows() ) {
data.prepStatementLookup.setMaxRows( 1 ); // alywas get only 1 line back!
}
} catch ( SQLException ex ) {
throw new KettleDatabaseException( "Unable to prepare combi-lookup statement", ex );
}
}
/**
* This inserts new record into a junk dimension
*/
public Long combiInsert( RowMetaInterface rowMeta, Object[] row, Long val_key, Long val_crc ) throws KettleDatabaseException {
String debug = "Combination insert";
DatabaseMeta databaseMeta = meta.getDatabaseMeta();
try {
if ( data.prepStatementInsert == null ) { // first time: construct prepared statement
debug = "First: construct prepared statement";
data.insertRowMeta = new RowMeta();
/*
* Construct the SQL statement...
*
* INSERT INTO d_test(keyfield, [crcfield,] keylookup[]) VALUES(val_key, [val_crc], row values with keynrs[]) ;
*/
String sql = "";
sql += "INSERT INTO " + data.schemaTable + ( "( " );
boolean comma = false;
if ( !isAutoIncrement() ) {
// NO AUTOINCREMENT
sql += databaseMeta.quoteField( meta.getTechnicalKeyField() );
data.insertRowMeta.addValueMeta( new ValueMetaInteger( meta.getTechnicalKeyField() ) );
comma = true;
} else if ( databaseMeta.needsPlaceHolder() ) {
sql += "0"; // placeholder on informix! Will be replaced in table by real autoinc value.
data.insertRowMeta.addValueMeta( new ValueMetaInteger( meta.getTechnicalKeyField() ) );
comma = true;
}
if ( meta.useHash() ) {
if ( comma ) {
sql += ", ";
}
sql += databaseMeta.quoteField( meta.getHashField() );
data.insertRowMeta.addValueMeta( new ValueMetaInteger( meta.getHashField() ) );
comma = true;
}
if ( !Utils.isEmpty( meta.getLastUpdateField() ) ) {
if ( comma ) {
sql += ", ";
}
sql += databaseMeta.quoteField( meta.getLastUpdateField() );
data.insertRowMeta
.addValueMeta( new ValueMetaDate( meta.getLastUpdateField() ) );
comma = true;
}
for ( int i = 0; i < meta.getKeyLookup().length; i++ ) {
if ( comma ) {
sql += ", ";
}
sql += databaseMeta.quoteField( meta.getKeyLookup()[i] );
data.insertRowMeta.addValueMeta( rowMeta.getValueMeta( data.keynrs[i] ) );
comma = true;
}
sql += ") VALUES (";
comma = false;
if ( !isAutoIncrement() ) {
sql += '?';
comma = true;
}
if ( meta.useHash() ) {
if ( comma ) {
sql += ',';
}
sql += '?';
comma = true;
}
if ( !Utils.isEmpty( meta.getLastUpdateField() ) ) {
if ( comma ) {
sql += ',';
}
sql += '?';
comma = true;
}
for ( int i = 0; i < meta.getKeyLookup().length; i++ ) {
if ( comma ) {
sql += ',';
} else {
comma = true;
}
sql += '?';
}
sql += " )";
String sqlStatement = sql;
try {
debug = "First: prepare statement";
if ( isAutoIncrement() && databaseMeta.supportsAutoGeneratedKeys() ) {
logDetailed( "SQL with return keys: " + sqlStatement );
data.prepStatementInsert =
data.db.getConnection().prepareStatement(
databaseMeta.stripCR( sqlStatement ), Statement.RETURN_GENERATED_KEYS );
} else {
logDetailed( "SQL without return keys: " + sqlStatement );
data.prepStatementInsert =
data.db.getConnection().prepareStatement( databaseMeta.stripCR( sqlStatement ) );
}
} catch ( SQLException ex ) {
throw new KettleDatabaseException( "Unable to prepare combi insert statement : "
+ Const.CR + sqlStatement, ex );
} catch ( Exception ex ) {
throw new KettleDatabaseException( "Unable to prepare combi insert statement : "
+ Const.CR + sqlStatement, ex );
}
}
debug = "Create new insert row rins";
Object[] insertRow = new Object[data.insertRowMeta.size()];
int insertIndex = 0;
if ( !isAutoIncrement() ) {
insertRow[insertIndex] = val_key;
insertIndex++;
}
if ( meta.useHash() ) {
insertRow[insertIndex] = val_crc;
insertIndex++;
}
if ( !Utils.isEmpty( meta.getLastUpdateField() ) ) {
insertRow[insertIndex] = new Date();
insertIndex++;
}
for ( int i = 0; i < data.keynrs.length; i++ ) {
insertRow[insertIndex] = row[data.keynrs[i]];
insertIndex++;
}
if ( isRowLevel() ) {
logRowlevel( "rins=" + data.insertRowMeta.getString( insertRow ) );
}
debug = "Set values on insert";
// INSERT NEW VALUE!
data.db.setValues( data.insertRowMeta, insertRow, data.prepStatementInsert );
debug = "Insert row";
data.db.insertRow( data.prepStatementInsert );
debug = "Retrieve key";
if ( isAutoIncrement() && databaseMeta.supportsAutoGeneratedKeys() ) {
ResultSet keys = null;
try {
keys = data.prepStatementInsert.getGeneratedKeys(); // 1 key
if ( keys.next() ) {
val_key = new Long( keys.getLong( 1 ) );
} else {
throw new KettleDatabaseException( "Unable to retrieve auto-increment of combi insert key : "
+ meta.getTechnicalKeyField() + ", no fields in resultset" );
}
} catch ( SQLException ex ) {
throw new KettleDatabaseException( "Unable to retrieve auto-increment of combi insert key : "
+ meta.getTechnicalKeyField(), ex );
} finally {
try {
if ( keys != null ) {
keys.close();
}
} catch ( SQLException ex ) {
throw new KettleDatabaseException( "Unable to retrieve auto-increment of combi insert key : "
+ meta.getTechnicalKeyField(), ex );
}
}
}
} catch ( Exception e ) {
logError( Const.getStackTracker( e ) );
throw new KettleDatabaseException( "Unexpected error in combination insert in part ["
+ debug + "] : " + e.toString(), e );
}
return val_key;
}
public boolean isRowLevel() {
return log.isRowLevel();
}
public boolean init( StepMetaInterface sii, StepDataInterface sdi ) {
if ( super.init( sii, sdi ) ) {
data.realSchemaName = environmentSubstitute( meta.getSchemaName() );
data.realTableName = environmentSubstitute( meta.getTableName() );
if ( meta.getCacheSize() > 0 ) {
data.cache = new HashMap<RowMetaAndData, Long>( (int) ( meta.getCacheSize() * 1.5 ) );
} else {
data.cache = new HashMap<RowMetaAndData, Long>();
}
if ( meta.getDatabaseMeta() == null ) {
logError( BaseMessages.getString( PKG, "CombinationLookup.Init.ConnectionMissing", getStepname() ) );
return false;
}
data.db = new Database( this, meta.getDatabaseMeta() );
data.db.shareVariablesWith( this );
try {
if ( getTransMeta().isUsingUniqueConnections() ) {
synchronized ( getTrans() ) {
data.db.connect( getTrans().getTransactionId(), getPartitionID() );
}
} else {
data.db.connect( getPartitionID() );
}
if ( log.isDetailed() ) {
logDetailed( BaseMessages.getString( PKG, "CombinationLookup.Log.ConnectedToDB" ) );
}
data.db.setCommit( meta.getCommitSize() );
return true;
} catch ( KettleDatabaseException dbe ) {
logError( BaseMessages.getString( PKG, "CombinationLookup.Log.UnableToConnectDB" ) + dbe.getMessage() );
}
}
return false;
}
public void dispose( StepMetaInterface smi, StepDataInterface sdi ) {
meta = (CombinationLookupMeta) smi;
data = (CombinationLookupData) sdi;
if ( data.db != null ) {
try {
if ( !data.db.isAutoCommit() ) {
if ( getErrors() == 0 ) {
data.db.commit();
} else {
data.db.rollback();
}
}
} catch ( KettleDatabaseException e ) {
logError( BaseMessages.getString( PKG, "CombinationLookup.Log.UnexpectedError" ) + " : " + e.toString() );
} finally {
data.db.disconnect();
}
}
super.dispose( smi, sdi );
}
/** Preload the cache
*
* @param hashRowMeta The RowMeta of the hashRow
* @author nwyrwa
* @throws KettleDatabaseException If something went wrong while selecting the values from the db
* @throws KettleValueException If something went wrong while adding the data to the cache
* @throws KettleConfigException If the step configuration is incomplete
*/
private void preloadCache( RowMetaInterface hashRowMeta ) throws KettleDatabaseException, KettleValueException, KettleConfigException {
// fast exit if no preload cache or no cache
if ( meta.getPreloadCache() && meta.getCacheSize() >= 0 ) {
if ( hashRowMeta == null ) {
throw new KettleConfigException( BaseMessages.getString( PKG, "CombinationLookup.Log.UnexpectedError" ) );
}
DatabaseMeta databaseMeta = meta.getDatabaseMeta();
if ( databaseMeta == null ) {
throw new KettleConfigException( BaseMessages.getString( PKG, "CombinationLookup.Log.UnexpectedError" ) );
}
String lookupKeys = "";
String sql = "";
List<Object[]> cacheValues;
/* build SQl Statement to preload cache
*
* SELECT
* min(<retval>) as <retval>,
* key1,
* key2,
* key3
* FROM <table>
*
* GROUP BY key1,
* key2,
* key3;
*
*/
// Build a string representation of the lookupKeys
for ( int i = 0; i < meta.getKeyLookup().length; i++ ) {
lookupKeys += databaseMeta.quoteField( meta.getKeyLookup()[i] );
// No comma after last field
if ( i < meta.getKeyLookup().length - 1 ) {
lookupKeys += "," + Const.CR;
}
}
// Use min in case of disambiguation
sql += "SELECT " + Const.CR;
sql += "MIN(" + databaseMeta.quoteField( meta.getTechnicalKeyField() ) + ") as "
+ databaseMeta.quoteField( meta.getTechnicalKeyField() ) + "," + Const.CR;
sql += lookupKeys + Const.CR;
sql += "FROM " + data.schemaTable + Const.CR;
sql += "GROUP BY" + Const.CR;
sql += lookupKeys + Const.CR;
if ( log.isDebug() ) {
logDebug( "Using preload cache statement:" + Const.CR + sql );
}
cacheValues = data.db.getRows( databaseMeta.stripCR( sql ), meta.getCacheSize() );
for ( Object[] cacheRow : cacheValues ) {
// Create a correctly structured array for the cache
Object[] hashRow = new Object[data.hashRowMeta.size()];
// Assumes the technical key is at position 0 !!
System.arraycopy( cacheRow, 1, hashRow, 0, hashRow.length );
// Potential Cache Overflow is ahndled inside
addToCache( hashRowMeta, hashRow, (Long) cacheRow[0] );
incrementLinesInput();
}
}
}
}