/*! ****************************************************************************** * * 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.tableoutput; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.pentaho.di.core.Const; import org.pentaho.di.core.KettleEnvironment; 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.KettleException; import org.pentaho.di.core.exception.KettleValueException; import org.pentaho.di.core.plugins.PluginRegistry; import org.pentaho.di.core.plugins.StepPluginType; 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.ValueMetaInteger; import org.pentaho.di.core.row.value.ValueMetaString; import org.pentaho.di.trans.RowProducer; import org.pentaho.di.trans.RowStepCollector; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransHopMeta; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.steps.injector.InjectorMeta; import junit.framework.TestCase; /** * Test class for tableinput. H2 is used as database in memory to get an easy playground for database tests. H2 does not * support all SQL features but it should proof enough for most of our tests. * * @author Sven Boden */ public class TableOutputTest extends TestCase { public static final String[] databasesXML = { "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<connection>" + "<name>db</name>" + "<server>127.0.0.1</server>" + "<type>H2</type>" + "<access>Native</access>" + "<database>mem:db</database>" + "<port></port>" + "<username>sa</username>" + "<password></password>" + "</connection>", }; private static String target_table = "table"; private static String target_table1 = "table1"; private static String target_table2 = "table2"; private static String target_table3 = "table3"; /** * Create table for the normal case. */ public void createTable( Database db, String tableName, RowMetaInterface rm ) throws Exception { String source = db.getCreateTableStatement( tableName, rm, null, false, null, true ); try { db.execStatement( source ); } catch ( KettleException ex ) { fail( "failure while creating table " + tableName + ": " + ex.getMessage() ); } } /** * Drop table */ public void dropTable( Database db, String tableName ) throws Exception { String source = "DROP TABLE " + tableName + ";"; try { db.execStatement( source ); } catch ( KettleException ex ) { fail( "failure while dropping table " + tableName + ": " + ex.getMessage() ); } } public RowMetaInterface createSourceRowMetaInterface1() { RowMetaInterface rm = new RowMeta(); ValueMetaInterface[] valuesMeta = { new ValueMetaInteger( "ID", 8, 0 ), new ValueMetaInteger( "CODE", 8, 0 ), }; for ( int i = 0; i < valuesMeta.length; i++ ) { rm.addValueMeta( valuesMeta[i] ); } return rm; } /** * Create the input rows used for the normal unit test. */ public List<RowMetaAndData> createNormalDataRows() { List<RowMetaAndData> list = new ArrayList<RowMetaAndData>(); RowMetaInterface rm = createSourceRowMetaInterface1(); Object[] r1 = new Object[] { new Long( 100L ), new Long( 1000L ) }; Object[] r2 = new Object[] { new Long( 101L ), new Long( 1001L ) }; Object[] r3 = new Object[] { new Long( 102L ), new Long( 1002L ) }; Object[] r4 = new Object[] { new Long( 103L ), new Long( 1003L ) }; Object[] r5 = new Object[] { new Long( 104L ), new Long( 1004L ) }; Object[] r6 = new Object[] { new Long( 105L ), new Long( 1005L ) }; Object[] r7 = new Object[] { new Long( 106L ), new Long( 1006L ) }; list.add( new RowMetaAndData( rm, r1 ) ); list.add( new RowMetaAndData( rm, r2 ) ); list.add( new RowMetaAndData( rm, r3 ) ); list.add( new RowMetaAndData( rm, r4 ) ); list.add( new RowMetaAndData( rm, r5 ) ); list.add( new RowMetaAndData( rm, r6 ) ); list.add( new RowMetaAndData( rm, r7 ) ); return list; } public RowMetaInterface createJIRA897RowMetaInterface() { RowMetaInterface rm = new RowMeta(); ValueMetaInterface[] valuesMeta = { new ValueMetaInteger( "ID", 8, 0 ), new ValueMetaString( "TABLE", 30, 0 ), new ValueMetaInteger( "CODE", 8, 0 ), }; for ( int i = 0; i < valuesMeta.length; i++ ) { rm.addValueMeta( valuesMeta[i] ); } return rm; } /** * Create the input rows used for the JIRA897 unit test. */ public List<RowMetaAndData> createJIRA897DataRows() { List<RowMetaAndData> list = new ArrayList<RowMetaAndData>(); RowMetaInterface rm = createJIRA897RowMetaInterface(); Object[] r1 = new Object[] { new Long( 100L ), target_table1, new Long( 1000L ) }; Object[] r2 = new Object[] { new Long( 101L ), target_table2, new Long( 1001L ) }; Object[] r3 = new Object[] { new Long( 102L ), target_table1, new Long( 1002L ) }; Object[] r4 = new Object[] { new Long( 103L ), target_table2, new Long( 1003L ) }; Object[] r5 = new Object[] { new Long( 104L ), target_table2, new Long( 1004L ) }; Object[] r6 = new Object[] { new Long( 105L ), target_table1, new Long( 1005L ) }; Object[] r7 = new Object[] { new Long( 106L ), target_table1, new Long( 1006L ) }; list.add( new RowMetaAndData( rm, r1 ) ); list.add( new RowMetaAndData( rm, r2 ) ); list.add( new RowMetaAndData( rm, r3 ) ); list.add( new RowMetaAndData( rm, r4 ) ); list.add( new RowMetaAndData( rm, r5 ) ); list.add( new RowMetaAndData( rm, r6 ) ); list.add( new RowMetaAndData( rm, r7 ) ); return list; } /** * Create the result rows for the JIRA897 case. */ public List<RowMetaAndData> createJIRA897ResultDataRows() { List<RowMetaAndData> list = new ArrayList<RowMetaAndData>(); RowMetaInterface rm = createSourceRowMetaInterface1(); Object[] r1 = new Object[] { new Long( 100L ), new Long( 1000L ) }; Object[] r2 = new Object[] { new Long( 101L ), new Long( 1001L ) }; Object[] r3 = new Object[] { new Long( 102L ), new Long( 1002L ) }; Object[] r4 = new Object[] { new Long( 103L ), new Long( 1003L ) }; Object[] r5 = new Object[] { new Long( 104L ), new Long( 1004L ) }; Object[] r6 = new Object[] { new Long( 105L ), new Long( 1005L ) }; Object[] r7 = new Object[] { new Long( 106L ), new Long( 1006L ) }; list.add( new RowMetaAndData( rm, r1 ) ); list.add( new RowMetaAndData( rm, r2 ) ); list.add( new RowMetaAndData( rm, r3 ) ); list.add( new RowMetaAndData( rm, r4 ) ); list.add( new RowMetaAndData( rm, r5 ) ); list.add( new RowMetaAndData( rm, r6 ) ); list.add( new RowMetaAndData( rm, r7 ) ); return list; } /** * Check the 2 lists comparing the rows in order. If they are not the same fail the test. */ public void checkRows( List<RowMetaAndData> rows1, List<RowMetaAndData> rows2 ) { int idx = 1; if ( rows1.size() != rows2.size() ) { fail( "Number of rows is not the same: " + rows1.size() + " and " + rows2.size() ); } Iterator<RowMetaAndData> it1 = rows1.iterator(); Iterator<RowMetaAndData> it2 = rows2.iterator(); while ( it1.hasNext() && it2.hasNext() ) { RowMetaAndData rm1 = it1.next(); RowMetaAndData rm2 = it2.next(); Object[] r1 = rm1.getData(); Object[] r2 = rm2.getData(); if ( rm1.size() != rm2.size() ) { fail( "row nr " + idx + " is not equal" ); } int[] fields = new int[r1.length]; for ( int ydx = 0; ydx < r1.length; ydx++ ) { fields[ydx] = ydx; } try { if ( rm1.getRowMeta().compare( r1, r2, fields ) != 0 ) { fail( "row nr " + idx + " is not equal" ); } } catch ( KettleValueException e ) { fail( "row nr " + idx + " is not equal" ); } idx++; } } /** * Check the results in the target dimension table. * * @param db * database to use. */ public void checkResultsNormal( Database db ) throws Exception { String query = "SELECT ID, CODE FROM " + target_table + " ORDER BY ID"; String[] correctResults = { "100|1000", "101|1001", "102|1002", "103|1003", "104|1004", "105|1005", "106|1006", }; ResultSet rs = db.openQuery( query ); int idx = 0; while ( rs.next() ) { int id = rs.getInt( "ID" ); int code = rs.getInt( "CODE" ); String result = id + "|" + code; if ( idx > correctResults.length ) { fail( "more rows returned than expected" ); } if ( !result.equals( correctResults[idx] ) ) { fail( "row " + ( idx + 1 ) + " is different than expected" ); } idx++; } if ( idx < correctResults.length ) { fail( "less rows returned than expected" ); } } /** * Check the results in the target dimension table. * * @param db * database to use. */ public void checkResultsJIRA897( Database db ) throws Exception { // Table 1 String query = "SELECT ID, CODE FROM " + target_table1 + " ORDER BY ID"; String[] correctResults = { "100|1000", "102|1002", "105|1005", "106|1006", }; ResultSet rs = db.openQuery( query ); int idx = 0; while ( rs.next() ) { int id = rs.getInt( "ID" ); int code = rs.getInt( "CODE" ); String result = id + "|" + code; if ( idx > correctResults.length ) { fail( "more rows returned than expected" ); } if ( !result.equals( correctResults[idx] ) ) { fail( "row " + ( idx + 1 ) + " is different than expected" ); } idx++; } if ( idx < correctResults.length ) { fail( "less rows returned than expected" ); } // Table 2 query = "SELECT ID, CODE FROM " + target_table2 + " ORDER BY ID"; String[] correctResults1 = { "101|1001", "103|1003", "104|1004", }; rs = db.openQuery( query ); idx = 0; while ( rs.next() ) { int id = rs.getInt( "ID" ); int code = rs.getInt( "CODE" ); String result = id + "|" + code; if ( idx > correctResults1.length ) { fail( "more rows returned than expected" ); } if ( !result.equals( correctResults1[idx] ) ) { fail( "row " + ( idx + 1 ) + " is different than expected" ); } idx++; } if ( idx < correctResults1.length ) { fail( "less rows returned than expected" ); } } /** * Test case for normal table output case. */ @SuppressWarnings( "deprecation" ) public void testTableOutputNormal() throws Exception { KettleEnvironment.init(); // // Create a new transformation... // TransMeta transMeta = new TransMeta(); transMeta.setName( "table output normal test" ); // Add the database connections for ( int i = 0; i < databasesXML.length; i++ ) { DatabaseMeta databaseMeta = new DatabaseMeta( databasesXML[i] ); transMeta.addDatabase( databaseMeta ); } DatabaseMeta dbInfo = transMeta.findDatabase( "db" ); // Execute our setup SQLs in the database. Database database = new Database( transMeta, dbInfo ); database.connect(); createTable( database, target_table, createSourceRowMetaInterface1() ); PluginRegistry registry = PluginRegistry.getInstance(); // // create an injector step... // String injectorStepname = "injector step"; InjectorMeta im = new InjectorMeta(); // Set the information of the injector. String injectorPid = registry.getPluginId( StepPluginType.class, im ); StepMeta injectorStep = new StepMeta( injectorPid, injectorStepname, im ); transMeta.addStep( injectorStep ); // // create the source step... // String outputname = "output to [" + target_table + "]"; TableOutputMeta tom = new TableOutputMeta(); tom.setDatabaseMeta( transMeta.findDatabase( "db" ) ); tom.setTablename( target_table ); String fromid = registry.getPluginId( StepPluginType.class, tom ); StepMeta fromstep = new StepMeta( fromid, outputname, tom ); fromstep.setDescription( "write data to table [" + target_table + "] on database [" + dbInfo + "]" ); transMeta.addStep( fromstep ); TransHopMeta hi = new TransHopMeta( injectorStep, fromstep ); transMeta.addTransHop( hi ); // Now execute the transformation... Trans trans = new Trans( transMeta ); trans.prepareExecution( null ); StepInterface si = trans.getStepInterface( outputname, 0 ); RowStepCollector rc = new RowStepCollector(); si.addRowListener( rc ); RowProducer rp = trans.addRowProducer( injectorStepname, 0 ); trans.startThreads(); // add rows List<RowMetaAndData> inputList = createNormalDataRows(); for ( RowMetaAndData rm : inputList ) { rp.putRow( rm.getRowMeta(), rm.getData() ); } rp.finished(); trans.waitUntilFinished(); List<RowMetaAndData> resultRows = rc.getRowsWritten(); List<RowMetaAndData> goldRows = createNormalDataRows(); checkRows( goldRows, resultRows ); checkResultsNormal( database ); } /** * Test case for normal table output where the table is included in the instream, but the tablename is not stored in * the table. */ public void testTableOutputJIRA897() throws Exception { KettleEnvironment.init(); // // Create a new transformation... // TransMeta transMeta = new TransMeta(); transMeta.setName( "table output JIRA897 test" ); // Add the database connections for ( int i = 0; i < databasesXML.length; i++ ) { DatabaseMeta databaseMeta = new DatabaseMeta( databasesXML[i] ); transMeta.addDatabase( databaseMeta ); } DatabaseMeta dbInfo = transMeta.findDatabase( "db" ); // Execute our setup SQLs in the database. Database database = new Database( transMeta, dbInfo ); database.connect(); createTable( database, target_table1, createSourceRowMetaInterface1() ); createTable( database, target_table2, createSourceRowMetaInterface1() ); PluginRegistry registry = PluginRegistry.getInstance(); // // create an injector step... // String injectorStepname = "injector step"; InjectorMeta im = new InjectorMeta(); // Set the information of the injector. String injectorPid = registry.getPluginId( StepPluginType.class, im ); StepMeta injectorStep = new StepMeta( injectorPid, injectorStepname, im ); transMeta.addStep( injectorStep ); // // create the source step... // String outputname = "output to [" + target_table1 + "] and [" + target_table2 + "]"; TableOutputMeta tom = new TableOutputMeta(); tom.setDatabaseMeta( transMeta.findDatabase( "db" ) ); tom.setTableNameInField( true ); tom.setTableNameField( "TABLE" ); tom.setTableNameInTable( false ); String fromid = registry.getPluginId( StepPluginType.class, tom ); StepMeta fromstep = new StepMeta( fromid, outputname, tom ); fromstep.setDescription( "write data to tables on database [" + dbInfo + "]" ); transMeta.addStep( fromstep ); TransHopMeta hi = new TransHopMeta( injectorStep, fromstep ); transMeta.addTransHop( hi ); // Now execute the transformation... Trans trans = new Trans( transMeta ); trans.prepareExecution( null ); StepInterface si = trans.getStepInterface( outputname, 0 ); RowStepCollector rc = new RowStepCollector(); si.addRowListener( rc ); RowProducer rp = trans.addRowProducer( injectorStepname, 0 ); trans.startThreads(); // add rows List<RowMetaAndData> inputList = createJIRA897DataRows(); for ( RowMetaAndData rm : inputList ) { rp.putRow( rm.getRowMeta(), rm.getData() ); } rp.finished(); trans.waitUntilFinished(); List<RowMetaAndData> resultRows = rc.getRowsWritten(); // The name of the table should still be in here. List<RowMetaAndData> goldRows = createJIRA897DataRows(); checkRows( goldRows, resultRows ); checkResultsJIRA897( database ); } /** * Test case for commitSize see PDI2733 in JIRA. */ @SuppressWarnings( "deprecation" ) public void testTableOutputJIRA2733() throws Exception { KettleEnvironment.init(); // // Create a new transformation... // TransMeta transMeta = new TransMeta(); transMeta.setName( "table output JIRA2733 test" ); // Add the database connections for ( int i = 0; i < databasesXML.length; i++ ) { DatabaseMeta databaseMeta = new DatabaseMeta( databasesXML[i] ); transMeta.addDatabase( databaseMeta ); } DatabaseMeta dbInfo = transMeta.findDatabase( "db" ); // Execute our setup SQLs in the database. Database database = new Database( transMeta, dbInfo ); database.connect(); createTable( database, target_table3, createSourceRowMetaInterface1() ); // Add "ts" timestamp field to target_table with a default value of NOW() database.execStatement( "ALTER TABLE " + target_table3 + " ADD COLUMN ts TIMESTAMP DEFAULT NOW() " ); PluginRegistry registry = PluginRegistry.getInstance(); // // create an injector step... // String injectorStepname = "injector step"; InjectorMeta im = new InjectorMeta(); // Set the information of the injector. String injectorPid = registry.getPluginId( StepPluginType.class, im ); StepMeta injectorStep = new StepMeta( injectorPid, injectorStepname, im ); transMeta.addStep( injectorStep ); // // create the source step... // String outputname = "output to [" + target_table3 + "]"; TableOutputMeta tom = new TableOutputMeta(); tom.setDatabaseMeta( transMeta.findDatabase( "db" ) ); tom.setTablename( target_table3 ); tom.setTruncateTable( true ); tom.setUseBatchUpdate( true ); String fromid = registry.getPluginId( StepPluginType.class, tom ); StepMeta fromstep = new StepMeta( fromid, outputname, tom ); fromstep.setDescription( "write data to table [" + target_table3 + "] on database [" + dbInfo + "]" ); transMeta.addStep( fromstep ); TransHopMeta hi = new TransHopMeta( injectorStep, fromstep ); transMeta.addTransHop( hi ); // With seven rows these are the number of commits that need to made // for "commitSize"s ranging between 0 and 8. (0=auto-commit=no commits) int[] goldRowCounts = { 1, 8, 4, 3, 2, 2, 2, 2, 1 }; for ( int commitSize = 0; commitSize <= 8; commitSize++ ) { tom.setCommitSize( commitSize ); // Now execute the transformation... Trans trans = new Trans( transMeta ); trans.prepareExecution( null ); StepInterface si = trans.getStepInterface( outputname, 0 ); RowStepCollector rc = new RowStepCollector(); si.addRowListener( rc ); RowProducer rp = trans.addRowProducer( injectorStepname, 0 ); trans.startThreads(); // add rows List<RowMetaAndData> inputList = createNormalDataRows(); for ( RowMetaAndData rm : inputList ) { rp.putRow( rm.getRowMeta(), rm.getData() ); } rp.finished(); trans.waitUntilFinished(); // Get the number of commits from the DB connection // in the table output step... // TableOutputData data = (TableOutputData) trans.findDataInterface( outputname ); int exp = goldRowCounts[commitSize]; // remove 1 commit too many in the dispose method. // int act = data.db.getNrExecutedCommits() - 1; assertEquals( "Incorrect number of commits with commitSize=" + commitSize + Const.CR, exp, act ); } dropTable( database, target_table3 ); } public static void main( String[] args ) throws Exception { TableOutputTest test = new TableOutputTest(); for ( int i = 0; i < 1000; i++ ) { test.testTableOutputJIRA2733(); } } }