This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 1aab3c37ba Fix#6984 Table Compare left/right join error counts when
keys mismatch (merge branch) (#6987)
1aab3c37ba is described below
commit 1aab3c37bad615fd5a6ddfc2ad8c3cf7e819693b
Author: Lance <[email protected]>
AuthorDate: Sun Apr 12 23:39:35 2026 +0800
Fix#6984 Table Compare left/right join error counts when keys mismatch
(merge branch) (#6987)
* Fix Table Compare left/right join error counts when keys mismatch (merge
branch)
Signed-off-by: lance <[email protected]>
* Fix Table Compare left/right join error counts when keys mismatch (merge
branch)
Signed-off-by: lance <[email protected]>
---------
Signed-off-by: lance <[email protected]>
---
.../transforms/tablecompare/TableCompare.java | 23 +-
.../src/main/samples/transforms/table-compare.hpl | 269 +++++++++++++
.../tablecompare/TableCompareDataTests.java | 126 ++++++
.../tablecompare/TableCompareMetaTest.java | 156 +++++++-
.../transforms/tablecompare/TableCompareTests.java | 442 +++++++++++++++++++++
5 files changed, 998 insertions(+), 18 deletions(-)
diff --git
a/plugins/transforms/tablecompare/src/main/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompare.java
b/plugins/transforms/tablecompare/src/main/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompare.java
index 6eae5940c3..5c51bf8f16 100644
---
a/plugins/transforms/tablecompare/src/main/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompare.java
+++
b/plugins/transforms/tablecompare/src/main/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompare.java
@@ -633,6 +633,8 @@ public class TableCompare extends
BaseTransform<TableCompareMeta, TableCompareDa
nrRecordsCompare++;
}
} else {
+ // Merge on sorted keys: same semantics as MergeRows (compare
< 0 => only reference,
+ // compare > 0 => only compare). Must match the
one==null/two==null branches above.
if (compare < 0) {
if (getTransformMeta().isDoingErrorHandling()) {
String keyDesc = getKeyDesc(keyRowMeta, keyNrs, one);
@@ -643,14 +645,14 @@ public class TableCompare extends
BaseTransform<TableCompareMeta, TableCompareDa
1,
BaseMessages.getString(
PKG,
-
"TableCompare.Error.RecordNotInReferenceFoundInCompareTable",
- cmpSchemaTable,
+
"TableCompare.Error.RecordInReferenceNotFoundInCompareTable",
+ refSchemaTable,
keyRowMeta.getString(one)),
null,
- "TAC004");
+ "TAC005");
}
nrErrors++;
- nrRightErrors++;
+ nrLeftErrors++;
one = data.referenceDb.getRow(refSet);
if (one != null) {
@@ -666,14 +668,14 @@ public class TableCompare extends
BaseTransform<TableCompareMeta, TableCompareDa
1,
BaseMessages.getString(
PKG,
-
"TableCompare.Error.RecordInReferenceNotFoundInCompareTable",
- refSchemaTable,
+
"TableCompare.Error.RecordNotInReferenceFoundInCompareTable",
+ cmpSchemaTable,
keyRowMeta.getString(two)),
null,
- "TAC005");
+ "TAC004");
}
nrErrors++;
- nrLeftErrors++;
+ nrRightErrors++;
two = data.compareDb.getRow(cmpSet);
if (two != null) {
@@ -706,7 +708,7 @@ public class TableCompare extends
BaseTransform<TableCompareMeta, TableCompareDa
result[index++] = nrRecordsCompare;
result[index++] = nrLeftErrors;
result[index++] = nrInnerErrors;
- result[index++] = nrRightErrors;
+ result[index] = nrRightErrors;
r[data.keyDescIndex] = null;
r[data.valueReferenceIndex] = null;
@@ -732,8 +734,7 @@ public class TableCompare extends
BaseTransform<TableCompareMeta, TableCompareDa
}
private Object[] constructErrorRow(
- IRowMeta rowMeta, Object[] r, String keyField, String referenceValue,
String compareValue)
- throws HopException {
+ IRowMeta rowMeta, Object[] r, String keyField, String referenceValue,
String compareValue) {
if (data.errorRowMeta == null) {
data.errorRowMeta = rowMeta.clone();
diff --git
a/plugins/transforms/tablecompare/src/main/samples/transforms/table-compare.hpl
b/plugins/transforms/tablecompare/src/main/samples/transforms/table-compare.hpl
new file mode 100644
index 0000000000..6fcad2adf9
--- /dev/null
+++
b/plugins/transforms/tablecompare/src/main/samples/transforms/table-compare.hpl
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+ <info>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <pipeline_type>Normal</pipeline_type>
+ <pipeline_status>0</pipeline_status>
+ <parameters/>
+ <name>table-compare</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2023/03/07 11:23:18.711</created_date>
+ <modified_date>2023/03/07 11:23:18.711</modified_date>
+ </info>
+ <transform>
+ <type>DataGrid</type>
+ <name>Data grid</name>
+ <fields>
+ <field>
+ <name>ref_schema</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>ref_table</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>cmp_schema</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>cmp_table</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>id_fields</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>excl_fields</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>key_description</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>ref_value</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>compare_value</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item/>
+ <item>TestTableOld</item>
+ <item/>
+ <item>TestTableNew</item>
+ <item>id</item>
+ <item/>
+ <item>ID</item>
+ <item>TextColumn</item>
+ <item>TextColumn</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>112</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>ERRORS</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>512</xloc>
+ <yloc>176</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>TableCompare</type>
+ <name>Table compare</name>
+ <reference_connection>localhost</reference_connection>
+ <reference_schema_field>ref_schema</reference_schema_field>
+ <reference_table_field>ref_table</reference_table_field>
+ <compare_connection>localhost</compare_connection>
+ <compare_schema_field>cmp_schema</compare_schema_field>
+ <compare_table_field>cmp_table</compare_table_field>
+ <key_fields_field>id_fields</key_fields_field>
+ <exclude_fields_field>excl_fields</exclude_fields_field>
+ <nr_errors_field>nrErrors</nr_errors_field>
+
<nr_records_reference_field>nrRecordsReferenceTable</nr_records_reference_field>
+ <nr_records_compare_field>nrRecordsCompareTable</nr_records_compare_field>
+ <nr_errors_left_join_field>nrErrorsLeftJoin</nr_errors_left_join_field>
+ <nr_errors_inner_join_field>nrErrorsInnerJoin</nr_errors_inner_join_field>
+ <nr_errors_right_join_field>nrErrorsRightJoin</nr_errors_right_join_field>
+ <key_description_field>key_description</key_description_field>
+ <value_reference_field>ref_value</value_reference_field>
+ <value_compare_field>compare_value</value_compare_field>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>288</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>Table results</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>512</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>WriteToLog</type>
+ <name>success log</name>
+ <displayHeader>Y</displayHeader>
+ <limitRows>N</limitRows>
+ <limitRowsNumber>0</limitRowsNumber>
+ <loglevel>Basic</loglevel>
+ <fields/>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>704</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>WriteToLog</type>
+ <name>fail log</name>
+ <displayHeader>Y</displayHeader>
+ <limitRows>N</limitRows>
+ <limitRowsNumber>0</limitRowsNumber>
+ <loglevel>Basic</loglevel>
+ <fields/>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>704</xloc>
+ <yloc>176</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Table compare</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Table compare</from>
+ <to>ERRORS</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Table compare</from>
+ <to>Table results</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Table results</from>
+ <to>success log</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>ERRORS</from>
+ <to>fail log</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling>
+ <error>
+ <source_transform>Table compare</source_transform>
+ <target_transform>ERRORS</target_transform>
+ <is_enabled>Y</is_enabled>
+ <max_errors/>
+ <max_pct_errors/>
+ <min_pct_rows/>
+ </error>
+ </transform_error_handling>
+</pipeline>
diff --git
a/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareDataTests.java
b/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareDataTests.java
new file mode 100644
index 0000000000..6102ae3b54
--- /dev/null
+++
b/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareDataTests.java
@@ -0,0 +1,126 @@
+/*
+ * 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.hop.pipeline.transforms.tablecompare;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.apache.hop.core.database.Database;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.pipeline.engine.EngineComponent.ComponentExecutionStatus;
+import org.apache.hop.pipeline.transform.ITransformData;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link TableCompareData} */
+class TableCompareDataTests {
+
+ @Test
+ void implementsTransformData() {
+ TableCompareData data = new TableCompareData();
+ assertInstanceOf(ITransformData.class, data);
+ }
+
+ @Test
+ void defaultConstructor_initializesBaseStatusAndNullReferences() {
+ TableCompareData data = new TableCompareData();
+
+ assertEquals(ComponentExecutionStatus.STATUS_EMPTY, data.getStatus());
+ assertTrue(data.isEmpty());
+
+ assertNull(data.outputRowMeta);
+ assertNull(data.convertRowMeta);
+ assertNull(data.referenceDb);
+ assertNull(data.compareDb);
+ assertNull(data.errorRowMeta);
+
+ assertEquals(0, data.refSchemaIndex);
+ assertEquals(0, data.refTableIndex);
+ assertEquals(0, data.refCteIndex);
+ assertEquals(0, data.cmpSchemaIndex);
+ assertEquals(0, data.cmpTableIndex);
+ assertEquals(0, data.cmpCteIndex);
+ assertEquals(0, data.keyFieldsIndex);
+ assertEquals(0, data.excludeFieldsIndex);
+ assertEquals(0, data.keyDescIndex);
+ assertEquals(0, data.valueReferenceIndex);
+ assertEquals(0, data.valueCompareIndex);
+ }
+
+ @Test
+ void rowMetaAndDatabaseFields_canBeAssigned() {
+ TableCompareData data = new TableCompareData();
+
+ IRowMeta out = mock(IRowMeta.class);
+ IRowMeta convert = mock(IRowMeta.class);
+ IRowMeta err = mock(IRowMeta.class);
+ Database refDb = mock(Database.class);
+ Database cmpDb = mock(Database.class);
+
+ data.outputRowMeta = out;
+ data.convertRowMeta = convert;
+ data.errorRowMeta = err;
+ data.referenceDb = refDb;
+ data.compareDb = cmpDb;
+
+ assertEquals(out, data.outputRowMeta);
+ assertEquals(convert, data.convertRowMeta);
+ assertEquals(err, data.errorRowMeta);
+ assertEquals(refDb, data.referenceDb);
+ assertEquals(cmpDb, data.compareDb);
+ }
+
+ @Test
+ void columnIndexFields_canBeAssigned() {
+ TableCompareData data = new TableCompareData();
+
+ data.refSchemaIndex = 1;
+ data.refTableIndex = 2;
+ data.refCteIndex = 3;
+ data.cmpSchemaIndex = 4;
+ data.cmpTableIndex = 5;
+ data.cmpCteIndex = 6;
+ data.keyFieldsIndex = 7;
+ data.excludeFieldsIndex = 8;
+ data.keyDescIndex = 9;
+ data.valueReferenceIndex = 10;
+ data.valueCompareIndex = 11;
+
+ assertEquals(1, data.refSchemaIndex);
+ assertEquals(2, data.refTableIndex);
+ assertEquals(3, data.refCteIndex);
+ assertEquals(4, data.cmpSchemaIndex);
+ assertEquals(5, data.cmpTableIndex);
+ assertEquals(6, data.cmpCteIndex);
+ assertEquals(7, data.keyFieldsIndex);
+ assertEquals(8, data.excludeFieldsIndex);
+ assertEquals(9, data.keyDescIndex);
+ assertEquals(10, data.valueReferenceIndex);
+ assertEquals(11, data.valueCompareIndex);
+ }
+
+ @Test
+ void statusFollowsBaseTransformData() {
+ TableCompareData data = new TableCompareData();
+ data.setStatus(ComponentExecutionStatus.STATUS_RUNNING);
+ assertEquals(ComponentExecutionStatus.STATUS_RUNNING, data.getStatus());
+ assertTrue(data.isRunning());
+ }
+}
diff --git
a/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareMetaTest.java
b/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareMetaTest.java
index e4aa95b986..734fca817a 100644
---
a/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareMetaTest.java
+++
b/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareMetaTest.java
@@ -16,27 +16,43 @@
*/
package org.apache.hop.pipeline.transforms.tablecompare;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.ICheckResult;
import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.variables.Variables;
import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.ITransformData;
+import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
import
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.extension.ExtendWith;
+/** Unit test for {@link TableCompareMeta} */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
class TableCompareMetaTest {
- LoadSaveTester loadSaveTester;
+ LoadSaveTester<TableCompareMeta> loadSaveTester;
Class<TableCompareMeta> testMetaClass = TableCompareMeta.class;
- @RegisterExtension
- static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
-
@BeforeEach
void setUpLoadSave() throws Exception {
HopEnvironment.init();
@@ -46,9 +62,11 @@ class TableCompareMetaTest {
"referenceConnection",
"referenceSchemaField",
"referenceTableField",
+ "referenceCteField",
"compareConnection",
"compareSchemaField",
"compareTableField",
+ "compareCteField",
"keyFieldsField",
"excludeFieldsField",
"nrErrorsField",
@@ -59,7 +77,8 @@ class TableCompareMetaTest {
"nrErrorsRightJoinField",
"keyDescriptionField",
"valueReferenceField",
- "valueCompareField");
+ "valueCompareField",
+ "subjectFieldName");
Map<String, String> getterMap = new HashMap<>();
Map<String, String> setterMap = new HashMap<>();
@@ -68,7 +87,7 @@ class TableCompareMetaTest {
Map<String, IFieldLoadSaveValidator<?>> typeValidatorMap = new HashMap<>();
loadSaveTester =
- new LoadSaveTester(
+ new LoadSaveTester<>(
testMetaClass, attributes, getterMap, setterMap, attrValidatorMap,
typeValidatorMap);
}
@@ -76,4 +95,127 @@ class TableCompareMetaTest {
void testSerialization() throws HopException {
loadSaveTester.testSerialization();
}
+
+ @Test
+ void setDefault_setsStatisticOutputFieldNames() {
+ TableCompareMeta meta = new TableCompareMeta();
+ meta.setDefault();
+ assertEquals("nrErrors", meta.getNrErrorsField());
+ assertEquals("nrRecordsReferenceTable", meta.getNrRecordsReferenceField());
+ assertEquals("nrRecordsCompareTable", meta.getNrRecordsCompareField());
+ assertEquals("nrErrorsLeftJoin", meta.getNrErrorsLeftJoinField());
+ assertEquals("nrErrorsInnerJoin", meta.getNrErrorsInnerJoinField());
+ assertEquals("nrErrorsRightJoin", meta.getNrErrorsRightJoinField());
+ }
+
+ @Test
+ void getFields_appendsSixIntegerFieldsWithLength9() throws
HopTransformException {
+ TableCompareMeta meta = new TableCompareMeta();
+ meta.setNrErrorsField("e");
+ meta.setNrRecordsReferenceField("rr");
+ meta.setNrRecordsCompareField("rc");
+ meta.setNrErrorsLeftJoinField("lj");
+ meta.setNrErrorsInnerJoinField("ij");
+ meta.setNrErrorsRightJoinField("rj");
+
+ RowMeta rowMeta = new RowMeta();
+ meta.getFields(rowMeta, "origin", null, null, new Variables(), null);
+
+ assertEquals(6, rowMeta.size());
+ String[] expected = {"e", "rr", "rc", "lj", "ij", "rj"};
+ for (int i = 0; i < expected.length; i++) {
+ IValueMeta vm = rowMeta.getValueMeta(i);
+ assertEquals(expected[i], vm.getName());
+ assertEquals(IValueMeta.TYPE_INTEGER, vm.getType());
+ assertEquals(9, vm.getLength());
+ assertEquals("origin", vm.getOrigin());
+ }
+ }
+
+ @Test
+ void getFields_throwsWhenAnyRequiredOutputFieldIsEmpty() {
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsField(""));
+ assertThrowsForEmptyField(meta -> meta.setNrRecordsReferenceField(""));
+ assertThrowsForEmptyField(meta -> meta.setNrRecordsCompareField(""));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsLeftJoinField(""));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsInnerJoinField(""));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsRightJoinField(""));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsField(null));
+ assertThrowsForEmptyField(meta -> meta.setNrRecordsReferenceField(null));
+ assertThrowsForEmptyField(meta -> meta.setNrRecordsCompareField(null));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsLeftJoinField(null));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsInnerJoinField(null));
+ assertThrowsForEmptyField(meta -> meta.setNrErrorsRightJoinField(null));
+ }
+
+ private void assertThrowsForEmptyField(Consumer<TableCompareMeta>
clearOneField) {
+ TableCompareMeta meta = new TableCompareMeta();
+ meta.setDefault();
+ meta.setNrErrorsField("e");
+ meta.setNrRecordsReferenceField("rr");
+ meta.setNrRecordsCompareField("rc");
+ meta.setNrErrorsLeftJoinField("lj");
+ meta.setNrErrorsInnerJoinField("ij");
+ meta.setNrErrorsRightJoinField("rj");
+ clearOneField.accept(meta);
+ assertThrows(
+ HopTransformException.class,
+ () -> meta.getFields(new RowMeta(), "o", null, null, new Variables(),
null));
+ }
+
+ @Test
+ void supportsErrorHandling_isTrue() {
+ assertTrue(new TableCompareMeta().supportsErrorHandling());
+ }
+
+ @Test
+ void createTransformData_returnsTableCompareData() {
+ ITransformData data = new TableCompareMeta().createTransformData();
+ assertInstanceOf(TableCompareData.class, data);
+ }
+
+ @Test
+ void check_emptyPrev_emitsWarningAndInputErrorWhenNoInput() {
+ TableCompareMeta meta = new TableCompareMeta();
+ List<ICheckResult> remarks = new ArrayList<>();
+ PipelineMeta pipelineMeta = mock(PipelineMeta.class);
+ TransformMeta transformMeta = mock(TransformMeta.class);
+ IHopMetadataProvider metadataProvider = mock(IHopMetadataProvider.class);
+
+ meta.check(
+ remarks,
+ pipelineMeta,
+ transformMeta,
+ null,
+ new String[0],
+ new String[0],
+ null,
+ new Variables(),
+ metadataProvider);
+
+ assertEquals(2, remarks.size());
+ assertEquals(ICheckResult.TYPE_RESULT_WARNING, remarks.get(0).getType());
+ assertEquals(ICheckResult.TYPE_RESULT_ERROR, remarks.get(1).getType());
+ }
+
+ @Test
+ void check_withPrevAndInput_emitsOkRemarks() {
+ TableCompareMeta meta = new TableCompareMeta();
+ List<ICheckResult> remarks = new ArrayList<>();
+ RowMeta prev = new RowMeta();
+ meta.check(
+ remarks,
+ mock(PipelineMeta.class),
+ mock(TransformMeta.class),
+ prev,
+ new String[] {"in"},
+ new String[0],
+ null,
+ new Variables(),
+ mock(IHopMetadataProvider.class));
+
+ assertEquals(2, remarks.size());
+ assertEquals(ICheckResult.TYPE_RESULT_WARNING, remarks.get(0).getType());
+ assertEquals(ICheckResult.TYPE_RESULT_OK, remarks.get(1).getType());
+ }
}
diff --git
a/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareTests.java
b/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareTests.java
new file mode 100644
index 0000000000..659fef9efd
--- /dev/null
+++
b/plugins/transforms/tablecompare/src/test/java/org/apache/hop/pipeline/transforms/tablecompare/TableCompareTests.java
@@ -0,0 +1,442 @@
+/*
+ * 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.hop.pipeline.transforms.tablecompare;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.hop.core.database.Database;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Unit tests for {@link TableCompare} merge-compare logic and validation
paths. Uses mocked {@link
+ * Database} streams to avoid JDBC and to assert left/right/inner error counts.
+ */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class TableCompareTests {
+
+ private TransformMockHelper<TableCompareMeta, TableCompareData> helper;
+ private TableCompareMeta meta;
+ private TableCompareData data;
+
+ @BeforeEach
+ void setUp() {
+ helper =
+ new TransformMockHelper<>("TableCompare", TableCompareMeta.class,
TableCompareData.class);
+ when(helper.pipeline.isRunning()).thenReturn(true);
+ when(helper.pipeline.isStopped()).thenReturn(false);
+ doReturn(helper.iLogChannel)
+ .when(helper.logChannelFactory)
+ .create(any(), any(ILoggingObject.class));
+
+ DatabaseMeta dbMeta = mock(DatabaseMeta.class);
+ when(dbMeta.quoteField(anyString())).thenAnswer(inv -> "\"" +
inv.getArgument(0) + "\"");
+ when(dbMeta.getQuotedSchemaTableCombination(any(IVariables.class),
anyString(), anyString()))
+ .thenAnswer(
+ inv ->
+ "\""
+ + inv.getArgument(1, String.class)
+ + "\".\""
+ + inv.getArgument(2, String.class)
+ + "\"");
+
+ when(helper.pipelineMeta.findDatabase(anyString(),
any(IVariables.class))).thenReturn(dbMeta);
+
+ meta = new TableCompareMeta();
+ meta.setDefault();
+ meta.setReferenceConnection("unit_ref");
+ meta.setCompareConnection("unit_cmp");
+
+ data = new TableCompareData();
+ data.refSchemaIndex = 0;
+ data.refTableIndex = 1;
+ data.cmpSchemaIndex = 2;
+ data.cmpTableIndex = 3;
+ data.keyFieldsIndex = 4;
+ data.excludeFieldsIndex = 5;
+ data.refCteIndex = -1;
+ data.cmpCteIndex = -1;
+ data.keyDescIndex = 6;
+ data.valueReferenceIndex = 7;
+ data.valueCompareIndex = 8;
+ }
+
+ @AfterEach
+ void tearDown() {
+ helper.cleanUp();
+ }
+
+ private TransformMeta transformMeta() {
+ TransformMeta tm = helper.transformMeta;
+ when(tm.isDoingErrorHandling()).thenReturn(false);
+ when(tm.getName()).thenReturn("TableCompare");
+ return tm;
+ }
+
+ /** Issue #6984 scenario: left-only, right-only, and inner value mismatch. */
+ @Test
+ void compare_merge_counts_match_merge_rows_semantics_issue6984() throws
Exception {
+ // Reference: id 1,2,3 — Compare: 2,3,4,5 — id 3 text differs
+ List<Object[]> refRows =
+ List.of(
+ new Object[] {1L, "First"}, new Object[] {2L, "Second"}, new
Object[] {3L, "Third"});
+ List<Object[]> cmpRows =
+ List.of(
+ new Object[] {2L, "Second"},
+ new Object[] {3L, "3."},
+ new Object[] {4L, "Fourth"},
+ new Object[] {5L, "Fith"});
+
+ Object[] result = runCompare(refRows, cmpRows, twoColTableMeta(), "id",
"name");
+
+ assertEquals(4L, n(result[0]), "nrErrors");
+ assertEquals(3L, n(result[1]), "nrRecordsReference");
+ assertEquals(4L, n(result[2]), "nrRecordsCompare");
+ assertEquals(1L, n(result[3]), "nrErrorsLeftJoin (id 1 only in
reference)");
+ assertEquals(1L, n(result[4]), "nrErrorsInnerJoin (id 3 value diff)");
+ assertEquals(2L, n(result[5]), "nrErrorsRightJoin (id 4,5 only in
compare)");
+ }
+
+ @Test
+ void compare_identical_tables_no_errors() throws Exception {
+ List<Object[]> rows = List.of(new Object[] {1L, "a"}, new Object[] {2L,
"b"});
+ Object[] result = runCompare(rows, rows, twoColTableMeta(), "id", "");
+ assertEquals(0L, n(result[0]));
+ assertEquals(2L, n(result[1]));
+ assertEquals(2L, n(result[2]));
+ assertEquals(0L, n(result[3]));
+ assertEquals(0L, n(result[4]));
+ assertEquals(0L, n(result[5]));
+ }
+
+ @Test
+ void compare_only_in_reference_until_end() throws Exception {
+ List<Object[]> refRows = List.<Object[]>of(new Object[] {1L, "a"});
+ List<Object[]> cmpRows = List.of();
+ Object[] result = runCompare(refRows, cmpRows, twoColTableMeta(), "id",
"");
+ assertEquals(1L, n(result[0]));
+ assertEquals(1L, n(result[3]), "left");
+ assertEquals(0L, n(result[5]), "right");
+ }
+
+ @Test
+ void compare_only_in_compare_until_end() throws Exception {
+ List<Object[]> refRows = List.of();
+ List<Object[]> cmpRows = List.<Object[]>of(new Object[] {9L, "z"});
+ Object[] result = runCompare(refRows, cmpRows, twoColTableMeta(), "id",
"");
+ assertEquals(1L, n(result[0]));
+ assertEquals(0L, n(result[3]), "left");
+ assertEquals(1L, n(result[5]), "right");
+ }
+
+ @Test
+ void compare_key_only_tables_no_value_columns() throws Exception {
+ IRowMeta keyOnly = new RowMeta();
+ keyOnly.addValueMeta(new ValueMetaInteger("id"));
+
+ List<Object[]> refRows = List.of(new Object[] {1L}, new Object[] {2L});
+ List<Object[]> cmpRows = List.of(new Object[] {2L}, new Object[] {3L});
+ Object[] result = runCompare(refRows, cmpRows, keyOnly, "id", "");
+ assertEquals(2L, n(result[0]));
+ assertEquals(1L, n(result[3]));
+ assertEquals(1L, n(result[5]));
+ assertEquals(0L, n(result[4]));
+ }
+
+ @Test
+ void compare_field_count_mismatch_increments_error() throws Exception {
+ IRowMeta refMeta = twoColTableMeta();
+ IRowMeta cmpMeta = new RowMeta();
+ cmpMeta.addValueMeta(new ValueMetaInteger("id"));
+ cmpMeta.addValueMeta(new ValueMetaString("text"));
+ cmpMeta.addValueMeta(new ValueMetaString("extra"));
+
+ List<Object[]> refRows = List.<Object[]>of(new Object[] {1L, "a"});
+ List<Object[]> cmpRows = List.<Object[]>of(new Object[] {1L, "a", "x"});
+ Object[] result = runCompare(refRows, cmpRows, refMeta, cmpMeta, "id", "");
+ assertEquals(1L, n(result[0]));
+ }
+
+ @Test
+ void compare_empty_reference_table_early_exit() throws Exception {
+ Object[] r = inputRow("public", "", "public", "t", "id", "");
+ TableCompare tc = newTableCompare();
+ doNothing().when(tc).putError(any(), any(), anyLong(), any(), any(),
any());
+ Object[] result =
+ invokeCompareTables(tc, rowMetaForInput(), r, "public", "", "public",
"t", "id", "");
+ assertEquals(1L, n(result[0]));
+ verify(tc).putError(any(), any(), eq(1L), anyString(), any(),
eq("TAC008"));
+ }
+
+ @Test
+ void compare_empty_compare_table_early_exit() throws Exception {
+ Object[] r = inputRow("", "t", "", "", "id", "");
+ TableCompare tc = newTableCompare();
+ doNothing().when(tc).putError(any(), any(), anyLong(), any(), any(),
any());
+ Object[] result = invokeCompareTables(tc, rowMetaForInput(), r, "", "t",
"", "", "id", "");
+ assertEquals(1L, n(result[0]));
+ }
+
+ @Test
+ void compare_empty_key_fields_early_exit() throws Exception {
+ Object[] r = inputRow("", "t", "", "t", "", "");
+ TableCompare tc = newTableCompare();
+ doNothing().when(tc).putError(any(), any(), anyLong(), any(), any(),
any());
+ Object[] result = invokeCompareTables(tc, rowMetaForInput(), r, "", "t",
"", "t", "", "");
+ assertEquals(1L, n(result[0]));
+ }
+
+ /** Composite key: one row only on reference, one only on compare (sorted
merge). */
+ @Test
+ void compare_composite_key_left_and_right() throws Exception {
+ IRowMeta m = new RowMeta();
+ m.addValueMeta(new ValueMetaInteger("region"));
+ m.addValueMeta(new ValueMetaInteger("id"));
+ m.addValueMeta(new ValueMetaString("text"));
+
+ List<Object[]> refRows = List.<Object[]>of(new Object[] {1L, 1L, "a"});
+ List<Object[]> cmpRows = List.<Object[]>of(new Object[] {1L, 2L, "b"});
+ Object[] result = runCompare(refRows, cmpRows, m, "region, id", "");
+ assertEquals(2L, n(result[0]));
+ assertEquals(1L, n(result[3]));
+ assertEquals(1L, n(result[5]));
+ }
+
+ @Test
+ void compare_value_mismatch_emits_tac006_when_error_handling() throws
Exception {
+ List<Object[]> refRows = List.<Object[]>of(new Object[] {1L, "old"});
+ List<Object[]> cmpRows = List.<Object[]>of(new Object[] {1L, "new"});
+ ResultSet refRs = mock(ResultSet.class);
+ ResultSet cmpRs = mock(ResultSet.class);
+ Database refDb = mock(Database.class);
+ Database cmpDb = mock(Database.class);
+ when(refDb.openQuery(anyString())).thenReturn(refRs);
+ when(cmpDb.openQuery(anyString())).thenReturn(cmpRs);
+ when(refDb.getTableFieldsMeta(anyString(),
anyString())).thenReturn(twoColTableMeta());
+ when(cmpDb.getTableFieldsMeta(anyString(),
anyString())).thenReturn(twoColTableMeta());
+
+ Iterator<Object[]> refIt = new ArrayList<>(refRows).iterator();
+ Iterator<Object[]> cmpIt = new ArrayList<>(cmpRows).iterator();
+ when(refDb.getRow(same(refRs))).thenAnswer(inv -> refIt.hasNext() ?
refIt.next() : null);
+ when(cmpDb.getRow(same(cmpRs))).thenAnswer(inv -> cmpIt.hasNext() ?
cmpIt.next() : null);
+ when(refDb.getReturnRowMeta()).thenReturn(twoColTableMeta().clone());
+ when(cmpDb.getReturnRowMeta()).thenReturn(twoColTableMeta().clone());
+
+ Object[] r = inputRow("", "T_OLD", "", "T_NEW", "id", "");
+ TransformMeta tm = transformMeta();
+ when(tm.isDoingErrorHandling()).thenReturn(true);
+ TableCompare tc =
+ spy(new TableCompare(tm, meta, data, 0, helper.pipelineMeta,
helper.pipeline));
+ doNothing().when(tc).putError(any(), any(), anyLong(), any(), any(),
any());
+ data.referenceDb = refDb;
+ data.compareDb = cmpDb;
+
+ Object[] result =
+ invokeCompareTables(tc, rowMetaForInput(), r, "", "T_OLD", "",
"T_NEW", "id", "");
+ assertEquals(1L, n(result[0]));
+ assertEquals(1L, n(result[4]), "inner");
+ verify(tc, times(1)).putError(any(), any(), eq(1L), anyString(),
eq("text"), eq("TAC006"));
+ }
+
+ @Test
+ void compare_key_field_missing_emits_tac002_and_tac003_when_error_handling()
throws Exception {
+ IRowMeta refMeta = new RowMeta();
+ refMeta.addValueMeta(new ValueMetaInteger("id"));
+ refMeta.addValueMeta(new ValueMetaString("text"));
+ IRowMeta cmpMeta = twoColTableMeta();
+
+ ResultSet refRs = mock(ResultSet.class);
+ ResultSet cmpRs = mock(ResultSet.class);
+ Database refDb = mock(Database.class);
+ Database cmpDb = mock(Database.class);
+ when(refDb.openQuery(anyString())).thenReturn(refRs);
+ when(cmpDb.openQuery(anyString())).thenReturn(cmpRs);
+ when(refDb.getTableFieldsMeta(anyString(),
anyString())).thenReturn(refMeta);
+ when(cmpDb.getTableFieldsMeta(anyString(),
anyString())).thenReturn(cmpMeta);
+
+ Object[] r = inputRow("", "T_OLD", "", "T_NEW", "unknown_key", "");
+ TransformMeta tm = transformMeta();
+ when(tm.isDoingErrorHandling()).thenReturn(true);
+ TableCompare tc =
+ spy(new TableCompare(tm, meta, data, 0, helper.pipelineMeta,
helper.pipeline));
+ doNothing().when(tc).putError(any(), any(), anyLong(), any(), any(),
any());
+ data.referenceDb = refDb;
+ data.compareDb = cmpDb;
+
+ Object[] result =
+ invokeCompareTables(tc, rowMetaForInput(), r, "", "T_OLD", "",
"T_NEW", "unknown_key", "");
+ assertEquals(2L, n(result[0]));
+ verify(tc, times(1)).putError(any(), any(), eq(1L), anyString(), any(),
eq("TAC002"));
+ verify(tc, times(1)).putError(any(), any(), eq(1L), anyString(), any(),
eq("TAC003"));
+ }
+
+ private static long n(Object o) {
+ return ((Number) o).longValue();
+ }
+
+ private Object[] runCompare(
+ List<Object[]> refRows,
+ List<Object[]> cmpRows,
+ IRowMeta tableFieldsMeta,
+ String keys,
+ String exclude)
+ throws Exception {
+ return runCompare(refRows, cmpRows, tableFieldsMeta, tableFieldsMeta,
keys, exclude);
+ }
+
+ private Object[] runCompare(
+ List<Object[]> refRows,
+ List<Object[]> cmpRows,
+ IRowMeta refTableMeta,
+ IRowMeta cmpTableMeta,
+ String keys,
+ String exclude)
+ throws Exception {
+ ResultSet refRs = mock(ResultSet.class);
+ ResultSet cmpRs = mock(ResultSet.class);
+ Database refDb = mock(Database.class);
+ Database cmpDb = mock(Database.class);
+
+ when(refDb.openQuery(anyString())).thenReturn(refRs);
+ when(cmpDb.openQuery(anyString())).thenReturn(cmpRs);
+ when(refDb.getTableFieldsMeta(anyString(),
anyString())).thenReturn(refTableMeta);
+ when(cmpDb.getTableFieldsMeta(anyString(),
anyString())).thenReturn(cmpTableMeta);
+
+ Iterator<Object[]> refIt = new ArrayList<>(refRows).iterator();
+ Iterator<Object[]> cmpIt = new ArrayList<>(cmpRows).iterator();
+ when(refDb.getRow(same(refRs))).thenAnswer(inv -> refIt.hasNext() ?
refIt.next() : null);
+ when(cmpDb.getRow(same(cmpRs))).thenAnswer(inv -> cmpIt.hasNext() ?
cmpIt.next() : null);
+
+ IRowMeta returnMeta = refTableMeta.clone();
+ when(refDb.getReturnRowMeta()).thenReturn(returnMeta);
+ when(cmpDb.getReturnRowMeta()).thenReturn(cmpTableMeta.clone());
+
+ Object[] r = inputRow("", "T_OLD", "", "T_NEW", keys, exclude);
+ TableCompare tc = newTableCompare();
+ doNothing().when(tc).putError(any(), any(), anyLong(), any(), any(),
any());
+ data.referenceDb = refDb;
+ data.compareDb = cmpDb;
+
+ return invokeCompareTables(tc, rowMetaForInput(), r, "", "T_OLD", "",
"T_NEW", keys, exclude);
+ }
+
+ private TableCompare newTableCompare() {
+ return spy(
+ new TableCompare(transformMeta(), meta, data, 0, helper.pipelineMeta,
helper.pipeline));
+ }
+
+ private static IRowMeta twoColTableMeta() {
+ IRowMeta m = new RowMeta();
+ m.addValueMeta(new ValueMetaInteger("id"));
+ m.addValueMeta(new ValueMetaString("text"));
+ return m;
+ }
+
+ private static IRowMeta rowMetaForInput() {
+ IRowMeta rm = new RowMeta();
+ rm.addValueMeta(new ValueMetaString("ref_schema"));
+ rm.addValueMeta(new ValueMetaString("ref_table"));
+ rm.addValueMeta(new ValueMetaString("cmp_schema"));
+ rm.addValueMeta(new ValueMetaString("cmp_table"));
+ rm.addValueMeta(new ValueMetaString("keys"));
+ rm.addValueMeta(new ValueMetaString("exclude"));
+ rm.addValueMeta(new ValueMetaString("key_desc"));
+ rm.addValueMeta(new ValueMetaString("ref_val"));
+ rm.addValueMeta(new ValueMetaString("cmp_val"));
+ return rm;
+ }
+
+ private static Object[] inputRow(
+ String refSchema,
+ String refTable,
+ String cmpSchema,
+ String cmpTable,
+ String keys,
+ String exclude) {
+ return new Object[] {refSchema, refTable, cmpSchema, cmpTable, keys,
exclude, null, null, null};
+ }
+
+ private static Object[] invokeCompareTables(
+ TableCompare transform,
+ IRowMeta rowMeta,
+ Object[] r,
+ String referenceSchema,
+ String referenceTable,
+ String compareSchema,
+ String compareTable,
+ String keyFields,
+ String excludeFields)
+ throws Exception {
+ Method m =
+ TableCompare.class.getDeclaredMethod(
+ "compareTables",
+ IRowMeta.class,
+ Object[].class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class);
+ m.setAccessible(true);
+ return (Object[])
+ m.invoke(
+ transform,
+ rowMeta,
+ r,
+ referenceSchema,
+ referenceTable,
+ "",
+ compareSchema,
+ compareTable,
+ "",
+ keyFields,
+ excludeFields);
+ }
+}