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);
+  }
+}


Reply via email to